Source code for controllab.xxsim

# 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