Graphics demo#

Requirements:

In this tutorial we will:

  • Create a two-link pendulum multibody

  • Attach a primitive geometry to a body

  • Attach a 3D mesh geometry to a body

  • Setup web-based 3d visualization

  • Render to a file

import atexit
import numpy as np
from typing import cast
from pathlib import Path

from IPython.display import display, Image

import Karana.Core as kc
import Karana.Math as km
import Karana.Frame as kf
import Karana.Dynamics as kd
import Karana.Dynamics.SOADyn_types as kdt
import Karana.Scene as ks
import Karana.KUtils as ku
import Karana.Models as kmdl
resources = Path().absolute().parent / "resources"
dodecahedron_file = str(resources / "dodecahedron.obj")

Create the multibody and lookup important objects#

mb = createMbody()

# get the first body
bd1 = mb.getBody("bd1")

# get the second body
bd2 = mb.getBody("bd2")

Geometry setup#

We create 3d geometries with associated material properties and attach them to bodies. This is commonly useful for collision detection and 3d visualization.

cleanup_graphics, web_scene = mb.setupGraphics(port=0, wait_for_clients=1)
proxy_scene = mb.getScene()

# create a ball geometry to be used for both bodies
ball_geom = ks.SphereGeometry(0.1)

# load a dodecahedron geometry from file
importer = ks.AssimpImporter()
d12_geom = importer.importFrom(dodecahedron_file).geometries[0]

# create red and blue materials to color the bodies
mat_info = ks.PhysicalMaterialInfo()
mat_info.color = ks.Color.BLUE
blue = ks.PhysicalMaterial(mat_info)
mat_info.color = ks.Color.RED
red = ks.PhysicalMaterial(mat_info)

# create a ks.ProxyScenePart and attach it to the body
bd1_part = ks.ProxyScenePart("bd1_part", scene=proxy_scene, geometry=ball_geom, material=blue)
bd1_part.attachTo(bd1)

bd2_part = ks.ProxyScenePart("bd2_part", scene=proxy_scene, geometry=d12_geom, material=red)
bd2_part.attachTo(bd2)
bd2_part.setScale(0.2)

# create some unattached objects (implicitly attached to the root frame)
root_scene_node = ks.ProxySceneNode("root_scene_node", scene=proxy_scene)

del red, blue, mat_info, d12_geom, ball_geom

Setup camera angle and add ornamental parts#

# position the viewpoint camera of the visualization
web_scene.defaultCamera().pointCameraAt([0.4, 4, -0.9], [0, 0, -1], [0, 0, 1])

# visualize axes for important frames
root_scene_node.graphics().showAxes(0.5)
bd1_part.graphics().showAxes(0.5)
bd2_part.graphics().showAxes(0.5)

# add a trail to track the motion of body 2
bd2_part.graphics().trail()

proxy_scene.update()
# set up state propagator
sp = kd.StatePropagator(mb, km.IntegratorType.CVODE)
integ = sp.getIntegrator()

# Create a UniformGravity model, and set it's gravitational acceleration.
ug = kmdl.Gravity("grav_model", sp, kmdl.UniformGravity("uniform_gravity"), mb)
ug.getGravityInterface().setGravity(np.array([0, 0, -9.81]), 0.0, kmdl.OutputUpdateType.PRE_HOP)
del ug

# Makes sure the visualization scene is updated after each state change.
kmdl.UpdateProxyScene("update_proxy_scene", sp, proxy_scene)

# sync the simulation time with real-time.
kmdl.SyncRealTime("sync_real_time", sp, 1.0)

Register callback for end of each step#

def post_step_fn(t, x):
    """Print out the time and state.

    Parameters
    ----------
    t : float
        The current time.
    x : NDArray[np.float64]
       The current state.
    """
    print(f"t = {float(integ.getTime())/1e9}s; x = {integ.getX()}")


sp.fns.post_hop_fns["update_and_info"] = post_step_fn

Initialize state and time#

x_init = np.array([0.5, -0.5, 0.0, 0.0])
t_init = np.timedelta64(0, "ns")
sp.setTime(t_init)
sp.setState(x_init)

Register event to limit step size#

h = np.timedelta64(int(1e7), "ns")
t = kd.TimedEvent("hop_size", h, lambda _: None, False)
t.period = h

# register after time has been initialized
sp.registerTimedEvent(t)
del t

print(f"t = {float(integ.getTime())/1e9}s; x = {integ.getX()}")

Run the simulation#

The advanceTo time below can be increased to lengthen the simulation

sp.advanceTo(2.0)
web_scene.renderToFile("render.png")
display(Image("render.png"))

Cleanup#

def cleanup():
    """Cleanup the simulation."""
    global integ, bd1, bd2, bd1_part, bd2_part, root_scene_node, web_scene, proxy_scene
    del integ, bd1, bd2, bd1_part, bd2_part, root_scene_node, web_scene, proxy_scene

    kc.discard(sp)
    cleanup_graphics()

    fc = mb.frameContainer()
    kc.discard(mb)
    kc.discard(fc)


atexit.register(cleanup)

Summary#

You now know how to add visual geometries to a simulation and render to a file.