def exportSMURFScene(entities, path): """Exports an arranged scene into SMURFS. It will export only entities with a valid entity/name, and entity/type property. Args: selected_only(bool): If True only selected entities get exported. subfolder(bool): If True the models are exported into separate subfolders entities: path: Returns: """ log("Exporting scene to " + path + '.smurfs', "INFO") with open(path + '.smurfs', 'w') as outputfile: sceneinfo = ( "# SMURF scene created at " + path + " " + datetime.now().strftime("%Y%m%d_%H:%M") + "\n" ) log(sceneinfo, "INFO") sceneinfo += "# created with Phobos " + version + " - " + repository + "\n\n" ioUtils.securepath(path) outputfile.write(sceneinfo) entitiesdict = roundFloatsInDict( {'entities': entities}, ioUtils.getExpSettings().decimalPlaces ) outputfile.write(yaml.dump(entitiesdict))
def exportSMURFScene(entities, path): """Exports an arranged scene into SMURFS. It will export only entities with a valid entity/name, and entity/type property. Args: selected_only(bool): If True only selected entities get exported. subfolder(bool): If True the models are exported into separate subfolders entities: path: Returns: """ log("Exporting scene to " + path + '.smurfs', "INFO") with open(path + '.smurfs', 'w') as outputfile: sceneinfo = ("# SMURF scene created at " + path + " " + datetime.now().strftime("%Y%m%d_%H:%M") + "\n") log(sceneinfo, "INFO") sceneinfo += "# created with Phobos " + version + " - " + repository + "\n\n" ioUtils.securepath(path) outputfile.write(sceneinfo) entitiesdict = roundFloatsInDict( {'entities': entities}, ioUtils.getExpSettings().decimalPlaces) outputfile.write(yaml.dump(entitiesdict))
def storePose(root, posename): """Stores the current pose of all of a model's selected joints. Existing poses of the same name will be overwritten. Args: root(bpy_types.Object): root of the model the pose belongs to posename(str): name the pose will be stored under Returns: : Nothing. """ if root: filename = nUtils.getModelName(root) + '::poses' posedict = json.loads(bUtils.readTextFile(filename)) if not posedict: posedict = {posename: {'name': posename, 'joints': {}}} else: posedict[posename] = {'name': posename, 'joints': {}} links = sUtils.getChildren(root, ('link', ), True, False) sUtils.selectObjects([root] + links, clear=True, active=0) bpy.ops.object.mode_set(mode='POSE') for link in (link for link in links if 'joint/type' in link and link['joint/type'] not in ['fixed', 'floating']): link.pose.bones['Bone'].rotation_mode = 'XYZ' posedict[posename]['joints'][nUtils.getObjectName( link, 'joint')] = link.pose.bones['Bone'].rotation_euler.y bpy.ops.object.mode_set(mode='OBJECT') posedict = gUtils.roundFloatsInDict( posedict, ioUtils.getExpSettings().decimalPlaces) bUtils.updateTextFile(filename, json.dumps(posedict)) else: log("No model root provided to store the pose for", "ERROR")
def storePose(root, posename): """Stores the current pose of all of a model's selected joints. Existing poses of the same name will be overwritten. Args: root(bpy_types.Object): root of the model the pose belongs to posename(str): name the pose will be stored under Returns: : Nothing. """ if root: filename = nUtils.getModelName(root) + '::poses' posedict = yaml.load(bUtils.readTextFile(filename)) if not posedict: posedict = {posename: {'name': posename, 'joints': {}}} else: posedict[posename] = {'name': posename, 'joints': {}} links = sUtils.getChildren(root, ('link',), True, False) sUtils.selectObjects([root] + links, clear=True, active=0) bpy.ops.object.mode_set(mode='POSE') for link in ( link for link in links if 'joint/type' in link and link['joint/type'] not in ['fixed', 'floating'] ): link.pose.bones['Bone'].rotation_mode = 'XYZ' posedict[posename]['joints'][nUtils.getObjectName(link, 'joint')] = link.pose.bones[ 'Bone' ].rotation_euler.y bpy.ops.object.mode_set(mode='OBJECT') posedict = gUtils.roundFloatsInDict(posedict, ioUtils.getExpSettings().decimalPlaces) bUtils.updateTextFile(filename, yaml.dump(posedict, default_flow_style=False)) else: log("No model root provided to store the pose for", "ERROR")
def deriveModelDictionary(root, name='', objectlist=[]): """Derives a dictionary representation of a Phobos model. If name is not specified, it overrides the modelname in the root. If the modelname is not defined at all, 'unnamed' will be used instead. :param root: root object of the model :type root: bpy.types.Object :param name: name for the derived model :type name: str :param objectlist: objects to derive the model from :type objectlist: list of bpy.types.Object :return: representation of the model based on the root object :rtype: dict """ if root.phobostype not in ['link', 'submodel']: log(root.name + " is no valid 'link' or 'submodel' object.", "ERROR") return None # define model name if name: modelname = name elif 'modelname' in root: modelname = root['modelname'] else: modelname = 'unnamed' model = { 'links': {}, 'joints': {}, 'sensors': {}, 'motors': {}, 'controllers': {}, 'materials': {}, 'meshes': {}, 'lights': {}, 'groups': {}, 'chains': {}, 'date': datetime.now().strftime("%Y%m%d_%H:%M"), 'name': modelname } log( "Creating dictionary for model " + modelname + " with root " + root.name + ".", 'INFO') # create tuples of objects belonging to model if not objectlist: objectlist = sUtils.getChildren( root, selected_only=ioUtils.getExpSettings().selectedOnly, include_hidden=False) linklist = [link for link in objectlist if link.phobostype == 'link'] # digest all the links to derive link and joint information log( "Parsing links, joints and motors... " + (str(len(linklist))) + " total.", "INFO") for link in linklist: # parse link information (including inertia) model['links'][nUtils.getObjectName(link, 'link')] = derive_link(link) if sUtils.getEffectiveParent(link): # joint may be None if link is a root jointdict = deriveJoint(link) model['joints'][jointdict['name']] = jointdict motordict = deriveMotor(link, jointdict) # motor may be None if no motor is attached if motordict: model['motors'][motordict['name']] = motordict # TODO what was this supposed to do? # as it is only ever used by deriveSubmechanism we might want to move it...? # combine inertia if certain objects are left out, and overwrite it # inertials = (i for i in objectlist if i.phobostype == 'inertial' and 'inertia' in i) # editlinks = {} # for i in inertials: # if i.parent not in linklist: # realparent = sUtils.getEffectiveParent(i, ignore_selection=bool(objectlist)) # if realparent: # parentname = nUtils.getObjectName(realparent) # if parentname in editlinks: # editlinks[parentname].append(i) # else: # editlinks[parentname] = [i] # for linkname in editlinks: # inertials = editlinks[linkname] # try: # inertials.append(bpy.context.scene.objects['inertial_' + linkname]) # except KeyError: # pass # # get inertia data # mass, com, inertia = inertiamodel.fuse_inertia_data(inertials) # if not any(mass, com, inertia): # continue # # add inertia to model # inertia = inertiamodel.inertiaMatrixToList(inertia) # model['links'][linkname]['inertial'] = { # 'mass': mass, 'inertia': inertia, # 'pose': {'translation': list(com), # 'rotation_euler': [0, 0, 0]} # } # complete link information by parsing visuals and collision objects log("Parsing visual and collision (approximation) objects...", 'INFO') for obj in objectlist: if obj.phobostype in ['visual', 'collision']: props = deriveDictEntry(obj) parentname = nUtils.getObjectName( sUtils.getEffectiveParent(obj, ignore_selection=bool(objectlist))) model['links'][parentname][obj.phobostype][nUtils.getObjectName( obj)] = props elif obj.phobostype == 'approxsphere': props = deriveDictEntry(obj) parentname = nUtils.getObjectName( sUtils.getEffectiveParent(obj, ignore_selection=bool(objectlist))) model['links'][parentname]['approxcollision'].append(props) # combine collision information for links for linkname in model['links']: link = model['links'][linkname] bitmask = 0 for collname in link['collision']: try: # bitwise OR to add all collision layers bitmask = bitmask | link['collision'][collname]['bitmask'] except KeyError: pass link['collision_bitmask'] = bitmask # parse sensors and controllers log("Parsing sensors and controllers...", 'INFO') for obj in objectlist: if obj.phobostype in ['sensor', 'controller']: props = deriveDictEntry(obj) model[obj.phobostype + 's'][nUtils.getObjectName(obj)] = props # parse materials log("Parsing materials...", 'INFO') model['materials'] = collectMaterials(objectlist) for obj in objectlist: if obj.phobostype == 'visual': mat = obj.active_material try: if mat.name not in model['materials']: # this should actually never happen model['materials'][mat.name] = deriveMaterial(mat) linkname = nUtils.getObjectName( sUtils.getEffectiveParent( obj, ignore_selection=bool(objectlist))) model['links'][linkname]['visual'][nUtils.getObjectName( obj)]['material'] = mat.name except AttributeError: log("Could not parse material for object " + obj.name, "ERROR") # identify unique meshes log("Parsing meshes...", "INFO") for obj in objectlist: try: if ((obj.phobostype == 'visual' or obj.phobostype == 'collision') and (obj['geometry/type'] == 'mesh') and (obj.data.name not in model['meshes'])): model['meshes'][obj.data.name] = obj for lod in obj.lod_levels: if lod.object.data.name not in model['meshes']: model['meshes'][lod.object.data.name] = lod.object except KeyError: log("Undefined geometry type in object " + obj.name, "ERROR") # gather information on groups of objects log("Parsing groups...", 'INFO') # TODO: get rid of the "data" part and check for relation to robot for group in bpy.data.groups: # skip empty groups if not group.objects: continue # handle submodel groups separately from other groups if 'submodeltype' in group.keys(): continue # TODO create code to derive Submodels # model['submodels'] = deriveSubmodel(group) elif nUtils.getObjectName(group, 'group') != "RigidBodyWorld": model['groups'][nUtils.getObjectName( group, 'group')] = deriveGroupEntry(group) # gather information on chains of objects log("Parsing chains...", "INFO") chains = [] for obj in objectlist: if obj.phobostype == 'link' and 'endChain' in obj: chains.extend(deriveChainEntry(obj)) for chain in chains: model['chains'][chain['name']] = chain # gather information on lights log("Parsing lights...", "INFO") for obj in objectlist: if obj.phobostype == 'light': model['lights'][nUtils.getObjectName(obj)] = deriveLight(obj) # gather submechanism information from links log("Parsing submechanisms...", "INFO") submechanisms = [] for link in linklist: if 'submechanism/name' in link: indep = [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/independent'] ] spann = [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/spanningtree'] ] active = [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/active'] ] submech = { 'type': link['submechanism/type'], 'contextual_name': link['submechanism/name'], 'jointnames_independent': indep, 'jointnames_spanningtree': spann, 'jointnames_active': active } submechanisms.append(submech) model['submechanisms'] = submechanisms # add additional data to model model.update(deriveTextData(model['name'])) # shorten numbers in dictionary to n decimalPlaces and return it log("Rounding numbers...", "INFO") # TODO: implement this separately return roundFloatsInDict(model, ioUtils.getExpSettings().decimalPlaces)
def deriveModelDictionary(root, name='', objectlist=[]): """Returns a dictionary representation of a Phobos model. If name is not specified, it overrides the modelname in the root. If the modelname is not defined at all, 'unnamed' will be used instead. Args: root(bpy_types.Object): root object of the model name(str, optional): name for the derived model (Default value = '') objectlist(list: bpy_types.Object): objects to derive the model from objectlist: (Default value = []) Returns: """ if root.phobostype not in ['link', 'submodel']: log(root.name + " is no valid 'link' or 'submodel' object.", "ERROR") return None # define model name if name: modelname = name elif 'model/name' in root: modelname = root['model/name'] else: modelname = 'unnamed' # define model version if 'model/version' in root: modelversion = root['model/version'] else: modelversion = 'undefined' modeldescription = bUtils.readTextFile('README.md') model = { 'links': {}, 'joints': {}, 'sensors': {}, 'motors': {}, 'controllers': {}, 'materials': {}, 'meshes': {}, 'lights': {}, 'groups': {}, 'chains': {}, 'date': datetime.now().strftime("%Y%m%d_%H:%M"), 'name': modelname, 'version': modelversion, 'description': modeldescription, } log( "Creating dictionary for model '" + modelname + "' with root '" + root.name + "'.", 'INFO', prefix="\n", ) # create tuples of objects belonging to model if not objectlist: objectlist = sUtils.getChildren( root, selected_only=ioUtils.getExpSettings().selectedOnly, include_hidden=False ) linklist = [link for link in objectlist if link.phobostype == 'link'] # digest all the links to derive link and joint information log("Parsing links, joints and motors... " + (str(len(linklist))) + " total.", "INFO") for link in linklist: # parse link information (including inertia) model['links'][nUtils.getObjectName(link, 'link')] = deriveLink( link, logging=True, objectlist=objectlist ) # parse joint and motor information if sUtils.getEffectiveParent(link): # joint may be None if link is a root # to prevent confusion links are always defining also joints jointdict = deriveJoint(link, logging=True, adjust=True) log(" Setting joint type '{}' for link.".format(jointdict['type']), 'DEBUG') # first check if we have motor information in the joint properties # if so they can be extended/overwritten by motor objects later on if '$motor' in jointdict: motordict = jointdict['$motor'] # at least we need a type property if 'type' in motordict: # if no name is given derive it from the joint if not 'name' in motordict: motordict["name"] = jointdict['name'] model['motors'][motordict['name']] = motordict # link the joint by name: motordict['joint'] = jointdict['name'] del jointdict['$motor'] model['joints'][jointdict['name']] = jointdict for mot in [child for child in link.children if child.phobostype == 'motor']: motordict = motormodel.deriveMotor(mot, jointdict) # motor may be None if no motor is attached if motordict: log(" Added motor {} to link.".format(motordict['name']), 'DEBUG') if motordict['name'] in model["motors"]: model['motors'][motordict['name']].update(motordict) else: model['motors'][motordict['name']] = motordict # parse sensors and controllers sencons = [obj for obj in objectlist if obj.phobostype in ['sensor', 'controller']] log("Parsing sensors and controllers... {} total.".format(len(sencons)), 'INFO') for obj in sencons: props = deriveDictEntry(obj, names=True, objectlist=objectlist) model[obj.phobostype + 's'][nUtils.getObjectName(obj)] = props # parse materials log("Parsing materials...", 'INFO') model['materials'] = collectMaterials(objectlist) for obj in objectlist: if obj.phobostype == 'visual': mat = obj.active_material if mat: if mat.name not in model['materials']: model['materials'][mat.name] = deriveMaterial(mat) linkname = nUtils.getObjectName( sUtils.getEffectiveParent(obj, ignore_selection=bool(objectlist)) ) model['links'][linkname]['visual'][nUtils.getObjectName(obj)][ 'material' ] = mat.name # identify unique meshes log("Parsing meshes...", "INFO") for obj in objectlist: try: if ( (obj.phobostype == 'visual' or obj.phobostype == 'collision') and (obj['geometry/type'] == 'mesh') and (obj.data.name not in model['meshes']) ): model['meshes'][obj.data.name] = obj #todo2.9: for lod in obj.lod_levels: # if lod.object.data.name not in model['meshes']: # model['meshes'][lod.object.data.name] = lod.object except KeyError: log("Undefined geometry type in object " + obj.name, "ERROR") # gather information on groups of objects log("Parsing groups...", 'INFO') #todo2.9: TODO: get rid of the "data" part and check for relation to robot # for group in bpy.data.groups: # # skip empty groups # if not group.objects: # continue # # handle submodel groups separately from other groups # if 'submodeltype' in group.keys(): # continue # # TODO create code to derive Submodels # # model['submodels'] = deriveSubmodel(group) # elif nUtils.getObjectName(group, 'group') != "RigidBodyWorld": # model['groups'][nUtils.getObjectName(group, 'group')] = deriveGroupEntry(group) # gather information on chains of objects log("Parsing chains...", "INFO") chains = [] for obj in objectlist: if obj.phobostype == 'link' and 'endChain' in obj: chains.extend(deriveChainEntry(obj)) for chain in chains: model['chains'][chain['name']] = chain # gather information on lights log("Parsing lights...", "INFO") for obj in objectlist: if obj.phobostype == 'light': model['lights'][nUtils.getObjectName(obj)] = deriveLight(obj) # gather submechanism information from links log("Parsing submechanisms...", "INFO") def getSubmechanisms(link): """ Args: link: Returns: """ if 'submechanism/name' in link.keys(): submech = { 'type': link['submechanism/type'], 'contextual_name': link['submechanism/name'], 'name': link['submechanism/subtype'] if 'submechanism/subtype' in link else link['submechanism/type'], 'jointnames_independent': [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/independent'] ], 'jointnames_spanningtree': [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/spanningtree'] ], 'jointnames_active': [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/active'] ], # TODO: this should work in almost all cases, still a bit of a hack: 'file_path': '../submechanisms/urdf/' + link['submechanism/name'] + '.urdf', } log(' ' + submech['contextual_name'], 'DEBUG') else: submech = None mechanisms = [submech] if submech else [] for c in link.children: if c.phobostype in ['link', 'interface'] and c in objectlist: mechanisms.extend(getSubmechanisms(c)) return mechanisms model['submechanisms'] = getSubmechanisms(root) # add additional data to model model.update(deriveTextData(model['name'])) # shorten numbers in dictionary to n decimalPlaces and return it log("Rounding numbers to {} digits.".format(ioUtils.getExpSettings().decimalPlaces), 'INFO') model = roundFloatsInDict(model, ioUtils.getExpSettings().decimalPlaces) log("Sorting objects.", 'DEBUG') model = sortListsInDict(model) return model
def deriveModelDictionary(root, name='', objectlist=[]): """Returns a dictionary representation of a Phobos model. If name is not specified, it overrides the modelname in the root. If the modelname is not defined at all, 'unnamed' will be used instead. Args: root(bpy_types.Object): root object of the model name(str): name for the derived model objectlist(list: bpy_types.Object): objects to derive the model from """ if root.phobostype not in ['link', 'submodel']: log(root.name + " is no valid 'link' or 'submodel' object.", "ERROR") return None # define model name if name: modelname = name elif 'modelname' in root: modelname = root['modelname'] else: modelname = 'unnamed' model = { 'links': {}, 'joints': {}, 'sensors': {}, 'motors': {}, 'controllers': {}, 'materials': {}, 'meshes': {}, 'lights': {}, 'groups': {}, 'chains': {}, 'date': datetime.now().strftime("%Y%m%d_%H:%M"), 'name': modelname } log( "Creating dictionary for model '" + modelname + "' with root '" + root.name + "'.", 'INFO') # create tuples of objects belonging to model if not objectlist: objectlist = sUtils.getChildren( root, selected_only=ioUtils.getExpSettings().selectedOnly, include_hidden=False) linklist = [link for link in objectlist if link.phobostype == 'link'] # digest all the links to derive link and joint information log( "Parsing links, joints and motors... " + (str(len(linklist))) + " total.", "INFO") for link in linklist: # parse link information (including inertia) model['links'][nUtils.getObjectName(link, 'link')] = deriveLink(link) if sUtils.getEffectiveParent(link): # joint may be None if link is a root jointdict = deriveJoint(link) model['joints'][jointdict['name']] = jointdict motordict = deriveMotor(link, jointdict) # motor may be None if no motor is attached if motordict: model['motors'][motordict['name']] = motordict # combine inertia for each link, taking into account inactive links inertials = (i for i in objectlist if i.phobostype == 'inertial' and 'inertial/inertia' in i) editlinks = {} for i in inertials: if i.parent not in linklist: realparent = sUtils.getEffectiveParent( i, ignore_selection=bool(objectlist)) if realparent: parentname = nUtils.getObjectName(realparent) if parentname in editlinks: editlinks[parentname].append(i) else: editlinks[parentname] = [i] for linkname in editlinks: inertials = editlinks[linkname] try: inertials.append(bpy.context.scene.objects['inertial_' + linkname]) except KeyError: pass # get inertia data mass, com, inertia = inertiamodel.fuse_inertia_data(inertials) if not any(mass, com, inertia): continue # add inertia to model inertia = inertiamodel.inertiaMatrixToList(inertia) model['links'][linkname]['inertial'] = { 'mass': mass, 'inertia': inertia, 'pose': { 'translation': list(com), 'rotation_euler': [0, 0, 0] } } # complete link information by parsing visuals and collision objects log("Parsing visual and collision (approximation) objects...", 'INFO') for obj in objectlist: if obj.phobostype in ['visual', 'collision']: props = deriveDictEntry(obj) parentname = nUtils.getObjectName( sUtils.getEffectiveParent(obj, ignore_selection=bool(objectlist))) model['links'][parentname][obj.phobostype][nUtils.getObjectName( obj)] = props elif obj.phobostype == 'approxsphere': props = deriveDictEntry(obj) parentname = nUtils.getObjectName( sUtils.getEffectiveParent(obj, ignore_selection=bool(objectlist))) model['links'][parentname]['approxcollision'].append(props) # combine collision information for links for linkname in model['links']: link = model['links'][linkname] bitmask = 0 for collname in link['collision']: try: # bitwise OR to add all collision layers bitmask = bitmask | link['collision'][collname]['bitmask'] except KeyError: pass link['collision_bitmask'] = bitmask # parse sensors and controllers log("Parsing sensors and controllers...", 'INFO') for obj in objectlist: if obj.phobostype in ['sensor', 'controller']: props = deriveDictEntry(obj) model[obj.phobostype + 's'][nUtils.getObjectName(obj)] = props # parse materials log("Parsing materials...", 'INFO') model['materials'] = collectMaterials(objectlist) for obj in objectlist: if obj.phobostype == 'visual': mat = obj.active_material if mat: if mat.name not in model['materials']: model['materials'][mat.name] = deriveMaterial(mat) linkname = nUtils.getObjectName( sUtils.getEffectiveParent( obj, ignore_selection=bool(objectlist))) model['links'][linkname]['visual'][nUtils.getObjectName( obj)]['material'] = mat.name # identify unique meshes log("Parsing meshes...", "INFO") for obj in objectlist: try: if ((obj.phobostype == 'visual' or obj.phobostype == 'collision') and (obj['geometry/type'] == 'mesh') and (obj.data.name not in model['meshes'])): model['meshes'][obj.data.name] = obj for lod in obj.lod_levels: if lod.object.data.name not in model['meshes']: model['meshes'][lod.object.data.name] = lod.object except KeyError: log("Undefined geometry type in object " + obj.name, "ERROR") # gather information on groups of objects log("Parsing groups...", 'INFO') # TODO: get rid of the "data" part and check for relation to robot for group in bpy.data.groups: # skip empty groups if not group.objects: continue # handle submodel groups separately from other groups if 'submodeltype' in group.keys(): continue # TODO create code to derive Submodels # model['submodels'] = deriveSubmodel(group) elif nUtils.getObjectName(group, 'group') != "RigidBodyWorld": model['groups'][nUtils.getObjectName( group, 'group')] = deriveGroupEntry(group) # gather information on chains of objects log("Parsing chains...", "INFO") chains = [] for obj in objectlist: if obj.phobostype == 'link' and 'endChain' in obj: chains.extend(deriveChainEntry(obj)) for chain in chains: model['chains'][chain['name']] = chain # gather information on lights log("Parsing lights...", "INFO") for obj in objectlist: if obj.phobostype == 'light': model['lights'][nUtils.getObjectName(obj)] = deriveLight(obj) # gather submechanism information from links log("Parsing submechanisms...", "INFO") def getSubmechanisms(link): if 'submechanism/name' in link.keys(): submech = { 'type': link['submechanism/type'], 'contextual_name': link['submechanism/name'], 'name': link['submechanism/subtype'] if 'submechanism/subtype' in link else link['submechanism/type'], 'jointnames_independent': [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/independent'] ], 'jointnames_spanningtree': [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/spanningtree'] ], 'jointnames_active': [ nUtils.getObjectName(j, 'joint') for j in link['submechanism/active'] ], # TODO: this should work in almost all cases, still a bit of a hack: 'file_path': '../submechanisms/urdf/' + link['submechanism/name'] + '.urdf' } log(' ' + submech['contextual_name'], 'DEBUG') else: submech = None mechanisms = [submech] if submech else [] for c in link.children: if c.phobostype in ['link', 'interface'] and c in objectlist: mechanisms.extend(getSubmechanisms(c)) return mechanisms model['submechanisms'] = getSubmechanisms(root) # add additional data to model model.update(deriveTextData(model['name'])) # shorten numbers in dictionary to n decimalPlaces and return it log("Rounding numbers...", "INFO") return roundFloatsInDict(model, ioUtils.getExpSettings().decimalPlaces)