Drawing objects#

This section explains how to render and interact with objects in HelloTriangle.

Which objects can be rendered?#

You can render both Mesh and Coords objects. For meshes, all supported element types can be rendered (see Element types).

Objects are drawn using the draw() function. You can set a color for the whole mesh with the color parameter, or use the style parameter for fine-grained control over individual representations:

draw(mymesh, name="mymesh", style={"faces": {"color": "red"}})
draw(mycoords, name="mycoords", style={"points": {"color": "black"}})

Note

If you do not specify a name, a random one will be assigned automatically. The name will appear in the Visualization Settings panel of the GUI.

Your script may contain multiple draw() statements — all corresponding objects will appear in the 3D viewer.

Choosing colors#

The color parameter is a convenient shorthand: it sets the same color on all representations (faces, lines, and points, depending on the mesh):

draw(mymesh, name="mymesh", color="red")

For representation-specific colors — or any property beyond color — use the style parameter described below.

Color values are passed as strings. The following formats are all accepted:

  • Hexadecimal: "#990000"

  • Named colors: "skyblue" (any of the 140 X11 color names)

  • RGB values: "rgb(255, 0, 0)" or "rgb(100%, 0%, 0%)"

  • HSL values: "hsl(0, 100%, 50%)"

Rendering scalar fields#

HelloTriangle meshes can contain fields (per-node or per-element) imported from files or added programmatically. These can be visualized directly in the 3D viewer.

Example: adding a nodal field and rendering it

rect = shapes.rectangle(div=1, eltype='quad4')
rect.add_field([0.0, 0.5, 1.0, 2.0], type="node", name="temp")
draw(rect, field='temp')

Field-based coloring is automatically applied when field is specified in draw().

The types of fields apart from nodal (vertex) fields are:

  • Element fields (per-element/face data): Use type="elem" when adding the field.

  • Nodal element fields (per-element-per-node data): Use type="elemn" when adding the field.

When rendering, HelloTriangle will automatically select the appropriate representation

Advanced styling with the style parameter#

For full control over mesh appearance, use the style parameter with a dictionary. Pass a sub-dict for each representation you want to style — "faces", "lines", or "points" — and set properties within it.

Example:

For 2D (surface) and 3D (volumetric) meshes, you can separately control faces, edges (lines), and vertices (points):

# Different colors for faces, edges, and points
draw(mymesh, name="mesh", style={
    "faces": {
        "color": "#00ff00",
        "opacity": 0.9,
        "metalness": 0.5,
        "roughness": 0.5
    },
    "lines": {
        "color": "#000000",
        "width": 2.0,
        "opacity": 1.0
    },
    "points": {
        "color": "#ff0000",
        "size": 3.0,
        "opacity": 1.0
    }
})

Available style properties:

  • Faces (faces dict):

  • Lines/Edges (lines dict):

    • color: Line color

    • opacity: Line transparency

    • width: Line width (float)

    • visible: Boolean to show/hide edges

    • transparencyMode: String, "default" or "simple"

    • colorSource: "solid" or "field"

    • renderOrder: Integer draw order (see Fixing flicker and z-fighting between overlapping objects below)

  • Points/Vertices (points dict):

    • color: Point color

    • opacity: Point transparency

    • size: Point size (float)

    • visible: Boolean to show/hide points

    • transparencyMode: String, "default" or "simple"

    • colorSource: "solid" or "field"

    • renderOrder: Integer draw order (see Fixing flicker and z-fighting between overlapping objects below)

Hiding a mesh entirely

The top-level "hidden" key suspends the whole object from the scene without discarding its style settings:

draw(mymesh, name="mesh", style={"hidden": True})

This is distinct from setting "visible": False on individual representations (faces, lines, points). Those flags control which representations are active; hidden operates at the object level and temporarily overrides all of them at once — the same toggle exposed in the Visualization Settings panel as the eye icon for each object. Removing "hidden" (or setting it to False) restores the object with its representations exactly as configured.

Note

Transparency modes:

  • "default": Standard transparency with depth writing (best for faces)

  • "simple": Disables depth writing when transparent (best for edges/points)

  • "dithered": Alpha hashing for order-independent transparency (faces only)

Fixing flicker and z-fighting between overlapping objects#

When a script draws several objects that occupy the same space — a liquid mesh sitting inside a transparent container, for example — you can run into two distinct visual glitches. They look similar but need different fixes.

An object flickers or disappears as you orbit the camera

This happens with nested transparent objects (e.g. liquid fully enclosed by a transparent container). The viewer normally decides transparent draw order automatically, sorting objects back-to-front by their distance from the camera each frame — but for objects with near-identical centers, that distance comparison is unstable, so which one draws first (and therefore which is visible) can flip as you orbit.

By default, objects already draw in the same order as their draw() calls in the script, which resolves this for most scenes without any extra work. If you need to force a specific stacking regardless of script order, set renderOrder explicitly — higher values draw later, i.e. on top:

draw(container, name="container", style={"faces": {"opacity": 0.35}})
draw(liquid, name="liquid", style={"faces": {"color": "#1e88e5", "renderOrder": 1}})

renderOrder is available on faces, lines, and points.

A speckled or dithered pattern flickers where two surfaces touch

