Project Connection

Default connection pattern

The default project connection pattern is illustrated below. A host application has started processes for communication with a backend server and the database, and the SDK Project() constructor will automatically join that session.

If multiple compatible applications are running, see Specific application instance / options for tips on working with specific applications or projects. Operating a project without a host application is possible and discussed in Application independent connections.

If a single compatible application is running, establishing a connection and gaining access to a project backend is performed with the default Project() constructor:

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

 

The Project class is provided as a singleton and only requires connection to the application once. An application will not be able to shut down its servers until the python session has ended or project is unloaded from the SDK.

Note:  On first initialisation of the singleton Project class, the SDK will discover and initialise dependencies provided within the host application's install directory. Once this singleton class has been initialised you will need to start a new Python session to connect to a different application or an application of a different version (e.g. PointStudio -> GeologyCore). However, if switching context between multiple instances of the same application and version (same dependencies), it is possible to disconnect and connect to a different instance within a single Python session.

Specific application instance / options

The following options can be used for cases where a script or application is intended to be run on a specific application (e.g. PointStudio and not GeologyCore or BlastLogic), or in cases where multiple simultaneous applications are running and you need to be able to decide which to connect to.

If there are multiple applications or multiple instances of the same application as illustrated in the image above, the following example demonstrates how to decide which to connect to:

from mapteksdk.project import Project

# Select from multiple instances of MDF applications to connect to

if __name__ == '__main__':
  # The find_running_applications function returns a list of
  # ExistingMcpdInstance. This is a named tuple with mcpd_process_id,
  # bin_path and mcp_socket_path.
  #
  # This is ordered by newest to oldest (start time not version) application
  # instance.
  applications_running = Project.find_running_applications()

  # List results
  print("\n---------\nSpecific index of host to connect:\n---------\n")
  for i, host in enumerate(applications_running):
    print(" --> Option: %i\n\tPID: %i \n\tDLL path: %s \n\tSocket path:%s" %
      (i, host.mcpd_process_id, host.bin_path, host.mcp_socket_path))

  selected = input("\n---------\nSpecify host number to connect to:\n")

  # Connect using the specified application.
  project = Project(existing_mcpd=applications_running[int(selected)])

  # List objects under root container
  print("\n\n-------\nProject contents:")
  print('\n'.join('{}: {}'.format(*k) for k in enumerate(project.get_children())))

Example output from snippet above:

It is possible to infer more information about each instance by using the psutil library to review environment variables of running processes, however this is not a recommended standard operating practice:

from mapteksdk.project import Project
from mapteksdk.internal.mcp import find_ps_by_exe, McpdConnection

def find_application_and_projects_running():
  # The find_ps_by_exe function returns a list of running exes of given name.
  processes = find_ps_by_exe()
  results = []
  if not processes:
    return results
  for proc in processes:
    try:
      env = proc.environ() # get collection of environment variables
      results.append(
        (
          int(env["MDF_LAUNCHER_PID"]), # needed for specific_mcpd
          env["MDF_BIN"], # needed for specific_mcpd
          env["MDF_MCP_SOCKET"], # needed for specific_mcpd
          env["MDF_APPLICATION_ALIAS"], # application name
          env["MDF_APPLICATION_VERSION_SUFFIX"], # application version
          env["MDF_PROJECT_PATH"], # project opened by application
        ))
    except:
      pass
  return results

# Select from multiple instances of MDF applications to connect to
if __name__ == '__main__':
  applications_running = find_application_and_projects_running()
  if not applications_running:
    print("No suitable applications found running to connect to.")
  else:
    # List results
    count = 0
    print("\n---------\nSpecificy application / project to connect:\n---------\n")
    for host in applications_running:
      print(" --> Option: %i\n\tApp: %s %s \n\tProject: %s" % \
        (count, host[3], host[4], host[5]))
      count += 1
    selected = input("\n---------\nSpecify host number to connect to:\n")

    # Make the connection and load DLLs prior
    selected_instance = McpdConnection(
      options=None, # ProjectOptions not required
      specific_mcpd=applications_running[int(selected)]
      )

    # Connect using the specified connection
    project = Project(existing_mcpd=selected_instance)

    # List objects under root container
    print("\n\n-------\nProject contents:")
    print('\n'.join('{}: {}'.format(*k) for k in enumerate(project.get_children())))

Example output from above:

If you need or expect your script / application to only work with specific applications (e.g. PointStudio), you could do this by filtering the application's bin path returned from find_mdf_hosts, or search for the application's unique executable name and use its environment variables similar to the above example.

