mirror of
https://github.com/CopterExpress/clever-show.git
synced 2026-05-26 07:07:58 +00:00
212 lines
7.2 KiB
Python
212 lines
7.2 KiB
Python
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": "clever-show animation (.csv)",
|
|
"author": "Artem Vasiunik & Arthur Golubtsov",
|
|
"version": (0, 5, 0),
|
|
"blender": (2, 80, 0),
|
|
#"api": 36079,
|
|
"location": "File > Export > clever-show animation (.csv)",
|
|
"description": "Export > clever-show animation (.csv)",
|
|
"warning": "",
|
|
"wiki_url": "https://github.com/CopterExpress/clever-show/blob/master/blender-addon/README.md",
|
|
"tracker_url": "https://github.com/CopterExpress/clever-show/issues",
|
|
"category": "Import-Export"
|
|
}
|
|
|
|
|
|
class ExportCsv(Operator, ExportHelper):
|
|
bl_idname = "export_animation.folder"
|
|
bl_label = "Export clever-show animation"
|
|
filename_ext = ''
|
|
use_filter_folder = True
|
|
|
|
use_namefilter: bpy.props.BoolProperty(
|
|
name="Use name filter for objects",
|
|
default=False,
|
|
)
|
|
|
|
drones_name: bpy.props.StringProperty(
|
|
name="Name identifier",
|
|
description="Name identifier for all drone objects",
|
|
default="clever"
|
|
)
|
|
|
|
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="clever-show animation (.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()
|