Working with Projects

When you open a Maptek application such as GeologyCore or PointStudio, you need to select or create a project to work in. A project serves as a local repository of data that you interact with during your session. When writing a script using the SDK to operate on your data, one of the first steps is to connect to a project that is open in a running application instance. This page explains how to do this and describes common ways of interacting with a project.

Connecting to a running application

The Project class represents a connection to a running application.

The simplest way to connect to an application is to call the Project constructor without any arguments, as shown in the example below.

from mapteksdk.project import Project
project = Project() # Connect to default project (whichever application is running)

The code above connects to the most recently opened instance of a Maptek application, such as PointStudio, BlastLogic, Evolution, or GeologyCore. When the script finishes, it will automatically disconnect from the application.

The Project class also supports being used as a context manager using a with block. This causes the script to disconnect from the application when the with block ends. This allows for more precise control of when the script disconnects from the application, as shown in the example below.

from mapteksdk.project import Project

with Project() as project:
    pass
# The script will automatically disconnect from the project
# when the with block ends.

Default connection behaviour

By default, the Project constructor connects to the most recently opened compatible application. To ensure you always connect to the desired application, it is recommended to have only one compatible application open when running scripts.

For instructions on selecting a specific application to connect to, see Selecting which application to connect to on this page.

Basic operations

The examples above demonstrate how to connect to an application. Once a script is connected to an application, it can access and manipulate data within that application using functions provided by the Project object.

Adding objects

To add an object to a container in the project, use the Project.add_object() method.

Adding multiple objects at once

The Project.add_objects() method is similar to Project.add_object(), but is more efficient at adding many objects to the same container. Thus:

The Project.add_objects() method accepts the following arguments:

  • The path to the container where the objects should be added.

  • The names and objects to add to the container, provided as a list of (name, object) tuples or a dictionary where keys are names and values are the objects to add.

  • A boolean overwrite parameter indicating whether existing objects should be overwritten (defaults to False).

The example below demonstrates how to use Project.add_objects() to efficiently handle cases where thousands of objects might be created.

from __future__ import annotations
 
from mapteksdk.project import Project
from mapteksdk.data import (
  Text2D, HorizontalAlignment, VerticalAlignment, ObjectID)
from mapteksdk.operations import(
  object_pick, active_view, show_message, Severity)
import numpy as np
 
def label_points(
    project: Project, points: np.ndarray) -> dict[str, ObjectID[Text2D]]:
  """Creates labels for the list of points.
 
  This creates one Text2D object in the project for each point in the points
  array with its location set to the point and its text set to that point's
  index in the array.
 
  Parameters
  ----------
  project
    The Project to use to create the labels.
  points
    Numpy array of shape (N, 3) where N is the point count. These are the
    points to create labels at.
 
  Returns
  -------
  dict[str, ObjectID[Text2D]]
    A dictionary where the key is the name of each label and the value
    is the object id of each label. Each label is an orphan. It is the
    caller's responsibility to add them to the Project.
  """
  labels: dict[str, ObjectID[Text2D]] = {}
  for i, point in enumerate(points):
    # A path of None means each label is created as an orphan.
    with project.new(None, Text2D) as label:
      label.location = point
      label.text = str(i)
      label.horizontal_alignment = HorizontalAlignment.CENTRED
      label.vertical_alignment = VerticalAlignment.CENTRED
      labels[f"{i}"] = label.id
  return labels
 
if __name__ == "__main__":
  project = Project()
 
  oid = object_pick(label="Pick an object to label the points of.")
  path = oid.path
  with project.read(oid) as data_object:
    if not hasattr(data_object, "points"):
      show_message(
        "Error", "The picked object does not have points", Severity.ERROR)
    else:
      labels = label_points(project, data_object.points)
 
  if labels:
    # Now that every label has been created, add them all at once.
    # This will greatly improve the performance if the object has
    # many thousands of points.
    project.add_objects(f"{path}_labels", labels, overwrite=True)
    view = active_view()
    view.add_objects(labels.values())

Renaming objects

The Project.rename_object() method can be used to rename an object in an application. The method takes two arguments: the path to the object to rename and the new path for the object.

The example below shows how to rename the object object_to_rename to new_name.

import sys
from mapteksdk.project import Project, ObjectDoesNotExistError


# It is possible for an object to exist with multiple paths in a project,
# so the Project().rename() function is used for renaming and/or moving an object.


with Project() as project:
    path = "scrapbook/surfaces/Pit model"
    try:
        project.rename_object(path, "Pit model 2", overwrite=True)
        found_at = project.find_object("/scrapbook/surfaces/Pit model 2")
        if found_at is None:
            print("Copy operation failed.")
            sys.exit(0)
        print(f"Object now called: {found_at.name}")
        project.rename_object(found_at, "Pit model")
        print(f"Object renamed back to: {found_at.name}")
    except ObjectDoesNotExistError:
        print(f"Object to copy not found at: {path}")


    # Example output:
    # Object now called: Pit model 2
    # Object renamed back to: Pit model

