def execute(self, context): """ Args: context: Returns: """ log("Jumping to object " + self.objectname + ".", 'DEBUG') # switch the scene if the object is anywhere else scene = None if self.objectname not in context.scene: for sce in bpy.data.scenes: if self.objectname in sce.objects: scene = sce break else: log("Could not find scene of object {}. Aborted.".format(self.objectname), 'ERROR') return {'CANCELLED'} bUtils.switchToScene(scene.name) # toggle layer to make object visible bUtils.setObjectLayersActive(scene.objects[self.objectname], extendlayers=True) sUtils.selectObjects([scene.objects[self.objectname]], clear=True, active=0) return {'FINISHED'}
def roundFloatsInDict(data, decimals): """Recursively rounds all floats in the dictionary to the specified decimal digits. If a float value is smaller than 10e-decimals it is set to zero. Args: data(dict): data dictionary decimals(int): number of decimals floats should be rounded to Returns: : dict -- dictionary with rounded floats """ epsilon = 10 ** -decimals if is_float(data): if isinstance(data, str): log("Skipping rounding of " + data + " due to its type 'str'", "WARNING") return data return 0 if abs(data) < epsilon else round(data, decimals) elif isinstance(data, list): return [roundFloatsInDict(a, decimals) for a in data] elif isinstance(data, dict): return {key: roundFloatsInDict(value, decimals) for key, value in data.items()} else: return data
def execute(self, context): """ Args: context: Returns: """ selection = [] if self.modelname: log("phobos: Selecting model" + self.modelname, "INFO") roots = sUtils.getRoots() for root in roots: if nUtils.getModelName(root) == self.modelname: selection = sUtils.getChildren(root) else: log("No model name provided, deriving from selection...", "INFO") roots = set() for obj in bpy.context.selected_objects: roots.add(sUtils.getRoot(obj)) for root in list(roots): selection.extend(sUtils.getChildren(root)) sUtils.selectObjects(list(selection), True) return {'FINISHED'}
def execute(self, context): """ Args: context: Returns: """ roots = set() # add root object of each selected object for obj in context.selected_objects: roots.add(sUtils.getRoot(obj)) # select all found root objects if roots: # toggle layer to make objects visible for root in roots: bUtils.setObjectLayersActive(root, extendlayers=True) # select objects sUtils.selectObjects(list(roots), True) context.scene.objects.active = list(roots)[0] else: log("Couldn't find any root object.", 'ERROR') return {'FINISHED'}
def deriveEntity(light, outpath): """This function handles a light entity in a scene to export it Args: entity(bpy.types.Object): The lights root object. outpath(str): The path to export to. Not used for light entity savetosubfolder(bool): If True data will be exported into subfolders. Not used for light entity light: Returns: : dict - An entry for the scenes entitiesList """ log("Exporting " + light["entity/name"] + " as a light entity", "INFO") entitypose = models.deriveObjectPose(light) lightobj = sUtils.getImmediateChildren(light)[0] color = lightobj.data.color entity = { "name": light["entity/name"], "type": "light", "light_type": "spotlight" if lightobj.data.type == "SPOT" else "omnilight", "anchor": light["anchor"] if "anchor" in light else "none", "color": { "diffuse": [color.r, color.g, color.b], "use_specular": lightobj.data.use_specular, # only specular information currently available }, "position": entitypose["translation"], "rotation": entitypose["rotation_quaternion"], } if entity["light_type"] == "spotlight": entity["angle"] = lightobj.data.spot_size return entity
def execute(self, context): """ Args: context: Returns: """ libPath = os.path.join(os.path.dirname(defs.__file__), "RobotLib.yml") path, file = os.path.split(self.filepath) if file.endswith(".bake"): with open(self.filepath, "r") as f: info = yaml.load(f.read()) if not os.path.isfile(libPath): open(libPath, "a").close() with open(libPath, "r+") as f: robot_lib = yaml.load(f.read()) robot_lib = robot_lib if robot_lib is not None else {} robot_lib[info["name"]] = path f.seek(0) f.write(yaml.dump(robot_lib)) f.truncate() else: log("This is no robot bake!", "ERROR") return {"FINISHED"}
def getRoots(scene=None): """Returns a list of all of the current/specified scene's root objects. Args: scene(bpy.types.Scene, optional): the scene of which to find the root objects (Default value = None) Returns: : list(bpy.types.Object) -- root objects of the scene """ if not scene: scene = bpy.context.scene roots = [obj for obj in scene.objects if isRoot(obj, scene=scene)] if roots is None: log("No root objects found in scene {}.".format(scene), 'WARNING') else: rootnames = ', '.join((root.name for root in roots)) log( "Found {} root object{} in scene {}: {}".format( len(roots), 's' if len(roots) > 1 else '', scene, rootnames ), 'DEBUG', ) return roots
def calculateSum(objects, numeric_prop): """Returns sum of *numeric_prop* in *objects*. Args: objects(list(bpy.types.Object): objects to sum up the property for numeric_prop(str): name of the custom property to sum Returns: : float/int -- sum of the values in the property of the objects """ numsum = 0 for obj in objects: try: numsum += obj[numeric_prop] except KeyError: log( "{0} object {1} does not contain '{2}'".format( obj.phobostype, obj.name, numeric_prop ), "WARNING", ) except TypeError: import phobos.utils.naming as nUtils log( "Could not add this type to the sum: " + str(type(obj[numeric_prop])) + " @" + nUtils.getObjectName(obj), 'WARNING', ) return numsum
def smoothen_surface(obj): """Applies various steps to make the specified object look clean and smooth. Args: obj(bpy.types.Object): object to make look clean Returns: """ bpy.context.scene.objects.active = obj # recalculate surface normals bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.normals_make_consistent() bpy.ops.mesh.mark_sharp(clear=True) bpy.ops.object.mode_set(mode='OBJECT') # add smooth shading bpy.ops.object.shade_smooth() # use edge split modifier to improve the look of CAD-models for mod in obj.modifiers: if mod.type == 'EDGE_SPLIT': log("Edge split modifier already added to object {}.".format(obj.name), 'DEBUG') break else: bpy.ops.object.modifier_add(type='EDGE_SPLIT')
def importBlenderModel(filepath, namespace='', prefix=False): """Imports an existing Blender model into the current .blend scene Args: filepath(str): Path of the .blend file namespace: (Default value = '') prefix: (Default value = False) Returns: """ if os.path.exists(filepath) and os.path.isfile(filepath) and filepath.endswith('.blend'): log("Importing Blender model" + filepath, "INFO") objects = [] with bpy.data.libraries.load(filepath) as (data_from, data_to): for objname in data_from.objects: objects.append({'name': objname}) bpy.ops.wm.append(directory=filepath + "/Object/", files=objects) imported_objects = bpy.context.selected_objects resources = [obj for obj in imported_objects if obj.name.startswith('resource::')] new_objects = [obj for obj in imported_objects if not obj.name.startswith('resource::')] if resources: if 'resources' not in bpy.data.scenes.keys(): bpy.data.scenes.new('resources') sUtils.selectObjects(resources) bpy.ops.object.make_links_scene(scene='resources') bpy.ops.object.delete(use_global=False) sUtils.selectObjects(new_objects) bpy.ops.view3d.view_selected(use_all_regions=False) # allow the use of both prefixes and namespaces, thus truly merging # models or keeping them separate for export if namespace != '': if prefix: for obj in bpy.context.selected_objects: # set prefix instead of namespace obj.name = namespace + '__' + obj.name # make sure no internal name-properties remain for key in obj.keys(): try: if obj[key].endswidth("/name"): del obj[key] except AttributeError: # prevent exceptions from non-string properties pass else: for obj in bpy.context.selected_objects: nUtils.addNamespace(obj, namespace) submechanism_roots = [ obj for obj in bpy.data.objects if obj.phobostype == 'link' and 'submechanism/spanningtree' in obj ] for root in submechanism_roots: partlist = [root] + root['submechanism/spanningtree'] if 'submechanism/freeloader' in root: partlist += root['submechanism/freeloader'] sUtils.selectObjects(partlist, active=0) bpy.ops.group.create(name='submechanism:' + root['submechanism/name']) return True else: return False
def getProperties(obj, category=None): """Returns a dictionary of custom property information of the object. If a category is provided, only the custom properties of the specified category are returned. Otherwise, the phobostype of the object will be used as category. The dictionary contains the custom property keys with the category removed (e.g. 'name' for 'link/name'). Args: obj(bpy.types.Object): object to get properties of category(str, optional): property category to look for (Default value = None) Returns: : dict -- custom property information of the phobostype/category for the object """ if not category: category = obj.phobostype try: diction = { key.replace(category + '/', ''): value for key, value in obj.items() if key.startswith(category + '/') } except KeyError: log("Failed filtering properties for category " + category, "ERROR") return diction
def execute(self, context): """ Args: context: Returns: """ if self.complete: roots = set([sUtils.getRoot(obj) for obj in context.selected_objects]) - {None} objects = set() for root in roots: objects = objects | set(sUtils.getChildren(root)) objlist = list(objects) else: objlist = [bpy.context.active_object] for obj in objlist: try: entityname = sUtils.getRoot(obj)['entity/name'] except (KeyError, TypeError): entityname = '' log(nUtils.getObjectName(obj) + " is not part of a well-defined entity.", "WARNING") namespace = self.namespace if self.namespace else entityname nUtils.toggleNamespace(obj, namespace) return {'FINISHED'}
def setLinkTransformations(model, parent): """Assigns the transformations recursively for a model parent link according to the model. This needs access to the **object** key of a link entry in the specified model. The transformations for each link object are extracted from the specified model and applied to the Blender object. Args: parent(dict): parent link you want to set the children for. model(dict): model dictionary containing the **object** key for each link Returns: """ bpy.context.scene.layers = bUtils.defLayers(defs.layerTypes['link']) for chi in parent['children']: child = model['links'][chi] # apply transform as saved in model location = mathutils.Matrix.Translation(child['pose']['translation']) rotation = ( mathutils.Euler(tuple(child['pose']['rotation_euler']), 'XYZ').to_matrix().to_4x4() ) log("Transforming link {0}.".format(child['name']), 'DEBUG') transform_matrix = location * rotation child['object'].matrix_local = transform_matrix # traverse the tree setLinkTransformations(model, child)
def deriveController(obj): """ Args: obj: Returns: """ import phobos.model.models as models props = models.initObjectProperties(obj, phobostype='controller') # return None if no controller is found (there will always be at least a name in the props) if len(props) < 2: return None if not obj.parent or obj.parent.phobostype not in defs.controllabletypes: log( ( "Can not derive controller from {}. " + "Insufficient requirements from parent object!" ).format(obj.name), 'ERROR', ) return None props['target'] = nUtils.getObjectName(obj.parent) log( " Derived controller '{}' for target '{}'.".format(props['name'], props['target']), 'DEBUG' ) return props
def assignMaterial(obj, materialname): """Assigns a material by name to an object. This avoids creating multiple copies and also omits duplicate material slots in the specified object. Args: obj(bpy.types.Object): The object to assign the material to. materialname(str): name of the material Returns: """ if materialname not in bpy.data.materials: if materialname in defs.definitions['materials']: createPhobosMaterials() else: log("Material '" + materialname + "' is not defined.", "ERROR") return None # add material slot never twice if materialname not in obj.data.materials: obj.data.materials.append(bpy.data.materials[materialname]) if obj.data.materials[materialname].use_transparency: obj.show_transparent = True
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 deriveLinkfromObject(obj, scale=0.2, parent_link=True, parent_objects=False, nameformat=''): """Derives a link from an object using its name, transformation and parenting. Args: obj(bpy_types.Object): object to derive a link from scale(float, optional): scale factor for bone size (Default value = 0.2) parent_link(bool, optional): whether to automate the parenting of the new link or not. (Default value = True) parent_objects(bool, optional): whether to parent all the objects to the new link or not (Default value = False) nameformat(str, optional): re-formatting template for obj names (Default value = '') Returns: : newly created link """ log('Deriving link from ' + nUtils.getObjectName(obj), level="INFO") try: nameparts = [p for p in re.split('[^a-zA-Z]', nUtils.getObjectName(obj)) if p != ''] linkname = nameformat.format(*nameparts) except IndexError: log('Invalid name format (indices) for naming: ' + nUtils.getObjectName(obj), 'WARNING') linkname = 'link_' + nUtils.getObjectName(obj) link = createLink({'scale': scale, 'name': linkname, 'matrix': obj.matrix_world}) # parent link to object's parent if parent_link: if obj.parent: eUtils.parentObjectsTo(link, obj.parent) # parent children of object to link if parent_objects: children = [obj] + sUtils.getImmediateChildren(obj) eUtils.parentObjectsTo(children, link, clear=True) return link
def exportSubmechanisms(model, path): """This function exports the submechanisms contained in a robot model. Args: model(dict): The robot model to export path(str): The filepath to export the submechanisms data to Returns: """ log("Phobos Submechanisms export: Creating submechanisms data at " + path, "INFO") for submechanism in model['submechanisms']: root = sUtils.getObjectByProperty('submechanism/name', submechanism['contextual_name']) linkobjs = [root] + root['submechanism/spanningtree'] if 'submechanism/freeloader' in root: linkobjs += root['submechanism/freeloader'] objects = [ o for link in linkobjs for o in link.children if o.phobostype in ['visual', 'collision', 'inertial'] ] + linkobjs model = deriveModelDictionary(root, root['submechanism/name'], objects) jointname = nUtils.getObjectName(root, 'joint') if jointname in model['joints']: del model['joints'][jointname] log('Removed joint which is not part of submodel: ' + jointname, 'DEBUG') exportModel(model, path, ['urdf'])
def createPreview(objects, export_path, modelname, render_resolution=256, opengl=False): """Creates a thumbnail of the given objects. Args: objects(list of bpy.types.Object): list of objects for the thumbnail export_path(str): folder to export image to modelname(str): name of model (used as file name) render_resolution(int, optional): side length of resulting image in pixels (Default value = 256) opengl(bool, optional): whether to use opengl rendering or not (Default value = False) Returns: """ log("Creating thumbnail of model: " + modelname, "INFO") # render presets bpy.context.scene.render.image_settings.file_format = 'PNG' bpy.context.scene.render.resolution_x = render_resolution bpy.context.scene.render.resolution_y = render_resolution bpy.context.scene.render.resolution_percentage = 100 # hide everything that is not supposed to show for ob in bpy.data.objects: if not (ob in objects): ob.hide_render = True ob.hide = True # render the preview if opengl: # use the viewport representation to create preview bpy.ops.view3d.view_selected() bpy.ops.render.opengl(view_context=True) else: # use real rendering # create camera bpy.ops.object.camera_add(view_align=True) cam = bpy.context.scene.objects.active bpy.data.cameras[cam.data.name].type = 'ORTHO' bpy.data.scenes[0].camera = cam # set camera as active camera sUtils.selectObjects(objects, True, 0) bpy.ops.view3d.camera_to_view_selected() # create light bpy.ops.object.lamp_add(type='SUN', radius=1) light = bpy.context.scene.objects.active light.matrix_world = cam.matrix_world # set background oldcolor = bpy.data.worlds[0].horizon_color.copy() bpy.data.worlds[0].horizon_color = mathutils.Color((1.0, 1.0, 1.0)) bpy.ops.render.render() bpy.data.worlds[0].horizon_color = oldcolor sUtils.selectObjects([cam, light], True, 0) bpy.ops.object.delete() # safe render and reset the scene log("Saving model preview to: " + os.path.join(export_path, modelname + '.png'), "INFO") bpy.data.images['Render Result'].save_render(os.path.join(export_path, modelname + '.png')) # make all objects visible again for ob in bpy.data.objects: ob.hide_render = False ob.hide = False
def execute(self, context): """ Args: context: Returns: """ wm = context.window_manager # FIXME: the following is a hack to fix the problem mentioned at the top if not model_data: compileModelList() filepath = os.path.join( model_data[wm.category][wm.modelpreview]['path'], 'blender', wm.modelpreview + '.blend' ) if ioUtils.importBlenderModel(filepath, self.namespace, self.use_prefix): return {'FINISHED'} else: log( "Model " + wm.modelpreview + " could not be loaded from library:" "No valid .blend file.", "ERROR", ) return {'CANCELLED'}
def draw(self, context): """ Args: context: Returns: """ layout = self.layout if self.asObject: layout.prop( self, 'asObject', text='add annotations as object(s)', icon='FORCE_LENNARDJONES' ) else: layout.prop(self, 'asObject', text='add annotations to object', icon='REC') layout.separator() layout.prop(self, 'annotationtype') if self.annotationcategories == 'None': layout.label('No categories available.') else: layout.prop(self, 'annotationcategories') # hide devicetype property when empty if self.devicetype == 'None': layout.label('No devices defined.') return layout.prop(self, 'devicetype') data = defs.definitions[self.annotationtype][self.devicetype] hidden_props = ['general'] # identify the property type for all the stuff in the definition self.annotation_data.clear() unsupported = DynamicProperty.assignDict( self.annotation_data.add, data, ignore=hidden_props ) # expose the parameters as the right Property if self.annotation_data: box = layout.box() for i in range(len(self.annotation_data)): name = self.annotation_data[i].name[2:].replace('_', ' ') # use the dynamic props name in the GUI, but without the type id self.annotation_data[i].draw(box, name) # add unsupported stuff as labels for item in unsupported: box.label(item, icon='ERROR') if unsupported: log( "These properties are not supported for generic editing: " + str(list(unsupported)), 'DEBUG', )
def deriveEntity(root, outpath): """Derives the dictionary for a SMURF entity from the phobos model dictionary. # TODO savetosubfolder is not a parameter Args: root(bpy.types.Object): The smurf root object. outpath(str): The path to export the smurf to. savetosubfolder(bool): If True the export path has a subfolder for this smurf entity. Returns: : dict - An entry for the scenes entitiesList """ entitypose = models.deriveObjectPose(root) entity = models.initObjectProperties(root, 'entity', ['link', 'joint', 'motor']) if 'parent' not in entity and 'joint/type' in root and root['joint/type'] == 'fixed': entity['parent'] = 'world' entity["position"] = entitypose["translation"] entity["rotation"] = entitypose["rotation_quaternion"] # check model data if entity is a reference # FIXME: this part is broken but not used at the moment anyways if "isReference" in entity: entity.pop("isReference") bpy.ops.scene.reload_models_and_poses_operator() modelsPosesColl = bUtils.getPhobosPreferences().models_poses for robot_model in modelsPosesColl: if (root["model/name"] == robot_model.robot_name) and ( root["entity/pose"] == robot_model.label ): pass entity['file'] = os.path.join( os.path.relpath(robot_model.path, outpath), root["name"] + ".smurf" ) """ with open(os.path.join(os.path.dirname(defs.__file__), "RobotLib.yml"), "r") as f: robots = yaml.load(f.read()) sourcepath = robots[smurf["model/name"]] for filename in os.listdir(sourcepath): fullpath = os.path.join(sourcepath, filename) if os.path.isfile(fullpath): shutil.copy2(fullpath, os.path.join(smurf_outpath, filename)) else: # remove old folders to prevent errors in copytree shutil.rmtree(os.path.join(smurf_outpath, filename), True) shutil.copytree(fullpath, os.path.join(smurf_outpath, filename)) """ else: modelpath = os.path.join(outpath, root['model/name'], 'smurf') # TODO why the spacing between the paths? log("Scene paths: " + outpath + ' ' + modelpath, "DEBUG") entity['file'] = os.path.join( os.path.relpath(modelpath, os.path.dirname(outpath)), root['model/name'] + ".smurf" ) return entity
def createInertial(inertialdict, obj, size=0.03, errors=None, adjust=False, logging=False): """Creates the Blender representation of a given inertial provided a dictionary. Args: inertialdict(dict): intertial data obj: size: (Default value = 0.03) errors: (Default value = None) adjust: (Default value = False) logging: (Default value = False) Returns: : bpy_types.Object -- newly created blender inertial object """ if errors and not adjust: log('Can not create inertial object.', 'ERROR') try: origin = mathutils.Vector(inertialdict['pose']['translation']) except KeyError: origin = mathutils.Vector() # create new inertial object name = nUtils.getUniqueName('inertial_' + nUtils.getObjectName(obj), bpy.data.objects) inertialobject = bUtils.createPrimitive( name, 'box', (size,) * 3, defs.layerTypes["inertial"], pmaterial='phobos_inertial', phobostype='inertial', ) sUtils.selectObjects((inertialobject,), clear=True, active=0) bpy.ops.object.transform_apply(scale=True) # set position according to the parent link inertialobject.matrix_world = obj.matrix_world parent = obj if parent.phobostype != 'link': parent = sUtils.getEffectiveParent(obj, ignore_selection=True) eUtils.parentObjectsTo(inertialobject, parent) # position and parent the inertial object relative to the link # inertialobject.matrix_local = mathutils.Matrix.Translation(origin) sUtils.selectObjects((inertialobject,), clear=True, active=0) # bpy.ops.object.transform_apply(scale=True) # add properties to the object for prop in ('mass', 'inertia'): inertialobject['inertial/' + prop] = inertialdict[prop] return inertialobject
def execute(self, context): """ Args: context: Returns: """ # identify all entities' roots in the scene rootobjects = ioUtils.getEntityRoots() if not rootobjects: log("There are no entities to export!", "WARNING") # derive entities and export if necessary models = set() for root in entities: log("Adding entity '" + str(root["entity/name"]) + "' to scene.", "INFO") if root["entity/type"] in entity_types: # TODO delete me? # try: if ( self.exportModels and 'export' in entity_types[root['entity/type']] and root['model/name'] not in models ): modelpath = os.path.join( ioUtils.getExportPath(), self.sceneName, root['model/name'] ) exportModel(models.deriveModelDictionary(root), modelpath) models.add(root['model/name']) # known entity export entity = entity_types[root["entity/type"]]['derive']( root, os.path.join(ioUtils.getExportPath(), self.sceneName) ) # TODO delete me? # except KeyError: # log("Required method ""deriveEntity"" not implemented for type " + entity["entity/type"], "ERROR") # continue # generic entity export else: entity = deriveGenericEntity(root) exportlist.append(entity) for scenetype in scene_types: typename = "export_scene_" + scenetype # check if format exists and should be exported if getattr(bpy.context.scene, typename): scene_types[scenetype]['export']( exportlist, os.path.join(ioUtils.getExportPath(), self.sceneName) ) return {'FINISHED'}
def execute(self, context): """ Args: context: Returns: """ log("Import robot bake", "INFO") modelsPosesColl = bUtils.getPhobosPreferences().models_poses activeModelPoseIndex = bpy.context.scene.active_ModelPose selected_robot = modelsPosesColl[bpy.data.images[activeModelPoseIndex].name] if selected_robot.type != "robot_name": if os.path.splitext(selected_robot.model_file)[-1] == ".obj": bpy.ops.import_scene.obj( filepath=selected_robot.model_file, axis_forward='-Z', axis_up='Y', filter_glob="*.obj;*.mtl", use_edges=True, use_smooth_groups=True, use_split_objects=True, use_split_groups=True, use_groups_as_vgroups=False, use_image_search=True, split_mode='ON', global_clamp_size=0, ) elif os.path.splitext(selected_robot.model_file)[-1] == ".stl": bpy.ops.import_mesh.stl( filepath=selected_robot.model_file, axis_forward='Y', axis_up='Z', filter_glob="*.stl", files=[], directory="", global_scale=1, use_scene_unit=True, use_facet_normal=False, ) robot_obj = bpy.context.selected_objects[0] bpy.context.scene.objects.active = robot_obj robot_obj.name = self.obj_name robot_obj["model/name"] = selected_robot.robot_name robot_obj["entity/name"] = self.obj_name robot_obj["entity/type"] = "smurf" robot_obj["entity/pose"] = selected_robot.label robot_obj["entity/isReference"] = True robot_obj.phobostype = 'entity' return {'FINISHED'}
def cloneGit(name, url, destination): """Clones the git repository which is specified by its url into the folder with the specified name in the destination folder. If the url provides the https:// start, it might be necessary to enter gituser and password into commandline, so better use the git@git start. If the destination folder is a git already, it will not be cloned, but True will be returned. If the destination folder does not exist, it is created on the fly. Args: name: url: destination: Returns: """ # check for existing git first try: if isGit(os.path.join(destination, name)): if not os.path.exists(os.path.join(destination, name, '.git', 'config')): log('Git folder found, but the config seems corrupted. Cloning aborted.', 'ERROR') return False # compare the git url with the existing git folder with open(os.path.join(destination, name, '.git', 'config'), 'r') as gitconfigfile: if url in gitconfigfile.read(): log('Git already cloned.', 'INFO') return True log('Git folder found, but with different url. Cloning aborted.', 'ERROR') return False except subprocess.CalledProcessError: pass # if there is no destination folder, create it if not os.path.exists(destination): os.makedirs(destination) # clone git if not existing already try: subprocess.check_output( ['git', 'clone', url, name], cwd=destination, universal_newlines=True ) log("Cloned git into " + destination + ".", "INFO") return True except subprocess.CalledProcessError: log( "Problem cloning git repository. Destination is either not empty or remote is incorrect.", "ERROR", ) return False
def getMechanismListForEnumProperty(self, context): """Returns a list of (str, str, str) elements which contains the mechanisms currently loaded. If there are none, returns ('-', '-', '-'). Args: context: Returns: """ try: return sorted(mechanismpreviewcollection.enum_items) except AttributeError: log('No mechanism previews available. Check config folder.', 'ERROR')
def openScriptInEditor(scriptname): """This function opens a script/textfile in an open blender text window. Nothing happens if there is no available text window. Args: scriptname(str): The scripts name. Returns: """ if scriptname in bpy.data.texts: for area in bpy.context.screen.areas: if area.type == 'TEXT_EDITOR': area.spaces.active.text = bpy.data.texts[scriptname] else: log("There is no script named " + scriptname + "!", "ERROR")
def datetimeFromIso(iso): """Accepts a date-time string in ISO format and returns a datetime object. Args: iso(str): ISO format string for a date and time Returns: : datetime.datetime -- datetime object from the specified string """ try: dtime = datetime(*[int(a) for a in re.split(":|-|T| |\.", iso)]) return dtime except ValueError as error: log("Could not convert ISO string: " + str(error), "ERROR") return datetime.now()
def execute(self, context): """ Args: context: Returns: """ log("Importing " + self.filepath + ' as ' + self.entitytype, "INFO") model = entity_io.entity_types[self.entitytype]['import'](self.filepath) # bUtils.cleanScene() models.buildModelFromDictionary(model) for layer in ['link', 'inertial', 'visual', 'collision', 'sensor']: bUtils.toggleLayer(defs.layerTypes[layer], True) return {'FINISHED'}
def deriveChainEntry(obj): """Derives a phobos dict entry for a kinematic chain ending in the provided object. :param obj: :return: """ returnchains = [] if 'endChain' in obj: chainlist = obj['endChain'] for chainName in chainlist: chainclosed = False parent = obj chain = { 'name': chainName, 'start': '', 'end': nUtils.getObjectName(obj), 'elements': [] } while not chainclosed: # FIXME: use effectiveParent if parent.parent is None: log("Unclosed chain, aborting parsing chain " + chainName, "ERROR") chain = None break chain['elements'].append(parent.name) # FIXME: use effectiveParent parent = parent.parent if 'startChain' in parent: startchain = parent['startChain'] if chainName in startchain: chain['start'] = nUtils.getObjectName(parent) chain['elements'].append(nUtils.getObjectName(parent)) chainclosed = True if chain is not None: returnchains.append(chain) return returnchains
def execute(self, context): # identify all entities' roots in the scene rootobjects = ioUtils.getEntityRoots() if not rootobjects: log("There are no entities to export!", "WARNING") # derive entities and export if necessary modellist = [] for root in rootobjects: log("Adding entity '" + str(root["entity/name"]) + "' to scene.", "INFO") if root["entity/type"] in entity_types: # TODO delete me? # try: if (self.exportModels and 'export' in entity_types[root['entity/type']] and root['modelname'] not in models): modelpath = os.path.join(ioUtils.getExportPath(), self.sceneName, root['modelname']) exportModel(models.deriveModelDictionary(root), modelpath) models.add(root['modelname']) # known entity export entity = entity_types[root["entity/type"]]['derive'](root, os.path.join(ioUtils.getExportPath(), self.sceneName)) # TODO delete me? # except KeyError: # log("Required method ""deriveEntity"" not implemented for type " + entity["entity/type"], "ERROR") # continue # generic entity export else: entity = deriveGenericEntity(root) exportlist.append(entity) for scenetype in scene_types: typename = "export_scene_" + scenetype # check if format exists and should be exported if getattr(bpy.context.scene, typename): scene_types[scenetype]['export'](exportlist, os.path.join( ioUtils.getExportPath(), self.sceneName)) return {'FINISHED'}
def deriveVisual(obj): """This function derives the visual information from an object. :param obj: The blender object to derive the visuals from. :type obj: bpy_types.Object :return: dict """ try: visual = initObjectProperties(obj, phobostype='visual', ignoretypes='geometry') visual['geometry'] = deriveGeometry(obj) visual['pose'] = deriveObjectPose(obj) if obj.lod_levels: if 'lodmaxdistances' in obj: maxdlist = obj['lodmaxdistances'] else: maxdlist = [ obj.lod_levels[i + 1].distance for i in range(len(obj.lod_levels) - 1) ] + [100.0] lodlist = [] for i in range(len(obj.lod_levels)): filename = obj.lod_levels[ i].object.data.name + ioUtils.getOutputMeshtype() lodlist.append({ 'start': obj.lod_levels[i].distance, 'end': maxdlist[i], 'filename': os.path.join('meshes', filename) }) visual['lod'] = lodlist except KeyError: log("Missing data in visual object " + obj.name, "ERROR", "deriveVisual") return None return visual
def deriveSensor(obj, names=False, objectlist=[], logging=False): """This function derives a sensor from a given blender object Args: obj(bpy_types.Object): The blender object to derive the sensor from. names(bool, optional): return the link object name instead of an object link. (Default value = False) objectlist(list(bpy.types.Object, optional): objectlist to which possible parents are restricted (Default value = []) logging(bool, optional): whether to write log messages or not (Default value = False) Returns: : dict -- phobos representation of the sensor """ from phobos.model.models import initObjectProperties if logging: log( "Deriving sensor from object " + nUtils.getObjectName(obj, phobostype='sensor') + ".", 'DEBUG', ) try: props = initObjectProperties(obj, phobostype='sensor', ignoretypes=('pose')) if names: props['link'] = nUtils.getObjectName(sUtils.getEffectiveParent( obj, objectlist=objectlist), phobostype='link') else: props['link'] = sUtils.getEffectiveParent(obj, objectlist=objectlist) except KeyError: if logging: log("Missing data in sensor " + obj.name, "ERROR") return None return props
def deriveGeometry(obj): """This function derives the geometry from an object. Args: obj(bpy_types.Object): The blender object to derive the geometry from. Returns: dict """ try: geometry = {'type': obj['geometry/type']} gt = obj['geometry/type'] if gt == 'box': geometry['size'] = list(obj.dimensions) elif gt == 'cylinder' or gt == 'capsule': geometry['radius'] = obj.dimensions[0]/2 geometry['length'] = obj.dimensions[2] elif gt == 'capsule': geometry['radius'] = obj.dimensions[0]/2 geometry['length'] = obj.dimensions[2] - obj.dimensions[0] elif gt == 'sphere': geometry['radius'] = obj.dimensions[0]/2 elif gt == 'mesh': geometry['filename'] = obj.data.name geometry['scale'] = list(obj.scale) # FIXME: is this needed to calculate an approximate inertia geometry['size'] = list(obj.dimensions) # any other geometry type, i.e. 'plane' else: geometry['size'] = list(obj.dimensions) return geometry except KeyError as err: log("Undefined geometry for object " + nUtils.getObjectName(obj) + " " + str(err), "ERROR") return None
def getRoots(scene=None): """Returns a list of all of the current/specified scene's root objects. Args: scene(bpy.types.Scene, optional): the scene of which to find the root objects (Default value = None) Returns: : list(bpy.types.Object) -- root objects of the scene """ if not scene: scene = bpy.context.scene roots = [obj for obj in scene.objects if isRoot(obj, scene=scene)] if roots is None: log("No root objects found in scene {}.".format(scene), 'WARNING') else: rootnames = ', '.join((root.name for root in roots)) log( "Found {} root object{} in scene {}: {}".format( len(roots), 's' if len(roots) > 1 else '', scene, rootnames), 'DEBUG', ) return roots
def updateDefs(defsFolderPath): """Updates the definitions with all yml files in the given folder Args: defsFolderPath(str): The path to the folder with the definitions yaml files. Returns: """ dicts = __parseAllYAML(defsFolderPath) for diction in dicts: for category in diction: for key, value in diction[category].items(): if category not in definitions: definitions[category] = {} if key in definitions[category]: log( "Entry for " + category + '/' + key + " will be overwritten while parsing definitions.", "WARNING") definitions[category][key] = value # Extending model definition definitions['model']['sensors']['$forElem'][ '$selection__type'] = definitions['sensors']
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
def deriveTextData(modelname): """Collect additional data stored for a specific model. Args: modelname: Name of the model for which data should be derived. Returns: : A dictionary containing additional data. """ datadict = {} datatextfiles = [text for text in bpy.data.texts if text.name.startswith(modelname + '::')] for text in datatextfiles: try: dataname = text.name.split('::')[-1] except IndexError: log("Possibly invalidly named model data text file: " + modelname, "WARNING") try: data = json.loads(bUtils.readTextFile(text.name)) except: log("Invalid formatting of data file: " + dataname, "ERROR") if data: datadict[dataname] = data return datadict
def retrieve_from_list(alist, prop, value): """Returns the first object in a list which has a field named *prop* with value *value*. If no such object is found, returns 'None'. Args: alist: prop: value: Returns: """ n = -1 for i in range(len(alist)): try: if alist[i][prop] == value: n = i break except KeyError: log("The object at index " + str(i) + " has no property " + str(prop), "ERROR") if n >= 0: return alist[n][prop] else: return "None"
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 addNamespace(obj): """This function namespaces a given blender object. :param obj: The object to namespace. :type obj: bpy.types.Object """ types = defs.subtypes name = obj.name root = selection.getRoot(obj) try: namespace = root["entity/name"] obj.name = namespace + "::" + name for ptype in types: typetag = ptype + "/type" nametag = ptype + "/name" if (typetag in obj or ("phobostype" in obj and obj.phobostype == ptype)) and nametag not in obj: obj[nametag] = name except (TypeError, KeyError): log( getObjectName(obj) + " is not part of a well-defined entity.", "ERROR", "utils/naming/addNamespace")
def exportSubmechanisms(model, path): """This function exports the submechanisms contained in a robot model. Args: model(dict): The robot model to export path(str): The filepath to export the submechanisms data to Returns: """ log("Phobos Submechanisms export: Creating submechanisms data at " + path, "INFO") for submechanism in model['submechanisms']: root = sUtils.getObjectsByProperty('submechanism/name', submechanism['contextual_name']) linkobjs = [ root ] + root['submechanism/freeloader'] + root['submechanism/spanningtree'] objects = [ o for link in linkobjs for o in link.children if o.phobostype in ['visual', 'collision', 'inertial'] ] + linkobjs model = deriveModelDictionary(root, root['submechanism/name'], objects) exportModel(model, path, ['urdf'])
def deriveCollision(obj): """This function derives the collision information from an object. Args: obj(bpy_types.Object): The blender object to derive the collision information from. Returns: dict """ try: collision = initObjectProperties(obj, phobostype='collision', ignoretypes='geometry') collision['geometry'] = deriveGeometry(obj) collision['pose'] = deriveObjectPose(obj) # the bitmask is cut to length = 16 and reverted for int parsing try: collision['bitmask'] = int( ''.join([ '1' if group else '0' for group in obj.rigid_body.collision_groups[:16] ])[::-1], 2) for group in obj.rigid_body.collision_groups[16:]: if group: log(( 'Object {0} is on a collision layer higher than ' + '16. These layers are ignored when exporting.').format( obj.name), "WARNING") break except AttributeError: pass except KeyError: log("Missing data in collision object " + obj.name, "ERROR") return None return collision
def createJoint(joint, linkobj=None): """Adds joint data to 'link' object. Args: joint (dict): dictionary containing the joint definition linkobj (bpy.types.Object): the obj of phobostype 'link' that receives the joint Returns: """ # add joint information if not linkobj: linkobj = sUtils.getObjectByName(joint['child']) if isinstance(linkobj, list): log( "Could not identify object to define joint '{0}'.".format( joint['name']), 'ERROR') return if joint['name'] != linkobj.name: linkobj['joint/name'] = joint['name'] # get hold of object bUtils.toggleLayer(list(linkobj.layers).index(True), True) # any layer containing the object sUtils.selectObjects([linkobj], clear=True, active=0) # set axis if 'axis' in joint: if mathutils.Vector(tuple(joint['axis'])).length == 0.: log('Axis of joint {0} is of zero length: '.format(joint['name']), 'ERROR') else: bpy.ops.object.mode_set(mode='EDIT') editbone = linkobj.data.edit_bones[0] length = editbone.length axis = mathutils.Vector(tuple(joint['axis'])) editbone.tail = editbone.head + axis.normalized() * length # add constraints for param in ['effort', 'velocity']: try: if 'limits' in joint: linkobj['joint/max' + param] = joint['limits'][param] except KeyError: log("Joint limits incomplete for joint {0}".format(joint['name']), 'ERROR') try: lower = joint['limits']['lower'] upper = joint['limits']['upper'] except KeyError: lower = 0.0 upper = 0.0 setJointConstraints(linkobj, joint['type'], lower, upper) for prop in joint: if prop.startswith('$'): for tag in joint[prop]: linkobj['joint/' + prop[1:] + '/' + tag] = joint[prop][tag]
def deriveGenericEntity(entityobj, outpath=None): """This function handles an entity of unknown type by simply exporting its custom properties. Args: entityobj(bpy.types.Object): The object representing the entity. outpath(str, optional): If True data will be exported into subfolders. (Default value = None) Returns: : dict - An entry for the scenes entitiesList """ log( "Exporting " + nUtils.getObjectName(entityobj, 'entity') + " as entity of type 'generic", "INFO", ) entity = models.initObjectProperties(entityobj, 'entity', ['geometry']) return entity # write urdf urdf_path = "../urdf/" if structured else '' urdf_filename = model['name'] + ".urdf" exportModelToURDF(model, os.path.join(path, urdf_path, urdf_filename), '../meshes/' if structured else '')
def placeChildLinks(model, parent): """Creates parent-child-relationship for a given parent and all existing children in Blender. Args: parent(dict): parent link you want to set the children for. model(dict): Returns: """ bpy.context.scene.layers = bUtils.defLayers(defs.layerTypes['link']) for c in parent['children']: child = model['links'][c] # apply transform as saved in model location = mathutils.Matrix.Translation(child['pose']['translation']) rotation = mathutils.Euler(tuple(child['pose']['rotation_euler']), 'XYZ').to_matrix().to_4x4() log('Placing link {0}'.format(child['name']), 'DEBUG') transform_matrix = location * rotation log("Joint transform: {0}".format(transform_matrix), 'DEBUG') child['object'].matrix_local = transform_matrix # traverse the tree placeChildLinks(model, child)
def execute(self, context): roots = ioUtils.getExportModels() if not roots: log("No properly defined models selected or present in scene.", 'ERROR') return {'CANCELLED'} elif not self.exportall: roots = [root for root in roots if nUtils.getModelName(root) == self.modelname] if len(roots) > 1: log("Ambiguous model definitions: " + self.modelname + " exists " + str(len(roots)) + " times.", "ERROR") return {'CANCELLED'} for root in roots: # setup paths exportpath = ioUtils.getExportPath() if not securepath(exportpath): log("Could not secure path to export to.", "ERROR") continue log("Export path: " + exportpath, "DEBUG") ioUtils.exportModel(models.deriveModelDictionary(root), exportpath) # select all exported models after export is done if ioUtils.getExpSettings().selectedOnly: for root in roots: objectlist = sUtils.getChildren( root, selected_only=True, include_hidden=False) sUtils.selectObjects(objectlist, clear=False) else: bpy.ops.object.select_all(action='DESELECT') for root in roots: sUtils.selectObjects(list([root]), False) bpy.ops.phobos.select_model() # report success to user log("Export successful.", "INFO") return {'FINISHED'}
def compileMechanismList(): """TODO Missing documentation""" from bpy.types import WindowManager from bpy.props import EnumProperty # DOCU missing some docstring global mechanismpreviewcollection log("Compiling mechanism list from local library...", "INFO") imagefolderpath = os.path.join(bUtils.getPhobosConfigPath(), 'images', 'mechanisms') if imagefolderpath == '' or not os.path.exists(imagefolderpath): log('Visual mechanism representations could not be found.') return # read in mechanism thumbnails mechanismpreviewcollection = bpy.utils.previews.new() enum_items = [] defaultimagepath = os.path.join(imagefolderpath, 'undefined.png') defaultpreview = mechanismpreviewcollection.load('undefined', defaultimagepath, 'IMAGE') i = 1 for mechanism in defs.definitions['submechanisms']: size = len(defs.definitions['submechanisms'][mechanism]['joints'] ['spanningtree']) imagepath = os.path.join(imagefolderpath, mechanism + '.png') if not (os.path.exists(imagepath) and os.path.isfile(imagepath)): log("No preview found, using default.", 'DEBUG') enum_items.append((mechanism, mechanism + ' [{0}] '.format(size), "", defaultpreview.icon_id, i)) else: log("Adding mechanism preview: " + imagepath, 'DEBUG') preview = mechanismpreviewcollection.load(mechanism, imagepath, 'IMAGE') enum_items.append((mechanism, mechanism + '[ {0}]'.format(size), "", preview.icon_id, i)) i += 1 mechanismpreviewcollection.enum_items = enum_items # reregister the enumproperty to ensure new items are displayed WindowManager.mechanismpreview = EnumProperty( items=getMechanismListForEnumProperty, name='Mechanism')
def fuse_inertia_data(inertials): """Computes combined mass, center of mass and inertia given a list of inertial objects. If no inertials are found (None, None, None) is returned. If successful, the tuple contains this information: *mass*: float *com*: mathutils.Vector(3) *inertia*: mathutils.Matrix(3) :param inertials: the alist of objects relevant for the inertia of a link :type inertials: list :return: tuple of mass, COM and inertia or None(3) if no inertials are found :rtype: tuple(3) """ # collect objects which contain inertia objects = [] for inertia_object in inertials: objdict = None try: pose = deriveObjectPose(inertia_object) objdict = {'name': inertia_object.name, 'mass': inertia_object['mass'], # FIXME: this is not nice, as we invert what is one when deriving the pose 'com': mathutils.Vector(pose['translation']), 'rot': pose['rawmatrix'].to_3x3(), 'inertia': list(inertia_object['inertia'])} except KeyError as e: log('Inertial object ' + inertia_object.name + ' is missing data: ' + str(e), 'WARNING') continue if objdict: objects.append(objdict) # fuse inertias of objects if objects: log(" Fusing inertials: " + str([i.name for i in inertials]), 'DEBUG') mass, com, inertia = compound_inertia_analysis_3x3(objects) log(" Fused mass: " + str(mass), 'DEBUG') return mass, com, inertia log("No inertial found to fuse.", 'DEBUG') return None, None, None
def fuseInertiaData(inertials): """Returns mass, center of mass and inertia of a link as a whole, taking a list of inertials. *mass*: double *com*: mathutils:Vector(3) *inertia*: mathutils:Matrix(3) :param inertials: The alist of objects relevant for the inertia of a link. :type inertials: list :return: tuple(3) -- see description for content. """ objects = [] for o in inertials: objdict = None try: pose = deriveObjectPose(o) # FIXME the following is really a short cut that points to a bigger problem inertia = o['inertia'] if 'inertia' in o else o['inertial/inertia'] mass = o['mass'] if 'mass' in o else o['inertial/mass'] objdict = { 'name': o.name, 'mass': mass, 'com': mathutils.Vector( pose['translation'] ), # FIXME: this is not nice, as we invert what is one when deriving the pose 'rot': pose['rawmatrix'].to_3x3(), 'inertia': inertia } except KeyError as e: log('Inertial object ' + o.name + ' is missing data: ' + str(e), "WARNING", "fuseInertiaData") if objdict: objects.append(objdict) if len(objects) > 0: log("Fusing inertials: " + str([i.name for i in inertials]), "DEBUG", "fuseInertiaData") mass, com, inertia = compound_inertia_analysis_3x3(objects) log("Fused mass: " + str(mass), "DEBUG", "fuseInertiaData") return mass, com, inertia else: log("No inertial found to fuse.", "DEBUG", "fuseInertiaData") return None, None, None
def createNewBranch(branch, workingdir): if not branch or not workingdir: log("No branch specified.", "ERROR") return False try: subprocess.check_output(['git', 'checkout', '-b', branch], cwd=workingdir, universal_newlines=True) log("Created branch " + branch + ".", "INFO") return True except subprocess.CalledProcessError: log("Could not create branch " + branch + ".", "ERROR") return False
def checkoutCommit(commit, workingdir): # DOCU add some docstring if not commit or not workingdir: log("No commit specified.", "ERROR") return False try: subprocess.check_output(['git', 'checkout', commit], cwd=workingdir, universal_newlines=True) log("Checked out commit " + commit + ".", "INFO") return True except subprocess.CalledProcessError: log("Problem checking out " + commit, "ERROR") return False
def isInertiaDataValid(inertialdict): """Returns True if the inertial data to be physical consistent, else False. """ # Check inertia vector for various properties inertia = numpy.array(inertiaListToMatrix(inertialdict['inertia'])) if not all(element >= 0.0 for element in inertia.diagonal()): log("Negative semidefinite main diagonal in inertia data!", "WARNING") return False # Calculate the determinant if consistent if numpy.linalg.det(inertia) <= 0.0: log("Negative semidefinite determinant in inertia data!", "WARNING") return False # Calculate the eigenvalues if consistent if any(element < 0.0 for element in numpy.linalg.eigvals(inertia)): log("Negative semidefinite eigenvalues in inertia data!", "WARNING") return False return 'mass' in inertialdict and inertialdict['mass'] > 0
def checkoutBranch(branch, workingdir, create=False, pushorigin=False): if not branch or not workingdir: log("No branch specified.", "ERROR") return False try: subprocess.check_output(['git', 'fetch'], cwd=workingdir, universal_newlines=True) subprocess.check_output(['git', 'checkout', branch], cwd=workingdir, universal_newlines=True) log("Checkout branch " + branch + " successful.", "INFO") return True except subprocess.CalledProcessError: if create: return createNewBranch(branch, workingdir, pushorigin) log("Could not checkout branch " + branch + ".", "ERROR") return False
def commit(destination, message='Automated commit', ignore=[], force_push=True): """Commits the current status in the git folder at destination. This can ignore the first level subfolders and files specified in the optional parameter. Args: destination: message: (Default value = 'Automated commit') ignore: (Default value = []) Returns: : TODO """ # Add the subfolders and files to git # TODO test this functionality for direc in os.listdir(destination): if direc not in ignore: try: subprocess.check_output(['git', 'add', direc], cwd=destination, universal_newlines=True) except subprocess.CalledProcessError: log('Could not add to git: ' + direc, 'ERROR') return False # Commit the changes try: subprocess.check_output( ['git', 'commit', '-m', '"{0}"'.format(message)], cwd=destination, universal_newlines=True, ) push(destination, force_push=force_push) log('Commit to ' + destination + ' successful.', 'DEBUG') return True except subprocess.CalledProcessError as error: log('Could not commit and push: ' + str(error), 'ERROR') return False
def checkInertia(inertia): """Checks if the inertia of an object leads to positive definite inertia matrix. :param inertia: inertia of the object. :type inertia: list, tuple or matrix :return: true if inertia matrix is positive definite, false if not :rtype: bool """ assert isinstance( inertia, (list, tuple, mathutils.Matrix)), ("Wrong inertia type: " + type(inertia) + ".") consistency = True # Convert to matrix if necessary if not isinstance(inertia, mathutils.Matrix): inertia = numpy.array(inertiaListToMatrix(inertia)) # Check the main diagonal for strictly positive values consistency = all(element >= 0.0 for element in inertia.diagonal()) if not consistency: log("Negative semidefinite main diagonal found!", "WARNING") return consistency # Calculate the determinant if consistent consistency = numpy.linalg.det(inertia) > 0.0 if not consistency: log("Negative semidefinite determinant found!", "WARNING") return consistency # Calculate the eigenvalues if consistent consistency = all(element > 0.0 for element in numpy.linalg.eigvals(inertia)) if not consistency: log("Negative semidefinite eigenvalues found!", "WARNING") return consistency
def loadPose(modelname, posename): """Load and apply a robot's stored pose. Args: modelname(str): the model's name posename(str): the name the pose is stored under Returns: """ load_file = bUtils.readTextFile(modelname + '::poses') if load_file == '': log('No poses stored.', 'ERROR') return loadedposes = yaml.load(load_file) if posename not in loadedposes: log('No pose with name ' + posename + ' stored for model ' + modelname, 'ERROR') return prev_mode = bpy.context.mode pose = loadedposes[posename] # apply rotations to all joints defined by the pose try: bpy.ops.object.mode_set(mode='POSE') for obj in sUtils.getObjectsByPhobostypes(['link']): if nUtils.getObjectName(obj, 'joint') in pose['joints']: obj.pose.bones['Bone'].rotation_mode = 'XYZ' obj.pose.bones['Bone'].rotation_euler.y = float( pose['joints'][nUtils.getObjectName(obj, 'joint')]) except KeyError as error: log("Could not apply the pose: " + str(error), 'ERROR') finally: # restore previous mode bpy.ops.object.mode_set(mode=prev_mode)
def exportSmurf(model, path): """This function exports a given model to a specific path as a smurf representation. Args: model(dict): The model you want to export. path: The path you want to save the smurf file *without file name!* Returns: """ collisiondata = deriveRefinedCollisionData(model) lodsettings = gatherLevelOfDetailSettings(model) exportdata = { 'state': False, # model['state'] != {}, # TODO: handle state 'materials': model['materials'] != {}, 'sensors': model['sensors'] != {}, 'motors': model['motors'] != {}, 'controllers': model['controllers'] != {}, 'collision': collisiondata != {}, 'visuals': lodsettings != {}, 'lights': model['lights'] != {}, 'submechanisms': model['submechanisms'] != [], } # create all filenames smurf_filename = model['name'] + ".smurf" filenames = { 'state': model['name'] + "_state.yml", 'materials': model['name'] + "_materials.yml", 'sensors': model['name'] + "_sensors.yml", 'motors': model['name'] + "_motors.yml", 'controllers': model['name'] + "_controllers.yml", 'collision': model['name'] + "_collision.yml", 'visuals': model['name'] + "_visuals.yml", 'lights': model['name'] + "_lights.yml", 'submechanisms': model['name'] + "_submechanisms.yml", } fileorder = [ 'collision', 'visuals', 'materials', 'motors', 'sensors', 'controllers', 'state', 'lights', 'submechanisms', ] urdf_path = '../urdf/' urdf_filename = model['name'] + '.urdf' # gather annotations and data from text files annotationdict = models.gatherAnnotations(model) # $mars annotated properties overwrite custom properties of objects for smurf if 'mars' in annotationdict: for category in annotationdict['mars']: for list_obj in annotationdict['mars'][category]: model[category + 's'][list_obj['name']].update(list_obj) del annotationdict['mars'] for category in annotationdict: # TODO use os.path? if category != 'sdf': filenames[category] = model['name'] + '_' + category + '.yml' fileorder.append(category) exportdata[category] = True customdatalist = [] for text in bpy.data.texts: if text.name.startswith(model['name'] + '::'): dataname = text.name.split('::')[-1] customdatalist.append(dataname) # TODO use os.path? filenames[dataname] = model['name'] + '_' + dataname + '.yml' fileorder.append(dataname) exportdata[dataname] = True infostring = ' definition SMURF file for "' + model[ 'name'] + '", ' + model["date"] + "\n\n" # write model information log("Writing SMURF model to " + smurf_filename, "INFO") # CHECK are these filepaths failsafe in Windows? modeldata = { "date": model["date"], "files": [urdf_path + urdf_filename] + [filenames[f] for f in fileorder if exportdata[f]], } # append custom data with open(os.path.join(path, smurf_filename), 'w') as op: op.write('# main SMURF file of model "' + model['name'] + '"\n') op.write('# created with Phobos ' + defs.version + ' - ' + defs.repository + '\n\n') op.write("SMURF version: " + defs.version + "\n") op.write("modelname: " + model['name'] + "\n") op.write(yaml.dump(modeldata, default_flow_style=False)) # TODO delete me? # #write semantics (SRDF information in YML format) # if export['semantics']: # with open(path + filenames['semantics'], 'w') as op: # op.write('#semantics'+infostring) # op.write("modelname: "+model['name']+'\n') # semantics = {} # if model['groups'] != {}: # semantics['groups'] = model['groups'] # if model['chains'] != {}: # semantics['chains'] = model['chains'] # op.write(yaml.dump(semantics, default_flow_style=False)) # for smurf, we parse the controller parameters into the motors for motor in model['motors']: motordict = model['motors'][motor] controllerparams = {} if 'controller' in motordict and motordict['controller'] in model[ 'controllers']: controllerparams = { key: value for key, value in model['controllers'][ motordict['controller']].items() if (key not in ['name', 'target']) } motordict.update(controllerparams) del motordict['controller'] else: log( "No controller assigned to motor {}!".format( motordict['name']), 'WARNING') # PID controlled motors get their min and max values from the joint limits if motordict['type'] == 'PID': try: joint = model['joints'][motordict['joint']] if 'limits' in joint: motordict['minValue'] = joint['limits']['lower'] motordict['maxValue'] = joint['limits']['upper'] except KeyError: log( "Missing data in motor {}! No limits given for type PID. Motor might be incomplete." .format(motordict['name']), "WARNING", ) # direct controllers are called generic_dc in mars elif motordict['type'] == 'direct': motordict['type'] = 'generic_dc' try: motordict['minValue'] = -motordict["maxSpeed"] motordict['maxValue'] = motordict["maxSpeed"] except KeyError: log( "Missing data in motor {}! No maxSpeed given for motor type direct. Motor might be incomplete." .format(motordict['name']), "WARNING", ) # TODO: implement everything but joints # write state (state information of all joints, sensor & motor activity etc.) if exportdata['state']: states = [] # gather all states for jointname in model['joints']: joint = model['joints'][jointname] # this should always be the case, but testing doesn't hurt if 'state' in joint: tmpstate = joint['state'].copy() tmpstate['name'] = jointname states.append(joint['state']) with open(os.path.join(path, filenames['state']), 'w') as op: op.write('#state' + infostring) op.write("modelname: " + model['name'] + '\n') # TODO am I still needed? op.write(yaml.dump(states)) # , default_flow_style=False)) # write materials, sensors, motors & controllers for data in ['materials', 'motors', 'sensors', 'controllers', 'lights']: if exportdata[data]: log("Writing {} to smurf file.".format(data), 'DEBUG') with open(os.path.join(path, filenames[data]), 'w') as op: op.write('#' + data + infostring) op.write( yaml.dump( sort_for_yaml_dump({data: list(model[data].values())}, data), default_flow_style=False, )) # write additional collision information if exportdata['collision']: with open(os.path.join(path, filenames['collision']), 'w') as op: op.write('#collision data' + infostring) # TODO delete me? # op.write(yaml.dump({'collision': list(bitmasks.values())}, default_flow_style=False)) op.write( yaml.dump( { 'collision': [ collisiondata[key] for key in sorted(collisiondata.keys()) ] }, default_flow_style=False, )) # write visual information (level of detail, ...) if exportdata['visuals']: with open(os.path.join(path, filenames['visuals']), 'w') as op: op.write('#visual data' + infostring) op.write( yaml.dump({'visuals': list(lodsettings.values())}, default_flow_style=False)) # write additional information for category in annotationdict.keys(): if category != 'sdf': if exportdata[category]: outstring = '#' + category + infostring for elementtype in annotationdict[category]: outstring += elementtype + ':\n' outstring += ( yaml.dump(annotationdict[category][elementtype], default_flow_style=False) + "\n") with open(os.path.join(path, filenames[category]), 'w') as op: op.write(outstring) # write custom data from textfiles for data in customdatalist: if exportdata[data]: with open(os.path.join(path, filenames[data]), 'w') as op: op.write('#' + data + infostring) op.write( yaml.dump({data: list(model[data].values())}, default_flow_style=False)) # write submechanisms if model['submechanisms']: with open(os.path.join(path, filenames['submechanisms']), 'w') as op: op.write('#submechanisms' + infostring) op.write(yaml.dump({'submechanisms': model['submechanisms'] })) # , default_flow_style=False))
def exportModel(root, export_path, entitytypes=None, model=None): # derive model model = models.buildModelDictionary(root) if not model: model = models.buildModelDictionary(root) # export model in selected formats if entitytypes is None: entitytypes = entities.entity_types for entitytype in entitytypes: typename = "export_entity_" + entitytype # check if format exists and should be exported if not getattr(bpy.data.worlds[0], typename, False): continue # format exists and is exported: if ioUtils.getExpSettings().structureExport: model_path = os.path.join(export_path, entitytype) else: model_path = export_path securepath(model_path) try: entities.entity_types[entitytype]['export'](model, model_path) log( "Export model: " + model['name'] + ' as ' + entitytype + " to " + model_path, "DEBUG") except KeyError: log( "No export function available for selected model type: " + entitytype, "ERROR") continue # TODO: Move mesh export to individual formats? This is practically SMURF # export meshes in selected formats i = 1 mt = len([ m for m in meshes.mesh_types if getattr(bpy.data.worlds[0], "export_mesh_" + m) ]) mc = len(model['meshes']) n = mt * mc for meshtype in meshes.mesh_types: mesh_path = ioUtils.getOutputMeshpath(export_path, meshtype) try: typename = "export_mesh_" + meshtype if getattr(bpy.data.worlds[0], typename): securepath(mesh_path) for meshname in model['meshes']: meshes.mesh_types[meshtype]['export']( model['meshes'][meshname], mesh_path) display.setProgress( i / n, 'Exporting ' + meshname + '.' + meshtype + '...') i += 1 except KeyError: log( "No export function available for selected mesh function: " + meshtype, "ERROR") print(sys.exc_info()[0]) display.setProgress(0) # TODO: Move texture export to individual formats? This is practically SMURF # TODO: Also, this does not properly take care of textures embedded in a .blend file # export textures if ioUtils.getExpSettings().exportTextures: for materialname in model['materials']: mat = model['materials'][materialname] for texturetype in [ 'diffuseTexture', 'normalTexture', 'displacementTexture' ]: if texturetype in mat: sourcepath = os.path.join( os.path.expanduser(bpy.path.abspath('//')), mat[texturetype]) if os.path.isfile(sourcepath): texture_path = securepath( os.path.join(export_path, 'textures')) log("Exporting textures to " + texture_path, "INFO") try: shutil.copy( sourcepath, os.path.join( texture_path, os.path.basename(mat[texturetype]))) except shutil.SameFileError: log("{} already in place".format(texturetype), "INFO")