GenSim2 / misc /pyBulletSimImporter.py
gensim2's picture
init
ff66cf3
raw
history blame contribute delete
No virus
8.86 kB
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]
# Load mesh of each link
# register material
if pybullet_obj['type'] == 'mesh':
extension = pybullet_obj['mesh_path'].split(
".")[-1].lower()
# Handle different mesh formats
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() # radius=pybullet_obj['mesh_scale'][0]
elif pybullet_obj['type'] == "cylinder":
bpy.ops.mesh.primitive_cylinder_add() # radius=pybullet_obj['mesh_scale'][0], length=pybullet_obj['mesh_scale'][-1]
# Delete lights and camera
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:
# if type(scale) is list:
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]
# join them
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:
# register material
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)
# attach material
# obj.data.materials[0] = bpy.data.materials[material_name]
if obj.data.materials:
obj.data.materials[0] = bpy.data.materials[material_name]
else:
obj.data.materials.append(bpy.data.materials[material_name])
# Keyframe motion of imported object
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)
# Apply position and rotation
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()