Object displays

Every object inheriting from DessiaObject or PhysicalObject can be represented on the Dessia platform using a set of displays. For example, a display can be text, a graph, or even a 3D model. The generic objects in dessia_common offer a set of methods by default to define these different representations. More specifically, the available displays are:

  • Free text using the markdown formatting options
  • 2D graph
  • 2D diagram
  • 3D geometry
  • Workflow

DessiaObject and PhysicalObject offer the ability to add representations (3D, 2D, graphs...) to each object on the platform, using decorators from the dessia_common library that are generic and can be configured by the user. Also, thanks to these decorators, each object can have multiple representations of the same type.

Display 3D

display 3d

Customizing the 3D display requires having a method decorated with the @cad_view decorator that returns the babylon_data of the VolumeModel object from the volmdlr module.

For example, to define the 3D presentation of theBallobject, we have separated the process into two methods:volmdlr_primitives, where we use themake_spheremethod of theSolidobject from theshapesmodule ofvolmdlrto create an elementary primitive constituting a sphere, and thecad_viewmethod which retrieves the primitives from the first method (in this case, we only have one), puts them in a VolumeModel, and returns the babylon_data of the latter. When this object is opened from the Dessia platform, a 3D viewer will be integrated into the object's display to represent its geometry, having as selector what is defined in thecad_viewdecorator.

Ball objects:

import math
from math import pi
 
from dessia_common.core import PhysicalObject
from dessia_common.decorators import cad_view
from volmdlr import OXYZ, Z3D, Point2D, Point3D, Frame3D, Vector3D
from volmdlr.core import VolumeModel
from volmdlr.curves import Circle2D
from volmdlr.wires import Contour2D
from volmdlr.shapes import Solid
 
 
class Ball(PhysicalObject):
    _standalone_in_db = False
 
    def __init__(self, diameter: float, name: str = ''):
        self.diameter = diameter
        PhysicalObject.__init__(self, name=name)
 
    def volmdlr_primitives(self, pos_x=0., pos_y=0., pos_z=0.,
                           distance=0., angle=0.):
        center_x = pos_x + distance * math.cos(angle)
        center_y = pos_y + distance * math.sin(angle)
        center_z = pos_z
        center = (center_x, center_y, center_z)
 
        u_axis = Vector3D(1, 0, 0)
        v_axis = Vector3D(0, 1, 0)
        w_axis = Vector3D(0, 0, 1)
 
        frame = Frame3D(
            origin=Point3D(*center),
            u=u_axis,
            v=v_axis,
            w=w_axis)
 
        sphere = Solid.make_sphere(
            radius=self.diameter / 2,
            frame=frame,
            angle1=-math.pi / 2,
            angle2=math.pi / 2,
            angle3=2 * math.pi,
            name='sphere')
 
        return [sphere]
 
    @cad_view(selector='Ball CAD')
    def cad_view(self):
        primitives = self.volmdlr_primitives()
        return VolumeModel(primitives=primitives).babylon_data()
 
