コード例 #1
0
ファイル: smurfs.py プロジェクト: Amudtogal/phobos
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))
コード例 #2
0
ファイル: smurfs.py プロジェクト: brendanaaa/Learnbgame
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))
コード例 #3
0
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")
コード例 #4
0
ファイル: poses.py プロジェクト: Amudtogal/phobos
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")
コード例 #5
0
ファイル: models.py プロジェクト: stpulleyking/phobos
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)
コード例 #6
0
ファイル: models.py プロジェクト: hwiedDFKI/phobos
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
コード例 #7
0
ファイル: models.py プロジェクト: gavanderhoorn/phobos
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)