# Please use the
# http://google-styleguide.googlecode.com/svn/trunk/pyguide.html
# as documentation format for this class
# disable some pylint checks.
# pylint: disable=broad-except, too-many-arguments
# pylint: disable=too-many-lines, too-many-public-methods
""" This module provides the Python class that allows XML-RPC communication
with 20-sim.
Example:
>>> import controllab
>>> xxsim = controllab.XXSim()
"""
from __future__ import absolute_import, print_function
import collections
import math
import os
from .tool import ExeTool
from .wrapper import Wrapper, WrapperError
from .wrapper import dict2value_with_properties
from .support import key_value_to_dict
# Try to import numpy (needed for linearize_model()
try:
import numpy as np
except ImportError:
pass
CodeTarget = collections.namedtuple(
'CodeTarget', ['name', 'submodelselection'])
ModelInfo = collections.namedtuple('ModelInfo', ['name', 'identifier'])
ImplementationInfo = collections.namedtuple(
'Implementation', [
'name', 'implementation', 'implementations'])
[docs]class XXSimWrapper(Wrapper):
"""20-sim scripting interface for Python.
This is a Python wrapper for 20-sim's XML-RPC interface.
Attributes:
errorlevel: The level of errors that should be raised.
"""
DEFAULT_PORT = 5580
REAL = 1
INTEGER = 2
BOOLEAN = 3
STRING = 4
UNKNOWN = -1
SIGNAL = 1
ICONIC = 2
BONDGRAPH = 3
[docs] class Simulator(object):
""" This dummy object makes the interface more similar to the old
Octave interface and the underlying XML-RPC interface.
It does this by giving the XXSimwrapper a simulator attribute.
"""
def __init__(self, xxsim):
self.xxsim = xxsim
self.get_fast_mode = xxsim.simulator_get_fast_mode
self.get_finish_time = xxsim.simulator_get_finish_time
self.get_output_after_each = xxsim.simulator_get_output_after_each
self.get_start_time = xxsim.simulator_get_start_time
self.set_fast_mode = xxsim.simulator_set_fast_mode
self.set_finish_time = xxsim.simulator_set_finish_time
self.set_output_after_each = xxsim.simulator_set_output_after_each
self.set_start_time = xxsim.simulator_set_start_time
self.single_step = xxsim.simulator_single_step
self.start = xxsim.simulator_start
self.stop = xxsim.simulator_stop
self.is_simulating = xxsim.simulator_is_simulating
self.copy_states_to_initials = \
xxsim.simulator_copy_states_to_initials
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self.simulator = self.Simulator(self)
self.destecs = None
def _get_default_exetool(self, version=None, **kwargs):
""" Get the default ExeTool for XXSimWrapper. Optionally a different
version of the default tool can be started.
Args:
version (optional) : Will be passed on to ExeTool.__init__
Default: '5.0'
command_args (str, optional): The command line arguments to pass
Add command-line options to the tool
Returns:
clp.ExeTool : An ExeTool representing 20-sim.
"""
return ExeTool('XXSIM', version or ExeTool.DEFAULT_XXSIM_VERSION, **kwargs)
def _set_xrc_server(self, xrc):
""" Correctly sets the self.server attribute.
Args:
xrc (xmlrpclib.ServerProxy) : The serverproxy
Example:
>>> import xmlrpc.client as xmlrpclib
>>> _set_xrc_server(self,
... xmlrpclib.ServerProxy('http://localhost:5580'))
"""
self.server = xrc.xxsim
self.destecs = xrc.destecs
[docs] def connect(self,
uri='http://localhost:{}'.format(DEFAULT_PORT), **kwargs):
"""Create a connection with 20-sim using XML-RPC.
Args:
uri (str): The URI to connect to. (default: http://localhost:5580)
autostart (bool): Start an instance of the 20-sim application, if
none is running. If no version is supplied, the latest version
will be started. (optional, default: True)
version (str, optional): Only connect if the running 20-sim
instance is this version. If omitted, it will also open a
scripting session against other versions of 20-sim. Combined
with autostart, this version of 20-sim will be started if no
20-sim is running.
Returns:
bool: True if successful, False otherwise.
Examples:
Connect to 20-sim on the local machine:
>>> xxsim.connect('http://localhost:5580')
True
Connect to 20-sim on a remote server. Requires that the firewall of the
remote server allows incoming TCP connections on port 5580.
>>> xxsim.connect('http://www.example.com:5580')
True
Try to connect to 20-sim version 4.7, when server is running on a different version:
>>> xxsim.connect('http://localhost:5580', version='4.7')
False
Try to connect to 20-sim, when no instance is running:
>>> xxsim.connect('http://localhost:5580', True)
"""
""" Check if we are running from 20-sim """
try:
if os.environ['RUNNING_FROM_20SIM'] == '1':
running_from_20sim = True
if running_from_20sim:
xxsim_HTTP_port = int(os.environ['XXSIM_HTTP_SCRIPT_PORT'])
""" override the uri """
uri='http://localhost:{}'.format(xxsim_HTTP_port)
except:
pass
return super(XXSimWrapper, self).connect(uri, **kwargs)
[docs] def disconnect(self):
"""Disable the 20-sim script mode and close the connection.
Returns:
bool: True on success.
Example:
>>> xxsim.disconnect()
"""
self.set_scriptmode(False)
return super(XXSimWrapper, self).disconnect()
[docs] def close(self):
"""
Close 20-sim (without saving changes to any opened models).
Returns:
bool: True if successful, False otherwise.
Example:
>>> result = xxsim.close()
"""
self.set_scriptmode(False)
return super(XXSimWrapper, self).close()
[docs] def new_model(self):
"""Opens a new empty 20-sim model
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.new_model()
"""
identifier = self.open_model('', True)
if identifier == 0:
return False
return True
[docs] def open_model(self, model, new_instance=False):
"""Open a 20-sim model file from its path.
After opening, the model is activated and initialized.
Args:
model (str): Name of the file to load.
new_instance (bool): Set to True to open a new model instance.
Set to False to close the current model and open a new model.
(default: False)
Returns:
int: The identifier of the model (1 or higher, 0=error).
It can be used to select the active model.
Example:
>>> model_id = xxsim.open_model('C:\\\\temp\\\\my_model.emx', False)
"""
try:
# Check whether the provided model exists
if not os.path.isfile(model) and model != '':
raise WrapperError("File not found: {}".format(model))
if model != '':
model = os.path.abspath(model)
# Open the model / project by passing over the absolute path
# the actual XML-RPC call
reply = self.server.openModel(
{'name': model, 'newinstance': new_instance})
if reply is not None:
return reply['identifier']
except Exception as error:
self.errorhandler(error)
return False
[docs] def open_processed_model(self, model):
"""Open a processed 20-sim model file from its path.
The current model is used for opening the processed model.
Args:
model (str): Name of the file to load (usually with extension .emp).
Returns:
boolean: True on success, False on failure
Example:
>>> result = xxsim.open_processed_model('C:\\\\temp\\\\my_model.emp')
"""
try:
# Check whether the provided model exists
if not os.path.isfile(model) and model != '':
raise WrapperError("File not found: {}".format(model))
if model != '':
model = os.path.abspath(model)
# Open the processed model by passing over the absolute path
# the actual XML-RPC call
return self.server.openProcessedModel({'name': model})
except Exception as error:
self.errorhandler(error)
return False
[docs] def save_simulation_state(self, filename):
"""Save the simulation state to a file
The current model is used for saving the state.
Args:
filename (str): Name of the file to save (usually with extension .xml).
Returns:
boolean: True on success, False on failure
Example:
>>> result = xxsim.save_simulation_state('C:\\\\temp\\\\my_sim_state.xml')
"""
try:
if filename != '':
filename = os.path.abspath(filename)
# Save the file by passing over the absolute path to
# the actual XML-RPC call
return self.server.simulator.saveState({'filename': filename})
except Exception as error:
self.errorhandler(error)
return False
[docs] def load_simulation_state(self, filename, initialize_simulator=True):
"""Loads the simulation state from file
The current model is used for loading the state.
Args:
filename (str): Name of the file to load (usually with extension .xml).
initializeSimulator (bool): Boolean to indicate whether the simulator needs to be (re)initialized
Returns:
boolean: True on success, False on failure
Example:
>>> result = xxsim.save_simulation_state('C:\\\\temp\\\\my_sim_state.xml', true)
"""
try:
# Check whether the provided filename exists
if not os.path.isfile(filename) and filename != '':
raise WrapperError("File not found: {}".format(filename))
if filename != '':
filename = os.path.abspath(filename)
# Load the file by passing over the absolute path to
# the actual XML-RPC call
return self.server.simulator.loadState({'filename': filename, 'initializeSimulator': initialize_simulator})
except Exception as error:
self.errorhandler(error)
return False
[docs] def close_model(self, close_editor=False):
""" Close the active 20-sim model without saving changes.
Args:
close_editor (bool, optional): Set to True to also close the 20-sim
editor. Defaults to False which keeps the editor open. In any
case this will never close the final editor window because it
would close 20-sim entirely. That functionality is provided by
XXSim.close()
Returns:
bool: True when closed properly, False otherwise.
Example:
>>> xxsim.close_model(True)
True
"""
try:
# the actual XML-RPC call
return self.server.closeModel({'closewindow': close_editor})
except Exception as error:
self.errorhandler(error)
return False
[docs] def save_model(self, filename=None):
""" Saves the changes made to active 20-sim model
Args:
filename (str, optional): The filename that should be used to save the
active model. If omitted, 20-sim will save the model under its
current name if it has any.
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.save_model('c:\\\\temp\\\\mymodel.emx')
"""
try:
if filename is None:
filename = ''
# the actual XML-RPC call
return self.server.saveModel({'name': filename})
except Exception as error:
self.errorhandler(error)
return False
[docs] def save_processed_model(self, model, onlyRunSpecs=False):
"""Save a processed 20-sim model file to the given path.
Args:
model (str): Name of the file to load (usually with extension .emp).
onlyRunSpecs (bool): Indicates if only run specification should be stored
or that also plots and other experiment information should be stored.
Returns:
boolean: True on success, False on failure
Example:
>>> result = xxsim.save_processed_model('C:\\\\temp\\\\my_model.emp', True)
"""
try:
# Save the processed model to the given path
# the actual XML-RPC call
return self.server.saveProcessedModel({'name': model, 'onlyRunSpecs': onlyRunSpecs})
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_active_model(self):
""" Return the name and unique identifier of the model that is active
in 20-sim.
Returns:
xxsim.ModelInfo: The information on the active model.
Example:
>>> xxsim.get_active_model()
ModelInfo(name='C:\\\\temp\\\\my_model.emx', identifier=2)
"""
try:
# the actual XML-RPC call
reply = self.server.getActiveModel()
return ModelInfo(**reply)
except Exception as error:
self.errorhandler(error)
return False
[docs] def linearize_model(self, u, y,
symbolic=True,
at_current_time=True,
closed_loop=False,
abs_error=1.0e-5,
rel_error=1.0e-5,
use_abs_when_needed=True):
""" Linearize a part of the model (SISO and MIMO)
Args:
u (str -or- list of str): The fully qualified name(s) of the input
variable(s).
y (str -or- list of str): The fully qualified name(s) of the output
variable(s).
symbolic (bool, optional): True indicates that a symbolic
linearisation should be done, False performs a numerical
linearisation. Defaults to True
at_current_time (bool, optional): Linearise at the current time if
True. Linearise at the start time if False. Defaults to True.
closed_loop (bool, optional): Do closed loop linearisation if True.
Do open loop linearisation if False. Defaults to False.
abs_error (float, optional): The absolute change in states.
Defaults to 1.0e-5
rel_error (float, optional): The relative change in states.
Defaults to 1.0e-5
use_abs_when_needed (bool, optional): Use the absolute error margin
for states that are aproximately 0.0
Returns:
A, B, C, D: Matrices
"""
# Check if Numpy is loaded and we've received a Numpy object.
if 'np' not in globals():
raise WrapperError(
"The linearize_model() function depends on numpy.\nInstall numpy first")
# Initialise return dict.
retval = {}
try:
# Make lists of single strings.s
if isinstance(u, str):
u = [u]
if isinstance(y, str):
y = [y]
if not isinstance(u, list):
# Should be a list
raise WrapperError("u argument should be a list[str] or a str")
if not isinstance(y, list):
# Should be a list
raise WrapperError("y argument should be list[str] or a str")
k = len(u)
p = len(y)
first_iteration = True
for k_, invar in enumerate(u):
for p_, outvar in enumerate(y):
retval = self.server.model.linearizeModel({
'u': [invar],
'y': [outvar],
'symbolic': symbolic,
'atCurrentTime': at_current_time,
'closedLoop': closed_loop,
'absError': abs_error,
'relError': rel_error,
'useAbsWhenNeeded': use_abs_when_needed})
# determine the amount of states
n = int(math.sqrt(len(retval['A'])))
if first_iteration:
# create the numpy arrays of the right size
A = np.zeros((n, n), dtype=float)
B = np.zeros((n, k), dtype=float)
C = np.zeros((p, n), dtype=float)
D = np.zeros((p, k), dtype=float)
first_iteration = False
# fill the matrices
A = np.array(retval['A'], dtype=float).reshape(n, n)
B[:, k_] = retval['B']
C[p_, :] = retval['C']
D[p_, k_] = retval['D'][0]
return A, B, C, D
except Exception as error:
self.errorhandler(error)
return False
[docs] def set_active_model(self, model):
"""Sets one of the opened 20-sim models as active.
If no active model is chosen, the model opened last is automatically set
as active. if only one model is opened, it is set as active model by
default.
Args:
model (str -or- int -or- ModelInfo): model name or the model id or a ModelInfo dict
Returns:
bool: True on success, False otherwise
Example:
>>> xxsim.set_active_model('C:\\temp\\PID.emx')
True
>>> xxsim.set_active_model(3)
True
"""
try:
identifier = -1
if isinstance(model, int):
identifier = model
elif isinstance(model, str):
# search through all models to find the right identifier
models = self.get_models()
for selected_model in models:
if selected_model.name == model:
identifier = selected_model.identifier
break
elif isinstance(model, ModelInfo):
identifier = model.identifier
if identifier == -1:
raise WrapperError(
'The specified model is not open in this 20-sim session.')
return self.server.setActiveModel({'identifier': identifier})
except Exception as error:
self.errorhandler(error)
return False
[docs] def process_model(self):
"""Process the active 20-sim model.
Returns:
dict: A dictionary with the processing results on success with the
following fields:
* ``errors`` *list of str*: processing errors
* ``results`` *list of dict*: key, value pairs with the
processing results (model characteristics)
* ``warnings`` *list of str*: processing warnings
False on failure.
Example:
>>> xxsim.process_model()
{'errors': [],
'results': [{'key': 'constraints', 'value': '0'},
{'key': 'states', 'value': '0'},
{'key': 'variables', 'value': '21'},
{'key': 'algebraic loop variables', 'value': '0'},
{'key': 'equations', 'value': '4'},
{'key': 'submodels', 'value': '1'},
{'key': 'discrete states', 'value': '0'},
{'key': 'dependent states', 'value': '0'},
{'key': 'discrete systems', 'value': '0'}],
'warnings': ['[LogVarTest] warning: LogVarTest\\p1 is never used',
'[LogVarTest] warning: LogVarTest\\p2 is never used']}
"""
try:
# the actual XML-RPC call
return self.server.model.process()
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_version(self):
"""Get the version number of the 20-sim instance with which a connection was made.
Returns:
dict: A dictionary with the following fields:
* ``major``: The major number of the version number.
* ``minor``: The minor number of the version number.
* ``patch``: The patch number of the version number.
* ``build``: The build number of the version number.
False: On failure.
Example:
>>> xxsim.get_version()
{'major': 4,
'minor': 7,
'patch': 2,
'build': 7336}
"""
try:
# Obtain the version information via XML-RPC call.
version_struct = self.server.getVersion()
# Obtain the version string.
version_number_string = version_struct['version']
# Split the version number string.
version_numbers_array = version_number_string.split(".")
# Return a dictionary with all version data.
return {'major':int(version_numbers_array[0]),
'minor':int(version_numbers_array[1]),
'patch':int(version_numbers_array[2]),
'build':int(version_numbers_array[3])}
except Exception as error:
self.errorhandler(error)
return False
[docs] def run(self, continue_simulation=False):
"""Run the active 20-sim model with saved settings.
Args:
continue_simulation (bool, optional): Set to True to attempt to
continue the simulation. Set to False to start a new run.
(default: False)
Returns:
dict: A dictionary with the following fields:
* ``result``: 1 if the model runs without errors.
* ``info``: a list with key-value dicts with information on.
the simulation run, e.g. total simulation time, number of
model calculations, number of output points.
* ``warning``: warning(s) during running the model, if any
* ``errors``: error(s) during running the model, if any
False: On failure.
Example:
>>> xxsim.run(False)
{'errors': [],
'info': [{'key': 'Total simulation time', 'value': '0.003'},
{'key': 'Number of Model Calculations', 'value': '1002'},
{'key': 'Number of Output Points', 'value': '1002'},
{'key': 'Average Steps per Second', 'value': '334000'}],
'result': 1,
'warnings': []}
"""
try:
# the actual XML-RPC call
return self.server.simulator.run(
{'continueSimulation': continue_simulation})
except Exception as error:
self.errorhandler(error)
return False
[docs] def clear_run(self, action=0):
"""Clear one or more runs.
Args:
action (int): Value indicating what action to take:
* 0 = clear all runs (also default if argument is omitted)
* 1 = clear last run
* 2 = clear previous runs
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.clear_run(0)
True
"""
try:
# the actual XML-RPC call
return self.server.simulator.clearRun({'clear': int(action)})
except Exception as error:
self.errorhandler(error)
return False
[docs] def clear_all_runs(self):
"""Clear all simulation runs.
Shortcut for: clear_run(0)
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.clear_all_runs()
True
"""
return self.clear_run(0)
[docs] def clear_last_run(self):
"""Clear only the last simulation run.
Shortcut for: clear_run(1)
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.clear_last_run()
True
"""
return self.clear_run(1)
[docs] def clear_previous_runs(self):
"""Clear all runs except the last one.
Shortcut for: clear_run(2)
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.clear_previous_runs()
True
"""
return self.clear_run(2)
[docs] def get_models(self):
"""Return information on all the models that are open in 20-sim.
Returns:
list: A list of *ModelInfo* objects, one per model, containing the
following fields:
* ``name`` *str*: the file name of the model
* ``identifier`` *int*: the identifier for this model in the
current 20-sim session.
False is returned on a failure.
Example:
>>> xxsim.get_models()
[ModelInfo(name='C:\\temp\\my_model.emx', identifier=2),
ModelInfo(name='C:\\temp\\another_model.emx', identifier=3)]
"""
try:
return [ModelInfo(**model) for model in self.server.getModels()]
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_parameters(self, submodel_names=None):
"""Returns a list with the specified parameters
Args:
submodel_names (str -or- list): (optional) A single name of a
parameter, a list with parameter names or a list with submodel
names. All parameter names should contain the full hierarchy
e.g ``PID.Kp``. When passing a submodel name, all parameters of
this submodel will be returned. When this argument is omitted,
this function will return all parameters of the active model.
Returns:
list *or* None: list with a variables dict
(when retrieving more than one parameter)
False is returned on a failure
Examples:
To retrieve a list of parameters found in the
submodels PID and Amplifier, you can use:
>>> params = xxsim.get_parameters(['PID','Amplifier'])
The output is a list of parameter dicts with the following fields:
* ``name``: the name of the retrieved parameter(s)
* ``size``: the size of the parameter(s)
* ``values``: the values of the retrieved parameter(s)
* ``properties``: the properties of the parameter(s)
To retrieve a single of parameter you can use
>>> xxsim.get_parameters('PID.kp')
[{'name': 'PID.kp',
'size': [1],
'values': [1.0],
'properties': [{'key': 'parameter', 'value': 'true'},
{'key': 'arithmetictype', 'value': 'real'},
{'key': 'quantity', 'value': 'Magnitude'},
{'key': 'unit', 'value': 'none'},
{'key': 'unitSymbol', 'value': ''},
{'key': 'description', 'value': 'Proportional gain'}]}]
"""
if not submodel_names:
submodel_names = []
return self.get_variables(submodel_names, 'parameter')
[docs] def get_parameters_with_properties(self, submodel_names=None):
""" Identical to get_parameters, but the data from 20-sim is returned
as a list of namedtuple ValueWithProperties
Args:
submodel_names (str -or- list): (optional) A single name of a
parameter, a list with parameter names or a list with submodel
names. All parameter names should contain the full hierarchy
e.g ``PID.Kp``. When passing a submodel name, all parameters of
this submodel will be returned. When this argument is omitted,
this function will return all parameters of the active model.
Returns:
list *or* None: list with ValueWithProperties namedtuple
Examples:
To retrieve a list of parameters found in the
submodels PID and Amplifier, you can use:
>>> params = xxsim.get_parameters(['PID','Amplifier'])
The output is a list of ValueWithPropertie namedtuples with the
following fields:
* ``name``: the name of the retrieved parameter
* ``value``: the value(s) of the retrieved parameter
* ``unit``: the unit of the parameter (e.g. 'meter')
* ``quantity``: the quantity of the parameter (e.g 'Length')
* ``unitSymbol``: the unit symbol (e.g. 'm')
* ``arithmetictype``: the parameter type (e.g. 'real')
* ``description``: the description of the parameter
To retrieve a single of parameter you can use:
>>> xxsim.get_parameters('PID.kp')
[ValueWithProperties(name='PID.kp',
value=0.2,
unit='none',
quantity='Magnitude',
unitSymbol='',
arithmetictype='real',
description='Proportional gain')]
"""
if not submodel_names:
submodel_names = []
return dict2value_with_properties(
self.get_variables(submodel_names, 'parameter'))
[docs] def set_parameters(self, names, values):
""" Sets the specified parameters to a new value
Args:
names (str or list): string (single parameter only) or a list of
strings with the names of the parameters to be set
values (float or list): the new values of the parameters
Returns:
bool: Returns True if the parameters are set properly,
False otherwise
Examples:
To set a single parameter, you can use:
>>> xsim.set_parameters('PID.kp', 0.2)
True
To set multiple parameters, you can use:
>>> xxsim.set_parameters(['PID.kp', 'Amplifier.Gain'], [0.3, 5])
True
"""
return self.set_variables(names, values)
[docs] def get_initial_values(self, variables=None):
"""Get the initial values of the given variables.
This is just a shorthand for the equivalent call to get_variables)
Args:
variables (list, optional): List of variable names (str).
If no variables are passed, all variables are retrieved.
When getting a single variable, this value can be passed as a
string.
Returns:
list: The output is a list of initial value dicts with the following
fields:
* ``name``: the name of the retrieved parameter(s)
* ``size``: the size of the parameter(s)
* ``values``: the values of the retrieved parameter(s)
* ``properties``: the properties of the parameter(s)
This function will return False in case of a failure
Examples:
>>> xxsim.get_initial_values('PID.pdstate_initial')
[{'name': 'PID.pdstate_initial',
'values': [0.0],
'size': [1],
'properties': [{'key': 'initialValue', 'value': 'true'},
{'key': 'arithmetictype', 'value': 'real'},
{'key': 'quantity', 'value': ''},
{'key': 'unit', 'value': ''},
{'key': 'unitSymbol', 'value': ''}]}]
The following are equivalent:
>>> xxsim.get_initial_values(['PID.pdstate_initial'])
>>> xxsim.get_variables(['PID.pdstate_initial'],['initialValue'])
"""
return self.get_variables(variables, 'initialValue')
[docs] def set_initial_values(self, names, values):
""" Set the specified initial values.
Args:
names (list): a list of strings with the names of the initial
values to be set (or a string for a single initial value)
values (list): a list of floats with the new values of the
initial values (or a float for a single new value)
Returns:
bool: True if the initial values are set properly, False otherwise.
Example:
To set a single initial value, you can use:
>>> xxsim.set_initial_values('PID.pdstate_initial', 1.0)
For multiple initial values, you can use:
>>> xxsim.set_initial_values(['PID.pdstate_initial',
'PID.pistate_initial'],
[1.0, 2.0])
True
"""
return self.set_variables(names, values)
[docs] def get_log_values(self, variables=None):
"""
Fetch the values which are logged during a simulation run of the
active model.
Args:
variables (list): List of strings with the names of the logged
variables to fetch. If empty, values of *all* logged variables
are fetched. For a single variable name, a string can be given.
(default: [])
Returns:
list: A list of dictionaries, one for each requested logged
variable, with keys the *name* of the variable and its
*values*.
"""
try:
# Check input arguments
if variables is None:
variables = []
elif isinstance(variables, str):
variables = [variables]
elif not isinstance(variables, list):
raise WrapperError('No list of variable names given')
# the actual XML-RPC call
return self.server.simulator.getLogValues(
{'variables': variables})
except Exception as error:
self.errorhandler(error)
return False
[docs] def set_log_variables(self, log_variables):
"""logs specified variables while running active model
Args:
log_variables (list -or- str): list of variables or a single
variable name
*Note*: make sure the variable name exists in active model. If
not, an error message will be returned
Returns:
bool: True on success, False otherwise
Examples:
>>> xxsim.set_log_variables('PID.error')
True
>>> xxsim.set_log_variables(['PID.error', 'PID.output'])
True
"""
try:
# Check input arguments
if isinstance(log_variables, str):
log_variables = [log_variables]
elif not isinstance(log_variables, list):
raise WrapperError('No list of variable names given')
# the actual XML-RPC call
return self.server.simulator.setLogVariables(
{'variables': log_variables})
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_value(self, var_name):
"""Retrieves the value of the specified variable or parameter name.
Note: For matrices, use get_variables or get_value_with_properties,
otherwise you will get the values as a list, but will not get the
information about the shape of the matrix.
Args:
var_name (str): the name of the variable or parameter
Returns:
float *or* None: The value of the variable. None is returned in
case the variable or parameter could not be found.
False is returned on a failure
Example:
>>> xxsim.get_value('PID.kp')
1.0
"""
if not isinstance(var_name, str):
raise WrapperError(
'Please pass a single variable name as a string')
var = self.get_variables(var_name)
# Nothing found, return nothing.
if var is None:
return None
# get_variables has failed and printed a user error.
if var is False:
# Return error state.
return False
# Only grab the value of the single variable returned
val = var[0]['values']
if len(val) == 1:
return val[0]
return val
[docs] def get_value_with_properties(self, var_name):
"""Retrieves the value of the specified variable or parameter name
Args:
var_name (str): the name of the variable or parameter
Returns:
ValueWithProperties: The value of the variable and the variable
properties stored in a *ValueWithProperties* object None is
returned in case the variable or parameter could not be found.
False is returned on a failure
Example:
>>> xxsim.get_value_with_properties('PID.pdstate')
ValueWithProperties(name='PID.pdstate',
value=0.0,
unit='',
quantity='',
unitSymbol='',
multiplicationToSI=1.0,
arithmetictype='real',
description='',
attributes='')
The output is a ValueWithProperties namedtuple with the
following fields:
* ``name``: the name of the retrieved variable
* ``value``: the value(s) of the retrieved variable
* ``unit``: the unit of the variable (e.g. 'meter')
* ``quantity``: the quantity of the variable (e.g 'Length')
* ``unitSymbol``: the unit symbol (e.g. 'm')
* ``arithmetictype``: the variable type (e.g. 'real')
* ``multiplicationToSI``: the multiplication factor to get the
value in SI units (e.g. 0.001 with unit symbol 'mm')
* ``description``: the description of the variable
* ``attributes``: the attributes defined for this variable
"""
try:
valprops = dict2value_with_properties(self.get_variables(var_name))
if valprops is False:
return False
if len(valprops) == 1:
return valprops[0]
return valprops
except Exception as error:
self.errorhandler(error)
return False
[docs] def set_value(self, var_name, value, properties=None):
"""Sets the value of the specified variable, initialvalue or parameter.
Args:
var_name (str): the name of the variable, initialvalue or parameter
value (float): the new value
properties (list, optional): list of key-value dicts with variable
properties
Returns:
float *or* None: The value of the variable. None is returned in
case the variable or parameter could not be found.
False is returned on a failure
Example:
>>> xxsim.set_value('Mass.m', 0.200)
1.0
>>> xxsim.set_value('Mass.m', 200.0, [{'key':'multiplicationToSI', 'value':'0.001'}])
1.0
"""
if not isinstance(var_name, str):
raise WrapperError(
'Please pass a single variable name as a string')
if isinstance(value, bool):
if value:
value = 1.0
else:
value = 0.0
if properties is not None:
properties = [properties]
return self.set_variables([var_name], [value], properties)
[docs] def query_settings(self, settings_names=()):
"""Queries the active 20-sim model for the settings
Args:
settings_names: (optional) A string, or list of strings, containing
the names of the settings that are to be queried. Defaults to an
empty tuple. This will query all available settings.
Returns:
list: A list of dicts with the following fields:
* ``value`` *str*: the value of the setting (as string)
* ``type`` *str*: original type of the value
* ``description`` *str*: description of the setting
* ``key`` *str*: setting name
* ``enumerations`` *list*:
* ``properties`` *list*: list of key-value dicts
False is returned on a failure
Example:
>>> xxsim.query_settings('model.simulator.finishtime')
[{'value': '10',
'properties': [{'value': '0.0', 'key': 'lowerbound'}],
'enumerations': [],
'type': 'double',
'key': 'model.simulator.finishtime',
'description': ''}]
"""
# Start with an empty key list
keys = []
if isinstance(settings_names, str):
keys = [settings_names]
else:
keys = settings_names
try:
# the actual XML-RPC call
settings = self.server.querySettings({'keys': keys})
return settings
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_get_finish_time(self):
""" Get the finish time used by the simulator.
Returns:
float: The finish time used by the simulator.
A return value of False indicates an error has occurred.
Example:
>>> xxsim.simulator_get_finish_time()
10.0
"""
try:
setting = self.query_settings('model.simulator.finishtime')
if len(setting) == 1:
return float(setting[0]['value'])
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_set_finish_time(self, finishtime):
""" Set the finish time of the simulator:
Args:
finishtime (float): The new finish time for the simulator.
Returns:
bool: True on success, False on failure.
Example:
>>> xxsim.simulator_set_finish_time(10.0)
True
"""
try:
return self.set_settings(
'model.simulator.finishtime', finishtime)
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_get_start_time(self):
""" Get the start time used by the simulator.
Returns:
float: The start time used by the simulator.
False is returned on a failure
Example:
>>> xxsim.simulator_get_start_time()
2.0
"""
try:
setting = self.query_settings('model.simulator.starttime')
if len(setting) == 1:
return float(setting[0]['value'])
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_set_start_time(self, starttime):
""" Set the start time for the simulator:
Args:
starttime (float): The new start time for the simulator.
Returns:
bool: True on of success, False on failure.
Example:
>>> xxsim.simulator_set_start_time(2.0)
True
"""
try:
return self.set_settings('model.simulator.starttime', starttime)
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_get_fast_mode(self):
""" Check whether the simulator is in 'fast mode'.
Returns:
bool: True if the simulator is in 'fast mode', False otherwise.
Example:
>>> xxsim.simulator_get_fast_mode()
True
"""
try:
reply = self.query_settings('model.simulator.fastmode')
return len(reply) == 1 and reply[0]['value'] == 'true'
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_set_fast_mode(self, fastmode=True):
""" Change the simulator mode to 'fast mode' or 'debug mode'
Args:
fastmode (bool, optional): True = fast mode (default), False = debug mode
Returns:
bool: True on success, False otherwise
Example:
>>> xxsim.simulator_set_fast_mode(True)
True
"""
try:
return self.set_settings('model.simulator.fastmode', fastmode)
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_get_output_after_each(self):
""" Gets the current value of the simulator 'Output After Each' Run property
Returns:
float: The 'output after each' time on success.
A value of -1.0 indicates an error has occurred.
False is returned on a failure
Example:
>>> xxsim.simulator_get_output_after_each()
0.0
"""
try:
reply = self.query_settings('model.simulator.outputaftereach')
if len(reply) == 1:
return reply[0]['properties'][0]['value']
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_set_output_after_each(self, output_time):
""" Change the 'Output After Each' Run property in the simulator
Args:
output_time (float): Set the 'output after each' option of the
simulator.to this value.
Returns:
bool: True on success, False on failure.
Example:
>>> result = xxsim.simulator_set_output_after_each()
"""
try:
return self.set_settings(
'model.simulator.outputaftereach',
output_time)
except Exception as error:
self.errorhandler(error)
return False
[docs] def query_implementations(self):
""" Returns a list of submodels names with multiple implementations and
their active implementation.
Returns:
list: List of ImplementationInfo objects, one for each submodel,
with the following fields:
* ``name`` *str*: the name of the submodel
* ``implementation`` *str*: the name of the active implementation
* ``implementations`` *list of str*: list of all available
implementations for this submodel
False is returned on a failure
Example:
>>> implementations = xxsim.query_implementations()
"""
try:
# the actual XML-RPC call
result = self.server.model.queryImplementations()
if not result:
return result
# Convert the result into a list of namedtuples
implist = [ImplementationInfo(**element) for element in result]
return implist
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_implementations(self, submodel_name):
""" Returns the implementations of a particular submodel
Args:
name (string): the hierarchical name of the submodel for the
implementations that should be requested
Returns:
dict: Dictionary with:
* activeImplementation
* list of all implementation names.
Example:
>>> implementations = xxsim.get_implementations('Submodel1')
>>> implementations['activeImplementations']
"""
try:
if not isinstance(submodel_name, str):
raise WrapperError(
'Error: submodel_name should be of string type ')
# the actual XML-RPC call
return self.server.model.getImplementations(
{'name': submodel_name})
except Exception as error:
self.errorhandler(error)
return False
[docs] def set_implementations(self,
submodel_names,
implementation_names=None,
process_model=True):
""" Change the implementation of one or more submodels
Args:
submodel_names (str, list or dict): a string or list of strings with
the submodel names or a dict with the submodel names as key and
the implementation name as value
implementation_names (str, optional): string or list of strings with
the corresponding implementation to set. This argument should be
omitted when you pass a dict to *submodel_names*.
process_model (bool, optional): boolean to indicate whether
the model should be processed afterwards. Defaults to True
Returns:
bool: True on success, False on failure
Examples:
>>> xxsim.set_implementations('Submodel1', 'implementationA')
>>> xxsim.set_implementations('Submodel1', 'implementationA',
... False)
The following are equivalent:
>>> xxsim.set_implementations(['Submodel1', 'Submodel2'],
... ['implementationA','implementationB'])
>>> xxsim.set_implementations({'Submodel1': 'implementationA',
... 'Submodel2': 'implementationB'})
"""
try:
# Check and transform input for further processing.
if isinstance(submodel_names, dict) and not implementation_names:
# dict of form: {'Submodel1': 'implementationB', 'Submodel2':
# turn it into double list form.
my_implementation_names = list(submodel_names.values())
my_submodel_names = list(submodel_names.keys())
elif isinstance(submodel_names, str) and isinstance(
implementation_names, str):
# two string arguments turn into two lists
my_submodel_names = [submodel_names]
my_implementation_names = [implementation_names]
elif isinstance(submodel_names, list) and isinstance(
implementation_names, list):
# two list arguments
if len(submodel_names) != len(implementation_names):
raise WrapperError(
'Error: Size mismatch submodel_names and '
'implementation_names should have the same number of '
'list elements')
# we already have two lists
my_submodel_names = submodel_names
my_implementation_names = implementation_names
else:
raise WrapperError(
'Error: Invalid parameters. submodel_names and '
'implementation_names should be of the same type, '
'alternatively only submodel_names should be passed as '
'a dict.')
# The input arguments have been reshaped into two lists.
# Put them in the correct format for the XML-RPC call.
implementations = [{'name': my_submodel_names[i],
'implementation': my_implementation_names[i]}
for i in range(len(my_submodel_names))]
data = {'implementations': implementations,
'processModel': process_model}
# the actual XML-RPC call
return self.server.model.changeImplementations(data)
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_c_code_targets(self):
"""Retrieve a list of C-code targets
Returns:
list : List of CodeTarget objects, one for each available target.
False in case of a failure
Example:
>>> xxsim.get_c_code_targets()
[CodeTarget(name='20simDLL', submodelselection=True),
CodeTarget(name='Arduino', submodelselection=True),
CodeTarget(name='CFunction', submodelselection=True),
CodeTarget(name='CppClass', submodelselection=True),
CodeTarget(name='Simulink', submodelselection=True),
CodeTarget(name='StandAloneC', submodelselection=False)]
"""
try:
# the actual XML-RPC call
targets = self.server.simulator.getCCodeTargets()
if not targets:
return []
# Convert to namedtuple
result = []
for target in targets:
result.append(CodeTarget(**target))
return result
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_matlab_code_targets(self):
"""Retrieve a list of Matlab code targets
Returns:
list : list of CodeTarget objects, one for each available target.
False in case of a failure.
Example:
>>> xxsim.get_matlab_code_targets()
[CodeTarget(name='Matlab', submodelselection=True),
CodeTarget(name='StandAloneMatlab', submodelselection=False),
CodeTarget(name='Simulink', submodelselection=True),
CodeTarget(name='StandAloneSimulink', submodelselection=False)]
"""
try:
# the actual XML-RPC call
targets = self.server.simulator.getMatlabCodeTargets()
if not targets:
return []
# Convert to namedtuple
result = []
for target in targets:
result.append(CodeTarget(**target))
return result
except Exception as error:
self.errorhandler(error)
return False
[docs] def set_scriptmode(self, scriptmode=True):
"""Turn On/Off 20-sim scripting mode.
Scripting mode deactivates all buttons on simulation window except the
stop button.
Args:
scriptmode (bool,optional): True sets 20-sim in scripting mode
(default), False resets 20-sim back to normal mode
Returns:
bool: True on success, False otherwise
Example:
>>> xxsim.set_scriptmode(True)
True
"""
try:
# the actual XML-RPC call
if self.server is None:
return True
return self.server.setScriptMode({'set': scriptmode})
except Exception as error:
self.errorhandler(error)
return False
[docs] def generate_c_code(
self,
target_name,
output_directory,
submodel_name='',
template_directory=''):
"""Generate C-Code for the active model using the specified C-code target template
Args:
target_name (str): The name of the code generation template to use.
Use :py:meth:`.get_c_code_targets()` to fetch the list of
available C-code targets.
output_directory (str): The output directory where the generated
C-Code should be generated
submodel_name (str, optional): The name of the submodel for
submodel code generation templates only. You should omit this
parameter if you are using a 'model' code generation template.
template_directory (str, optional): The directory where the
template is located. If omitted, 20-sim will search for the
template in the standard list of C-code folders.
Returns:
bool *or* dict: False on failure, on success a *dict* with the
following fields:
* ``warnings`` *list* of warning messages.
* ``result`` *True*
* ``errors`` *list* of error messages.
Examples:
>>> result = generate_c_code(
target_name='CFunction',
output_directory='c:\\temp\\%SUBMODEL_NAME%',
submodel_name='MySubmodel')
>>> result = generate_c_code(
target_name='StandAloneC',
output_directory='c:\\temp\\%MODEL_NAME%')
"""
try:
# the actual XML-RPC call
return self.server.simulator.generateCCode({
'targetName': target_name,
'outputDirectory': output_directory,
'submodelName': submodel_name,
'templateDirectory': template_directory})
except Exception as error:
self.errorhandler(error)
return False
[docs] def generate_matlab_code(
self,
target_name,
output_directory,
submodel_name='',
template_directory=''):
"""Generate Matlab/Simulink code for the active model using the specified
Matlab or Simulink M-code target template
Args:
target_name (str): The name of the code generation template to use.
Use :py:meth:`.get_matlab_code_targets()` to fetch the list of
available Matlab-code targets.
output_directory (str): The output directory where the generated
Matlab code should be generated.
submodel_name (str, optional): For submodel code generation
templates only: the name of the submodel. You should omit this
parameter if you are using a 'model' code generation template.
template_directory (str, optional): The directory where the
template is located. If omitted, 20-sim will search for the
template in the standard list of M-code folders.
Returns:
bool *or* dict: False on failure, on success a *dict* with the
following fields:
* ``warnings`` *list* of warning messages.
* ``result`` *True*
* ``errors`` *list* of error messages.
Examples:
>>> result = generate_matlab_code(
target_name='Matlab',
output_directory='c:\\\\temp\\\\%SUBMODEL_NAME%',
submodel_name='MySubmodel')
>>> result = generate_matlab_code(
target_name='StandAloneSimulink',
output_directory='c:\\\\temp\\\\%MODEL_NAME%')
"""
try:
# the actual XML-RPC call
return self.server.simulator.generateMatlabCode({
'targetName': target_name,
'outputDirectory': output_directory,
'submodelName': submodel_name,
'templateDirectory': template_directory})
except Exception as error:
self.errorhandler(error)
return False
[docs] def log2csv(self, path, variables=None, overwrite=True):
""" Write the logged variables from 20-sim to a CSV on the file system.
Args:
path (string): The path and file name of the csv file.
variables (list, optional): The list of variables names that should
be stored in this csv file. Default (=None) stores all
variables marked for logging with
:py:meth:`.set_log_variables()`
overwrite (bool, optional): Determines if an existing file should
be overwritten
"""
if not variables:
variables = []
try:
return self.server.simulator.writeLogToCsv(
{'name': path, 'variables': variables, 'overwrite': overwrite})
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_start(self):
""" Start the simulation.
The simulation will run until it reaches its finish time, or until the
*simulator_stop()* function is called.
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.simulator_start()
True
"""
try:
return self.server.simulator.start()
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_stop(self):
""" Stop the simulation.
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.simulator_stop()
True
"""
try:
return self.server.simulator.stop()
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_single_step(self):
"""Execute a single simulation step for the active 20-sim model.
Returns:
dict : simulation results dictionary with the following fields:
* ``result`` *int*:
* ``info`` *list of dict*: list with key, value pairs with
simulation statistics
* ``warnings`` *list of str*: list of warnings that occured
during the simulation step
* ``errors`` *list of str*: list of warnings that occured
during the simulation step
False on failure.
Example:
>>> xxsim.simulator_single_step()
{'errors': [],
'info': [{'key': 'Total simulation time', 'value': '0.02'},
{'key': 'Number of Model Calculations', 'value': '2'},
{'key': 'Number of Output Points', 'value': '1'},
{'key': 'Average Steps per Second', 'value': '100'}],
'result': 0,
'warnings': []}
"""
try:
return self.server.simulator.singleStep()
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_is_simulating(self):
""" Check whether the simulator is running.
WARNING: If the function fails and the error handling is set to not
raise that error (the default), the function will print an error for
the user and return False.
In this case, the function returns False even when the simulator is
running.
If you initialise XXSim with RAISEERRORS.ALL it will always raise an
error that can be caught by the calling script.
This is the recommended way for automation.
Returns:
bool: True if the simulator is running, otherwise False.
Example:
>>> xxsim.simulator_is_simulating()
False
"""
try:
return self.server.simulator.isSimulating()
except Exception as error:
self.errorhandler(error)
return False
[docs] def simulator_copy_states_to_initials(self):
""" Copy current states to initial values
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.simulator_copy_states_to_initials()
True
"""
try:
return self.server.simulator.copyStatesToInitials()
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_memory_usage(self):
"""Retrieve the memory usage of 20-sim and the available system memory
Returns:
dict: resource usage dictionary
with the following fields:
* ``success`` *bool* is True on a successful call
* ``USERObjects`` *int* the number of USER objects
* ``GDIObjects`` *int* the number of GDI objects
* ``WorkingSetMemory`` *float* the current memory usage
in megabytes (MB)
* ``PeakWorkingSetMemory`` *float* the peak memory usage
in megabytes (MB)
* ``AvailablePhysicalMemory`` *float* the available physical
memory on the system
Example:
>>> xxsim.get_memory_usage()
{'WorkingSetMemory': 95.7109375,
'PeakWorkingSetMemory': 109.328125,
'GDIObjects': 781,
'AvailablePhysicalMemory': 4251.203125,
'success': True,
'USERObjects': 71}
"""
try:
return self.server.util.getMemoryUsage()
except Exception as error:
self.errorhandler(error)
return False
[docs] def open_simulator(self):
""" Open the simulator window for the current model
Returns:
bool: True if if the simulator opened without errors,
False otherwise.
Example:
>>> xxsim.open_simulator()
True
"""
try:
return self.server.simulator.openSimulator()
except Exception as error:
self.errorhandler(error)
return False
[docs] def add_plot_window(self, window_name):
""" For the given name, this method will create a plotting window with that name.
The return value is the window ID as assigned by 20-sim.
Args:
window_name (str): Name of the to be added plot window.
Returns:
int: a unique ID corresponding to the window that was created.
Example:
>>> xxsim.add_plot_window('Test')
1
"""
try:
return self.server.plot.addPlotWindow(
{'windowName': window_name})
except Exception as error:
self.errorhandler(error)
return False
[docs] def add_plot_to_window(self, window, plot_name):
""" For the given plotName, this method will create a plot with that name in the window
specified by window. The return value is the plot ID as assigned by 20-sim.
Args:
window (str, int): Name or ID of the plot window to which the plot should be added.
plot_name (str): Name of the to be created plot.
Returns:
int: a unique ID corresponding to the plot that was created.
Example:
>>> xxsim.add_plot_to_window('Test','Demo')
1
>>> xxsim.add_plot_to_window(1,'Demo')
2
"""
try:
window_id = self.get_plot_window_id_from_name(window)
return self.server.plot.addPlotToWindow(
{'windowID': window_id, 'plotName': plot_name})
except Exception as error:
self.errorhandler(error)
return False
[docs] def add_curve_to_plot(self, window, plot, variable, x_variable='time', label_name='', key_values=[]):
""" Add a new curve to the specified plot
Args:
window (int or str): Name or The ID corresponding to the window to which the curve will
be added.
plot (int or str): Name or ID corresponding to the plot to which the curve will be
added.
variable (str): Path of the 20-sim variable that is plotted in the curve.
x_variable (str, optional): The variable plotted on the X-axis of the curve (default:
'time').
label_name (str, optional): The label of the curve as shown in the legend of the plot.
If empty, the variable name is taken. Default is variable name.
key_values (structured array): Structured array that contains key-value pairs.
Accepted keys:
* ``colorR``: The red component of the RGB color that the curve should have.
Valid range: 0-255. Default*: 0
* ``colorG``: The green component of the RGB color that the curve should have.
Valid range: 0-255. Default*: 0
* ``colorB``: The blue component of the RGB color that the curve should have.
Valid range: 0-255. Default*: 0
* ``thickness``: The line thickness. Valid range is 1-50.
**Note:** If no color is specified, 20-sim will select the next color from its
internal list.
Returns:
int: a unique ID corresponding to the curve that was created.
Example:
>>> xxsim.add_curve_to_plot('PlotWindow','Plot','time')
0
>>> xxsim.add_curve_to_plot('PlotWindow','Plot','time','SomeTime')
1
>>> xxsim.add_curve_to_plot('PlotWindow','Plot','time','Sometime',
[{'key':'colorB','value':'255'}])
2
"""
if label_name == '':
label_name = variable
try:
window_id = self.get_plot_window_id_from_name(window)
plot_id = self.get_plot_id_from_name(window, plot)
# the actual XML-RPC call
return self.server.plot.addCurveToPlot({
'windowID': window_id, 'plotID': plot_id, 'xVariablePath': x_variable,
'yVariablePath': variable, 'zVariablePath': 'dummy', 'labelName': label_name,
'keyvalues': key_values})
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_curves_from_plot(self, window, plot):
""" Returns an array of structs that each represent a curve in the specified plot.
These structs have the curve path and variable ID of each specific variable.
Args:
window (int or str): Name or ID from the plot window where the curve is added to.
plot (int or str): Name or ID from the plot where the curve is added to.
Returns:
list of dict: list with all curves in the specified plot window with the following
fields:
* ``curveID``: *int* the ID of the curve.
* ``xPath``: *str* the path of the 20-sim variable on the X-axis.
* ``yPath``: *str* the path of the 20-sim variable on the Y-axis.
* ``zPath``: *str* the path of the 20-sim variable on the Z-axis.
* ``isHidden``: *bool* boolean that indicates if the curve is hidden.
"""
try:
window_id = self.get_plot_window_id_from_name(window)
plot_id = self.get_plot_id_from_name(window, plot)
return self.server.plot.getCurvesFromPlot(
{'windowID': window_id, 'plotID': plot_id})
except Exception as error:
self.errorhandler(error)
return False
[docs] def hide_curve(self, window, plot, curve, hidden):
""" Hides the curve if isHidden is true, and otherwise shows it.
Args:
window (int or str): Name or ID corresponding to the plot window in which the curve
should be hidden or shown.
plot (int or str): Name or ID corresponding to the plot in which the curve should be
hidden or shown.
curve (int): ID corresponding to the curve that is either hidden or shown.
hidden (bool): Boolean indicating if the given curve will be hidden (True) or shown
(false).
Returns:
bool: True if if the simulator opened without errors,
False otherwise.
"""
try:
window_id = self.get_plot_window_id_from_name(window)
plot_id = self.get_plot_id_from_name(window, plot)
return self.server.plot.hideCurve({
'windowID': window_id, 'plotID': plot_id, 'curveID': curve, 'isHidden': hidden})
except Exception as error:
self.errorhandler(error)
return False
[docs] def remove_curve_from_plot(self, window, plot, curve):
""" For the specified plot window name or ID and plot name or ID, remove the given curve of
the plot.
Args:
window (int or str): Name or ID corresponding to the window to which in the specified
plot the specified curve will be removed.
plot (int or str): Name or ID corresponding to the plot of which the curve will be
removed.
curve (int): ID corresponding to the to be deleted curve.
Returns:
bool: True if if the simulator opened without errors,
False otherwise.
"""
try:
window_id = self.get_plot_window_id_from_name(window)
plot_id = self.get_plot_id_from_name(window, plot)
return self.server.plot.removeCurveFromPlot({
'windowID': window_id, 'plotID': plot_id, 'curveID': curve})
except Exception as error:
self.errorhandler(error)
return False
[docs] def hide_plot(self, window, plot, hidden):
""" Hides the plot if hidden is true, and otherwise shows it.
Args:
window (int or str): Name or ID corresponding to the plot window in which the plot
should be hidden or shown.
plot (int or str): Name or ID corresponding to the plot that is either hidden or shown.
hidden (bool): Boolean indicating if the given plot will be hidden (True) or shown
(false).
Returns:
bool: True if if the plot was hidden without any errors,
False otherwise.
"""
try:
window_id = self.get_plot_window_id_from_name(window)
plot_id = self.get_plot_id_from_name(window, plot)
return self.server.plot.hidePlot({
'windowID': window_id, 'plotID': plot_id, 'isHidden': hidden})
except Exception as error:
self.errorhandler(error)
return False
[docs] def remove_plot_from_window(self, window, plot):
""" For the specified plot window name or ID, the plot with plot name or ID will be removed.
Args:
window (int or str): Name or ID corresponding to the window of which the plot will be
removed.
plot (int or str): Name or ID corresponding to the plot that should be deleted.
Returns:
bool: True if if the plot was removed from the window without errors,
False otherwise.
"""
try:
window_id = self.get_plot_window_id_from_name(window)
plot_id = self.get_plot_id_from_name(window, plot)
return self.server.plot.removePlotFromWindow({
'windowID': window_id, 'plotID': plot_id})
except Exception as error:
self.errorhandler(error)
return False
[docs] def hide_plot_window(self, window, hidden):
""" Hides the plot window if isHidden is true, and otherwise shows it.
Args:
window (int or str): Name or ID corresponding to the window that is either hidden or
shown.
hidden (bool): Boolean indicating if the given plot window will be hidden (True) or
shown (false).
Returns:
bool: True if if the plot window was hidden without any errors,
False otherwise.
"""
try:
window_id = self.get_plot_window_id_from_name(window)
return self.server.plot.hidePlotWindow({
'windowID': window_id, 'isHidden': hidden})
except Exception as error:
self.errorhandler(error)
return False
[docs] def remove_plot_window(self, window):
""" For the given window name or ID, the corresponding plot window will be removed.
Args:
window (int or str): Name or ID of the to be removed window.
Returns:
bool: True if if the plot window was removed without any errors,
False otherwise.
"""
try:
window_id = self.get_plot_window_id_from_name(window)
return self.server.plot.removePlotWindow({
'windowID': window_id})
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_plot_windows(self):
""" Get all currently available plot windows that are present in the
simulator by both name and ID.
Returns:
list of dict: list with all plot windows for the current model
with the following fields:
* ``windowName`` *str* the name of the plot window
* ``windowID`` *int* the corresponding plot identifier
Example:
>>> xxsim.get_plot_windows()
[{'windowName': 'Crank Rod - Plots', 'windowID': 1},
{'windowName': '3D Animation', 'windowID': 2},
{'windowName': 'Window 3', 'windowID': 3}]
"""
try:
return self.server.plot.getPlotWindows()
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_plots_from_window(self, window):
""" Get all currently available plots that are present in the specified
window by both name and ID.
Args:
window (str -or- int): The id or name of the plot window for which
the plots should be returned
Returns:
list of dict: list with all plots in the specified plot window
with the following fields:
* ``plotName``: *str* the name of the plot
* ``plotID``: *int* the id of the plot
* ``isHidden``: *bool* True when the plot is hidden, False when shown
* ``plotType``: *str* the type of the plot, e.g. 'GraphPlot', 'GLPlot' or 'FFTPlot'
Example:
>>> xxsim.get_plots_from_window(1)
[{'plotName': '3D Animation', 'plotID': 6, isHidden},
{'plotName': 'x (load)', 'plotID': 7},
{'plotName': 'x (sledge)', 'plotID': 8},
{'plotName': 'T (motor)', 'plotID': 9},
{'plotName': 'omega', 'plotID': 10}]
"""
window_id = -1
try:
if isinstance(window, str):
window_id = self.get_plot_window_id_from_name(window)
if window_id == -1:
raise WrapperError(
'The specified plot window does not exist.')
elif isinstance(window, int):
if window >= 0:
window_id = window
if window_id < 0:
raise WrapperError(
'Invalid parameter.\n'
'Pass a plot window name or a plot window id (>0)')
return self.server.plot.getPlotsFromWindow(
{'windowID': window_id})
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_plot_window_id_from_name(self, window_name):
"""Find the corresponding window ID for a certain window name.
Args:
window_name (str, int): The name of the plot window (case sensitive) or its ID.
Returns:
int: The ID of the window (when an ID is specified as window_name, then it returns this
ID). The return value is -1 if the window does not exist.
Example:
>>> xxsim.get_plot_window_id_from_name('3D Animation')
2
>>> xxsim.get_plot_window_id_from_name(2)
2
"""
window_id = -1
try:
if isinstance(window_name, str):
window_list = self.get_plot_windows()
name_count = 0
for window in window_list:
if window['windowName'] == window_name:
name_count += 1
if name_count == 1:
# We found a window with the corresponding name
window_id = window['windowID']
elif name_count == 2:
print('Warning: multiple plot windows found '
'with the specified name. '
'Selected the first plot window.')
break
elif isinstance(window_name, int):
return window_name
else:
raise WrapperError('The specified plot window name is neither '
'a string nor a number.')
return window_id
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_plot_id_from_name(self, window_name, plot_name):
""" Find the corresponding plot ID for a certain window and plot name.
Args:
window_name (str -or- int): The name of the plot window
(str, case sensitive) or the plot window id (int)
plot_name (str): The name of the plot (case sensitive)
Returns:
int: The ID of the plot (when an ID is specified as plot_name, then it returns this
ID). The return value is -1 if the plot does not exist.
Example:
>>> xxsim.get_plot_id_from_name('Crank Rod - Plots', 'omega')
10
"""
try:
# Get the window id
window_id = self.get_plot_window_id_from_name(window_name)
if window_id == -1:
raise WrapperError('Could not find the specified plot window.')
# Get the plot id, either via the name of the plot or directly
plot_id = -1
# If the input is a string, we got the name of a plot
if isinstance(plot_name, str):
# Get all id, name pairs for all plots
plot_list = self.get_plots_from_window(window_id)
name_count = 0
for plot in plot_list:
if plot['plotName'] == plot_name:
name_count += 1
if name_count > 1:
print(
'Multiple plots found with the specified name.'
'Selected the first matching plot.')
break
plot_id = plot['plotID']
elif isinstance(plot_name, int):
# the plot name is already a number
plot_id = plot_name
else:
raise WrapperError('The specified plot name is invalid.')
return plot_id
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_plots(self):
""" Get all plots in all plot windows that are present in the simulator.
Returns:
list of dict: list with all plots for the current model
with the following fields:
* ``windowName`` *str* the name of the plot window
* ``windowID`` *int* the corresponding plot identifier
* ``plotName`` *str* the name of the plot
* ``plotID`` *int* the id of the plot
Example:
>>> xxsim.get_plots()
[{'windowName': 'Crank Rod - Plots',
'plotName': '3D Animation','windowID': 1,'plotID': 6},
{'windowName': 'Crank Rod - Plots',
'plotName': 'x (load)','windowID': 1,'plotID': 7},
{'windowName': 'Crank Rod - Plots',
'plotName': 'x (sledge)','windowID': 1,'plotID': 8},
{'windowName': 'Crank Rod - Plots',
'plotName': 'T (motor)','windowID': 1,'plotID': 9},
{'windowName': 'Crank Rod - Plots',
'plotName': 'omega','windowID': 1,'plotID': 10},
{'windowName': '3D Animation',
'plotName': '3D Animation','windowID': 2,'plotID': 4},
{'windowName': 'Window 3',
'plotName': '3D Animation','windowID': 3,'plotID': 5}]
"""
try:
window_list = self.get_plot_windows()
all_plots = []
# Iterate all windows
for window in window_list:
# to retrieve the names and id's of all plots
plot_list = self.get_plots_from_window(window['windowID'])
for plot in plot_list:
# to add them to one list
all_plots.append({'windowName': window['windowName'],
'windowID': window['windowID'],
'plotID': plot['plotID'],
'plotName': plot['plotName']})
return all_plots
except Exception as error:
self.errorhandler(error)
return False
[docs] def save_plot_as_bitmap(
self, filename, window, plot=-1, width=-1, height=-1, dpi=-1):
"""Save a plot or plot window as bitmap.
Supported formats: png, bmp, jpg, gif
Args:
filename (str): Filename of the bitmap to store.
Supported file extensions: .png, .bmp, .jpg,
.gif
window (str -or- int): The name or id of the plot window to select
plot (str -or- int, , optional): The name or id of the plot to
save. Leave empty to store all plots in this
plot window (default).
width (int, optional): The preferred width of the plot in
pixels. 0 or negative => use same
size as the plot (default).
height (int, optional): The preferred height of the plot in
pixels. 0 or negative => use same
size as the plot (default).
dpi (int, optional): The dpi the bitmap should be stored in
(e.g. 300). Zero or negative means:
use the resolution of the screen (default).
Returns:
bool: True when the plot was saved successfully, False otherwise.
Example:
>>> xxsim.save_plot_as_bitmap('c:\\temp\\myplot.png',
'Window 1',
'plot 1',
640,
480)
True
"""
try:
window_id = -1
plot_id = -1
if isinstance(window, str):
window_id = self.get_plot_window_id_from_name(window)
elif isinstance(window, int):
window_id = window
else:
raise WrapperError('Window should be a string or number.')
if isinstance(plot, str):
plot_id = self.get_plot_id_from_name(window_id, plot)
elif isinstance(plot, int):
plot_id = plot
else:
raise WrapperError('Plot should be a string or number.')
if not isinstance(height, int):
raise WrapperError('Plot height should be an integer number.')
if not isinstance(width, int):
raise WrapperError('Plot width should be an integer number.')
if not isinstance(dpi, int):
raise WrapperError('Plot dpi should be an integer number.')
return self.server.plot.savePlotAsBitmap(
{'fileName': filename,
'windowID': window_id,
'plotID': plot_id,
'width': width,
'height': height,
'dpi': dpi})
except Exception as error:
self.errorhandler(error)
return False
[docs] def export_3d_scenery(self, sceneryFileName, plotWindow, plot):
"""Export a 3D scenery from a 3D plot. If the plot is not
a 3D plot, then no scenery file will be exported, but no
error will be given either.
Args:
sceneryFileName (str): The name and path to where the scenery
file should be exported.
plotWindow (str -or- int): The name or ID of the plot window.
plot (str -or- int): The name or id of the plot.
Returns:
bool: True when the plot is not a 3D animation plot or the plot is a 3D
animation plot and a scenery file has been exported succesfully, False
otherwise.
Example:
>>> xxsim.export_3d_scenery('c:\\temp\\myscenery.scn',
'Window 1',
'plot 1')
True
"""
try:
window_id = -1
plot_id = -1
if isinstance(plotWindow, str):
window_id = self.get_plot_window_id_from_name(plotWindow)
elif isinstance(plotWindow, int):
window_id = plotWindow
else:
raise WrapperError('plotWindow should be a string or number.')
if isinstance(plot, str):
plot_id = self.get_plot_id_from_name(window_id, plot)
elif isinstance(plot, int):
plot_id = plot
else:
raise WrapperError('Plot should be a string or number.')
if not isinstance(sceneryFileName, str):
raise WrapperError('sceneryFileName should be a string.')
return self.server.plot.export3DScenery(
{'windowID': window_id,
'plotID': plot_id,
'sceneryFileLocation': sceneryFileName
})
except Exception as error:
self.errorhandler(error)
return False
[docs] def import_3d_scenery(self, sceneryFileName, plotWindow=None, plot=None):
"""Import a 3D scenery to all 3D plots.
Args:
sceneryFileName (str): The name and path to where the scenery
file should be imported from.
plotWindow (str -or- int): The name or ID of the plot window. (Currently not implementd, adds scene to all 3d-animations)
plot (str -or- int): The name or id of the plot. (Currently not implementd, adds scene to all 3d-animations)
Returns:
bool: True when the scenery file has been imported succesfully, False
otherwise.
Example:
>>> xxsim.import_3d_scenery('c:\\temp\\myscenery.scn')
True
"""
try:
window_id = -1
plot_id = -1
if plotWindow is not None:
if isinstance(plotWindow, str):
window_id = self.get_plot_window_id_from_name(plotWindow)
elif isinstance(plotWindow, int):
window_id = plotWindow
else:
raise WrapperError('plotWindow should be a string or number.')
if plot is not None:
if isinstance(plot, str):
plot_id = self.get_plot_id_from_name(window_id, plot)
elif isinstance(plot, int):
plot_id = plot
else:
raise WrapperError('Plot should be a string or number.')
if not isinstance(sceneryFileName, str):
raise WrapperError('sceneryFileName should be a string.')
return self.server.plot.import3DScenery(
{'windowID': window_id,
'plotID': plot_id,
'sceneryFileLocation': sceneryFileName
})
except Exception as error:
self.errorhandler(error)
return False
[docs] def save_submodel(self, submodel, filename, save_encrypted=0):
""" Save the given submodel as a separate file
Args:
submodel (str): The hierarchical submodel name
filename (str): The filename (with full path) that should be used to
save the selected submodel.
save_encrypted (int, optional): 2 if the submodel should be encrypted and C-code
generation is allowed,
1 if the submodel should be encrypted, but C-code generation is not allowed,
0 if the submodel should not be encrypted. Default is 0.
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.save_submodel('mysubmodel',
'c:\\\\temp\\\\mysubmodel.emx')
"""
try:
if not isinstance(submodel, str):
raise WrapperError('submodel should be a string.')
if not isinstance(filename, str):
raise WrapperError('filename should be a string.')
if not isinstance(save_encrypted, int):
raise WrapperError('save_encrypted should be an integer.')
if save_encrypted not in range(3):
raise WrapperError('save_encrypted should be between 0 and 2.')
# the actual XML-RPC call
return self.server.model.saveSubmodel(
{'submodelName': submodel,
'fileName': filename,
'encryption': save_encrypted})
except Exception as error:
self.errorhandler(error)
return False
[docs] def save_encrypted_model(self, submodel_name, filename, password='', allow_edit_with_password=True, allow_processing_without_password=True, allow_edit_parameters=True, allow_code_generation=True):
""" Save the given submodel encrypted as a separate file
Args:
submodel (str): The hierarchical submodel name
filename (str): The filename (with full path) that should be used to
save the selected submodel.
password (str): Optional password. Can be omitted if allow_processing_without_password==True
and allow_edit_with_password==False. In that case 20-sim will encrypt the model with
a random generated key. Default = ''.
allow_edit_with_password (boolean): If True, editing of the submodel in the editer is
possible when the password is given. For this case password must be given.
Default = True.
allow_edit_parameters (boolean): If True, editing of parameters of the submodel is possible
with the Parameters/Initial Values dialog.
Default = True.
allow_code_generation (boolean): If True, C-Code generation and Matlab-code generation is
possible for the submodel.
Default = True.
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.save_encrypted_model('mysubmodel',
'c:\\\\temp\\\\mysubmodel.emx',
password='secret',
allow_code_generation=False)
"""
try:
return self.server.model.saveEncryptedModel({'submodelName' : submodel_name, 'fileName': filename, 'options':[{'key':'password','value':password},\
{'key':'allow_edit_with_password','value':str(allow_edit_with_password)},\
{'key':'allow_processing_without_password','value':str(allow_processing_without_password)},\
{'key':'allow_edit_parameters','value':str(allow_edit_parameters)},\
{'key':'allow_code_generation','value':str(allow_code_generation)}]})
except Exception as error:
self.errorhandler(error)
return False
[docs] def rename_submodel(self, submodel, newname):
""" Rename an existing submodel.
Args:
submodel (str): The hierarchical submodel name
newname (str): The new submodel name
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.rename_submodel('mysubmodel',
'mynewsubmodel')
"""
try:
if not isinstance(submodel, str):
raise WrapperError('submodel should be a string.')
if not isinstance(newname, str):
raise WrapperError('new submodel name should be a string.')
# the actual XML-RPC call
return self.server.model.renameSubmodel(
{'submodelName': submodel,
'newSubmodelName': newname})
except Exception as error:
self.errorhandler(error)
return False
[docs] def replace_submodel(self, submodel, filename, implementation=''):
""" Replace an existing submodel with the version from the provided file
Args:
submodel (str): The hierarchical submodel name
filename (str): The filename (with full path) that should be used to
replace the selected submodel.
implementation (str, optional): The implementation that should be
made active when the stored submodel has multiple
implementations
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.replace_submodel('mysubmodel',
'c:\\\\temp\\\\mysubmodel.emx')
>>> result = xxsim.replace_submodel('mysubmodel',
'c:\\\\temp\\\\mysubmodel.emx',
'mysubmodel implementation')
"""
try:
if not isinstance(submodel, str):
raise WrapperError('submodel should be a string.')
if not isinstance(filename, str):
raise WrapperError('filename should be a string.')
if not isinstance(implementation, str):
raise WrapperError('implementation should be a string.')
# the actual XML-RPC call
return self.server.model.replaceSubmodel(
{'submodelName': submodel,
'fileName': filename,
'implementation': implementation})
except Exception as error:
self.errorhandler(error)
return False
[docs] def insert_submodel(self, submodel, filename, position_x, position_y, implementation=''):
""" Insert an new submodel with the version from the provided file
Args:
submodel (str): The hierarchical submodel name
filename (str): The filename (with full path) that should be used to
to insert the model.
position_x (int): x-position of the new submodel
position_y (int): y-position of the new submodel
implementation (str, optional): The implementation that should be
made active when the submodel has multiple
implementations
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.insert_submodel('mysubmodel',
'c:\\\\temp\\\\mysubmodel.emx',
200, 200)
>>> result = xxsim.insert_submodel('mysubmodel',
'c:\\\\temp\\\\mysubmodel.emx',
200, 200,
'mysubmodel implementation')
"""
try:
if not isinstance(submodel, str):
raise WrapperError('submodel should be a string.')
if not isinstance(filename, str):
raise WrapperError('filename should be a string.')
if not isinstance(implementation, str):
raise WrapperError('implementation should be a string.')
# the actual XML-RPC call
return self.server.model.insertSubmodel(
{'submodelName': submodel,
'fileName': filename,
'position': {'x': int(position_x), 'y': int(position_y)},
'implementation': implementation})
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_submodel_tree(self, submodelName, recursive = False, includeSelf = False):
""" Get the list of submodels underneath the specified submodel.
Args:
submodelName (str): The hierarchical submodel name or the empty
string for the overall model.
recursive (bool, optional, default = False): If True, then all
submodels are recursively obtained from underneath the
specified submodel, if False, then only the submodels directly
underneath the current submodel are returned.
includeSelf (bool, optional, default = False): If True, then
include the submodel with name submodelName in the list of
return values. If False, do not include the submodel with name
submodelName in the list of return values, but only the
submodels underneath.
Returns:
list of dict: list with all submodels for the specified submodel
with the following fields:
* ``name`` *str* the name of the child submodel
* ``hierarchicalName`` *str* the hierarchical model path of the child submodel
* ``type`` *str* the type of the child submodel
* ``category`` *int* -1 if undefined, 0 if equation model, 1 is graphical model
* ``hasMultipleImplementations`` *bool* True if the submodel has more than 1 implementation, False otherwise.
Example:
>>> result = xxsim.get_submodel_tree('Controller.Kp', False)
"""
try:
if not isinstance(submodelName, str):
raise WrapperError('submodelName should be a string.')
if not isinstance(recursive, bool):
raise WrapperError('recursive should be a boolean.')
if not isinstance(includeSelf, bool):
raise WrapperError('includeSelf should be a boolean.')
# the actual XML-RPC call
return self.server.model.getSubmodelTree(
{'submodelName': submodelName,
'recursive': recursive,
'includeSelf': includeSelf})
except Exception as error:
self.errorhandler(error)
return False
[docs] def goto_submodel(self, submodel):
""" Select the provided submodel in the tree and show its contents
Args:
submodel (str): The hierarchical submodel name
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.goto_submodel('mysubmodel')
"""
try:
if not isinstance(submodel, str):
raise WrapperError('submodel should be a string.')
# the actual XML-RPC call
return self.server.gui.gotoSubmodel({'submodelName': submodel})
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_submodel_properties(self, submodel_name='', properties=[]):
""" Get the submodel properties description fields.
Args:
submodel_name (str, optional): The hierarchical submodel name.
If no name is given or the name is the empty string,
then the top-level model is used.
properties (str or list of str, optional): The specific set of properties that is returned.
If left empty or not specified, all properties will be returned, otherwise only the
given set of properties is returned. See the return section about what keys can be
queried.
Returns:
dict: Submodel properties description fields:
* ``name`` *str* the submodel name
* ``description`` *str* the description field
* ``title`` *str* the title field
* ``keywords`` *str* the keywords field
* ``version`` *dict* dict with fields 'major', 'minor', and 'patch'
* ``author`` *str* the author field
* ``manager`` *str* the manager field
* ``project`` *str* the project field
* ``company`` *str* the company field
* ``department`` *str* the string field
* ``helppage`` *str* the helppage field
Example:
>>> result = xxsim.get_submodel_properties('CrankRod')
{'name':'CrankRod',
'description':'A submodel of a crankrod mechanism.',
'title':'CrankRod',
'keywords':'mechanics',
'version':{'major':'1','minor':'0','patch':'0'},
'author':'Dummy',
'manager':'Dummy',
'project':'Crank Rod Mechanism with Sledge',
'company':'Controllab Products B.V.',
'department':'Software',
'helppage':''}
>>> result = xxsim.get_submodel_properties('CrankRod','name')
{'name':'CrankRod'}
"""
#try:
if not isinstance(submodel_name, str):
raise WrapperError(
'get_submodel_properties expects a string input argument.')
# If the properties section is a string...
if isinstance(properties, str):
# Make sure that it becomes a list.
properties_list = [properties]
# Otherwise keep it a list.
else:
properties_list = properties
# The actual XML-RPC call
result = self.server.model.getSubmodelProperties({'submodelName': submodel_name,
'keys': properties_list})
return key_value_to_dict(result["properties"])
[docs] def set_submodel_properties(self, submodel_name='', properties={}):
""" Set the properties of a submodel.
Args:
submodel_name (str): name of the requested submodel.
properties (structured array): key-value pairs of the properties to set.
Returns:
bool: True on success, False otherwise.
Example:
>>> result = xxsim.set_submodel_properties('BeltPulley', [{'key':'title','value':'controller'}])
True
"""
#try:
if not isinstance(submodel_name, str):
raise WrapperError(
'set_submodel_properties expects a string input argument.')
# If the properties section is a string...
if isinstance(properties, str):
# Make sure that it becomes a dictionary.
properties_dict = {properties}
# Otherwise keep it a dictionary.
else:
properties_dict = properties
# The actual XML-RPC call
return self.server.model.setSubmodelProperties({'submodelName': submodel_name,
'submodelProperties': properties_dict})
[docs] def create_connection(self, source_submodel, source_port, target_submodel, target_port, intermediate_points = None):
"""Create a connection between a source port on the source submodel and the target port on the target submodel.
Args:
source_submodel (str): The submodel path and name of the submodel from which the connection should start.
source_port (str): The name of a port from the source_submodel from which the connection should start.
target_submodel (str): The submodel path and name of the submodel at which the connection should end.
target_port (str): The name of a port of the target_submodel at which the connection should end.
intermediate_points (list of dict, optional, default is None): An optional list of x-y coordinates for
intermedate drawing points for the connection line
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.create_connection("Plant", "output", "Controller", "Input")
True
>>> xxsim.create_connection("Plant", "output", "Controller", "Input", [{'x': 100, 'y': 100}, {'x': 150, 'y': 100}])
True
"""
if intermediate_points:
if isinstance(intermediate_points, dict):
intermediate_points = [intermediate_points]
else:
intermediate_points = []
args_dict = {
'sourceSubmodelName': source_submodel,
'sourcePortName': source_port,
'targetSubmodelName': target_submodel,
'targetPortName': target_port
}
# Intermediate points are only supported starting from 20-sim 5.0
if self.tool.version['major'] > 4:
args_dict['intermediatePoints'] = intermediate_points
return self.server.model.createConnection(args_dict)
[docs] def add_port(self, submodel_name, port_name, is_output=False,
port_type=SIGNAL, rows=1, columns=1, quantity='', unit='', data_type=REAL, domain='',
across='', through='', causality='', has_separate_high_low_terminals=False,
accepts_any_number_of_terminals=False, description=''):
""" Add a new port to the given submodel.
Args:
submodel_name (str): name of the submodel to add the port to.
port_name (str): name of the port to add to the submodel.
is_output (bool, optional): Is the port an output (True) or input (False)? Default: input (False).
port_type (int, optional): Is the port a signal port (1), iconic diagram port (2) or bondgraph port (3). Default: Signal (1).
rows (int, optional): The amount of rows for this port. If rows=1 and columns=1, then the port is a scalar. Default: 1.
columns (int, optional): the amount of columns for this port. If rows=1 and columns=1, then the port is a scalar. Default: 1.
quantity (str, optional): physics quantity for this port. Default: empty string. Limit: Only used when port_type == Signal (1).
unit (str, optional): physics unit for this port. Default: empty string. Limit: Only used when port_type == Signal (1).
data_type (int, optional): datatype of the port. Real=1, Integer=2, Bool=3, String=4. Default: Real (1). Limit: Only used when port_type == Signal (1).
domain (str, optional): Domain of the port. Default: empty string. Limit: Only used when port_type == Iconic (2) or port_type == Bondgraph (3).
accross (str, optional): Accross variable for this port. Default: empty string. Limit: Only used when port_type == Iconic (2) or port_type == Bondgraph (3).
through (str, optional): Through variable for this port. Default: empty string. Limit: Only used when port_type == Iconic (2) or port_type == Bondgraph (3).
causality (str, optional): The causality of the port. Default: empty string. Limit: Only used when port_type == Iconic (2) or port_type == Bondgraph (3).
has_separate_high_low_terminals (bool, optional): Show separate terminals (graphical port connectors) for low and high terminals in iconic diagrams. Default: False (show as one terminal). Limit: Only used when port_type == Iconic (2).
accepts_any_number_of_terminals (bool, optional): Accept any number of terminals when True. Default: False. Limit: Only used when port_type == Iconic (2).
description (str, optional): Set the description for this port. Default: empty string.
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.add_port('Controller', 'input', description="Input port for the controller.")
True
>>> xxsim.add_port('Controller', 'output', is_output = True, description="Output port for the controller.")
True
>>> xxsim.add_port('Controller', 'enable', data_type = 3, description="Enable port of type boolean for the controller.")
True
>>> xxsim.add_port('Reference', 'RotationMatrix', rows=3, columns=3, description="3 by 3 rotation matrix for the Reference submodel")
True
>>> xxsim.add_port('Inertia', 'p', port_type=2, domain="rotation", across='omega', through='T', causality='preferred flow out', accepts_any_number_of_terminals=True, description="Add rotation type port to Inertia submodel.")
True
"""
if not isinstance(submodel_name, str):
raise WrapperError(
'add_port expects a string for the "submodel_name" argument.')
if not isinstance(port_name, str):
raise WrapperError(
'add_port expects a string for the "port_name" argument.')
# Check the size fields
if isinstance(rows, int) and isinstance(columns, int):
size_dict = {'rows': rows, 'columns': columns}
else:
raise WrapperError(
'add_port expects an int or a list for the "rows" and "columns" arguments.')
# The actual XML-RPC call
port_definition = {
'name': port_name,
'portType': port_type,
'size': size_dict,
'orientationOut': is_output,
'quantity': quantity,
'unit': unit,
'dataType': data_type,
'domain': domain,
'across': across,
'through': through,
'causality': causality,
'hasSeparateHighAndLowTerminals': has_separate_high_low_terminals,
'acceptsAnyNumberOfTerminals': accepts_any_number_of_terminals,
'description': description
}
return self.server.model.addPort({'submodelName': submodel_name, 'portDefinition': port_definition})
[docs] def remove_port(self, submodel_name, port_name):
""" Remove the specified port form the specified submodel.
Args:
submodel_name (str): name of the submodel from which the port should be removed.
port_name (str): name of the port to be removed.
Returns:
bool: True on success, False otherwise.
Example:
>>> xxsim.remove_port('Controller', 'output')
True
"""
if not isinstance(submodel_name, str):
raise WrapperError(
'remove_port expects a string for the "submodel_name" argument.')
if not isinstance(port_name, str):
raise WrapperError(
'remove_port expects a string for the "port_name" argument.')
# The actual XML-RPC call
return self.server.model.removePort({'submodelName': submodel_name, 'portName': port_name})
[docs] def get_ports(self, submodel_name=''):
""" Get all ports for a submodel.
Args:
submodel_name (str): Name of the submodel to obtain the port definitions from.
Returns:
dict containing a 'ports' member that contains:
list of dict: list with all ports with the following fields:
* ``name``: *str* the name of this port.
* ``portType``: *int* 1 = Signal, 2 = Iconic, 3 = Bondgraph, -1 = unknown.
* ``size``: *struct* contains rows and columns fields for the port.
* ``orientationOut``: *bool* True if the orientation is out (output), False if in (input).
* ``quantity``: *str* the quantity of the port (signal-only).
* ``unit``: *str* the unit of the port (signal-only).
* ``dataType``: *int* 1=real, 2=integer, 3=boolean, 4=string, -1 = unknown (signal-only).
* ``domain``: *str* domain of the port (Iconic/Bondgraph-only).
* ``across``: *str* the across variable for this port (Iconic/Bondgraph-only).
* ``through``: *str* the through variable for this port (Iconic/Bondgraph-only).
* ``causality``: *str* the causality of the port (Iconic/Bondgraph-only).
* ``hasSeparateHighAndLowTerminals``: *bool* True if high and low terminals are separated, False otherwise (Iconic-only).
* ``acceptsAnyNumberOfTerminals``: *bool* True if any number of terminals is accepted, False otherwise (Iconic-only).
* ``description``: *str* the description of the port.
Example:
>>> result = xxsim.get_ports('Controller.Kp')
"""
if not isinstance(submodel_name, str):
raise WrapperError(
'get_ports expects a string input argument.')
# The actual XML-RPC call
return self.server.model.getPorts({'submodelName': submodel_name})
[docs] def get_ports2(self, submodel_name=''):
""" Get all ports for a submodel. Simplified version of get_ports
Args:
submodel_name (str): Name of the submodel to obtain the port definitions from.
Returns:
list of dict: list with all ports with the following fields:
* ``name``: *str* the name of this port.
* ``portType``: *int* 1 = Signal, 2 = Iconic, 3 = Bondgraph, -1 = unknown.
* ``size``: *struct* contains rows and columns fields for the port.
* ``orientationOut``: *bool* True if the orientation is out (output), False if in (input).
* ``quantity``: *str* the quantity of the port (signal-only).
* ``unit``: *str* the unit of the port (signal-only).
* ``dataType``: *int* 1=real, 2=integer, 3=boolean, 4=string, -1 = unknown (signal-only).
* ``domain``: *str* domain of the port (Iconic/Bondgraph-only).
* ``across``: *str* the across variable for this port (Iconic/Bondgraph-only).
* ``through``: *str* the through variable for this port (Iconic/Bondgraph-only).
* ``causality``: *str* the causality of the port (Iconic/Bondgraph-only).
* ``hasSeparateHighAndLowTerminals``: *bool* True if high and low terminals are separated, False otherwise (Iconic-only).
* ``acceptsAnyNumberOfTerminals``: *bool* True if any number of terminals is accepted, False otherwise (Iconic-only).
* ``description``: *str* the description of the port.
Example:
>>> result = xxsim.get_ports('Controller.Kp')
"""
if not isinstance(submodel_name, str):
raise WrapperError(
'get_ports expects a string input argument.')
# The actual XML-RPC call
result = self.server.model.getPorts({'submodelName': submodel_name})
if 'ports' in result:
return result['ports']
return []
[docs] def get_icon_text(self, submodel_name):
""" Get the icon text for a submodel.
Args:
submodel_name (str): Name of the submodel to obtain the icon text from.
Returns:
string: the Sidops icon text
Example:
>>> result = xxsim.get_icon_text('Controller.Kp')
>>> icon_text = result['icon']
>>> print(icon_text)
icon bg top
figures
rectangle 0 0 32 32 color 0 fill 15132390;
text 'K' 16 16 color 16711680 16 bold;
end;
"""
if not isinstance(submodel_name, str):
raise WrapperError(
'get_icon_text expects a string input argument.')
# The actual XML-RPC call
return self.server.model.getIconText({'submodelName': submodel_name})
[docs] def set_icon_text(self, submodel_name, icon_text):
""" Set the icon text for a submodel.
Args:
submodel_name (str): Name of the submodel to change the icon text for.
icon_text (str): The Sidops icon text
Returns:
bool: True on success, False otherwise.
"""
if not isinstance(submodel_name, str):
raise WrapperError(
'set_icon_text: submodel_name should be a string argument.')
if not isinstance(icon_text, str):
raise WrapperError(
'set_icon_text: icon_text should be a string argument.')
# The actual XML-RPC call
return self.server.model.setIconText({
'submodelName': submodel_name,
'iconText': icon_text
})
[docs] def get_type_text(self, submodel_name):
""" Get the type text for a submodel.
This function returns the Sidops source text of the submodel interface
Args:
submodel_name (str): Name of the submodel to obtain the type text from.
Returns:
string: the Sidops type text
Example:
>>> result = xxsim.get_type_text('Controller.Kp')
>>> type_text = result['type']
>>> print(type_text)
type Gain
ports
signal in input;
signal out output;
parameters
real K = 100.0;
end;
"""
if not isinstance(submodel_name, str):
raise WrapperError(
'get_icon_text expects a string input argument.')
# The actual XML-RPC call
return self.server.model.getTypeText({'submodelName': submodel_name})
[docs] def set_type_text(self, submodel_name, type_text):
""" Set the type text for a submodel.
Args:
submodel_name (str): Name of the submodel to change the type text for.
type_text (str): The Sidops type text
Returns:
bool: True on success, False otherwise.
"""
if not isinstance(submodel_name, str):
raise WrapperError(
'set_type_text: submodel_name should be a string argument.')
if not isinstance(type_text, str):
raise WrapperError(
'set_type_text: type_text should be a string argument.')
# The actual XML-RPC call
return self.server.model.setTypeText({
'submodelName': submodel_name,
'typeText': type_text
})
[docs] def set_window_size(self, left, top, width, height):
""" Set the size of the last opened window
Args:
left (int):
top (int):
width (int):
height (int):
Returns:
bool: True on success, False otherwise
Example:
>>> result = xxsim.set_window_size(0, 0, 640, 480)
"""
try:
if not (isinstance(left, int) and isinstance(top, int) and
isinstance(width, int) and isinstance(height, int)):
raise WrapperError(
'set_window_size arguments should be integers.')
# the actual XML-RPC call
return self.server.util.setWindowSize({
'left': left, 'top': top, 'width': width, 'height': height})
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_window_size(self):
""" Returns the current size of the last opened window
Returns:
dict: size information with the following fields:
* ``left`` *int* distance from the left screen border
* ``top`` *int* distance from the top screen border
* ``width`` *int* the width of the window
* ``height`` *int* the height of the window
Example:
>>> xxsim.get_window_size()
{'top': 0, 'height': 480, 'left': 0, 'width': 640}
"""
try:
# the actual XML-RPC call
return self.server.util.getWindowSize()
except Exception as error:
self.errorhandler(error)
return False
[docs] def set_monitor_variables(self, variables):
""" Learn 20-sim a list of variables that it should monitor.
Use the get_monitor_variables() function to fetch with one call all
variables set using this function. Calling this function again will
erase the existing list of registered monitor variables.
Args:
variables (list of str): String list with the names of all variables
to monitor. Names can be specified with indices to indicate a
member in a vector or matrix e.g. var1[2,3]
Returns:
list of dict: list with 1 entry for each specified variable with the
following dict members:
* ``id`` *int* the identifier for this entry in the list
* ``size`` *list of int* sizes for each dimension: rows, columns
Example:
>>> xxsim.set_monitor_variables(['var1','submodel.matrix'])
[{'id': 0, 'size': [1]}, {'id': 1, 'size': [2,2]}]
"""
try:
if not isinstance(variables, list):
if isinstance(variables, str):
variables=[variables]
result = self.server.simulator.setMonitorVariables({'variables': variables, 'add': True})
return result
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_monitor_variables(self, ids=[]):
""" Retrieves the selected monitor variables (see set_monitor_variables).
If an empty array is given, every registered monitor variable will be
returned, else only the provided selection.
Args:
ids (list of int): Integer list with the ids of the variables to
return. The variable id is the id as returned by the
set_monitor_variables function.
Returns:
list of dict: list with entries for each specified variable or False on
error. The dict contains the following dict members:
* ``id`` *int* the id for this monitor variable
* ``size`` *list of int* list of sizes for each dimension: rows, columns
* ``values`` *list of float* all values for this variable
Example:
>>> xxsim.get_monitor_variables()
[{'id': 0, 'size': [1], 'values': [0.0]},
{'id': 1, 'size': [2,2], 'values': [1.0, 2.0, 3.0, 4.0]}]
"""
try:
if not isinstance(ids, list):
if isinstance(ids, int):
ids=[ids]
result = self.server.simulator.getMonitorValues({'ids': ids})
return result
except Exception as error:
self.errorhandler(error)
return False
[docs] def get_monitor_values(self, ids=[]):
""" Retrieves the selected monitor values (see set_monitor_variables).
This function returns the contents of the values member in the dict
list as returned by the get_monitor_variables() function.
If an empty array is given, the value of every registered monitor
variable will be returned, else only the provided selection.
Args:
ids (list of int): Integer list with the ids of the variables to
return. The variable id is the id as returned by the
set_monitor_variables function.
Returns:
list of float: list with the values of the monitored variables or
False on error
Example:
>>> xxsim.get_monitor_values()
[0.0, [1.0, 2.0, 3.0, 4.0]]
"""
variables = self.get_monitor_variables(ids)
if isinstance(variables,bool):
return False
values = []
for variable in variables:
if variable['size'] == [1]:
# Scalar
values.append(variable['values'][0])
else:
values.append(variable['values'])
return values