class Bearing(PhysicalObject):
    _standalone_in_db = True
 
    def __init__(self, ball: Ball, internal_diameter: float,
                 external_diameter: float, height: float,
                 thickness: float = 0., name: str = ''):
        self.ball = ball
        self.internal_diameter = internal_diameter
        self.external_diameter = external_diameter
        self.height = height
        self.thickness = thickness
        PhysicalObject.__init__(self, name=name)
 
    def volmdlr_primitives(self, pos_x=0., pos_y=0., pos_z=0.,
                           number_balls=10):
        # Setup circles for extrusions
        outer_circle = Circle2D.from_center_and_radius(
            center=Point2D(pos_x, pos_y),
            radius=self.external_diameter / 2)
        inner_outer_circle = Circle2D.from_center_and_radius(
            center=Point2D(pos_x, pos_y),
            radius=self.external_diameter / 2 - self.thickness)
        inner_circle = Circle2D.from_center_and_radius(
            center=Point2D(pos_x, pos_y),
            radius=self.internal_diameter / 2)
        outer_inner_circle = Circle2D.from_center_and_radius(
            center=Point2D(pos_x, pos_y),
            radius=self.internal_diameter / 2 + self.thickness)
        # Extrusions
        frame = OXYZ.copy()
        frame.origin += Z3D * (pos_z - self.height/2)
        outer_extrusion = Solid.make_extrusion_from_frame_and_wires(
            frame=frame, extrusion_length=self.height, outer_contour2d=Contour2D.from_circle(outer_circle),
            inner_contours2d=[Contour2D.from_circle(inner_outer_circle)])
        inner_extrusion = Solid.make_extrusion_from_frame_and_wires(
            frame=frame, extrusion_length=self.height, outer_contour2d=Contour2D.from_circle(outer_inner_circle),
            inner_contours2d=[Contour2D.from_circle(inner_circle)])
        # Balls
        ball_primitives = []
        ball_distance = self.internal_diameter / 2 + \
            (self.external_diameter - self.internal_diameter) / 4
        for i in range(number_balls):
            ball_primitive = self.ball.volmdlr_primitives(
                pos_x=pos_x, pos_y=pos_y, pos_z=pos_z,
                distance=ball_distance,
                angle=i * 2 * pi / number_balls)[0]
            ball_primitives.append(ball_primitive)
        return [outer_extrusion, inner_extrusion] + ball_primitives
 
    @cad_view(selector='Bearing CAD')
    def cad_view(self):
        primitives = self.volmdlr_primitives()
        return VolumeModel(primitives=primitives).babylon_data()

Display 2D

display 2d

Five types of 2D display are possible: a Scatter, an Histogram, a Draw, a Graph and a Parallel Plot. All five display modes require the use of the "plot_data" library developed by Dessia. This is a Python library that is structured in a similar way to "volmdlr" and whose components are decomposed from Python objects inheriting from DessiaObject. Similarly, all objects can be stored from the Dessia platform. For more details on the "plot_data" library, please refer to the 2D graphics chapter.

2D display from the "plot_data" library requires custom objects to implement the "plot_data_view" decorator. When a method is assigned this decorator, the Dessia platform will offer a graphical representation in the object's display area. It is important to realize that the term "plot_data" is used to define 2 different concepts:

  • Name of the 2D representation library
  • Name of the file format allowing to write all components of the plot_data library in a generic way

For ease of use, the volmdlr library provides methods for generating these plot_data objects for its 2D objects (which are required for 2D plotting). For more details, it is necessary to refer to the plot_data documentation (opens in a new tab) (opens in a new tab).

The following code represents this method for generating a 2D representation of Ball and Bearing objects:

from math import cos, pi, sin
 
from dessia_common.core import PhysicalObject
from dessia_common.decorators import plot_data_view, cad_view
from plot_data import EdgeStyle, PrimitiveGroup, SurfaceStyle
from plot_data.colors import BLACK, GREY
from volmdlr import Point2D
from volmdlr.curves import Circle2D
 
class Ball(PhysicalObject):
    _standalone_in_db = False
 
    def __init__(self, diameter: float, name: str = ''):
        self.diameter = diameter
        PhysicalObject.__init__(self, name=name)
 
    @plot_data_view("2D display for Ball")
    def display_2d(self, pos_x=0., pos_y=0., distance=0., angle=0.):
        # Color settings
        edge_style = EdgeStyle(color_stroke=BLACK)
        surface_style = SurfaceStyle(color_fill=GREY, opacity=0.5)
 
        ball_x = distance * cos(angle)
        ball_y = distance * sin(angle)
        center = Point2D(pos_x + ball_x, pos_y + ball_y)
        circle = Circle2D.from_center_and_radius(
            center=center, radius=self.diameter / 2)
        primitives = [circle.plot_data(
            edge_style=edge_style, surface_style=surface_style)]
        return PrimitiveGroup(primitives=primitives,
                              name='Circle')
 
