3D Text

3D text is a text label at a given location with an orientation and size in 3D space that are maintained when zooming in and out. That is, it behaves like a 3D object.

The Text3D class represents a single 3D text label.

Text3D properties

All properties for 2D Text objects are applicable to 3D text objects. Additionally, use these properties to customise the look of your 3D text object:

direction

Set the direction of the label in space. The direction is defined from the start of the first character to the end of the last character. This property is 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 in front of the label.

facing

Use this enum property to set where the text is facing. It can be set to the following values:

Creating a Text3D object

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

object_path = "scrapbook/text3d_example"

# Create Text
with proj.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 Text3D properties

The following example will modify the colour, size, text, and position of the text object created in the last example.

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

object_path = "scrapbook/text3d_example"

# Create Text
with proj.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 will add 3D text at each point, with its height, in any selected object that has point primitives.

from mapteksdk.project import Project
from mapteksdk.data import Text3D, Surface
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))

proj = Project() # Connect to default project
selection = proj.get_selected()
for item in selection:
    with proj.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 proj.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

The following example will add 3D text at each point, with its index position in the points array in any selected object that has point primitives.

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

proj = Project() # Connect to default project
selection = proj.get_selected()
for item in selection:
    with proj.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 proj.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 will add 3D text at the centre of each edge, with its index position in the edges array in any selected object that has edges primitives.

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)
    
proj = Project() # Connect to default project
selection = proj.get_selected()
for item in selection:
    with proj.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 proj.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 will add 3D text at the centre of each facet, with its index position in the facets array in any selected Surface.

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)
    
proj = Project() # Connect to default project
selection = proj.get_selected()
for item in selection:
    if item.is_a(Surface):
        with proj.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]
            perimiters = triangle_perimeters(triangles)
            centroids = np.mean(triangles, axis=1)
            for i in range(triangles.shape[0]):
                with proj.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 = perimiters[i] / 15