Projects

Connecting to a running application

The Project class represents a connection to a running application. Creating an instance of the Project class allows the Extend Python Script to interact with a running application.

The simplest way to connect to an application is to call the Project constructor with no arguments as shown in the example below.

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

The above example will find the most recently opened Maptek PointStudio, Eureka, Blastlogic or Evolution and connect to it. 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

The default Project constructor will connect to the most recently opened compatible application. To ensure you always connect to the expected application, it is recommended that you have only one compatible application open when running scripts.

To select which application to connect to, see Advanced: Selecting which application to connect to.

Basic operations

Both examples in the previous section only connect to an application. Once a script has connected to an application, it can access and manipulate the data in that application by calling functions on the Project object.

Renaming objects

The rename_object function of the Project class can be used to rename an object in an application. The function 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.

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

# 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.

path = "scrapbook/surfaces/Pit model"
try:
  proj.rename_object(path, "Pit model 2", overwrite=True)
  found_at = proj.find_object("/scrapbook/surfaces/Pit model 2")
  print("Object now called: {}".format(found_at.name))
  proj.rename_object(found_at, "Pit model")
  print("Object renamed back to: {}".format(found_at.name))
except ReferenceError:
  print("Object not found at: {}".format(path))

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

If there is no object at the path object_to_rename, then the script will fail with an ObjectDoesNotExistError.

Creating a new object

You can create a new object in the connected application by using the Project.new() function. This requires two arguments:

  • object_path: The path in the project to place the new object.

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

In the example below, we create 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() function should always be called inside a with block. The properties of the new object are set within the with block. See the documentation 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

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

In the example below, we read the square created in the previous example and print out 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

The Project class also supports editing an existing object using the Project.edit() function. Similar to Project.read(), Project.edit() accepts the path to (or of) the object to edit and determines the type of the object automatically. Unlike Project.read(), Project.edit() allows the properties of the object to be edited.

In the example below, we use Project.edit() to move the square created in a previous example in metres in the X direction.

from mapteksdk.project import Project
from mapteksdk.data import Polyline
 
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 an object if it does not exist

Project.new() will raise an error if there is already an object at the specified path. In some cases it is desirable to edit the object if it already exists. This can be achieved via the Project.new_or_edit() function.

If there is no object at the specified path, Project.new_or_edit() will create a new object. If there is an object at the specified path which matches the specified type, then that object will be 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

In some cases it is desirable to delete any existing objects at the specified path rather than edit them. The Project.new_or_edit() function will raise an error if the object at the specified path is a different type to the given type, so if you wish to place an object of a different type at a path then you need to delete the existing object rather than edit it. This can be achieved by passing the overwrite argument to Project.new() as shown in the example below, where we replace the square created in a previous example with a diamond. This will always result in a new Polyline.

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]]

Other operations on Projects

For information on other operations available in the Project package, go to the reference documentation.

ObjectIDs vs Object Paths

The above examples refer to objects in the Project via their object paths. However always referring to objects via a path can be problematic because it will fail if an object is renamed. To counteract this, most functions which accept paths also accept an ObjectID. An ObjectID uniquely identifies a single object in a project. Unlike a path, an object’s ID never changes. If a script passes an ObjectID instead of an object path then it will always open the expected object even if that object is renamed or moved. In the example below, we demonstrate a script referring to an object which has been renamed with 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]]

Whether you should pass a path or an ObjectID to a function depends on context. Here are a few factors to keep in mind when deciding which to use:

  • Project.new() does not support ObjectIDs and must always be passed a path.

  • ObjectIDs only uniquely refer to an object within the context of a single maptekdb. They should never be hardcoded into a script or saved to a file.

  • If an object will be opened multiple times in a script, it is generally best to create or read the object the first time using its path then use the ObjectID for all future accesses.

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

In the example below, we show how a path can be converted into an ObjectID without using Project.edit() or Project.read(). Additionally, the example shows how the path to an object can be extracted from the ObjectID.

from mapteksdk.project import Project
from mapteksdk.data import Polyline
 
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. We can use this to check for errors.

Advanced: 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 a work in progress. Currently the only method for performing 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 which requests for 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)