class Bearing(PhysicalObject):
    _standalone_in_db = True
 
    def __init__(self, ball: Ball, internal_diameter: float,
                 external_diameter: float, height: float,
                 thickness: float = 0., name: str = ''):
        self.ball = ball
        self.internal_diameter = internal_diameter
        self.external_diameter = external_diameter
        self.height = height
        self.thickness = thickness
        PhysicalObject.__init__(self, name=name)
 
    @plot_data_view("2D display for Bearing")
    def display_2d(self, pos_x=0., pos_y=0., number_balls=1):
        # Color settings
        edge_style = EdgeStyle(color_stroke=BLACK)
        surface_style = SurfaceStyle(opacity=0)
 
        primitives = []
        center = Point2D(pos_x, pos_y)
        # External circle
        external_circle = Circle2D.from_center_and_radius(
            center=center, radius=self.external_diameter / 2)
        primitives.append(external_circle.plot_data(
            edge_style=edge_style, surface_style=surface_style))
        # Internal circle
        internal_circle = Circle2D.from_center_and_radius(
            center=center, radius=self.internal_diameter / 2)
        primitives.append(internal_circle.plot_data(
            edge_style=edge_style, surface_style=surface_style))
        # Balls
        ball_distance = self.internal_diameter / 2 + \
            (self.external_diameter - self.internal_diameter) / 4
        for i in range(number_balls):
            primitives.extend(self.ball.display_2d(
                pos_x=pos_x,
                pos_y=pos_y,
                distance=ball_distance,
                angle=i * 2 * pi / number_balls).primitives)
        return PrimitiveGroup(primitives=primitives)

For the Ball object, we start by using the volmdlr library to define the 2D geometric shapes. Then, from the "plot_data()" method of volmdlr objects, we generate these generic plot_data objects that can be used to fill PrimitiveGroup.

If you wish to include a scatter plot in the plot_data method, here is an example of a possible data input:

from dessia_common.core import PhysicalObject
import random
from plot_data import colors
import plot_data.core as plot_data
from dessia_common.decorators import plot_data_view
 
class Graph(PhysicalObject):
 
    def __init__(self, diameter: float,
                 name: str = ""):
        self.diameter = diameter
        PhysicalObject.__init__(self, name=name)
 
    @plot_data_view("ScatterPlot")
    def my_scatter_plot(self):
        elements = []
        SHAPES = ['round', 'square', 'triangle', 'ellipse']
        COLORS = [colors.RED, colors.BLUE, colors.GREEN, colors.YELLOW,
                  colors.ORANGE, colors.VIOLET]
        for i in range(50):
            random_shape = SHAPES[random.randint(0, len(SHAPES) - 1)]
            random_color = COLORS[random.randint(0, len(SHAPES) - 1)]
            elements.append(plot_data.Sample({'mass': random.uniform(0, 50),
                                              'length': random.uniform(0, 100),
                                              'shape': random_shape,
                                              'color': random_color}))
 
        return plot_data.Scatter(elements=elements, x_variable='mass',
                                 y_variable='length')

A large number of different graphs are possible and detailed in the plot_data documentation (opens in a new tab) (opens in a new tab).

Display markdown

To include free text in the display, it is possible to write a method in custom objects that returns a string formatted according to the markdown standard (# for a Heading 1, ## for a Heading 2, etc.). It is necessary to insert the '\n' character into the string when a line break is desired.

Note that for the markdown display, it is the only display for which you do not need to use a decorator for it to work by naming the method to_markdown. However, to have multiple markdown displays on the same object, you must use the markdown_view decorator for it to be integrated into your object's displays, and in this case, you can give the function any name you want.

The following code presents an example of how to apply this to the Bearing object:

class Bearing(PhysicalObject):
    _standalone_in_db = True
 
    def __init__(self, ball: Ball,
                 internal_diameter: float,
                 external_diameter: float,
                 height: float,
                 name: str = ""):
        self.ball = ball
        self.internal_diameter = internal_diameter
        self.external_diameter = external_diameter
        self.height = height
        PhysicalObject.__init__(self, name=name)
 
    def to_markdown(self):
        infos = ''
        infos += '## Bearing Infos \n\n'
        infos += '|name|height|external_diameter|internal_diameter|' + '\n'
        infos += '|:---------------:|:---------------:|:---------------:|:-----------------:|' + '\n'
        infos += '|' + self.name + '|' + str(self.height) + '|' + str(self.external_diameter) \
                 + '|' + str(self.internal_diameter) + '|\n'
 
        return infos