This Python package contains examples of how a user can define custom tools for integration with XMS. Included are the following examples:
- 2D Mesh from 2dm: Reads a tri/quad mesh from a .2dm formatted file and creates a 2D Mesh module object in SMS.
- UGrid from xmc: Reads a geometry from an .xmc formatted file and creates a UGrid module object in XMS.
- Dataset from dat: Reads a dataset from a .DAT formatted file and adds it to an existing geometry in XMS.
- Compute Dataset Difference: Creates a diff dataset from two input datasets already loaded in XMS.
All tools inherit from an abstract base class called Tool. The definition for Tool is located in xmstool_core.
A tool should inherit from Tool and call its constructor as follows:
from xms.tool_core import Tool
class UGridFromXmcTool(Tool):
def __init__(self):
super().__init__(name='UGrid from xmc')The name passed to the Tool constructor is a user-friendly short name for the tool.
Tools can have input and output arguments that allow the user to select input data for your tool to process. There are different argument types that are available to specify what type of data your tool can receive or output. The available argument types are listed below:
- Float: A floating point value.
- Integer: An integer value.
- String: A string value.
- Boolean: A (yes/no) value.
- File: Allows the user to select a file/folder using a file select dialog.
- Grid: Allows the user to select a Grid from the project explorer via a drop-down menu.
- Dataset: Allows the user to select a Dataset from the project explorer via a drop-down menu.
- Coverage: Allows the user to select a Coverage from the project explorer via a drop-down menu.
- Raster: Allows the user to select a Raster from the project explorer via a drop-down menu.
- Time step: Allows the user to select a dataset time step.
- Table: Allows the user to enter a table of values.
Arguments can either be input or output. When an argument is input, the tool is taking in data from the user to process. When an argument is output, the tool gives the user data that resulted from the tool's operations. An example of a Dataset output argument would be when a Dataset is generated by a tool and added to the project explorer upon completion.
Each tool must override the initial_arguments() method inherited from the base Tool class. The initial_arguments()
method returns a list of the tool's arguments in their initial states. The implementation of the UGridFromXmcTool
example's initial_arguments() method is shown below:
from xms.tool_core import IoDirection
def initial_arguments(self):
"""Define initial arguments for the tool.
Returns:
List[Argument]: The list of initial arguments.
"""
# Set up the dialog arguments
args = [
self.file_argument(
name='xmc_file', description='The .xmc file to import', file_filter='XMS constraint geometry files (*.xmc)'
),
self.grid_argument(name='imported_geom', description='Imported UGrid name', optional=True, value='',
io_direction=IoDirection.OUTPUT)
]
return argsTo fill our list with arguments, we use the file_argument() and grid_argument() methods.
The Tool class provides methods for creating each argument type.
Often tools will have arguments that are only needed or applicable based on the state of another input argument. To
handle dependencies of input fields in the GUI, tools should override enable_arguments(). This method is called to
update the state of the GUI when input values change. For reference, here is the MeshFrom2dmTool example's
implementation.
def enable_arguments(self, arguments):
"""Called to show/hide arguments, change argument values and add new arguments.
Args:
arguments(list): The tool arguments.
"""
# Only show the input field for the mesh's name if the user has chosen to ignore the name from the file.
arguments[self.ARG_INPUT_MESHNAME].show = arguments[self.ARG_INPUT_OVERRIDE_NAME].valueIn this tool, the edit field for the output mesh name is only shown when the toggle option for overriding the name is checked.
Before a tool is run, it has an opportunity to check that the user has provided valid input. The base Tool class
provides some basic checks, such as ensuring required arguments have been specified. Most tools require more
specific validation of their arguments. This is done by defining the validate_arguments() method. In the code below,
the DatasetDiffTool example makes sure that its input datasets are on the same geometry and have the same number of
values.
def validate_arguments(self, arguments):
"""Called to determine if arguments are valid.
Args:
arguments (list): The tool arguments.
Returns:
(dict): Dictionary of errors for arguments.
"""
errors = {}
self._reader1 = self._validate_input_dataset(arguments[self.ARG_INPUT_DATASET1], errors)
self._reader2 = self._validate_input_dataset(arguments[self.ARG_INPUT_DATASET2], errors)
if self._reader1.geom_uuid != self._reader2.geom_uuid:
errors[arguments[self.ARG_INPUT_DATASET1].name] = 'Datasets must be on the same geometry.'
if self._reader1.num_values != self._reader2.num_values:
errors[arguments[self.ARG_INPUT_DATASET1].name] = 'Datasets must have the same dataset location.'
if self._reader1.num_times != self._reader2.num_times:
errors[arguments[self.ARG_INPUT_DATASET1].name] = 'Datasets must have matching number of timesteps.'
return errorsIf the validate_arguments() returns a non-empty dict, running the tool is blocked. The messages in the return value
are then displayed to the user. Once all the inputs pass validation, the tool will run.
The run() method is called to execute your tool on the input data. This is where the tool does its work, and sends
outputs back to be loaded into XMS. Here is the implementation from the UGridFromXmcTool example.
from pathlib import Path
from xms.constraint import read_grid_from_file
def run(self, arguments):
"""Override to run the tool.
Args:
arguments (list): The tool arguments.
"""
filename = arguments[self.ARG_INPUT_XMC_FILE].text_value
self.logger.info(f'Reading {filename}')
cogrid = read_grid_from_file(filename)
# Set the output grid with the name specified by the user or the file basename if it wasn't.
arguments[self.ARG_OUTPUT_GRID].value = arguments[self.ARG_OUTPUT_GRID].text_value or Path(filename).name
self.set_output_grid(cogrid, arguments[self.ARG_OUTPUT_GRID])The Tool base class provides several methods for retrieving input data from XMS. Note that when referring to an
object's "name" below, we mean the object's tree item path in XMS.
get_input_grid(): Returns anxms.constraint.Gridgiven the name of the grid.get_input_dataset(): Returns anxms.datasets.dataset_reader.DatasetReadergiven the name of the dataset.get_input_dataset_grid(): Returns anxms.constraint.Gridgiven the name of one of its child datasets.get_input_coverage(): Returns ageopandas.GeoDataFramegiven the name of a map module coverage.get_input_raster(): Returns anxms.gdal.rasters.raster_input.RasterInputgiven the name of a raster.
Similarly, several methods are also provided to send data back to XMS.
set_output_grid(): Sends anxms.constraint.Gridconstructed by the tool to XMS.set_output_dataset(): Sends anxms.datasets.dataset_writer.DatasetWriterconstructed by the tool to XMS.set_output_coverage(): Sends ageopandas.GeoDataFrameconstructed by the tool to XMS.set_output_raster_file(): Sends a raster file written by the tool to be read by XMS.
In order for our tool to appear when running XMS, we need to add its information into a Tools.xml file. The XML file
is used to find and load the available tools when the XMS application starts up. Here is a trimmed version of this
package's XML file, containing only the MeshFrom2dmTool example.
<?xml version="1.0"?>
<tools filetype="xmstools" version="1">
<tool name="2D Mesh from 2dm"
category="Tool Examples"
description="Read a .2dm file and create a 2D Mesh."
class_name="MeshFrom2dmTool"
module_name="user_tool_examples.tools.mesh_from_2dm_tool"
program_name="SMS"
uuid="b61dd624-1d45-4f1b-bc64-9e42fe1c6461"
/>
</tools>tool element attributes:
- name: The name of the tool as it should appear in the Toolbox GUI.
- category: Used to group common tools within the Toolbox dialog.
- description: A brief description of the tool.
- class_name: The Python class name of the
Toolimplementation. - module_name: The Python module name of the
Toolimplementation. - program_name: This is an optional attribute a tool can use to restrict the XMS application it is available in.
- uuid: A unique identifier for the tool.
# TODO: This needs to be updated for aqmeta stuffTo complete the tool registration, the pyproject.toml file must include a classifier as follows:
[project]
...
classifiers = ['XMS DMI Definition :: XML :: xms/tool_runner/tools/Tools.xml']
[project.entry-points."xms.dmi.interfaces"]
tool_runner = "xms.tool_runner"
When the XMS application starts it will find and load all tools from Python packages that have the properly defined package metadata.