The following example would only connect to PointStudio application instances and ignore other possible applications running:

from mapteksdk.project import Project
from mapteksdk.internal.mcp import find_ps_by_exe, McpdConnection

def find_pointstudio_hosts():
  """Locate active PointStudio.exe processes and provide a connection
     tuple for use with McpdConnection
  """
  # Locate running pointstudio.exe list ordered by start time (descending)
  ps_proc = find_ps_by_exe("pointstudio.exe") # locate pointstudio.exe instance only
  results = []
  if not ps_proc:
    return results
  for proc in ps_proc:
    env = proc.environ()
    if "MDF_PROJECT_PATH" in env and "MDF_MCP_SOCKET" in env and "MDF_BIN" in env:
      results.append((proc.pid, env["MDF_BIN"], env["MDF_MCP_SOCKET"], env["MDF_PROJECT_PATH"]))
  return results

# Start multiple instance of MDF applications (+ at least one PointStudio) to test
if __name__ == '__main__':
  applications_running = find_pointstudio_hosts() # list[tuple(int, str, str)]

  if not applications_running:
    print("Didn't find any suitable applications running.")
  else:
    # Make the connection and load DLLs - connect to first found instance
    existing_mcp = McpdConnection(None, applications_running[0])
    # Connect using the specified mcpd connection
    project = Project(None, existing_mcp)

    # List objects under root container
    print("\nProject contents:")
    print('\n'.join('{}: {}'.format(*k) for k in enumerate(project.get_children())))

 

Application independent connections

A more advanced use case is to connect directly to a maptekdb or host an in-memory project, without a host application such as PointStudio running. The following connection patterns are available for this purpose.

Stand-alone Project (open or create)

A project (.maptekdb) along with running servers (processes started by the SDK) to communicate with the project. The Maptek Python SDK starts and is responsible for managing its own messaging and backend servers rather than using an existing set managed by an application.

Use case: Data interoperability between multiple applications and versions and development of new applications. This will generally be less flexible than the default Project connection as it won't be possible to communicate with an application and within its context such as user interface interactions, selections, algorithms and functionality, etc.

Important:  Specific executable servers and dependencies (EXEs and DLLs) are required to be discoverable by the SDK to operate this way. These may not be packaged with the standard SDK distribution.

Important:  This mode does not implicitly allow project sharing with other processes simultaneously using the same project.

This is illustrated below by using the ProjectOptions class while initialising the Project class:

import os
from mapteksdk.project import Project, ProjectOptions, ProjectOpenMode, McpdMode
# ProjectOptions provides a set of options for initialising Project class

# ProjectOpenMode (enum) defines whether to open, create, open or create, or start
#       an in-memory project

# McpMode (enum) defines whether to connect to an existing application or start a new
#       messaging server (MCP(D) -> Master Control Program (Daemon))

script_path = os.path.dirname(__file__) # path to this script
bin_path = os.path.join(script_path, "bin") # path to dependencies

project_options = ProjectOptions(
    project_path=os.path.join(script_path, "Python SDK.maptekdb"), # existing or new project
    mcpd_mode=McpdMode.CREATE_NEW,
    open_mode=ProjectOpenMode.OPEN_OR_CREATE,
    mcpd_path=bin_path, # path to find mcpd.exe
    dll_path=bin_path, # other dll dependencies path
    )

# Initialise Project class with above options
project = Project(project_options)
# Print project contents
for obj in project.get_descendants().ids():
    print("{}".format(obj.path))

In-Memory Project

A project (in-memory) that has no data persistence between sessions.

Use case: Data interoperability between multiple applications and versions, basic data conversion, testing and import / export purposes. While this may be the least flexible operating pattern, it has the least overhead and dependencies to start and run.

import os
from mapteksdk.project import Project, ProjectOptions, ProjectOpenMode

script_path = os.path.dirname(__file__) # path to this script
bin_path = os.path.join(script_path, "bin") # path to dependencies

project_options = ProjectOptions(
    project_path="",
    open_mode=ProjectOpenMode.MEMORY_ONLY,
    mcpd_path=bin_path,
    dll_path=bin_path, # dll dependencies path
    )

# Initialise Project class with above options
project = Project(project_options)

# In-Memory projects start of empty, add something:
from mapteksdk.data import VisualContainer
with project.new("test", VisualContainer) as test:
    pass
# Print project contents
for obj in project.get_descendants().ids():
    print("{}".format(obj.path))

Important:  Dependencies (DLLs) are required to be discoverable by the SDK to operate this way. These may not be packaged with the standard SDK distribution.