3D Text

3D text is a text label positioned at a specific location in 3D space with a defined orientation and size. Unlike 2D text, 3D text behaves like other 3D objects in the view; its orientation and size on the screen change when zooming or rotating. A single 3D text label is represented in the SDK with the Text3D class.

Text3D properties

All properties for 2D Text objects also apply to 3D text objects. In addition, you can use the following properties to customise the appearance of your 3D text object:

direction

Set the direction of the label in space. The direction is defined as a vector from the start of the first character to the end of the last character. This property is represented by a 3D vector [X, Y, Z].

up_direction

Set the direction from the bottom of the text to the top.

always_visible

Set this boolean property to control whether the label should be visible or not when there are other 3D objects positioned in front of it.

facing

Set the direction the text should face, one of the following enum values:

3D text examples

The following examples illustrate how to create and modify 3D text labels in a 3D environment. These examples cover creating a basic Text3D object, adjusting its properties such as text content, size, colour, and orientation, and using 3D text labels to annotate points, edges, and facets in a project.

Creating a Text3D object

The following example demonstrates how to create a 3D text object in a project, specifying its text, location, colour, and size properties.

from mapteksdk.project import Project
from mapteksdk.data import Text3D
project = Project() # Connect to default project

object_path = "scrapbook/text3d_example"

# Create Text
with project.new(object_path, Text3D, overwrite=True) as text:
    text.text = 'hello world'
    text.location = [0, 0, 0]
    # Colours are R,G,B,A, where A is Alpha (255 = opaque)
    text.colour = [38, 128, 0, 255] # Light Green
    text.size = 100

Editing 3D text properties

The following example modifies the colour, size, text, and location of the Text3D object created in the last example.

from mapteksdk.project import Project
project = Project() # Connect to default project

object_path = "scrapbook/text3d_example"

# Create Text
with project.edit(object_path) as text:
    text.text = 'new text'
    text.location = [50, 50, 50]
    # Colours are R,G,B,A, where A is Alpha (255 = opaque)
    text.colour = [255, 50, 50, 255]
    text.size = 150

Labelling points at a particular height

The following example demonstrates how to create 3D text labels at the points of selected objects in a project, displaying their heights and colouring them based on their vertical position.

from mapteksdk.project import Project
from mapteksdk.data import Text3D
import numpy as np

def colour_by_height(z_coordinates):
    # Sets a basic colour map over points based on height value within height range
    lowest, highest = np.min(z_coordinates), np.max(z_coordinates)
    ratio = 2 * (z_coordinates - lowest) / (highest - lowest)
    b = np.clip((255*(1-ratio)), 0, 255).astype(np.uint8)
    r = np.clip((255*(ratio-1)), 0, 255).astype(np.uint8)
    g = np.clip((255 - b - r), 0, 255).astype(np.uint8)
    a = np.full(z_coordinates.shape, 255, dtype=np.uint8)
    return np.column_stack((r,g,b,a))

project = Project() # Connect to default project
selection = project.get_selected()
for item in selection:
    with project.read(item) as obj:
        if hasattr(obj, 'points'):
            # Append _facet_index_labels/ to the surface path as the container to
            # store the text objects in
            parent_path = f"{obj.id.path}_point_heights"
            for i in range(obj.point_count):
                # For convenience, get z coordinates as an array
                z = obj.points[: ,2]
                # Create colours to assign to each value based on height in range
                colours = colour_by_height(z)
                with project.new_or_edit(f"{parent_path}/{i}", Text3D, overwrite=True) as label:
                    # Position on the point
                    label.location = obj.points[i]
                    # Set label to point z coordinate
                    label.text = str(f"{round(z[i],1)}m")
                    label.size = 3
                    label.colour = colours[i]

Labelling point indices

This following example illustrates how to create 3D text labels at each point of selected objects in a project, displaying the index of each point.

from mapteksdk.project import Project
from mapteksdk.data import Text3D

project = Project() # Connect to default project
selection = project.get_selected()
for item in selection:
    with project.read(item) as obj:
        if hasattr(obj, 'points'): # Support anything with points
            # Append _edge_index_labels/ to the surface path as the container to
            # store the text objects in
            parent_path = f"{obj.id.path}_point_index_labels"
            for i in range(obj.point_count):
                with project.new_or_edit(f"{parent_path}/{i}", Text3D, overwrite=True) as label:
                    # Position on the point
                    label.location = obj.points[i]
                    # Set label to point index
                    label.text = str(i)
                    label.size = 1
                    label.colour = [0, 255, 0, 255]

Labelling edge indices

The following example demonstrates how to create 3D text labels at the centre of each edge in selected objects, displaying the index of each edge with a size proportional to the length of the edge.

from mapteksdk.project import Project
from mapteksdk.data import Text3D
import numpy as np

def edge_distances(edge_lines:np.ndarray) -> np.ndarray:
    # Calculate distance for each edge
    p1, p2 = edge_lines[:, 0, :], edge_lines[:, 1, :]
    line_lengths = np.sqrt((p2-p1)**2)
    return np.sum(line_lengths, axis=1)

project = Project() # Connect to default project
selection = project.get_selected()
for item in selection:
    with project.read(item) as obj:
        if hasattr(obj, 'edges'): # Support lines or surfaces
            # Append _edge_index_labels/ to the surface path as the container to
            # store the text objects in
            parent_path = f"{obj.id.path}_edge_index_labels"
            edge_lines = obj.points[obj.edges]
            edge_lengths = edge_distances(edge_lines)
            edge_centres = np.mean(edge_lines, axis=1)
            for i in range(edge_lines.shape[0]):
                with project.new_or_edit(f"{parent_path}/{i}", Text3D, overwrite=True) as label:
                    # Position in the centre of the edge
                    label.location = edge_centres[i]
                    # Set label to edge index
                    label.text = str(i)
                    # Variably size the text based on the length of the edge
                    label.size = edge_lengths[i] / 10
                    label.colour = [255, 0, 0, 255]

Labelling facet indices

The following example demonstrates how to create 3D text labels at the centre of each facet in selected surface objects, displaying the index of each facet in the facets array with a size proportional to its perimeter.

from mapteksdk.project import Project
from mapteksdk.data import Text3D, Surface
import numpy as np

def triangle_perimeters(triangles:np.ndarray) -> np.ndarray:
    # Calculate perimeter for each triangle
    p1, p2, p3 = triangles[:, 0, :], triangles[:, 1, :], triangles[:, 2, :]
    line_lengths = np.sqrt((p2-p1)**2 + (p3-p2)**2 + (p3-p1)**2)
    return np.sum(line_lengths, axis=1)

project = Project() # Connect to default project
selection = project.get_selected()
for item in selection:
    if item.is_a(Surface):
        with project.read(item) as surface:
            # Append _facet_index_labels/ to the surface path as the container to
            # store the text objects in
            parent_path = f"{surface.id.path}_facet_index_labels"
            triangles = surface.points[surface.facets]
            perimeters = triangle_perimeters(triangles)
            centroids = np.mean(triangles, axis=1)
            for i in range(triangles.shape[0]):
                with project.new_or_edit(f"{parent_path}/{i}", Text3D, overwrite=True) as label:
                    # Position in the centre of the facet
                    label.location = centroids[i]
                    # Set label to facet index
                    label.text = str(i)
                    # Variably size the text based on the perimeter of the facet
                    label.size = perimeters[i] / 15