This happens when two faces from different objects are genuinely coincident — occupying the exact same surface, like a liquid’s surface flush against the inside wall of its container. renderOrder does not fix this: it only changes which object draws first, not which one is judged closer to the camera at that shared surface, so the two still flicker against each other pixel-by-pixel.

For this case, set depthOffset on the faces sub-dict of whichever object should reliably win and render in front — higher values render more in front:

draw(container, name="container", style={"faces": {"opacity": 0.35}})
draw(liquid, name="liquid", style={
    "faces": {"color": "#1e88e5", "depthOffset": 1}
})

depthOffset is faces-only and has no automatic default — unlike renderOrder, it must be set explicitly on the object(s) you know overlap. A small integer (1 is usually enough) resolves the flicker; there is normally no need to go higher.

Note

Most scripts never need renderOrder or depthOffset — they only matter when two or more drawn objects occupy the same physical space. Objects that merely sit near each other, or touch only at a shared edge, render correctly with no extra styling.

Restyling or hiding an object after drawing it#

draw() returns a handle to the object it just added. Use it to restyle, hide, or show that object later in the script — after pick_points(), for example, once you know which object should stay the visible one:

aorta = draw(anatomy, name="aorta")

inlet, outlet = pick_points("Click the inlet and outlet")
draw(extend_artery(anatomy, inlet, outlet), name="extension", color="red")

aorta.set_style({"faces": {"opacity": 0.3}})

set_style() deep-merges into the object’s current style — the same semantics as the style parameter at draw time, so properties you do not mention (color, metalness, other representations, …) are left as they were. hide() and show() are shorthand for setting the top-level "hidden" key described above.

If the handle returned by draw() is not in scope — a different function, or a later part of a long script — resolve it by name with get_drawn():

get_drawn("aorta").set_style({"faces": {"opacity": 0.3}})

get_drawn() raises a clear error if no object with that name has been drawn yet. Restyling or hiding an object never counts as redrawing it — the name stays taken, and drawing again with the same name is still an error.

Animating meshes#

Pass a list of frames to draw() to produce an animated object in the 3D viewer. The viewer shows playback controls (play/pause, scrubber, speed buttons, loop mode) as soon as any frame has finished loading, so you can start exploring the animation while later frames are still downloading.

Evenly-spaced frames (simplest — frames get integer times 0, 1, 2, …):

frames = [mesh_at_t0, mesh_at_t1, mesh_at_t2]
draw(frames, name="my_animation", style={"faces": {"color": "red"}})

Explicit physical times (when the time steps matter for display):

times  = [0.0, 0.1, 0.25]
frames = [mesh_at_t0, mesh_at_t1, mesh_at_t2]
draw(frames, times=times, name="my_animation")

Animated field visualization#

Field coloring works the same way as for static meshes — pass field= to draw() and ensure every frame’s mesh carries a field with that name. The colormap range is automatically derived from the global min/max across all loaded frames and updates progressively as frames arrive:

data = load("mysimulation.ht5")

frames = []
times  = []
for i, t in enumerate(data.times):
    mesh = data.get_mesh("PART-1", frame=i)
    frames.append(mesh)
    times.append(t)

draw(frames, times=times, name="PART-1", field="S_VON_MISES")

GUI options#

After running a script with one or more draw() calls, all rendered objects will appear in the Visualization Settings panel on the right-hand side of the screen. From this panel you can:

  • Interactively change object colors

  • Adjust transparency

  • For 2D (surface) and 3D (volumetric) meshes, separately control the appearance of faces, edges, and vertices

  • Copy the current style as Python code to paste into your script

Note

When you re-run a script, any manual changes made in the visualization settings panel will be reset. To preserve your styling, copy the style from the visualization panel and add it to your script using the style parameter.

Scene-level settings#

Use set_background() and set_camera() to configure scene-level properties. These settings apply to the entire 3D viewer, not individual objects.

# Set background color
set_background("#f0f0f0")

# Set camera type and position
set_camera({"type": "orthographic"})
set_camera({"type": "perspective", "position": [10, 5, 20], "target": [0, 0, 0]})

# Draw your meshes
draw(mymesh, name="mesh", style={"faces": {"color": "red"}})

set_background(color)

Sets the scene background color. Accepts any CSS color value:

set_background("white")
set_background("#1a1a2e")
set_background("lightblue")

set_camera(config)

Sets the camera type and optional position. Pass a dictionary with any of these keys:

  • "type": "perspective" (default) or "orthographic"

  • "position": [x, y, z] — world-space camera position

  • "target": [x, y, z] — look-at point (default [0, 0, 0])

  • "up": [x, y, z] — up vector (default [0, 1, 0])

  • "fov": field of view in degrees (perspective only)

  • "zoom": zoom multiplier (orthographic only)

set_camera({"type": "perspective", "fov": 60})
set_camera({"type": "orthographic", "zoom": 1.5})

Note

set_background() and set_camera() are typically placed at the beginning of a script. Settings defined in code take precedence over any manual adjustments made in the visualization panel. When a setting is removed from the script on rerun, any previously saved manual adjustment for that setting is restored.

The older set_scene() function (e.g. set_scene(background="#f0f0f0", camera={"type": "orthographic"})) is still supported for backwards compatibility, but the dedicated functions are preferred.