From 3e283222cb8ec5916320ff4d9b096be6c0f18bd8 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 8 Jul 2019 11:59:45 +0300 Subject: [PATCH] Add blender-csv-animation addon v0.4.0 --- blender-addon/README.md | 25 +++++ blender-addon/addon.py | 213 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 blender-addon/README.md create mode 100644 blender-addon/addon.py diff --git a/blender-addon/README.md b/blender-addon/README.md new file mode 100644 index 0000000..5c594ba --- /dev/null +++ b/blender-addon/README.md @@ -0,0 +1,25 @@ +# blender-csv-animation +A Blender extension that export paths of objects in blender animation to a csv files + +## CSV file format +First row is the animation filename. +Every next row of the file contains following information about an object: +- frame number, +- x coordinate, +- y coordinate, +- z coordinate, +- rotaion around z-axis angle (yaw for copter), +- rgb. + +## How to use it +Clone or download this repository +```bash +git clone https://github.com/artem30801/blender-csv-animation +``` +Open Blender and install the addon: +1) Open User Prerences windows using main menu or shortcut (Ctrl + Alt + U): Files - User Preferences +2) Under Add-ons tab click Install Add-on from File... +3) Choose addon.py file from the directory of this repository +4) Enable the Add-on + +Use [official docs](https://docs.blender.org/manual/en/latest/preferences/addons.html) for getting additional information diff --git a/blender-addon/addon.py b/blender-addon/addon.py new file mode 100644 index 0000000..1a9d527 --- /dev/null +++ b/blender-addon/addon.py @@ -0,0 +1,213 @@ +import os +import csv +import math + +import bpy +from bpy_extras.io_utils import ExportHelper +from bpy.types import Operator +from bpy.props import StringProperty, BoolProperty, FloatProperty, IntProperty + +bl_info = { + "name": "Export > CSV Drone Swarm Animation Exporter (.csv)", + "author": "Artem Vasiunik", + "version": (0, 4, 0), + "blender": (2, 80, 0), + #"api": 36079, + "location": "File > Export > CSV Drone Swarm Animation Exporter (.csv)", + "description": "Export > CSV Drone Swarm Animation Exporter (.csv)", + "warning": "", + "wiki_url": "https://github.com/artem30801/blender-csv-animation/blob/master/README.md", + "tracker_url": "https://github.com/artem30801/blender-csv-animation/issues", + "category": "Import-Export" +} + + +class ExportCsv(Operator, ExportHelper): + bl_idname = "export_swarm_anim.folder" + bl_label = "Export Drone Swarm animation" + filename_ext = '' + use_filter_folder = True + + use_namefilter: bpy.props.BoolProperty( + name="Use name filter for objects", + default=True, + ) + + drones_name: bpy.props.StringProperty( + name="Name identifier", + description="Name identifier for all drone objects", + default="copter" + ) + + show_warnings: bpy.props.BoolProperty( + name="Show detailed animation warnings", + default=False, + ) + + speed_warning_limit: bpy.props.FloatProperty( + name="Speed limit", + description="Limit of drone movement speed (m/s)", + unit='VELOCITY', + default=3, + min=0, + ) + drone_distance_limit: bpy.props.FloatProperty( + name="Distance limit", + description="Closest possible distance between drones (m)", + unit='LENGTH', + default=1.5, + min=0, + ) + + filepath: StringProperty( + name="File Path", + description="File path used for exporting CSV files", + maxlen=1024, + subtype='DIR_PATH', + default="" + ) + + def execute(self, context): + + create_folder_if_does_not_exist(self.filepath) + scene = context.scene + objects = context.visible_objects + + drone_objects = [] + if self.use_namefilter: + for drone_obj in objects: + if self.drones_name.lower() in drone_obj.name.lower(): + drone_objects.append(drone_obj) + else: + drone_objects = objects + + frame_start = scene.frame_start + frame_end = scene.frame_end + + for drone_obj in drone_objects: + with open(os.path.join(self.filepath, '{}.csv'.format(drone_obj.name.lower())), 'w') as csv_file: + animation_file_writer = csv.writer( + csv_file, + delimiter=',', + quotechar='|', + quoting=csv.QUOTE_MINIMAL + ) + speed_exeeded = False + distance_exeeded = False + + prev_x, prev_y, prev_z = 0, 0, 0 + + animation_file_writer.writerow([ + os.path.splitext(bpy.path.basename(bpy.data.filepath))[0] + ]) + + for frame_number in range(frame_start, frame_end + 1): + scene.frame_set(frame_number) + rgb = get_rgb_from_object(drone_obj) + x, y, z = drone_obj.matrix_world.to_translation() + rot_z = drone_obj.matrix_world.to_euler('XYZ')[2] + + speed = calc_speed((x, y, z), (prev_x, prev_y, prev_z)) if frame_number != frame_start else 1 + prev_x, prev_y, prev_z = x, y, z + + if speed > self.speed_warning_limit: + speed_exeeded = True + if self.show_warnings: + self.report({'WARNING'}, + "Speed of drone '%s' is greater than %s m/s (%s m/s) on frame %s" % + (drone_obj.name, round(self.speed_warning_limit, 5), round(speed, 5), frame_number)) + + for second_drone_obj in drone_objects: + if second_drone_obj is not drone_obj: + x2, y2, z2 = second_drone_obj.matrix_world.to_translation() + distance = calc_distance((x, y, z), (x2, y2, z2)) + if distance < self.drone_distance_limit: + distance_exeeded = True + if self.show_warnings: + self.report({'WARNING'}, + "Distance beteween drones '%s' and '%s' is less than %s m (%s m) on frame %s" % + (drone_obj.name, second_drone_obj.name, + round(self.drone_distance_limit, 5), round(distance, 5), frame_number)) + + animation_file_writer.writerow([ + str(frame_number), + round(x, 5), round(y, 5), round(z, 5), + round(rot_z, 5), + *rgb, + ]) + + + + if speed_exeeded: + self.report({'WARNING'}, "Drone '%s' speed limits exeeded" % drone_obj.name) + if distance_exeeded: + self.report({'WARNING'}, "Drone '%s' distance limits exeeded" % drone_obj.name) + self.report({'WARNING'}, "Animation file exported for drone '%s'" % drone_obj.name) + return {'FINISHED'} + + +def create_folder_if_does_not_exist(folder_path): + if os.path.isdir(folder_path): + return + os.mkdir(folder_path) + + +def get_rgb_from_object(obj): + rgb = [0, 0, 0] + try: + if len(obj.material_slots) > 0: + print('material slots true') + for slot in obj.material_slots: + if "led_color" in slot.name.lower(): + print('led color') + if slot.material.use_nodes: + for node in slot.material.node_tree.nodes: + if node.type in ('EMISSION', 'BSDF_DIFFUSE'): + alpha = node.inputs[0].default_value[3] + for component in range(3): + rgb[component] = int(node.inputs[0].default_value[component] * alpha * 255) + else: + print('no led color') + for component in range(3): + rgb[component] = int(slot.material.diffuse_color[component] * 255) + + except AttributeError: + pass + finally: + return rgb + + +def calc_speed(start_point, end_point): + time_delta = 0.1 + distance = calc_distance(start_point, end_point) + return distance / time_delta + + +def calc_distance(start_point, end_point): + distance = math.sqrt( + (start_point[0] - end_point[0]) ** 2 + + (start_point[1] - end_point[1]) ** 2 + + (start_point[2] - end_point[2]) ** 2 + ) + return distance + + +def menu_func(self, context): + self.layout.operator( + ExportCsv.bl_idname, + text="CSV Drone Swarm Animation Exporter (.csv)" + ) + + +def register(): + bpy.utils.register_class(ExportCsv) + bpy.types.TOPBAR_MT_file_export.append(menu_func) + + +def unregister(): + bpy.utils.unregister_class(ExportCsv) + bpy.types.TOPBAR_MT_file_export.remove(menu_func) + + +if __name__ == "__main__": + register()