From fe3fcde2fb151e9e84c2e21c343721a203c301ed Mon Sep 17 00:00:00 2001 From: Henrik Lindgren <henrik@hlindgren.xyz> Date: Sun, 9 Oct 2022 18:45:41 +0200 Subject: [PATCH] add control panel and pose feature --- exporter.py | 195 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 166 insertions(+), 29 deletions(-) diff --git a/exporter.py b/exporter.py index 40463f4..4c72224 100644 --- a/exporter.py +++ b/exporter.py @@ -3,9 +3,15 @@ import json import bpy from bpy_extras.io_utils import ExportHelper -from bpy.props import StringProperty +from bpy.props import StringProperty, IntProperty, PointerProperty from bpy.types import Operator +import requests + + +DOFBOT_IP = '192.168.50.172' +DOFBOT_PORT = 5000 + def print(data): for window in bpy.context.window_manager.windows: @@ -33,17 +39,17 @@ def get_servo_objects(): def get_moving_angle(servo_name, rot_deg): if servo_name == 'bottom_rotation': - return rot_deg.z + return rot_deg.z + 90 if servo_name == 'joint_1': - return rot_deg.y + return rot_deg.y + 90 if servo_name == 'joint_2': - return rot_deg.y + return rot_deg.y + 90 if servo_name == 'joint_3': - return rot_deg.y + return rot_deg.y + 90 if servo_name == 'claw_rotation': - return rot_deg.z + return rot_deg.z + 90 if servo_name == 'claw_grip': - return rot_deg.x + return rot_deg.x*2 def frame2time(frame): @@ -58,9 +64,15 @@ class RotationDegrees: self.z = round(math.degrees(euler_rot.z)) -# This method of stepping through frame by frame and looking at the objects matrix_world -# was taken from the below export script by @astronotter -# https://gist.github.com/astronotter/faec735fc5e270df57741705052096ac +def get_servo_angle(servo_object): + rot_deg = RotationDegrees(servo_object.rotation_euler) + angle = get_moving_angle(servo_object.name, rot_deg) + + # print((frame, servo_object.name, angle, servo_object.rotation_euler)) + + return angle + + def get_servo_data(): frame_range = range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1) @@ -68,21 +80,35 @@ def get_servo_data(): previous_angle = None for frame in frame_range: bpy.context.scene.frame_set(frame) - matrix = obj.matrix_world.copy() - # posx, posy, posz = matrix.to_translation()[:] - # scalex, scaley, scalez = matrix.to_scale()[:] - rot_deg = RotationDegrees(matrix.to_euler()) - angle = get_moving_angle(obj.name, rot_deg) - + angle = get_servo_angle(obj) + if previous_angle == angle: continue - + previous_angle = angle - yield (obj.name, [frame2time(frame), angle]) + + bpy.context.scene.frame_set(1) + + +def collect_servo_data_snapshot(): + servos = { + 'bottom_rotation': [], + 'joint_1': [], + 'joint_2': [], + 'joint_3': [], + 'claw_rotation': [], + 'claw_grip': [] + } + + for obj in get_servo_objects(): + angle = get_servo_angle(obj) + servos[obj.name].append([2000, angle]) + + return servos -def calc_servo_data(): +def collect_servo_data(): servos = { 'bottom_rotation': [], 'joint_1': [], @@ -98,10 +124,44 @@ def calc_servo_data(): return servos +class DofbotProperties(bpy.types.PropertyGroup): + dofbot_ip: StringProperty( + name="IP", + description="The IP address of the Dofbot", + default=DOFBOT_IP, + ) + + dofbot_port: StringProperty( + name="Port", + description="The port of the Dofbot control API", + default=str(DOFBOT_PORT), + ) + + +class DofbotRunAnimation(Operator): + """Run animation on dofbot""" + bl_label = "Run animation" + bl_idname = "dofbot.run_animation" + + def execute(self, context): + run_animation() + return {'FINISHED'} + + +class DofbotSetCurrentPose(Operator): + """Set Dofbot to the current pose""" + bl_label = "Set pose" + bl_idname = "dofbot.set_current_pose" + + def execute(self, context): + set_current_pose() + return {'FINISHED'} + + class ExportDofbotAnimation(Operator, ExportHelper): """Exports Dofbot animation as json""" - bl_idname = "export_dofbot.json_animation" # important since its how bpy.ops.import_test.some_data is constructed - bl_label = "Export Dofbot Json Animation" + bl_idname = 'dofbot.export_json_animation' # important since its how bpy.ops.import_test.some_data is constructed + bl_label = '' # "Export Dofbot Json Animation" # ExportHelper mixin class uses this filename_ext = ".json" @@ -113,31 +173,108 @@ class ExportDofbotAnimation(Operator, ExportHelper): ) def execute(self, context): - servo_data = calc_servo_data() + servo_data = collect_servo_data() with open(self.filepath, 'w') as f: f.write(json.dumps(servo_data)) return {'FINISHED'} -# Only needed if you want to add into a dynamic menu -def menu_func_export(self, context): - self.layout.operator(ExportDofbotAnimation.bl_idname, text="Dofbot Animation Export") +class ExportDofbotPose(Operator, ExportHelper): + """Exports Dofbot pose as json""" + bl_idname = 'dofbot.export_json_pose' # important since its how bpy.ops.import_test.some_data is constructed + bl_label = '' # "Export Dofbot Json Pose" + + # ExportHelper mixin class uses this + filename_ext = ".json" + + filter_glob: StringProperty( + default="*.json", + options={'HIDDEN'}, + maxlen=255, # Max internal buffer length, longer would be clamped. + ) + + def execute(self, context): + servo_data = collect_servo_data_snapshot() + + with open(self.filepath, 'w') as f: + f.write(json.dumps(servo_data)) + return {'FINISHED'} +class DofbotPanel(bpy.types.Panel): + """Open Dofbot Control Panel""" + bl_label = 'Dofbot Control' + bl_idname = 'wm.dofbot_panel' + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Dofbot Control' + + def draw(self, context): + layout = self.layout + dofbot = context.scene.dofbot + + row = layout.row() + row.prop(dofbot, 'dofbot_ip') + row = layout.row() + row.prop(dofbot, 'dofbot_port') + + row = layout.row() + row.operator('dofbot.run_animation', icon='PLAY') + row.operator('dofbot.export_json_animation', icon='EXPORT') + + row = layout.row() + row.operator('dofbot.set_current_pose', icon='FF') + row.operator('dofbot.export_json_pose', icon='EXPORT') + + # Register and add to the "file selector" menu (required to use F3 search "Text Export Operator" for quick access). def register(): + bpy.utils.register_class(DofbotProperties) + bpy.types.Scene.dofbot = PointerProperty(type=DofbotProperties) + + bpy.utils.register_class(DofbotPanel) + bpy.utils.register_class(DofbotRunAnimation) + bpy.utils.register_class(DofbotSetCurrentPose) bpy.utils.register_class(ExportDofbotAnimation) - bpy.types.TOPBAR_MT_file_export.append(menu_func_export) + bpy.utils.register_class(ExportDofbotPose) def unregister(): + bpy.utils.unregister_class(DofbotProperties) + del bpy.types.scene.dofbot + + bpy.utils.unregister_class(DofbotPanel) + bpy.utils.unregister_class(DofbotRunAnimation) + bpy.utils.unregister_class(DofbotSetCurrentPose) bpy.utils.unregister_class(ExportDofbotAnimation) - bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) + bpy.utils.unregister_class(ExportDofbotPose) + + +def send_motion(servo_data): + requests.post('http://' + DOFBOT_IP + ':' + str(DOFBOT_PORT) + '/motion', json = servo_data) + + +def run_animation(): + servo_data = collect_servo_data() + send_motion(servo_data) + + +def set_current_pose(): + servo_data = collect_servo_data_snapshot() + send_motion(servo_data) + + +def export_animation(): + bpy.ops.export_dofbot.json_animation('INVOKE_DEFAULT') if __name__ == "__main__": register() - # test call - bpy.ops.export_dofbot.json_animation('INVOKE_DEFAULT') +# # test call +# bpy.ops.export_dofbot.json_animation('INVOKE_DEFAULT') + + + + -- GitLab