Source code for controllab.tdmech

# 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 3D Mechanics Editor.

    Example:
        >>> import controllab
        >>> tdmech = controllab.TDMech()
"""
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 TDMechWrapper(Wrapper): """3D Mechanics Editor scripting interface for Python. This is a Python wrapper for 3D Mechanics's XML-RPC interface. Attributes: errorlevel: The level of errors that should be raised. """ DEFAULT_PORT = 5680 def _get_default_exetool(self, version=None, **kwargs): """ Get the default ExeTool for TDMechWrapper. Optionally a different version of the default tool can be started. Args: version (optional) : Will be passed on to ExeTool.__init__ Default: '5.0' Returns: ExeTool : An ExeTool representing 3D Mechanics Editor """ return ExeTool('TDMECH', version or ExeTool.DEFAULT_XXSIM_VERSION) def _set_xrc_server(self, xrc): # pylint: disable=arguments-differ """ Correctly sets the self.server attribute. Args: xrc (xmlrpclib.ServerProxy) : The serverproxy Example: >>> import xmlrpc.client as xmlrpclib >>> _set_xrc_server(self, ... xmlrpclib.Server_proxy('http://localhost:5680')) """ self.server = xrc.tdmech
[docs] def connect(self, uri='http://localhost:{}'.format(DEFAULT_PORT), version=ExeTool.DEFAULT_XXSIM_VERSION, **kwargs): """Create a connection with 3D Mechanics Editor using XML-RPC Args: uri (str): The URI to connect to. Defaults to 'http://localhost:5680' for connecting to 3D Mechanics Editor. Returns: bool: True if successful, False otherwise. Example: >>> connect('http://localhost:5680') True """ return super(TDMechWrapper, self).connect(uri, version, **kwargs)
[docs] def get_version(self): """Get the version number of the 3D Mechanics instance to 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: >>> tdmech.get_version() {'major': 5, 'minor': 0, 'patch': 0, 'build': 10000} """ 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 open_model(self, model): """Opens a model file from its path. Args: model (str): Name of the file or model to load. Returns: int: The identifier of the model (1 or higher). It can be used to select the active model Returns False on error. The model is activated and initialized after opening. Example: >>> model_id = tdmech.open_model('c:\\\\temp\\\\myproject.3dm') """ try: # Check whether the provided model exists if os.path.isabs(model) and not os.path.isfile(model): raise FileNotFoundError('File not found: {}'.format(model)) # Open the model reply = self.server.openModel({'name': model}) # the actual XML-RPC call if reply is not None: return reply['identifier'] except Exception as error: self.errorhandler(error) return False
[docs] def close_model(self, close_window=False): """ Closes active 3D Mechanics model without saving changes Args: close_window (bool, optional): indicate if the window should be closed if other models are also still open Returns: bool: True when closed properly, False otherwise Example: >>> result = tdmech.close_model() """ try: # the actual XML-RPC call return self.server.closeModel({'closewindow': close_window}) except Exception as error: self.errorhandler(error) return False
[docs] def save_model(self, filename=None): """ Saves the changes made to active 3D Mechanics model Args: filename (str, optional): The filename that should be used to save the active model. If omitted, 3D Mechanics Editor will save the model under its current name if it has any. Returns: bool: True on success, False otherwise Example: >>> result = tdmech.save_model('c:\\\\temp\\\\mymodel.3dm') """ 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 generate_code(self): """ Generates code for the active model. It uses the current setting in the model this will change, where we can pass arguments, like filename, option to generate scenerey generate icon, and what kind of icon Returns: filename (str): The 20-sim submodel filename the code has been generated in submodelname (str): The submodel name used in the scenery export, see scenery argument scenery (str): The 20-sim 3D scenery filename. This can be imported in a 3D animation plot Example: >>> result = tdmech.generate_code() """ try: # the actual XML-RPC call return self.server.generateCode() except Exception as error: self.errorhandler(error) return False
[docs] def generate_submodel(self, filename, currentConfigAsInitial=False, generateIcon=1, iconFilename=None): """ Generates submodel code for the active model. Args: filename (str): The 20-sim submodel filename for which the code should be generated currentConfigAsInitial (bool, optional): should the current configuration be used as initial value generateIcon (int, optional): 0=no icon, 1=automatic (raytrace), 2=from file iconFilename (str, optional): bitmap file to be used as filename. Only used if generateIcon == 2 Returns: bool: True when code has been generated properly, False otherwise Example: >>> result = tdmech.generate_submodel('mysubmodel.emx') """ try: if iconFilename is None: iconFilename = '' # the actual XML-RPC call return self.server.generateSubmodel( {'filename': filename, 'currentConfigAsInitial': currentConfigAsInitial, 'generateIcon': generateIcon, 'iconFilename': iconFilename}) except Exception as error: self.errorhandler(error) return False
[docs] def generate_scenery(self, filename, submodelname, generateJoints=False): """ Generates submodel code for the active model. Args: filename (str): The scenery filename to be generated submodelname (str): the hierarchical name of the submodel generated by 3D mechanics generateJoints (boolean, optional): Indicates if generate joint representation should be generated Returns: bool: True when scenery has been generated properly, False otherwise Example: >>> result = tdmech.generate_scenery('myscenery.scn', 'mysubmodelin20sim') """ try: # the actual XML-RPC call return self.server.generateScenery( {'filename': filename, 'submodelname': submodelname, 'generateJoints': generateJoints}) except Exception as error: self.errorhandler(error) return False
[docs] def get_design_parameters(self, parameters=None): """Returns a list with the specified design parameters Args: list of parameters to return. If empty return the full list Returns: list *or* None: list with a design parameters dict (when retrieving more than one parameter) False is returned on a failure Examples: To retrieve a list of design parameters you can use: >>> params = tdmech.get_design_parameters(['Body1.Ixx','Body2.PosX']) 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), for now this is always 1 * ``values``: the values of the retrieved parameter(s) * ``unit``: the unit of the retrieved parameters(s) * ``global``: the global boolean of the retrieved parameter(s) To retrieve a single of parameter you can use >>> tdmech.get_design_parameters('Body1.Ixx') [{'name': 'Body1.Ixx', 'size': [1], 'values': [1.0]}. 'unit': 'kg', 'global: True] """ if not parameters: parameters = [] if isinstance(parameters, list): # Make sure that the list contains a dict try: if len(parameters) > 0: new_parameters_list = [] for parameter in parameters: if isinstance(parameter, dict) and 'name' in parameter.keys(): # Correct layout new_parameters_list.append(parameter) elif isinstance(parameter, str): # List of str new_parameters_list.append({'name': parameter}) parameters = new_parameters_list except: pass elif isinstance(parameters, str): # Apparently only a name given. Create a proper array of structs parameters = [{'name': parameters}] try: return self.server.getDesignParameters(parameters) except Exception as error: self.errorhandler(error) return False
[docs] def set_design_parameters(self, parameters, values = None): """Set a list of design parameters Args: list of parameters to set. Returns: True if the function succeeds Examples: To set a list of design parameters you can use: >>> tdmech.set_design_parameters([{'name': 'Body1.Ixx', 'size': [1], 'value': [1.0], 'unit': 'kg', 'global': True}, {'name': 'Body1.Iyy', 'size': [1], 'value': [2.0], 'unit': 'kg', 'global': True}]) To update an existing of parameter you can use >>> tdmech.set_design_parameters('Body1.Ixx', 1.0) True """ try: if not parameters: return False # Argument checks _parameters = [] if isinstance(parameters, list): if values is not None and isinstance(values, list): # Parameter name array + value array # This way of calling requires existing design parameters if len(parameters) != len(values): self.errorhandler(Exception('Size mismatch between parameters and values array')) return False else: # Build the list of structs for index in range(0, len(parameters)): if isinstance(values[index], list): value_arr = values[index] for i in range(len(value_arr)): value_arr[i] = float(value_arr[i]) else: value = float(values[index]) value_arr = [value] _parameters.append({'name': parameters[index], 'value': value_arr}) else: # Expected: list of dict if len(parameters) == 0: return False for index in range(0, len(parameters)): if isinstance(parameters[index], dict): _parameters.append(parameters[index]) else: self.errorhandler(Exception('Type mismatch for parameter list element {0}. Expected type: dict'.format(index))) return False elif isinstance(parameters, str) and values is not None: # Update one existing design parameter if isinstance(values, list): value_arr = values for i in range(len(value_arr)): value_arr[i] = float(value_arr[i]) else: value = float (values) value_arr = [value] _parameters.append({'name': parameters, 'value': value_arr}) else: self.errorhandler(Exception("Unexpected argument type '{0}' for the parameters argument".format(type(parameters)))) return False return self.server.setDesignParameters(_parameters) except Exception as error: self.errorhandler(error) return False
[docs] def set_STL_file(self, body_name: str, filename: str, properties: dict = None): """ Set/change the STL representation of a body Args: body_name (str): The name of an existing body in the 3D model filename (str): The name of the STL file to set for this body properties (dict, optional): A dictionary with additional settings related to the STL representation (reserved for future use) Returns: True when the function succeeds, False otherwise """ try: _properties = [] # TODO: Parse the properties dictionary and create an array with key-value # dicts for each item return self.server.setSTLFile({'bodyname': body_name, 'filename': filename}) except Exception as error: self.errorhandler(error) return False