Running a Script

Running scripts from context menus

The mapteksdk.context_menu package contains functionality designed for scripts run from context menus in the Workbench. It allows for querying the object right clicked on to open the context menu and the location on that object the mouse was over when the context menu was opened.

Getting the object right clicked on

There are two ways to get the object right clicked on to open the context menu: context_object_path() and context_object_id().

The context_object_path() function:

  • Returns the path to the object right clicked on to open the context menu. This can be used to open the right clicked object, but nothing else.

  • It will not raise an error if not connected to an application.

    • Without an application it won’t be useful unless you pass it to another script or program that will or is connected to an application.

  • It will raise an error if the script was not run from a context menu or if there was no object under the mouse when the context menu was opened.

The context_object_id() function:

  • Returns the object ID of the object right clicked to open the context menu. This can be used to query metadata about the object (e.g. its type) and to open the right clicked object.

  • This will raise an error if called before the script connects to an application.

  • It will raise an error if the script was not run from a context menu or if there was no object under the mouse when the context menu was opened.

The following example script uses this to colour the object right clicked on purple.

from mapteksdk.project import Project
from mapteksdk.context_menu import context_object_path
 
PURPLE = [221, 160, 221, 255]
 
if __name__ == "__main__":
  # Call context_object_path() before Project() so that if the script is not
  # called from a context menu, it will encounter an error before it
  # wastes any time attempting to connect to an application.
  path = context_object_path()
  with Project() as project:
    with project.edit(path) as context_object:
      try:
        context_object.point_colours[:] = PURPLE
      except AttributeError:
        pass
      try:
        context_object.facet_colours[:] = PURPLE
      except AttributeError:
        pass
      try:
        context_object.block_colours[:] = PURPLE
      except AttributeError:
        pass

The following animation demonstrates this script:

Getting the location of the object that was right-clicked on

The context_location function returns the location the point the mouse was over when the context menu was opened.

  • The context_location() function will raise an error if the script was not called from a context menu, or if there was no object under the mouse when the context menu was opened.

  • The context_location() function will not raise an error if it is called before connecting to an application.

The following example script creates a label where the mouse was when the context menu was opened:

from __future__ import annotations
 
from mapteksdk.project import Project
from mapteksdk.data import Text2D
from mapteksdk.context_menu import context_object_id, context_location
from mapteksdk.operations import active_view
 
def next_label_name(project: Project, template: str) -> tuple[int, str]:
  """Get the next unused number and path for the next label to create.
 
  Parameters
  ----------
  project
    Project to use to check for objects in.
  template
    A string containing a single "%i". The %i will be replaced with an integer
    until it is a path which does not exist in a project.
 
  Returns
  -------
  tuple
    A tuple where the first element is the number inserted into the
    template string which resulted in an object which did not exist.
    The second element is the path for the next label.
 
  Warnings
  --------
  This function is not thread safe.
  """
  i = 1
  while True:
    result_path = template % i
    if not project.find_object(result_path):
      return i, result_path
    i += 1
 
if __name__ == "__main__":
  with Project() as project:
    oid = context_object_id()
    template = f"{oid.path}_labels/%i"
    i, path = next_label_name(project, template)
    with project.new(path, Text2D) as label:
      label.text = str(i)
      label.location = context_location()
    view = active_view()
    view.add_objects([label.id])

The following animation shows this script in action:

Advanced - Using context object and location

The context menu functions are not mutually exclusive and can be used together, as demonstrated in the following example. This example colours the right clicked object by distance from the mouse cursor when the context menu was opened (Red for close, transitioning to orange and then blue for points far away from the cursor):

from mapteksdk.project import Project
from mapteksdk.data import PointSet, NumericColourMap, ObjectID
from mapteksdk.context_menu import context_location, context_object_id
import numpy as np
 
DISTANCE_FROM_CLICK = "distance_from_click"
 
def calculate_distance_from_point(
    points: np.ndarray, target_point: np.ndarray) -> np.ndarray:
  """Calculate the distance of many points from the target point.
 
  Parameters
  ----------
  points
    The points to calculate their distance from the target point.
    This should be a numpy array of shape (N, 3) where N is the point count.
  target_point
    The target point.
    This should be a numpy array of shape (3,)
 
  Returns
  -------
  ndarray
    The distance of each point in points from the target point.
    This is a numpy array of shape (N,) where N is the number of points
    in points.
  """
  return np.linalg.norm(points - target_point, axis=1)
 
def generate_colour_map(
    project: Project, minimum: float, middle: float, maximum: float
    ) -> ObjectID[NumericColourMap]:
  """Generate a colour map.
 
  The colour map is red at the minimum, orange at the middle and
  blue at the maximum.
 
  Parameters
  ----------
  project
    The Project to use to create the colour map.
  minimum
    The minimum value in the colour map. This will be red in the colour map.
  middle
    The midpoint of the colour map. This should be greater than minimum
    and less than middle. It does not need to be the exact midpoint.
    This will be orange in the colour map.
  maximum
    The maximum value in the colour map. This will be blue in the colour
    map.
 
  Returns
  -------
  ObjectID[ColourMap]
    The object ID of the colour map.
  """
  with project.new(None, NumericColourMap) as colour_map:
        colour_map.ranges = [minimum, middle, maximum]
        colour_map.colours = [
          [255, 0, 0, 255],
          [255, 165, 0, 255],
          [0, 0, 255, 255]
        ]
  return colour_map.id
 
if __name__ == "__main__":
  with Project() as project:
    with project.edit(context_object_id()) as data_object:
      data_object: PointSet
 
      # Calculate the distance of every point in the object from the
      # picked point.
      try:
        distance_from_click = calculate_distance_from_point(
          data_object.points, context_location())
        data_object.point_attributes[DISTANCE_FROM_CLICK] = distance_from_click
      except AttributeError as error:
        raise RuntimeError("This operation only supports objects with points."
          ) from error
 
      minimum = np.min(distance_from_click)
      maximum = np.max(distance_from_click)
      middle = (minimum + maximum) / 2
      # Colour the object by distance from the picked point.
      colour_map_id = generate_colour_map(project, minimum, middle, maximum)
      data_object.point_attributes.set_colour_map(
        DISTANCE_FROM_CLICK, colour_map_id)

The following animation shows this script in action: