| from bpy.types import ( |
| Operator, |
| OperatorFileListElement, |
| Panel |
| ) |
| from bpy.props import ( |
| StringProperty, |
| CollectionProperty |
| ) |
| from bpy_extras.io_utils import ImportHelper |
| import bpy |
| import pickle |
| from os.path import splitext, join, basename |
|
|
| bl_info = { |
| "name": "PyBulletSimImporter", |
| "author": "Huy Ha <hqh2101@columbia.edu>", |
| "version": (0, 0, 1), |
| "blender": (2, 92, 0), |
| "location": "3D View > Toolbox > Animation tab > PyBullet Simulation Importer", |
| "description": "Imports PyBullet Simulation Results", |
| "warning": "", |
| "category": "Animation", |
| } |
|
|
|
|
| class ANIM_OT_import_pybullet_sim(Operator, ImportHelper): |
| bl_label = "Import simulation" |
| bl_idname = "pybulletsim.import" |
| bl_description = "Imports a PyBullet Simulation" |
| bl_options = {'REGISTER', 'UNDO'} |
| files: CollectionProperty( |
| name="Simulation files", |
| type=OperatorFileListElement, |
| ) |
| directory: StringProperty(subtype='DIR_PATH') |
| filename_ext = ".pkl" |
| filter_glob: StringProperty( |
| default='*.pkl', |
| options={'HIDDEN'}) |
| skip_frames: bpy.props.IntProperty( |
| name="Skip Frames", default=10, min=1, max=100) |
| max_frames: bpy.props.IntProperty( |
| name="Max Frames", default=-1, min=-1, max=100000) |
|
|
| def execute(self, context): |
| for file in self.files: |
| filepath = join(self.directory, file.name) |
| print(f'Processing {filepath}') |
| with open(filepath, 'rb') as pickle_file: |
| data = pickle.load(pickle_file) |
| collection_name = splitext(basename(filepath))[0] |
| collection = bpy.data.collections.new(collection_name) |
| bpy.context.scene.collection.children.link(collection) |
| context.view_layer.active_layer_collection = \ |
| context.view_layer.layer_collection.children[-1] |
|
|
| for obj_key in data: |
| pybullet_obj = data[obj_key] |
| |
| |
|
|
|
|
| if pybullet_obj['type'] == 'mesh': |
| extension = pybullet_obj['mesh_path'].split( |
| ".")[-1].lower() |
| |
| if 'obj' in extension: |
| bpy.ops.import_scene.obj( |
| filepath=pybullet_obj['mesh_path'], |
| axis_forward='Y', axis_up='Z') |
| elif 'dae' in extension: |
| bpy.ops.wm.collada_import( |
| filepath=pybullet_obj['mesh_path']) |
| elif 'stl' in extension: |
| bpy.ops.import_mesh.stl( |
| filepath=pybullet_obj['mesh_path']) |
| else: |
| print("Unsupported File Format:{}".format(extension)) |
| pass |
| elif pybullet_obj['type'] == 'cube': |
| bpy.ops.mesh.primitive_cube_add() |
| elif pybullet_obj['type'] == "sphere": |
| bpy.ops.mesh.primitive_uv_sphere_add() |
| elif pybullet_obj['type'] == "cylinder": |
| bpy.ops.mesh.primitive_cylinder_add() |
|
|
| |
| parts = 0 |
| final_objs = [] |
| for import_obj in context.selected_objects: |
| bpy.ops.object.select_all(action='DESELECT') |
| import_obj.select_set(True) |
| if 'Camera' in import_obj.name \ |
| or 'Light' in import_obj.name\ |
| or 'Lamp' in import_obj.name: |
| bpy.ops.object.delete(use_global=True) |
| else: |
| scale = pybullet_obj['mesh_scale'] |
| if scale is not None: |
| |
| import_obj.scale.x = scale[0] |
| import_obj.scale.y = scale[1] |
| import_obj.scale.z = scale[2] |
|
|
| final_objs.append(import_obj) |
| parts += 1 |
|
|
| bpy.ops.object.select_all(action='DESELECT') |
| for obj in final_objs: |
| if obj.type == 'MESH': |
| obj.select_set(True) |
| if len(context.selected_objects): |
| context.view_layer.objects.active =\ |
| context.selected_objects[0] |
| |
| bpy.ops.object.join() |
| blender_obj = context.view_layer.objects.active |
| blender_obj.name = obj_key |
|
|
| if pybullet_obj['mesh_material_name'] is not None: |
| |
| material_name = pybullet_obj['mesh_material_name'] |
| material_color = pybullet_obj['mesh_material_color'] |
|
|
| print("registering material:", material_name) |
| mat = bpy.data.materials.new(name=material_name) |
| mat.use_nodes = True |
| nodes = mat.node_tree.nodes |
| links = mat.node_tree.links |
| for node in nodes: |
| nodes.remove(node) |
| mat_node = nodes.new(type='ShaderNodeBsdfPrincipled') |
| mat_node.inputs["Base Color"].default_value = material_color |
| mat_node_output = mat_node.outputs['BSDF'] |
| output_node = nodes.new(type='ShaderNodeOutputMaterial') |
| links.new(output_node.inputs['Surface'], mat_node_output) |
|
|
| |
| |
| if obj.data.materials: |
| obj.data.materials[0] = bpy.data.materials[material_name] |
| else: |
| obj.data.materials.append(bpy.data.materials[material_name]) |
|
|
| |
| for frame_count, frame_data in enumerate( |
| pybullet_obj['frames']): |
| if frame_count % self.skip_frames != 0: |
| continue |
| if self.max_frames > 1 and frame_count > self.max_frames: |
| print('Exceed max frame count') |
| break |
| percentage_done = frame_count / \ |
| len(pybullet_obj['frames']) |
| print(f'\r[{percentage_done*100:.01f}% | {obj_key}]', |
| '#' * int(60*percentage_done), end='') |
| pos = frame_data['position'] |
| orn = frame_data['orientation'] |
| context.scene.frame_set( |
| frame_count // self.skip_frames) |
| |
| blender_obj.location.x = pos[0] |
| blender_obj.location.y = pos[1] |
| blender_obj.location.z = pos[2] |
| blender_obj.rotation_mode = 'QUATERNION' |
| blender_obj.rotation_quaternion.x = orn[0] |
| blender_obj.rotation_quaternion.y = orn[1] |
| blender_obj.rotation_quaternion.z = orn[2] |
| blender_obj.rotation_quaternion.w = orn[3] |
| bpy.ops.anim.keyframe_insert_menu( |
| type='Rotation') |
| bpy.ops.anim.keyframe_insert_menu( |
| type='Location') |
| return {'FINISHED'} |
|
|
|
|
| class VIEW3D_PT_pybullet_recorder(Panel): |
| bl_space_type = 'VIEW_3D' |
| bl_region_type = 'UI' |
| bl_category = "Animation" |
| bl_label = 'PyBulletSimImporter' |
|
|
| def draw(self, context): |
| layout = self.layout |
| row = layout.row() |
| row.operator("pybulletsim.import") |
|
|
|
|
| def register(): |
| bpy.utils.register_class(VIEW3D_PT_pybullet_recorder) |
| bpy.utils.register_class(ANIM_OT_import_pybullet_sim) |
|
|
|
|
| def unregister(): |
| bpy.utils.unregister_class(VIEW3D_PT_pybullet_recorder) |
| bpy.utils.unregister_class(ANIM_OT_import_pybullet_sim) |
|
|
|
|
| if __name__ == "__main__": |
| register() |
|
|