If no object is found at the specified path, the script will raise an ObjectDoesNotExistError.

Creating a new object

To create a new object in the connected application, use the Project.new() method. This method takes two arguments, as follows:

  • object_path: The path in the project where the new object will be placed.

  • object_class: The type of object to create. See the mapteksdk.data module documentation for a complete list of supported types.

The example below demonstrates creating a square in the XY plane, centred at the origin with an edge length of two.

from mapteksdk.project import Project
from mapteksdk.data import Polygon

project = Project()

with project.new("cad/square", Polygon) as square:
    # See Data section for an explanation of what this means:
    square.points = [[-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]]
# The square will appear in the application when the with block ends.

The Project.new() method should always be called within a with block. The properties of the new object are set within the with block. See the API Reference for a type to see which properties it supports.

The new object is not saved and will not appear in the application until the with block ends. If an error occurs before the with block ends, the object will not be saved.

Reading an existing object

To read the properties of an existing object in the application, use the Project.read() method.

Project instances also support reading the properties of existing objects in the application. This is done via the Project.read() method. Unlike Project.new(), Project.read() accepts a single parameter that specifies the path to (or ObjectIdT of) the object to read. The SDK automatically determines the type of the read object.

The example below shows how to read a square created earlier and print its points:

from mapteksdk.project import Project

project = Project()

with project.read("cad/square") as read_square:
    print(read_square.points)

Project.read() is useful to query the properties of an object without editing it. Attempting to edit the object will either raise an error or be ignored.

Editing an existing object

To edit an existing object, you can use the Project.edit() method. This method accepts the path of the object to edit (or its ObjectIdT), determines its type automatically, and allows its properties to be modified.

For example, if we have a square object stored at cad/square and want to move it by 1 metre in the X direction:

from mapteksdk.project import Project

project = Project()

with project.edit("cad/square") as edit_square:
    edit_square.points[:, 0] += 1
# The changes are saved when the with block ends.

Editing an object or creating it if it does not exist

Project.new() will raise an error if there is already an object at the specified path. In cases where you want to edit an object if it exists or create it if it doesn't, use the Project.new_or_edit() method. If there is no object at the specified path, it will create a new object. If there is an object at that path matching the specified type, the existing object is opened for editing.

In the example below, we use Project.new_or_edit() to change the points of the polyline created previously to a diamond centred at the origin. If the object is deleted and the example is run, it will instead create a new polyline representing a diamond centred at the origin.

from mapteksdk.project import Project
from mapteksdk.data import Polyline

project = Project()

with project.new_or_edit("cad/square", Polyline) as polyline:
  polyline.points = [[-1, 0, 0], [0, 1, 0], [1, 0, 0], [0, -1, 0]]

Overwriting objects

The Project.new_or_edit() method will raise an error if the object at the specified path is of a different type than the given type. Therefore, if you wish to place an object of a different type at a path, you need to delete the existing object rather than edit it. To avoid this problem, you can use the Project.new() method instead and pass the overwrite argument, as demonstrated in the example below.

from mapteksdk.project import Project
from mapteksdk.data import Polyline

project = Project()

with project.new("cad/square", Polyline, overwrite=True) as new_line:
    new_line.points = [[-1, 0, 0], [0, 1, 0], [1, 0, 0], [0, -1, 0]]

Filtering types for Project.read() and Project.edit()

It is possible to pass the expected type of the object to Project.read() and Project.edit(). If the object is not of the specified type, a TypeMismatchError will be raised. This is useful to ensure that the correct type is being read or edited.

The following example shows how to specify that an object to be read and edited is a surface.

from mapteksdk.project import Project
from mapteksdk.data import Surface

if __name__ == "__main__":
    path_to_surface = "path/to/surface"
    with Project() as project:
        with project.read("path/to/surface", Surface) as surface:
            # surface is known to be a Surface in this with block.
            pass

        # This works for both read and edit.
        with project.edit(path_to_surface, Surface) as edit_surface:
            pass

Because the object type is known in the with block, this allows interactive development environments to provide better autocomplete suggestions than would be possible in a with block with no type specified. For example, in the following animation Visual Studio Code provides autocomplete suggestions for the surface having points and facets, but not for blocks.

Specifying the type is useful if the following are true:

  • The script knows that the object must be of a specified type.

  • The script should fail quickly with an error if the object to open is not of the expected type. The type mismatch error makes it very clear that the problem is the object is the wrong type.

    For example, consider a script that performs a lengthy calculation on the points of a surface and then another lengthy calculation on the facets of a surface. If such a script was run on a PointSet, then it wouldn’t fail until the second lengthy calculation, wasting time and computation power. By checking that the object is a surface at the beginning, this waste can be avoided.

Note
  • If the object is not of the specified type, a TypeMismatchError will be raised. This error can be caught to trigger different behaviour if the object is of the wrong type.

  • Specifying a type should cause autocomplete suggestions for that type to appear in the with block in an interactive development environment (IDE) such as VS Code.

  • When opening an object multiple times, it is more robust to pass the ObjectID rather than a path to Project.edit() and Project.read().

  • If you provide an ObjectID to Project.read() or Project.edit(), the IDE will remember the type of the object the ObjectID came from and provide autocomplete suggestions based on that type.

    • However, unlike explicitly specifying the type with Project.read() or Project.edit(), using an ObjectID does not perform any runtime checks to confirm the object is of the expected type (and it may actually be of a different type).

    • As a best practice, the first call to Project.edit() or Project.read() for an object should specify the type (unless the type is already established from Project.new()). Subsequent calls can then use the ObjectID.

Recycle bin

The Project.recycle() method moves the specified object to the recycle bin. If an object with the same name already exists in the recycle bin, a numerical suffix will be added to the new object to maintain uniqueness.

This functionality is particularly useful for making changes to an object while retaining access to its original version. For instance, you can create a copy of a user’s object before making modifications and either keep it in the same container or move it to the recycle bin, providing the user with a fallback option.

When recycling objects from a selection, caution is advised. A selection may include a container and its child objects. Attempting to recycle each object individually will result in the loss of hierarchy, as the child objects will no longer be nested within their container in the recycle bin. To maintain the hierarchy, querying the roots of a selection can be beneficial, as shown in the example below.

from mapteksdk.project import Project
    project = Project()
    selection = project.get_selected()
    for object_to_recycle in selection.roots:
        project.recycle(object_to_recycle)

Other Project operations

For information on other operations available in the package, see mapteksdk.project in the API reference.

Overwrite parameter

Overwriting objects

A common problem scripts face when creating new objects is handling situations where an object already exists at the destination path. For example, consider the following example, which creates a tetrahedron at the path surfaces/tetrahedron:

from mapteksdk.project import Project, OverwriteMode
from mapteksdk.data import Surface, ObjectID
from mapteksdk.operations import open_new_view


def create_tetrahedron_with_undo(project: Project, path: str) -> ObjectID[Surface]:
    """Create a tetrahedron which can be undone.


    Parameters
    ----------
    project
      Project to create the tetrahedron surface in.
    path
      The path to create the tetrahedron at. If there is already an object
      with this path the path will be postfixed with the smallest possible
      number to ensure that an existing object is not overwritten.
    """
    with project.undo():
        with project.new(path, Surface) as tetrahedron:
            size = 3
            tetrahedron.points = [
                [-size, -size, -size], [-size, size, size],
                [size, -size, size], [size, size, -size]]
            tetrahedron.facets = [[0, 1, 2], [0, 2, 3], [0, 1, 3], [1, 2, 3]]
    return tetrahedron.id


if __name__ == "__main__":
    main_project = Project()
    oid = create_tetrahedron_with_undo(main_project, "surfaces/tetrahedron")
    open_new_view([oid])

When this script is executed multiple times, it will append a number to the name to ensure that each tetrahedron has a unique identifier:

A warning on re-opening

If you set the overwrite parameter to OverwriteMode.UNIQUE_NAME and you need to re-open the object within the same script, then you must not re-open the object using the path. Consider the following script that demonstrates the issue:

from mapteksdk.project import Project, OverwriteMode
from mapteksdk.data import Surface, ObjectID
from mapteksdk.operations import open_new_view


def create_tetrahedron_with_undo(project: Project, path: str) -> ObjectID[Surface]:
    """Create a tetrahedron which can be undone.


    Parameters
    ----------
    project
      Project to create the tetrahedron surface in.
    path
      The path to create the tetrahedron at. If there is already an object
      with this path the path will be postfixed with the smallest possible
      number to ensure that an existing object is not overwritten.
    """
    with project.undo():
        with project.new(path, Surface, overwrite=OverwriteMode.UNIQUE_NAME
                ) as tetrahedron:
            size = 3
            tetrahedron.points = [
                [-size, -size, -size], [-size, size, size],
                [size, -size, size], [size, size, -size]]
            tetrahedron.facets = [[0, 1, 2], [0, 2, 3], [0, 1, 3], [1, 2, 3]]


        with project.edit(path, Surface) as edit_tetrahedron:
            edit_tetrahedron.facet_colours = [220, 165, 0, 255]
    return tetrahedron.id


if __name__ == "__main__":
    main_project = Project()
    oid = create_tetrahedron_with_undo(main_project, "surfaces/orange_
tetrahedron")
    open_new_view([oid])

This script is similar to the previous one, except it re-opens the tetrahedron and colours it orange. If you run the script twice, the first tetrahedron will appear as follows:

and the second tetrahedron appear as follows:

You will notice that the script fails to colour the second tetrahedron orange and instead leaves it in the default green. This behaviour becomes apparent when you carefully follow the sequence of both script runs:

  1. The first run of the script creates the first tetrahedron and places it at surfaces/tetrahedron.

  2. The first run of the script then opens the object at surfaces/tetrahedron and colours it orange.

  3. The second run of the script creates the second tetrahedron and places it at surfaces/tetrahedron 1

  4. The first run of the script then opens the object at surfaces/tetrahedron and colours it orange.

Postfixing the name with a number to make it unique changed the path, so opening the object with the path given to the method opened the object made by the first run of the script rather than the second. To correct this issue, the object must be opened for edit with its ObjectID:

with project.edit(tetrahedron.id, Surface) as edit_tetrahedron:
    edit_tetrahedron.facet_colours = [220, 165, 0, 255]

Other functions that support overwrite

The Project.new() method is not the only method that supports the overwrite parameter; other methods within the Project class that support it include:

The overwrite parameter has the same effect in all functions, as detailed in the table below:

Value of overwrite parameter Resulting behaviour
False Raises an error if an object would be overwritten.

OverwriteMode.ERROR

True Silently overwrites existing objects.

OverwriteMode.OVERWRITE

OverwriteMode.UNIQUE_NAME

If an object already exists at the destination path, appends the smallest possible unique number to ensure the name is unique.

Compatibility Note

The OverwriteMode enum was added in mapteksdk 1.6. Earlier versions only support setting the overwrite parameter to True or False.

Object IDs vs paths

The previous examples refer to objects in the project using their object paths. However, relying solely on paths can be problematic, as the script will fail if an object is renamed. To address this issue, most functions that accept paths also support using an ObjectID. An ObjectID uniquely identifies a single object within a project and remains constant, even if the object is renamed or moved. By using an ObjectID, a script can reliably access the intended object regardless of changes to its path.

The example below demonstrates how to refer to an object that has been renamed, using its ObjectID:

from mapteksdk.project import Project
from mapteksdk.data import Polyline

project = Project()

with project.new("cad/square", Polyline, overwrite=True) as new_line:
    new_line.points = [[-1, 0, 0], [0, 1, 0], [1, 0, 0], [0, -1, 0]]

Choosing whether to pass a path or an ObjectID to a function depends on the context. Consider the following factors when making your decision:

  • Project.new() does not support taking an ObjectID and must always be provided with a path.

  • An ObjectID only uniquely refers to an object within the context of a single Maptek project database (.maptekdb). It should never be hard-coded into scripts or saved to files.

  • If an object needs to be accessed multiple times within a script, it is generally advisable to create or retrieve the object initially using its path, then use the ObjectID for all subsequent accesses.

  • Project.get_selected() returns a list of object IDs so there is no need to use the path for selected objects.

The example below demonstrates how to convert a path into an ObjectID without using Project.edit() or Project.read(). It also shows how to extract the path to an object from its ObjectID.

from mapteksdk.project import Project

project = Project()

object_id = project.find_object("cad/points")

if object_id:
  print(f"The object id refers to the object at {object_id.path}")

An invalid ObjectID will evaluate to False. This can be used to check for errors.

Examples

The following examples assume you have imported sample data Maptek_PythonSDK_SampleData_20200101.maptekobj. You can import this file using one of the following methods:

  • Within an application: In PointStudio or GeologyCore, go to HomeImport (or FileImport).
  • Using the SDK: Use the first example below, Importing a Maptek object file.

All examples were created using Maptek PointStudio 2020 or later versions.

Importing a Maptek object file

This example demonstrates how to import the provided sample dataset, Maptek_PythonSDK_SampleData_20200101.maptekobj, into your current project. The code snippet below replicates the outcome of importing the data through Home > Import within the application.

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

# Use the path you have saved Maptek_PythonSDK_SampleData_20200101.maptekobj
path_to_maptekobj = "F:/Python SDK Help/Maptek_PythonSDK_SampleData_20200101.maptekobj"

# This will import the maptekobj file which may contain an arbitrary
# number of objects within. These will not automatically be assigned
# a project path.
object_id = io.import_maptekobj(path_to_maptekobj)

# Assign all of the imported data to a path
for id in project.get_children(object_id).ids():
    # Move each child found in the imported data into the scrapbook container
    # Project().rename_object can be used for moving objects
    project.rename_object(
        id, # The ObjectID representing the current object
        "scrapbook/{}".format(id.name), # New path to assign
        overwrite=True # Replace any existing data at this path
    )

After running the script, check that the contents of the project have been imported.

Checking the existence of an object

The snippet below demonstrates how to verify whether an object exists. If the object is found, a valid ObjectID is returned.

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

# The Project().find_object() function will return an
# ObjectID if an object exists. If it doesn't, None
# will be returned.
if project.find_object("/scrapbook/surfaces/Pit model"):
    print("it exists!")

# Expected output:
# >>> it exists!

Checking the existence of a container

A container in a Maptek project is similar to a folder in your file system. It can contain sub-containers and objects. The following snippet checks if the data from the example above has been successfully imported into /scrapbook/scans and /scrapbook/surfaces.

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

scans_container = project.find_object("/scrapbook/scans")
# Verify it is a VisualContainer using the ObjectID.is_a() function
if scans_container and scans_container.is_a(VisualContainer):
    print("/scrapbook/scans is a VisualContainer!")

surfaces_container = project.find_object("/scrapbook/surfaces")
# Verify it is a VisualContainer using the ObjectID.is_a() function
if surfaces_container and surfaces_container.is_a(VisualContainer):
    print("/scrapbook/surfaces is a VisualContainer!")

Listing the contents of a container

Since a container can contain an arbitrary number of objects, it is often useful to list its contents. The following snippet lists the names of the objects located directly under /scrapbook/scans/set1.

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

for name in project.get_children("scrapbook/scans/set1/").names():
    print("name: {}".format(name))

# Expected output:
# >>> name: points1of4
# >>> name: points2of4
# >>> name: points3of4
# >>> name: points4of4

Listing all objects in all containers

The previous example demonstrated the Project.get_children() method, which retrieves the direct children within a container. Another useful method, Project.get_descendants(), retrieves all children within all sub-containers from a specified starting point. When specifying a path, using "" refers to the root of the project. In this example, "scrapbook" will be used to obtain all objects imported earlier.

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

for child_name, child in project.get_descendants("scrapbook"):
    print("name: {}, path: {}, type: {}".format(
        child_name,
        child.path, # ObjectID.path property.
        child.type_name # The name of type of object.
    ))

# Project().get_children() and Project().get_descendants() return
# a ChildView (mapteksdk.data.ChildView) type.
# This can be used like:
#    for name, id in proj.get_descendants("path" or ObjectID)
#       (name is a string, id is an ObjectID)
# or
#    for id in proj.get_descendants().ids()
#       (results as list of ObjectID)
# or
#    for name in proj.get_descendants().names()
#       (results as list of string)

 

Listing only containers or objects of a given type

The example above lists all contents. However, you may want to retrieve only specific types of objects, such as PointSet objects under a certain path or VisualContainer objects within the project. This can be achieved by applying a type check, as shown in the snippet below.

from mapteksdk.project import Project, Selection
from mapteksdk.data import VisualContainer, PointSet, ObjectID


with Project() as project:
    print("List of all VisualContainer objects in project with count of children")
    selection = Selection(project.get_descendants().ids()).where([VisualContainer])
    for container in selection:
        container: ObjectID[Selection]
        child_count = len(project.get_children(container))
        print(
            f"Path: {container.path}, count of children: {child_count}"
        )


    print("\nList of all PointSet objects in project")
    point_set_selection = Selection(project.get_descendants().ids()).where([PointSet])
    for point_set_id in point_set_selection:
        point_set_id: ObjectID[PointSet]
        print(f"Path: {point_set_id.path}")

Listing contents of a selection

The Maptek Python SDK allows you to query the objects currently selected by the user. This is useful when you need the user to select an object of a specific type to perform an operation.

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

# Ensure user has made a selection
selection = project.get_selected()
while len(selection) == 0:
    input("Please selection one or more objects and press any key to continue.")
    selection = project.get_selected()

# get_selection() returns a list of ObjectID
# ObjectIDs are useful for getting names, paths, parents, icon names
# and use within several project functions

print("You have selected these objects:")
for item in selection:
    print("  name: {}, path: {}, type: {}, icon name: {}".format(
        item.name,
        item.path,
        item.type_name,
        item.icon_name))

# Example output:
# >>> You have selected these objects:
# >>>  name: Pit model toes and crests, path: /scrapbook/surfaces/Pit model toes and crests, type
# >>>  name: legends, path: /legends, type: VisualContainer, icon name: StandardContainerClosed
# >>>  name: actual toes and crests, path: /scrapbook/surfaces/Pit model toes and crests/actual tork
# >>>  name: Pit model, path: /scrapbook/surfaces/Pit model, type: Surface, icon name: FacetNetwo
# >>>  name: points3of4, path: /scrapbook/scans/set1/points3of4, type: PointSet, icon name: Point

Listing a specific type from a selection

The following example filters the user’s selection to include only PointSet and Surface objects.

from mapteksdk.project import Project


# Import some data types to work with for this example:
from mapteksdk.data import Surface, PointSet


if __name__ == "__main__":
    with Project() as main_project:
        # Filter existing selection to PointSet and Surface objects
        selection = main_project.get_selected().where([PointSet, Surface])


        print("You have selected the following PointSet and Surface objects:")


        for item in selection:
            print(f"  Name: {item.name}, Path: {item.path}, Type: {item.type_name}")

# Example output:
# >>> Please make a selection of the following types and press any key to continue: PointSet,Surface
# >>> You have selected the following PointSet, Surface objects:
# >>>  Name: Pit model, Path: /scrapbook/surfaces/Pit model, Type: Surface
# >>>  Name: points1of4, Path: /scrapbook/scans/set1/points1of4, Type: PointSet
# >>>  Name: points2of4, Path: /scrapbook/scans/set1/points2of4, Type: PointSet
# >>>  Name: points3of4, Path: /scrapbook/scans/set1/points3of4, Type: PointSet
# >>>  Name: points4of4, Path: /scrapbook/scans/set1/points4of4, Type: PointSet

Accessing ObjectID properties

The ObjectID class is used extensively throughout the SDK and can be used in many functions that also accept a path string to an object. Instances of ObjectID are typically returned when querying the Project class for object information or selections. Using ObjectID is particularly useful when objects are assigned multiple paths or no path at all (orphan objects).

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

path = "scrapbook/surfaces/Pit model"
example_object = project.find_object(path)
# If the object has been found it will be returned as an ObjectID
# ObjectID <mapteksdk.data.ObjectID>
if example_object:
    print("Object properties for {}:".format(path))
    print(("  Name: {}\n  Icon name: {}\n  Path: {}\n  Parent path: {}"
           "\n  String ID: {}\n  Handle: {}\n  Is hidden: {}\n  Type name: {}"
           "\n  Exists: {}\n  Is orphan: {}").format(
        example_object.name,
        example_object.icon_name,
        example_object.path, # Note: Primary path only if it has multiple
        example_object.parent.path,
        example_object,
        example_object.native_handle,
        example_object.hidden, # Note: use of hidden objects must be explicitly enabled
        project.type_name(example_object),
        example_object.exists,
        example_object.is_orphan # Note: An orphan is an object without a path
        ))
else:
    print("{} not found".format(path))

# Example output:
# >>> Object properties for scrapbook/surfaces/Pit model:
# >>>  Name: Pit model
# >>>  Icon name: FacetNetwork
# >>>  Path: /scrapbook/surfaces/Pit model
# >>>  Parent path: /scrapbook/surfaces
# >>>  String ID: OID(I27, C0, T146)
# >>>  Handle: 41095346599755803
# >>>  Is hidden: False
# >>>  Type name: Surface
# >>>  Exists: True
# >>>  Is orphan: False

Opening an object for read-only access

The following example demonstrates how to open an existing object for reading using the Project.read() method with context management.

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

path = "scrapbook/surfaces/Pit model"
# Project().find_object() will return an ObjectID if found
if project.find_object(path):
    # Project().read(path or ObjectID) provides read-only access to an object.
    # Attempting changes on read-only arrays will result in exceptions.
    with project.read(path) as surface:
        print("Point count: {}\nFacet count: {}\nEdge count: {}".format(
            surface.point_count,
            surface.facet_count,
            surface.edge_count
        ))

# Example output:
# Point count: 64268
# Facet count: 127778
# Edge count: 192047

Opening an object for read and write

The following example demonstrates how to open an existing object for editing using the Project.edit() method with context management. In this example, half of the points within the surface are coloured red.

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

path = "scrapbook/surfaces/Pit model"
if project.find_object(path):
    # Project().edit(path) provides edit access to an object with context management.
    # Once leaving he context of the 'with' statement, the object is saved.
    with project.edit(path) as surface:
        # Make half of the points on surface red [Red,Green,Blue,Alpha]
        first_half = math.floor(surface.point_count / 2)
        surface.point_colours[first_half:] = [255, 0, 0, 255]

Creating a new object and opening for read and write

The following example demonstrates how to create a new object of a specific type in the project using the Project.new() method. This example creates a Polyline object, sets all of its edges to blue, and stores it under /cad/zigzag line.

from mapteksdk.project import Project
from mapteksdk.data import Polyline
import numpy as np
project = Project() # Connect to default project

path = "/cad/zigzag line"
# Project().new(path, Type) provides creation and edit access to a new object
# of the specified type with context management. The object is stored in the
# project when exiting the scope of the with statement.
with project.new(path, Polyline, overwrite=True) as zigzag:
    # Create a blue zigzag line
    zigzag.points = np.array([[0, 0, 0], [10, 10, 0], [20, 0, 0], [30, 10, 0],
                              [40, 0, 0], [50, 10, 0]])
    zigzag.point_colours = [0,0,255]

if project.find_object(path):
    print("Saved {}".format(path))

Renaming an object

To rename an object, use the Project.rename_object() method. This requires providing the absolute path to the object and specifying the new name, either as a new absolute path or by name only, as demonstrated in the example below.

In this example, the object located at scrapbook/surfaces/Pit model model is renamed Pit model 2, and then changed back to its original name.

import sys
from mapteksdk.project import Project, ObjectDoesNotExistError


# It is possible for an object to exist with multiple paths in a project,
# so the Project().rename() function is used for renaming and/or moving an object.


with Project() as project:
    path = "scrapbook/surfaces/Pit model"
    try:
        project.rename_object(path, "Pit model 2", overwrite=True)
        found_at = project.find_object("/scrapbook/surfaces/Pit model 2")
        if found_at is None:
            print("Copy operation failed.")
            sys.exit(0)
        print(f"Object now called: {found_at.name}")
        project.rename_object(found_at, "Pit model")
        print(f"Object renamed back to: {found_at.name}")
    except ObjectDoesNotExistError:
        print(f"Object to copy not found at: {path}")


    # Example output:
    # Object now called: Pit model 2
    # Object renamed back to: Pit model

Moving an object within a project

The Project.rename_object() method can be used not only to rename objects but also to move them. This dual functionality arises because an object may have multiple paths. Thus, when moving an object, you are essentially reassigning one of its destination paths (though it typically has only one).

In this example, an existing object is moved to a new location and then returned to its original parent container.

from mapteksdk.project import Project


# The main difference is to move an object, you define an absolute path
# in the 'new_name' parameter.
old_path = "scrapbook/surfaces/Pit model"
new_path = "/surfaces/Pit model"


with Project() as project:
    project.rename_object(old_path, new_path)
    print(f"Object now exists here: {new_path}")
    project.rename_object(new_path, old_path)
    print("Object moved back to: {}".format(old_path))

Copying an object

The Project.copy_object() method provides an efficient way to make copies of objects within the project.

from mapteksdk.project import Project


with Project() as project:
    path = "/scrapbook/surfaces/Pit model"
    copy_to = "/scrapbook/surfaces/copies/Pit model (copy)"
    item_to_copy = project.find_object(path)
    if item_to_copy:
        # Project().copy_object() allows convenient copying of project objects
        # by using a path or ObjectID as source and a path as destination.
        # An ObjectID for the new copy is returned from the function.
        copied = project.copy_object(item_to_copy, copy_to, overwrite=True)
        print("Made a copy of \n '{}'\n to '{}'".format(item_to_copy.path, copied.path))
    else:
        raise RuntimeError(f"Source object not found at: {path}")


    # Example output:
    # >>> Made a copy of
    # >>>  '/scrapbook/surfaces/Pit model'
    # >>>  to '/scrapbook/surfaces/copies/Pit model (copy)'

Deleting an object

The Project.delete() method can be used to delete objects from the project.

from mapteksdk.project import Project


with Project() as project:
    path = "scrapbook/surfaces/Pit model"
    item_to_delete = project.find_object(path)
    # Project().delete() allows deletion of an object. Note that
    # if deleting a container, you may leave 'orphan' child objects in the
    # project that no longer have a parent path available.


    if item_to_delete:
        # Temporarily copy the object to put back when finished.
        copy = project.copy_object(path, path + "(temp copy)")


        project.delete(path)
        deleted_exists = project.find_object(path) != None
        print(f"Verify if anything found at path {path}? {deleted_exists}")


        if project.rename_object(copy, "Pit model"):
            print(f"Restored original {copy.path}")
    else:
        raise RuntimeError(f"Source object not found at: {path}")


    # Example output:
    # >>> Verify if anything found at path scrapbook/surfaces/Pit model? False
    # >>> Restored original scrapbook/surfaces/Pit model

Deleting the contents of a container

The Project.delete_container_contents() method can be used to clear all objects within a container. The example below first creates some copies of data into /surfaces/, then deletes it.

from mapteksdk.project import Project


with Project() as project, project.undo():
    # WARNING: This will delete everything in your surfaces container. Run
    # this script at your own risk (But you can undo it).
    project.delete_container_contents("surfaces")

Determining object type

Within the project contents, you may encounter objects of various types. Relying on an object's name alone is not a reliable method for inferring its type. The following example demonstrates how to query the type of the data you are interacting with.

from mapteksdk.project import Project


with Project() as project:
    selection = project.get_selected()
    if selection:
        for item in selection:
            print(f"'{item.path}' is a {item.type_name}, displayed with icon: {item.icon_name or 'Unknown'}")


            try:
                with project.read(item) as reader:
                    pass
            except TypeError:
                print("  - Note: This object is not yet supported properly by the SDK")
    else:
        print("You have not selected anything.")


    # Example output:
    # >>> '/scrapbook/other types/legend' is a NumericColourMap, displayed with icon: ColourMapNumeric1D
    # >>> '/surfaces' is a StandardContainer displayed with icon: StandardContainerClosed
    # >>> '/cad/zigzag line' is a Polyline, displayed with icon: EdgeChain
    # >>> '/scrapbook/surfaces/Pit model toes and crests/actual toes and crests' is a EdgeNetwork, displayed with icon: EdgeNetwork
    # >>> '/scrapbook/other types/ellipsoid' is a Ellipsoid, displayed with icon: Ellipsoid
    # >>>  - Note: This object is not yet supported properly by the SDK
    # >>> '/scrapbook/surfaces/Pit model' is a Surface, displayed with icon: FacetNetwork
    # >>> '/scrapbook/scans/set1/points2of4' is a PointSet, displayed with icon: PointSet
    # >>> '/scrapbook/other types/3d text' is a Text3D, displayed with icon: Text3D

Checking if an object is supported by the SDK version

Maptek databases support hundreds of data types. Not all of these are available for creation and modification through the Maptek Python SDK, even if they can be seen, copied, renamed or deleted within the project. Before working with an object, it may be necessary to verify that it is fully supported by your version of the SDK, as well as any dependencies or applications you are using.

from mapteksdk.project import Project
from mapteksdk.data import ObjectID


# As this SDK works across multiple products and a project may contain a
# significant number of data types yet to be exposed via the SDK or
# available in certain contexts. Therefore some objects that can be
# found may not be supported directly for reading, writing or editing.


# Via Project() functions these objects can be moved/renamed, etc, but
# will be unsupported by read(), edit() and new()


# Objects that are unsupported will raise TypeError when they are opened for
# read or edit. There is no way to create a new object of an unsupported type
# as there is is no type to refer to.




def is_read_supported(project: Project, item: ObjectID) -> bool:
    """Check if reading the object is supported.


    This does not indicate whether editing the object is supported.
    And this does not indicate whether any useful information can
    be read from the object.


    Parameters
    ----------
    project
      Project to use to check if the object is readable.
    item
      The ObjectID of the object to check if it is readable.


    Returns
    -------
    bool
      True if the object can be opened for reading, False if it cannot.
    """
    try:
        with project.read(item):
            return True
    except TypeError:
        return False




with Project() as project:
    for oid in project.get_selected():
        if is_read_supported(project, oid):
            print(
                f"{oid.path} is supported by the SDK and the current application."
            )
        else:
            print(
                f"{oid.path} is either not supported for reading or editing by the SDK"
                " or the reference DLLs used in this context."
            )

Note:  If you have a use case or need for additional data not yet supported, please contact Maptek Support. We are actively developing to expand SDK support for more data types. While the underlying architecture includes several hundred data types, not all are generally applicable in this context.

Selecting which application to connect to

The default Project constructor always connects to the most recently opened application. Support for choosing which application a Python script should connect to is still in development. At present, the only method available for this is Project.find_running_applications(), which returns a list of running Maptek applications. These can be passed to the existing_mcpd argument of the Project constructor to ensure the script will connect to the specified application. In the example below, we show a simple script that requests the user to select which running application to connect to (if there is more than one application running) and then prints every object outside a container in the application.

"""Script which lists all running applications and asks the user to
select one of them. The script will connect to the application and
print all of the top-level objects contained in it.

If only one application is running, it will skip the application
selection step and connect to the application.

"""

from mapteksdk.project import Project

# Get a list of running applications.
applications = Project.find_running_applications()

for i, application in enumerate(applications):
  print(f"{i} - {application.bin_path}")

if len(applications) == 1:
  print("Only one application running - automatically connecting")
  index = 0
else:
  index = int(input("Which application do you want to connect to?\n"))

try:
  instance = applications[index]
except IndexError as error:
  raise IndexError(f"No application with index: {index}") from error

project = Project(existing_mcpd=instance)

for name, oid in project.get_children():
  print(name, oid)