diff --git a/Drone/animation_lib.py b/Drone/animation_lib.py index 2018fdb..f96fd46 100644 --- a/Drone/animation_lib.py +++ b/Drone/animation_lib.py @@ -1,10 +1,12 @@ -import time +import os import csv import copy +import math +import time +import numpy import rospy import logging import threading -import ConfigParser try: from FlightLib import FlightLib @@ -15,198 +17,350 @@ try: except ImportError: print("Can't import LedLib") -import tasking_lib as tasking - logger = logging.getLogger(__name__) interrupt_event = threading.Event() -anim_id = "Empty id" +def moving(f1, f2, delta, x=True, y=True, z=True): + return ((abs(f1.x - f2.x) > delta) and x + or (abs(f1.y - f2.y) > delta) and y + or (abs(f1.z - f2.z) > delta) and z) -# TODO refactor as class -# TODO separate code for frames transformations (e.g. for gps) +def get_numbers(frames): + numbers = [] + if frames: + for frame in frames: + numbers.append(frame.number) + return numbers -def get_id(filepath="animation.csv"): - global anim_id - try: - animation_file = open(filepath) - except IOError: - logger.debug("File {} can't be opened".format(filepath)) - anim_id = "No animation" - return anim_id - else: - with animation_file: - csv_reader = csv.reader( - animation_file, delimiter=',', quotechar='|' - ) - row_0 = csv_reader.next() - if len(row_0) == 1: - anim_id = row_0[0] - logger.debug("Got animation_id: {}".format(anim_id)) - else: - anim_id = "Empty id" - logger.debug("No animation id in file") - return anim_id +class Frame(object): + params_dict = { + "number": None, + "x": None, + "y": None, + "z": None, + "yaw": None, + "red": None, + "green": None, + "blue": None, + "delay": None, + } + def __init__(self, csv_row=None, delay=None): + for key, value in self.params_dict.items(): + setattr(self, key, value) + if csv_row: + self.load_csv_row(csv_row) + if delay: + self.delay = delay -def get_start_xy(filepath="animation.csv", x_ratio=1, y_ratio=1, z_ratio=1): - try: - animation_file = open(filepath) - except IOError: - logger.debug("File {} can't be opened".format(filepath)) - anim_id = "No animation" - return float('nan'), float('nan') - else: - with animation_file: - csv_reader = csv.reader( - animation_file, delimiter=',', quotechar='|' - ) - try: - row_frame = csv_reader.next() - except: - return float('nan'), float('nan') - if len(row_frame) == 1: - anim_id = row_frame[0] - logger.debug("Got animation_id: {}".format(row_frame[0])) - row_frame = csv_reader.next() - if len(row_frame) == 2: - logger.debug("Got frame delay: {}".format(row_frame[1])) - row_frame - csv_reader.next() - try: - frame_number, x, y, z, yaw, red, green, blue = row_frame - except: - return float('nan'), float('nan') - return float(x)*x_ratio, float(y)*y_ratio + def load_csv_row(self, csv_row): + number, x, y, z, yaw, red, green, blue = csv_row + self.number = int(number) + self.x = float(x) + self.y = float(y) + self.z = float(z) + self.yaw = float(yaw) + self.red = int(red) + self.green = int(green) + self.blue = int(blue) + def get_pos(self): + if None in [self.x, self.y, self.z]: + return [] + else: + return [self.x, self.y, self.z] -def load_animation(filepath="animation.csv", default_delay = 0.1, x0=0, y0=0, z0=0, x_ratio=1, y_ratio=1, z_ratio=1): - imported_frames = [] - global anim_id - try: - animation_file = open(filepath) - except IOError: - logger.debug("File {} can't be opened".format(filepath)) - anim_id = "No animation" - else: - with animation_file: - current_frame_delay = default_delay - csv_reader = csv.reader( - animation_file, delimiter=',', quotechar='|' - ) - row_0 = csv_reader.next() - if len(row_0) == 1: - anim_id = row_0[0] - logger.debug("Got animation_id: {}".format(anim_id)) - elif len(row_0) == 2: - current_frame_delay = float(row_0[1]) - logger.debug("Got new frame delay: {}".format(current_frame_delay)) - else: - logger.debug("No animation id in file") - frame_number, x, y, z, yaw, red, green, blue = row_0 - imported_frames.append({ - 'number': int(frame_number), - 'x': x_ratio*float(x) + x0, - 'y': y_ratio*float(y) + y0, - 'z': z_ratio*float(z) + z0, - 'yaw': float(yaw), - 'red': int(red), - 'green': int(green), - 'blue': int(blue), - 'delay': current_frame_delay - }) - for row in csv_reader: - if len(row) == 2: - current_frame_delay = float(row[1]) + def get_color(self): + if None in [self.red, self.green, self.blue]: + return [] + else: + return [self.red, self.green, self.blue] + + def set_yaw(self, yaw): + if yaw != "animation": + self.yaw = math.radians(float(yaw)) + + def pose_is_valid(self): + return self.get_pos() and (self.yaw is not None) + +class Animation(object): + def __init__(self, config=None, filepath="animation.csv"): + self.reset(filepath) + if config is not None: + self.update_frames(config, filepath) + + def reset(self, filepath): + self.id = None + self.start_delay = 0 + self.original_frames = None + self.static_begin_frames = None + self.static_begin_time = 0 + self.takeoff_frames = None + self.takeoff_time = 0 + self.route_frames = None + self.route_time = 0 + self.land_frames = None + self.land_time = 0 + self.static_end_frames = None + self.static_end_time = 0 + self.output_frames = None + self.output_frames_min_z = None + self.filepath = filepath + self.state = None + + def load(self, filepath="animation.csv", config = None): + self.original_frames = [] + self.corrected_frames = [] + self.filepath = filepath + self.state = "OK" + delay = config.animation_frame_delay + try: + animation_file = open(filepath) + except IOError: + logger.debug("File {} can't be opened".format(filepath)) + self.state = "No animation" + else: + with animation_file: + current_frame_delay = delay + csv_reader = csv.reader( + animation_file, delimiter=',', quotechar='|' + ) + row_0 = csv_reader.next() + if len(row_0) == 1: + self.id = row_0[0] + logger.debug("Got animation_id: {}".format(self.id)) + elif len(row_0) == 2: + current_frame_delay = float(row_0[1]) + logger.debug("Got new frame delay: {}".format(current_frame_delay)) else: - frame_number, x, y, z, yaw, red, green, blue = row - imported_frames.append({ - 'number': int(frame_number), - 'x': x_ratio*float(x) + x0, - 'y': y_ratio*float(y) + y0, - 'z': z_ratio*float(z) + z0, - 'yaw': float(yaw), - 'red': int(red), - 'green': int(green), - 'blue': int(blue), - 'delay': current_frame_delay - }) - return imported_frames + logger.debug("No animation id in file") + self.id = "No animation id" + try: + frame = Frame(row_0, current_frame_delay) + except ValueError as e: + logger.error("Can't parse row in csv file. {}".format(e)) + self.state = "Bad animation file" + return + try: + frame.set_yaw(config.animation_yaw) + except ValueError as e: + logger.error("Can't set yaw from configuration") + self.state = "Bad yaw from config" + return + self.original_frames.append(frame) + for row in csv_reader: + if len(row) == 2: + current_frame_delay = float(row[1]) + logger.debug("Got new frame delay: {}".format(current_frame_delay)) + else: + try: + frame = Frame(row, current_frame_delay) + except ValueError as e: + logger.error("Can't parse row in csv file. {}".format(e)) + self.state = "Bad animation file" + return + try: + frame.set_yaw(config.animation_yaw) + except ValueError as e: + logger.error("Can't set yaw from configuration") + self.state = "Bad yaw from config" + return + self.original_frames.append(frame) + self.split_animation() -def correct_animation(frames, frame_delay=0.1, min_takeoff_height=0.5, move_delta=0.01, check_takeoff=True, check_land=True): - corrected_frames = copy.deepcopy(frames) - start_action = 'takeoff' - frames_to_start = 0 - time_to_start = 0 - if len(corrected_frames) == 0: - raise Exception('Nothing to correct!') - # Check takeoff - # If copter takes off in animation file, copter must be armed first and then all animation can be played - if (corrected_frames[0]['z'] < min_takeoff_height) and check_takeoff: - start_action = 'arm' - # If the first point is low, then detect moment to arm, - # delete all points, where copter is standing, and count time_delta - for i in range(len(corrected_frames)-1): - if corrected_frames[i-frames_to_start+1]['z'] - corrected_frames[i-frames_to_start]['z'] > move_delta: + ''' + Split animation into 5 parts: static_begin, takeoff, route, land, static_end + * static_begin and static_end are chains of frames in the beginning and the end of animation, + where the drone doesn't move + * takeoff and land are chains of frames after and before static frames of animation, + where the drone doesn't move in xy plane, and it's z coordinate only increases or decreases, respectively. + * route is the rest of the animation + Count static_begin_time and takeoff_time + ''' + def split_animation(self, move_delta=0.01): + self.static_begin_frames = [] + self.takeoff_frames = [] + self.route_frames = [] + self.land_frames = [] + self.static_end_frames = [] + self.static_begin_time = 0 + self.takeoff_time = 0 + self.route_time = 0 + self.land_time = 0 + if len(self.original_frames) == 0: + return + frames = copy.deepcopy(self.original_frames) + i = 0 # Moving index from the beginning + # Select static begin frames + while i < len(frames) - 1: + self.static_begin_time += frames[i].delay + if moving(frames[i], frames[i+1], move_delta): break - time_to_start += corrected_frames[i-frames_to_start]['delay'] - del corrected_frames[i-frames_to_start] - frames_to_start += 1 - start_delay = time_to_start - # Check Land - # If copter lands in animation, landing points can be deleted - if (corrected_frames[len(corrected_frames)-1]['z'] < min_takeoff_height) and check_land: - for i in range(len(corrected_frames)-1,0,-1): - # print i - if abs(corrected_frames[i-1]['z'] - corrected_frames[i]['z']) < move_delta: + i += 1 + if i > 0: + self.static_begin_frames = frames[:i+1] + frames = frames[i+1:] + i = 0 + else: + self.static_begin_time = 0 + # Select takeoff frames + while i < len(frames) - 1: + self.takeoff_time += frames[i].delay + if moving(frames[i], frames[i+1], move_delta, z = False) or (frames[i+1].z - frames[i].z <= 0): break - del corrected_frames[i] - for i in range(len(corrected_frames)-1,0,-1): - if (abs(corrected_frames[i-1]['x'] - corrected_frames[i]['x']) > move_delta or - abs(corrected_frames[i-1]['y'] - corrected_frames[i]['y']) > move_delta): + i += 1 + if i > 0: + self.takeoff_frames = frames[:i+1] + frames = frames[i+1:] + else: + self.takeoff_time = 0 + i = len(frames) - 1 # Moving index from the end + # Select static end frames + while i >= 0: + self.static_end_time += frames[i].delay + if moving(frames[i], frames[i-1], move_delta): break - del corrected_frames[i] - return corrected_frames, start_action, start_delay + i -= 1 + if i < len(frames) - 1: + self.static_end_frames = frames[i+1:] + frames = frames[:i+1] + i = len(frames) - 1 + else: + self.static_end_time = 0 + # Select land frames + while i >= 0: + self.land_time += frames[i].delay + if moving(frames[i], frames[i-1], move_delta, z = False) or (frames[i-1].z - frames[i].z <= 0): + break + i -= 1 + if i < len(frames) - 1: + self.land_frames = frames[i+1:] + frames = frames[:i+1] + else: + self.land_time = 0 + # Get route frames + self.route_frames = frames + for frame in self.route_frames: + self.route_time += frame.delay -# Needs for test -def save_corrected_animation(frames, filename="corrected_animation.csv"): - corrected_animation = open(filename, mode='w+') - csv_writer = csv.writer(corrected_animation, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) - for frame in frames: - csv_writer.writerow([frame['number'],frame['x'], frame['y'], frame['z'], frame['delay']]) - # print frame - corrected_animation.close() + def make_output_frames(self, static_begin, takeoff, route, land, static_end): + self.output_frames = [] + self.output_frames_min_z = None + self.start_delay = 0. + if not self.original_frames: + return + if not static_begin and not takeoff and not route and not land and not static_end: + return + if static_begin: + self.output_frames += self.static_begin_frames + if not static_begin: + self.start_delay += self.static_begin_time + if takeoff: + self.output_frames += self.takeoff_frames + if not static_begin and not takeoff: + self.start_delay += self.takeoff_time + if route: + self.output_frames += self.route_frames + if not static_begin and not takeoff and not route: + self.start_delay += self.route_time + if land: + self.output_frames += self.land_frames + if not static_begin and not takeoff and not route and not land: + self.start_delay += self.land_time + if static_end: + self.output_frames += self.static_end_frames + if self.output_frames: + self.output_frames_min_z = min(self.output_frames, key = lambda p: p.z).z -def convert_frame(frame): - return ((frame['x'], frame['y'], frame['z']), (frame['red'], frame['green'], frame['blue']), frame['yaw']) + def update_frames(self, config, filepath): + self.reset(filepath) + self.load(filepath, config) + self.make_output_frames(config.animation_output_static_begin, + config.animation_output_takeoff, + config.animation_output_route, + config.animation_output_land, + config.animation_output_static_end) + + def get_scaled_output(self, ratio=(1,1,1), offset=(0,0,0)): + x0, y0, z0 = offset + x_ratio, y_ratio, z_ratio = ratio + scaled_frames = copy.deepcopy(self.output_frames) + if scaled_frames: + for frame in scaled_frames: + frame.x = x_ratio*frame.x + x0 + frame.y = y_ratio*frame.y + y0 + frame.z = z_ratio*frame.z + z0 + return scaled_frames + + def get_scaled_output_min_z(self, ratio=(1,1,1), offset=(0,0,0)): + if self.output_frames_min_z is None: + return None + z0 = offset[2] + z_ratio = ratio[2] + return self.output_frames_min_z*z_ratio + z0 + + def get_start_point(self, ratio=(1,1,1), offset=(0,0,0)): + if not self.output_frames: + return [] + x0, y0, z0 = offset + x_ratio, y_ratio, z_ratio = ratio + first_frame = self.output_frames[0] + x = x_ratio*first_frame.x + x0 + y = y_ratio*first_frame.y + y0 + z = z_ratio*first_frame.z + z0 + return [x, y, z] + + def get_start_yaw(self): + if not self.output_frames: + return float('nan') + return math.degrees(self.output_frames[0].yaw) + + def check_ground(self, ground_level=0, ratio=(1,1,1), offset=(0,0,0)): + return ground_level <= self.get_scaled_output_min_z(ratio, offset) + + def get_start_action(self, start_action, current_height, takeoff_level, + ground_level, ratio=(1,1,1), offset=(0,0,0)): + # Check output frames + if not self.output_frames: + return 'error: empty output frames' + if math.isnan(current_height): + return 'error: bad current_height' + # Check that bottom point of animation is higher than ground level + if ground_level > self.get_scaled_output_min_z(ratio, offset): + return 'error: some animation points are lower than ground level' + # Select start action + if start_action == 'auto': + if self.get_start_point(ratio, offset)[2] - current_height > takeoff_level: + return 'takeoff' + else: + return 'fly' + elif start_action in ('takeoff', 'fly'): + return start_action + else: + return 'error' + + # Need for tests + def save_corrected_animation(self): + name, ext = os.path.splitext(self.filepath) + filepath = name + '_corrected' + ext + with open(filepath, mode='w+') as corrected_animation: + csv_writer = csv.writer(corrected_animation, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) + for frame in self.corrected_frames: + csv_writer.writerow([frame.number, frame.x, frame.y, frame.z, frame.red, frame.green, frame.blue, frame.delay]) try: - def execute_frame(point=(), color=(), yaw=float('Nan'), frame_id='aruco_map', use_leds=True, + def execute_frame(frame, frame_id='aruco_map', use_leds=True, flight_func=FlightLib.navto, auto_arm=False, flight_kwargs=None, interrupter=interrupt_event): if flight_kwargs is None: flight_kwargs = {} - - flight_func(*point, yaw=yaw, frame_id=frame_id, auto_arm=auto_arm, interrupter=interrupt_event, **flight_kwargs) + if frame.pose_is_valid(): + flight_func(x=frame.x, y=frame.y, z=frame.z, yaw=frame.yaw, frame_id=frame_id, auto_arm=auto_arm, interrupter=interrupt_event, **flight_kwargs) + else: + logger.debug("Frame pose is not valid for flying") if use_leds: - if color: + if frame.get_color: LedLib.fill(*color) - - - - def execute_animation(frames, frame_delay, frame_id='aruco_map', use_leds=True, flight_func=FlightLib.navto, - interrupter=interrupt_event): - next_frame_time = 0 - for frame in frames: - if interrupter.is_set(): - logger.warning("Animation playing function interrupted!") - interrupter.clear() - return - execute_frame(*convert_frame(frame), frame_id=frame_id, use_leds=use_leds, flight_func=flight_func, - interrupter=interrupter) - - next_frame_time += frame_delay - tasking.wait(next_frame_time, interrupter) - - def takeoff(z=1.5, safe_takeoff=True, frame_id='map', timeout=5.0, use_leds=True, interrupter=interrupt_event): if use_leds: diff --git a/Drone/config/spec/configspec_client.ini b/Drone/config/spec/configspec_client.ini index a6d56d5..1574baa 100644 --- a/Drone/config/spec/configspec_client.ini +++ b/Drone/config/spec/configspec_client.ini @@ -39,11 +39,11 @@ decrease_thrust_after = float(default=5.0, min=0) [COPTER] frame_id = string(default=map) +arming_time = float(default=0.5) takeoff_height = float(default=1.0) takeoff_time = float(default=5.0, min=0) -safe_takeoff = boolean(default=False) reach_first_point_time = float(default=5.0, min=0) -land_time = float(default=1.0, min=0) +land_delay = float(default=1.0, min=0) land_timeout = float(default=10.0, min=0) # __list__ x y z common_offset = float_list(default=list(0, 0, 0), min=3, max=3) @@ -58,19 +58,33 @@ translation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3) rotation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3) [GPS FRAME] -lat = float(default=0) -lon = float(default=0) +lat = string(default=0) +lon = string(default=0) yaw = float(default=0) [ANIMATION] -takeoff_detection = boolean(default=True) -land_detection = boolean(default=True) +# Available options: +# 'auto' - automatic action selection from 'takeoff' or 'fly' based on current copter level +# 'takeoff' - takeoff to first output animation point after static_begin_time then execute 'takeoff logic' +# 'fly' - execute animation frames after static_begin_time +start_action = string(default=auto) +takeoff_level = float(default=0.5) +ground_level = float(default=0) frame_delay = float(default=0.1, min=0.01) # Animation ratio (x, y, z) # __list__ x y z ratio = float_list(default=list(1.0, 1.0, 1.0), min=3, max=3) -# Available options: 'animation', 'nan' or a number in degrees +# Available options: +# 'animation' - take yaw from animation +# 'nan' - don't change yaw during flight +# or a number in degrees yaw = string(default=180.0) +[[OUTPUT]] +static_begin = boolean(default=False) +takeoff = boolean(default=True) +route = boolean(default=True) +land = boolean(default=False) +static_end = boolean(default=False) [LED] use = boolean(default=False) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 4349565..5c87562 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -9,7 +9,10 @@ import numpy try: from clever import srv except ImportError: - from clover import srv + try: + from clover import srv + except ImportError: + print("Can't import clever or clover") import datetime import logging @@ -17,8 +20,17 @@ import threading import psutil import subprocess from collections import namedtuple +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler -from FlightLib import FlightLib +try: + from FlightLib import FlightLib +except ImportError: + print("Can't import FlightLib") +try: + from FlightLib import LedLib +except ImportError: + print("Can't import LedLib") import client @@ -33,7 +45,26 @@ from geometry_msgs.msg import Point, Quaternion, TransformStamped from tf.transformations import quaternion_from_euler, euler_from_quaternion, quaternion_multiply import tf2_ros -static_bloadcaster = tf2_ros.StaticTransformBroadcaster() +from geographiclib.geodesic import Geodesic + +Earth = Geodesic.WGS84 + +def dist(x, y): + return math.sqrt(x**2+y**2) + +def azi(x, y): + return 90 - math.atan2(y,x)*180/math.pi + +def get_xy(dist, azi): + return dist*math.sin(math.radians(azi)), dist*math.cos(math.radians(azi)) + +def valid(pos): + for coord in pos: + if math.isnan(coord): + return False + return True + +static_broadcaster = tf2_ros.StaticTransformBroadcaster() emergency = False @@ -83,7 +114,8 @@ class CopterClient(client.Client): def __init__(self, config_path="config/client.ini"): super(CopterClient, self).__init__(config_path) self.load_config() - self.frames = {} + self.telemetry = None + self.animation = animation.Animation(self.config, "animation.csv") def load_config(self): super(CopterClient, self).load_config() @@ -97,17 +129,22 @@ class CopterClient(client.Client): if self.config.led_use: from FlightLib import LedLib LedLib.init_led(self.config.led_pin) - task_manager_instance.start() # TODO move to self + task_manager_instance.start() + start_subscriber() + self.telemetry = Telemetry() + self.telemetry.start_loop() if self.config.copter_frame_id == "floor": self.start_floor_frame_broadcast() elif self.config.copter_frame_id == "gps": self.start_gps_frame_broadcast() - start_subscriber() - - telemetry.start_loop() - super(CopterClient, self).start() + client_thread = threading.Thread(target=super(CopterClient, self).start, name="Client thread") + client_thread.daemon = True + client_thread.start() + #super(CopterClient, self).start() def start_floor_frame_broadcast(self): + if self.config.floor_frame_parent == "gps": + self.start_gps_frame_broadcast() trans = TransformStamped() trans.transform.translation.x = self.config.floor_frame_translation[0] trans.transform.translation.y = self.config.floor_frame_translation[1] @@ -117,10 +154,49 @@ class CopterClient(client.Client): math.radians(self.config.floor_frame_rotation[2]))) trans.header.frame_id = self.config.floor_frame_parent trans.child_frame_id = self.config.copter_frame_id - static_bloadcaster.sendTransform(trans) + static_broadcaster.sendTransform(trans) def start_gps_frame_broadcast(self): - return + trans = TransformStamped() + trans.transform.translation.x = 0 + trans.transform.translation.y = 0 + trans.transform.translation.z = 0 + trans.transform.rotation = Quaternion(*quaternion_from_euler(0,0,0)) + trans.header.frame_id = "map" + trans.child_frame_id = "gps" + static_broadcaster.sendTransform(trans) + gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread") + gps_frame_thread.daemon = True + gps_frame_thread.start() + + def gps_frame_broadcast_loop(self): + rate = rospy.Rate(1) + while not rospy.is_shutdown(): + telem = FlightLib.get_telemetry_locked(frame_id = "map") + if telem is not None: + if math.isnan(telem.lat) or math.isnan(telem.lon) or math.isnan(telem.x) or math.isnan(telem.y): + logger.info("Can't get position from telemetry") + else: + lat = float(self.config.gps_frame_lat) + lon = float(self.config.gps_frame_lon) + geo_delta = Earth.Inverse(telem.lat, telem.lon, lat, lon) + #logger.info("dist: {} | azi: {}".format(geo_delta['s12'], geo_delta['azi1'])) + dx, dy = get_xy(geo_delta['s12'], geo_delta['azi1']) + gps_dx = telem.x + dx + gps_dy = telem.y + dy + #logger.info("GPS frame dx: {} | dy: {}".format(gps_dx, gps_dy)) + trans = TransformStamped() + trans.transform.translation.x = gps_dx + trans.transform.translation.y = gps_dy + trans.transform.translation.z = 0 + trans.transform.rotation = Quaternion(*quaternion_from_euler(0,0, + math.radians(self.config.gps_frame_yaw))) + trans.header.frame_id = "map" + trans.child_frame_id = "gps" + static_broadcaster.sendTransform(trans) + + rate.sleep() + def restart_service(name): os.system("systemctl restart {}".format(name)) @@ -260,13 +336,13 @@ def _execute(*args, **kwargs): def _response_id(*args, **kwargs): new_id = kwargs.get("new_id", None) if new_id is not None: - old_id = client.active_client.client_id + old_id = copter.client_id if new_id != old_id: - client.active_client.config.set('PRIVATE', 'id', new_id, write=True) - client.active_client.client_id = new_id + copter.config.set('PRIVATE', 'id', new_id, write=True) + copter.client_id = new_id if new_id != '/hostname': - if client.active_client.config.system_restart_after_rename: - hostname = client.active_client.client_id + if copter.config.system_restart_after_rename: + hostname = copter.client_id configure_hostname(hostname) configure_hosts(hostname) configure_bashrc(hostname) @@ -293,28 +369,14 @@ def _response_selfcheck(*args, **kwargs): @messaging.request_callback("telemetry") def _response_telemetry(*args, **kwargs): - telemetry.update() - return telemetry.create_msg_contents() + copter.telemetry.update() + return copter.telemetry.create_msg_contents() @messaging.request_callback("anim_id") def _response_animation_id(*args, **kwargs): # Load animation - result = animation.get_id() - if result != 'No animation': - logger.debug("Saving corrected animation") - offset = numpy.array(client.active_client.config.private_offset) + numpy.array(client.active_client.config.copter_common_offset) - frames = animation.load_animation(os.path.abspath("animation.csv"), client.active_client.config.animation_frame_delay, - offset[0], offset[1], offset[2], *client.active_client.config.animation_ratio) - # Correct start and land frames in animation - corrected_frames, start_action, start_delay = animation.correct_animation(frames, - check_takeoff=client.active_client.config.animation_takeoff_detection, - check_land=client.active_client.config.animation_land_detection, - ) - logger.debug("Start action: {}".format(start_action)) - # Save corrected animation - animation.save_corrected_animation(corrected_frames) - return result + return copter.animation.id @messaging.request_callback("batt_voltage") @@ -351,9 +413,9 @@ def _response_cal_status(*args, **kwargs): @messaging.request_callback("position") def _response_position(*args, **kwargs): - telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) + telem = copter.telemetry.ros_telemetry return "{:.2f} {:.2f} {:.2f} {:.1f} {}".format( - telem.x, telem.y, telem.z, math.degrees(telem.yaw), client.active_client.config.copter_frame_id) + telem.x, telem.y, telem.z, math.degrees(telem.yaw), copter.config.copter_frame_id) @messaging.request_callback("calibrate_gyro") @@ -382,60 +444,61 @@ def _command_test(*args, **kwargs): print("stdout test") +@messaging.message_callback("update_animation") +def _command_update_animation(*args, **kwargs): + copter.animation.update_frames(copter.config, "animation.csv") + + @messaging.message_callback("move_start") def _command_move_start_to_current_position(*args, **kwargs): - x_start, y_start = animation.get_start_xy(os.path.abspath("animation.csv"), - *client.active_client.config.animation_ratio) - logger.debug("x_start = {}, y_start = {}".format(x_start, y_start)) - if not math.isnan(x_start): - telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) - logger.debug("x_telem = {}, y_telem = {}".format(telem.x, telem.y)) - if not math.isnan(telem.x): - client.active_client.config.set('PRIVATE', 'offset', - [telem.x - x_start, telem.y - y_start, client.active_client.config.private_offset[2]], - write=True) - logger.info("Set start delta: {:.2f} {:.2f}".format(client.active_client.config.private_offset[0], - client.active_client.config.private_offset[1])) - else: - logger.debug("Wrong telemetry") + offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) + try: + xs, ys, zs = copter.animation.get_start_point(copter.config.ratio, offset) + except ValueError: + logger.error("Can't get start point. Check animation file!") else: - logger.debug("Wrong animation file") + logger.debug("start x = {}, y = {}".format(xs, ys)) + telem = copter.telemetry.ros_telemetry + logger.debug("telemetry x = {}, y = {}".format(telem.x, telem.y)) + if valid([telem.x, telem.y, telem.z]): + copter.config.set('PRIVATE', 'offset', + [telem.x - xs, telem.y - ys, copter.config.private_offset[2]], write=True) + logger.info("Set start delta: {:.2f} {:.2f}".format(copter.config.private_offset[0], + copter.config.private_offset[1])) + else: + logger.error("Wrong telemetry") @messaging.message_callback("reset_start") def _command_reset_start(*args, **kwargs): - client.active_client.config.set('PRIVATE', 'offset', - [0, 0, client.active_client.config.private_offset[2]], - write=True) - logger.info("Reset start to {:.2f} {:.2f}".format(client.active_client.config.private_offset[0], - client.active_client.config.private_offset[1])) + copter.config.set('PRIVATE', 'offset', + [0, 0, copter.config.private_offset[2]], write=True) + logger.info("Reset start to {:.2f} {:.2f}".format(copter.config.private_offset[0], + copter.config.private_offset[1])) @messaging.message_callback("set_z_to_ground") def _command_set_z(*args, **kwargs): - telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) - client.active_client.config.set('PRIVATE', 'offset', - [client.active_client.config.private_offset[0], client.active_client.config.private_offset[1], telem.z], - write=True) - logger.info("Set z offset to {:.2f}".format(client.active_client.config.private_offset[2])) + telem = copter.telemetry.ros_telemetry + if valid([telem.x, telem.y, telem.z]): + copter.config.set('PRIVATE', 'offset', + [copter.config.private_offset[0], copter.config.private_offset[1], telem.z], write=True) + logger.info("Set z offset to {:.2f}".format(copter.config.private_offset[2])) + else: + logger.error("Wrong telemetry") @messaging.message_callback("reset_z_offset") def _command_reset_z(*args, **kwargs): - client.active_client.config.set('PRIVATE', 'offset', - [client.active_client.config.private_offset[0], client.active_client.config.private_offset[1], 0], - write=True) - logger.info("Reset z offset to {:.2f}".format(client.active_client.config.private_offset[2])) + copter.config.set('PRIVATE', 'offset', + [copter.config.private_offset[0], copter.config.private_offset[1], 0], write=True) + logger.info("Reset z offset to {:.2f}".format(copter.config.private_offset[2])) @messaging.message_callback("update_repo") def _command_update_repo(*args, **kwargs): - os.system("mv /home/pi/clever-show/Drone/client_config.ini /home/pi/clever-show/Drone/client_config_tmp.ini") - os.system("git reset --hard HEAD") - os.system("git checkout master") os.system("git fetch") os.system("git pull --rebase") - os.system("mv /home/pi/clever-show/Drone/client_config_tmp.ini /home/pi/clever-show/Drone/client_config.ini") os.system("chown -R pi:pi /home/pi/clever-show") @@ -453,12 +516,16 @@ def _command_reboot(*args, **kwargs): @messaging.message_callback("service_restart") def _command_service_restart(*args, **kwargs): service = kwargs["name"] + if service=="clover": + restart_service("clever") + if service=="clever-show": + restart_service("clever-show@{}".format(copter.client_id)) restart_service(service) @messaging.message_callback("repair_chrony") def _command_chrony_repair(*args, **kwargs): - repair_chrony(client.active_client.config.server_host) + repair_chrony(copter.config.server_host) @messaging.message_callback("led_test") @@ -473,13 +540,12 @@ def _command_led_fill(*args, **kwargs): r = kwargs.get("red", 0) g = kwargs.get("green", 0) b = kwargs.get("blue", 0) - LedLib.fill(r, g, b) @messaging.message_callback("flip") def _copter_flip(*args, **kwargs): - FlightLib.flip(frame_id=client.active_client.config.copter_frame_id) + FlightLib.flip(frame_id=copter.config.copter_frame_id) @messaging.message_callback("takeoff") @@ -487,30 +553,36 @@ def _command_takeoff(*args, **kwargs): logger.info("Takeoff at {}".format(datetime.datetime.now())) task_manager.add_task(0, 0, animation.takeoff, task_kwargs={ - "z": client.active_client.config.copter_takeoff_height, - "timeout": client.active_client.config.copter_takeoff_time, - "safe_takeoff": client.active_client.config.copter_safe_takeoff, - "use_leds": client.active_client.config.led_use & client.active_client.config.led_takeoff_indication, - } - ) + "z": copter.config.copter_takeoff_height, + "timeout": copter.config.copter_takeoff_time, + "safe_takeoff": False, + "use_leds": copter.config.led_use & copter.config.led_takeoff_indication, + }) @messaging.message_callback("takeoff_z") def _command_takeoff_z(*args, **kwargs): - z_str = kwargs.get("z", None) - if z_str is not None: - telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) - logger.info("Takeoff to z = {} at {}".format(z_str, datetime.datetime.now())) - task_manager.add_task(0, 0, FlightLib.reach_point, - task_kwargs={ - "x": telem.x, - "y": telem.y, - "z": float(z_str), - "frame_id": client.active_client.config.copter_frame_id, - "timeout": client.active_client.config.copter_takeoff_time, - "auto_arm": True, - } - ) + try: + z = float(kwargs.get("z", None)) + except TypeError: + logger.error("takeoff_z: No z argument!") + except ValueError: + logger.error("takeoff_z: Wrong z argument!") + else: + telem = FlightLib.get_telemetry_locked(copter.config.copter_frame_id) + if valid([telem.x, telem.y, telem.z]): + logger.info("Takeoff to z = {} at {}".format(z, datetime.datetime.now())) + task_manager.add_task(0, 0, FlightLib.reach_point, + task_kwargs={ + "x": telem.x, + "y": telem.y, + "z": z, + "frame_id": copter.config.copter_frame_id, + "timeout": copter.config.copter_takeoff_time, + "auto_arm": True, + }) + else: + logger.error("Wrong telemetry!") @messaging.message_callback("land") @@ -518,12 +590,11 @@ def _command_land(*args, **kwargs): task_manager.reset() task_manager.add_task(0, 0, animation.land, task_kwargs={ - "z": client.active_client.config.copter_takeoff_height, - "timeout": client.active_client.config.copter_takeoff_time, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use & client.active_client.config.led_land_indication, - } - ) + "z": copter.config.copter_takeoff_height, + "timeout": copter.config.copter_land_timeout, + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use & copter.config.led_land_indication, + }) @messaging.message_callback("emergency_land") @@ -537,8 +608,7 @@ def _command_disarm(*args, **kwargs): task_manager.add_task(-5, 0, FlightLib.arming_wrapper, task_kwargs={ "state": False - } - ) + }) @messaging.message_callback("stop") @@ -558,109 +628,107 @@ def _command_resume(*args, **kwargs): @messaging.message_callback("start") def _play_animation(*args, **kwargs): - start_time = float(kwargs["time"]) - # Check if animation file is available - if animation.get_id() == 'No animation': - logger.error("Can't start animation without animation file!") + + # Validate start_time + try: + start_time = float(kwargs["time"]) + except ValueError: + logger.error("start: Wrong time argument!") + return + except KeyError: + logger.error("start: No time argument!") return + # Check animation state + if copter.animation.state is not "OK": + logger.error("start: Bad animation state") + return + + # Get output frames + offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) + frames = copter.animation.get_scaled_output(copter.config.animation_ratio, offset) + if not frames: + logger.error("start: No frames in animation!") + return + + # Get current telemetry + telem = copter.telemetry.ros_telemetry + if not valid([telem.x, telem.y, telem.z]): + logger.error("start: Position is not valid!") + return + + # Get start action and delay + try: + start_action, start_delay = copter.telemetry.start_position[-2:] + except ValueError: + logger.error("start: Can't get animation start position and delay") + return + # Reset task manager task_manager.reset(interrupt_next_task=False) - logger.info("Start time = {}, wait for {} seconds".format(start_time, start_time - time.time())) - # Load animation - offset = numpy.array(client.active_client.config.private_offset) + numpy.array(client.active_client.config.copter_common_offset) - frames = animation.load_animation(os.path.abspath("animation.csv"), client.active_client.config.animation_frame_delay, - offset[0], offset[1], offset[2], *client.active_client.config.animation_ratio) - # Correct start and land frames in animation - corrected_frames, start_action, start_delay = animation.correct_animation(frames, - check_takeoff=client.active_client.config.animation_takeoff_detection, - check_land=client.active_client.config.animation_land_detection, - ) - # Choose start action + # Set animation logic if start_action == 'takeoff': - # Takeoff first - task_manager.add_task(start_time, 0, animation.takeoff, + # Takeoff first at start_time + start_delay_time + takeoff_time = start_time + start_delay + task_manager.add_task(takeoff_time, 0, animation.takeoff, task_kwargs={ - "z": client.active_client.config.copter_takeoff_height, - "timeout": client.active_client.config.copter_takeoff_time, - "safe_takeoff": client.active_client.config.copter_safe_takeoff, - # "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use & client.active_client.config.led_takeoff_indication, - } - ) + "z": copter.config.copter_takeoff_height, + "timeout": copter.config.copter_takeoff_time, + "safe_takeoff": False, + "use_leds": copter.config.led_use & copter.config.led_takeoff_indication, + }) # Fly to first point - rfp_time = start_time + client.active_client.config.copter_takeoff_time - if client.active_client.config.animation_yaw == "animation": - yaw = frame["yaw"] - else: - yaw = math.radians(float(client.active_client.config.animation_yaw)) + rfp_time = takeoff_time + copter.config.copter_takeoff_time task_manager.add_task(rfp_time, 0, animation.execute_frame, task_kwargs={ - "point": animation.convert_frame(corrected_frames[0])[0], - "color": animation.convert_frame(corrected_frames[0])[1], - "yaw": yaw, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "frame": frames[0], + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use, "flight_func": FlightLib.reach_point, - } - ) + }) # Calculate first frame start time - frame_time = rfp_time + client.active_client.config.copter_reach_first_point_time + frame_time = rfp_time + copter.config.copter_reach_first_point_time - elif start_action == 'arm': + elif start_action == 'fly': # Calculate start time - start_time += start_delay - frame_time = start_time # + 1.0 - point, color, yaw = animation.convert_frame(corrected_frames[0]) - task_manager.add_task(frame_time, 0, animation.execute_frame, + arm_time = start_time + start_delay # + 1.0 + task_manager.add_task(arm_time, 0, animation.execute_frame, task_kwargs={ - "point": point, - "color": color, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "frame": frames[0], + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use, "flight_func": FlightLib.navto, "auto_arm": True, - } - ) + }) # Calculate first frame start time - frame_time += corrected_frames[0]["delay"] # TODO Think about arming time - logger.debug(task_manager.task_queue) + frame_time = arm_time + copter.config.copter_arming_time + logger.debug("Start queue {}".format(task_manager.task_queue)) # Play animation file - for frame in corrected_frames: - point, color, yaw = animation.convert_frame(frame) - if client.active_client.config.animation_yaw == "animation": - yaw = frame["yaw"] - else: - yaw = math.radians(float(client.active_client.config.animation_yaw)) + for frame in frames: task_manager.add_task(frame_time, 0, animation.execute_frame, task_kwargs={ - "point": point, - "color": color, - "yaw": yaw, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "frame": frame, + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use, "flight_func": FlightLib.navto, - } - ) - frame_time += frame["delay"] - + }) + frame_time += frame.delay # Calculate land_time - land_time = frame_time + client.active_client.config.copter_land_time + land_time = frame_time + copter.config.copter_land_delay # Land task_manager.add_task(land_time, 0, animation.land, task_kwargs={ - "timeout": client.active_client.config.copter_land_timeout, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use & client.active_client.config.led_land_indication, - }, - ) + "timeout": copter.config.copter_land_timeout, + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use & copter.config.led_land_indication, + }) # noinspection PyAttributeOutsideInit class Telemetry: params_default_dict = { "git_version": None, - "animation_id": None, + "animation_info": None, "battery": None, "armed": False, "fcu_status": None, @@ -705,19 +773,25 @@ class Telemetry: @classmethod def get_config_version(cls): - return "{} V{}".format(client.active_client.config.config_name, client.active_client.config.config_version) + return "{} V{}".format(copter.config.config_name, copter.config.config_version) - @classmethod - def get_start_position(cls): - x_start, y_start = animation.get_start_xy(os.path.abspath("animation.csv"), - *client.active_client.config.animation_ratio) - offset = numpy.array(client.active_client.config.private_offset) + numpy.array(client.active_client.config.copter_common_offset) - x = x_start + offset[0] - y = y_start + offset[1] - z = offset[2] - if not FlightLib._check_nans(x, y, z): - return x, y, z - return 'NO_POS' + def get_start_position(self): + offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) + try: + x, y, z = copter.animation.get_start_point(copter.config.animation_ratio, offset) + except ValueError: + return [float('nan'),float('nan'),float('nan'),float('nan'),'error: no start pos in animation',float('nan')] + else: + start_delay = copter.animation.start_delay + yaw = copter.animation.get_start_yaw() + if not self.ros_telemetry: + start_action = 'error: no telemetry data' + else: + start_action = copter.animation.get_start_action( + copter.config.animation_start_action, self.ros_telemetry.z, + copter.config.animation_takeoff_level, copter.config.animation_ground_level, + copter.config.animation_ratio, offset) + return [x,y,z,yaw,start_action,start_delay] @classmethod def get_battery(cls, ros_telemetry): @@ -751,16 +825,21 @@ class Telemetry: @classmethod def get_position(cls, ros_telemetry): - x, y, z = ros_telemetry.x, ros_telemetry.y, ros_telemetry.z + try: + x, y, z = ros_telemetry.x, ros_telemetry.y, ros_telemetry.z + except AttributeError: + return 'NO_POS' if not math.isnan(x): - return x, y, z, math.degrees(ros_telemetry.yaw), client.active_client.config.copter_frame_id + return x, y, z, math.degrees(ros_telemetry.yaw), copter.config.copter_frame_id return 'NO_POS' + def get_ros_telemetry(self): + return self.ros_telemetry + def update_telemetry_fast(self): - self.start_position = self.get_start_position() self.last_task = task_manager.get_current_task() try: - self.ros_telemetry = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) + self.ros_telemetry = FlightLib.get_telemetry_locked(copter.config.copter_frame_id) if self.ros_telemetry.connected: self.armed = self.ros_telemetry.armed self.mode = self.ros_telemetry.mode @@ -779,9 +858,10 @@ class Telemetry: self.round_telemetry() def update_telemetry_slow(self): - self.animation_id = animation.get_id() + self.animation_info = [copter.animation.id, copter.animation.state] self.git_version = self.get_git_version() self.config_version = self.get_config_version() + self.start_position = self.get_start_position() try: self.calibration_status = get_calibration_status() self.fcu_status = get_sys_status() @@ -848,7 +928,7 @@ class Telemetry: def transmit_message(self): # todo if connected try: - client.active_client.server_connection.send_message('telemetry', kwargs={'value': self.create_msg_contents()}) + copter.server_connection.send_message('telemetry', kwargs={'value': self.create_msg_contents()}) except AttributeError as e: logger.debug(e) @@ -878,7 +958,7 @@ class Telemetry: self.update_telemetry_fast() self.check_failsafe_and_interruption() - if client.active_client.config.telemetry_transmit and client.active_client.connected: + if copter.config.telemetry_transmit and copter.connected: self.transmit_message() rate.sleep() @@ -887,18 +967,20 @@ class Telemetry: rate = rospy.Rate(1) while not rospy.is_shutdown(): self.update_telemetry_slow() - if client.active_client.config.telemetry_log_resources: + if copter.config.telemetry_log_resources: self.log_cpu_and_memory() rate.sleep() def start_loop(self): - if client.active_client.config.telemetry_frequency > 0: + if copter.config.telemetry_frequency > 0: telemetry_thread = threading.Thread(target=self._update_loop, name="Telemetry getting thread", - args=(client.active_client.config.telemetry_frequency,)) # TODO MOVE? Daemon? + args=(copter.config.telemetry_frequency,)) + telemetry_thread.daemon = True + telemetry_thread.start() slow_telemetry_thread = threading.Thread(target=self._slow_update_loop, name="Slow telemetry getting thread") + slow_telemetry_thread.daemon = True slow_telemetry_thread.start() - telemetry_thread.start() else: logger.info("Telemetry loop is not created because of zero or negative telemetry frequency") @@ -914,9 +996,25 @@ def emergency_callback(data): emergency = data.data +class AnimationEventHandler(FileSystemEventHandler): + def on_any_event(self, event): + logger.info('{} is {}'.format(event.src_path, event.event_type)) + # logger.info(os.path.splitext(event.src_path)) + if (os.path.splitext(event.src_path)[-1] == '.csv' and event.event_type != "deleted") or event.src_path.split('/')[-1] == 'client.ini': + if os.path.exists("animation.csv"): + logger.info("Update frames from animation.csv") + copter.animation.update_frames(copter.config, "animation.csv") + + if __name__ == "__main__": - telemetry = Telemetry() - copter_client = CopterClient() + copter = CopterClient() task_manager = tasking.TaskManager() rospy.Subscriber('/emergency', Bool, emergency_callback) - copter_client.start(task_manager) + event_handler = AnimationEventHandler() + observer = Observer() + observer.schedule(event_handler, ".", recursive=True) + observer.daemon = True + observer.start() + copter.start(task_manager) + while not rospy.is_shutdown(): + rospy.sleep(0.1) diff --git a/Drone/requirements.txt b/Drone/requirements.txt index f46bac3..260a5f8 100644 --- a/Drone/requirements.txt +++ b/Drone/requirements.txt @@ -1,3 +1,4 @@ selectors2 psutil configobj +watchdog diff --git a/Server/config/spec/configspec_server.ini b/Server/config/spec/configspec_server.ini index 7242d1d..6a191c8 100644 --- a/Server/config/spec/configspec_server.ini +++ b/Server/config/spec/configspec_server.ini @@ -17,17 +17,17 @@ config_version = float(default='1.0') [[[DEFAULT]]] copter_id = preset_param(default=list(True, 100)) git_version = preset_param(default=list(True, 75)) - config_version = preset_param(default=list(True, 140)) - animation_id = preset_param(default=list(True, 100)) + config_version = preset_param(default=list(True, 105)) + animation_info = preset_param(default=list(True, 100)) battery = preset_param(default=list(True, 100)) fcu_status = preset_param(default=list(True, 100)) calibration_status = preset_param(default=list(True, 65)) mode = preset_param(default=list(True, 100)) selfcheck = preset_param(default=list(True, 65)) current_position = preset_param(default=list(True, 250)) - start_position = preset_param(default=list(True, 150)) - last_task = preset_param(default=list(True, 250)) - time_delta = preset_param(default=list(True, 100)) + start_position = preset_param(default=list(True, 240)) + last_task = preset_param(default=list(True, 275)) + time_delta = preset_param(default=list(True, 70)) [[[__many__]]] __many__ = preset_param diff --git a/Server/copter_table.py b/Server/copter_table.py index 2a1df89..f216432 100644 --- a/Server/copter_table.py +++ b/Server/copter_table.py @@ -226,8 +226,8 @@ class CopterTableWidget(QTableView): @pyqtSlot(QtCore.QPoint) def open_menu(self, point): menu = QMenu(self) - index = self.indexAt(point) - item = self.model.get_row_data(index) + id = self.indexAt(point).siblingAtColumn(0).data() + item = self.model.get_row_by_attr('copter_id', id) edit_config = QAction("Edit config") edit_config.triggered.connect(partial(self.edit_copter_config, item)) diff --git a/Server/copter_table_models.py b/Server/copter_table_models.py index 5af3894..aa4917a 100644 --- a/Server/copter_table_models.py +++ b/Server/copter_table_models.py @@ -81,16 +81,19 @@ class ModelChecks: def check_ver(item): if not ModelChecks.check_git: return True - + version = get_git_version() if version is not None: return version == item return True -@ModelChecks.column_check("animation_id") +@ModelChecks.column_check("animation_info") def check_anim(item): - return str(item) != 'No animation' + if item: + return str(item[1]) == 'OK' + else: + return False @ModelChecks.column_check("battery") @@ -142,6 +145,10 @@ def check_time_delta(item): @ModelChecks.column_check("start_position", pass_context=True) def check_start_pos(item, context): + if len(item) == 6: + if not item[4] in ["takeoff", "fly"]: + return False + if ModelChecks.start_pos_delta_max == 0: return True @@ -150,6 +157,7 @@ def check_start_pos(item, context): delta = get_distance(get_position(context.current_position), get_position(context.start_position)) + if math.isnan(delta): return False @@ -157,7 +165,7 @@ def check_start_pos(item, context): def get_position(position): - if position != 'NO_POS' and position[0] != 'nan': # float('nan')? + if not isinstance(position, str) and position[0] != float('nan'): return position[:3] return [float('nan')] * 3 @@ -276,6 +284,17 @@ def place_id(value): msgbox.exec_() return None +@ModelFormatter.view_formatter("animation_info") +def view_animation_info(value): + try: + id, state = value + except ValueError: + return "" + else: + if state == 'OK': + return id + else: + return state @ModelFormatter.place_formatter("battery") def place_battery(value): @@ -307,15 +326,18 @@ def view_selfcheck(value): def view_current_position(value): if isinstance(value, list): x, y, z, yaw, frame = value - return f"{x: .2f} {y: .2f} {z: .2f} {int(yaw): d} {frame}" + return f"{x: .2f} {y: .2f} {z: .2f} {yaw: .0f} {frame}" return value @ModelFormatter.view_formatter("start_position") def view_start_position(value): if isinstance(value, list): - x, y, z = value - return f"{x: .2f} {y: .2f} {z: .2f}" + x, y, z, yaw, action, delay = value + if action in ['fly', 'takeoff']: + return f"{x: .2f} {y: .2f} {z: .2f} {yaw: .0f} {action} {delay: .1f}" + else: + return f"{action}" return value @@ -340,14 +362,14 @@ class CopterDataModel(QtCore.QAbstractTableModel): columns_dict = {'copter_id': 'copter ID', 'git_version': 'version', 'config_version': 'configuration', - 'animation_id': ' animation ID ', + 'animation_info': 'animation ID', 'battery': ' battery ', 'fcu_status': 'FCU status', 'calibration_status': 'sensors', 'mode': ' mode ', 'selfcheck': ' checks ', 'current_position': 'current x y z yaw frame_id', - 'start_position': ' start x y z ', + 'start_position': 'start x y z yaw action delay', 'last_task': 'last task', 'time_delta': 'dt', } diff --git a/Server/server_gui.py b/Server/server_gui.py index 448c60a..a163801 100644 --- a/Server/server_gui.py +++ b/Server/server_gui.py @@ -13,7 +13,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(1360, 816) + MainWindow.resize(1360, 869) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setEnabled(True) self.centralwidget.setObjectName("centralwidget") @@ -44,30 +44,22 @@ class Ui_MainWindow(object): self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize) self.verticalLayout.setObjectName("verticalLayout") self.formLayout = QtWidgets.QFormLayout() - self.formLayout.setLabelAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) + self.formLayout.setLabelAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.formLayout.setFormAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) self.formLayout.setObjectName("formLayout") self.start_text = QtWidgets.QLabel(self.centralwidget) self.start_text.setLayoutDirection(QtCore.Qt.RightToLeft) - self.start_text.setAlignment(QtCore.Qt.AlignCenter) + self.start_text.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.start_text.setObjectName("start_text") self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.start_text) self.start_delay_spin = QtWidgets.QSpinBox(self.centralwidget) self.start_delay_spin.setObjectName("start_delay_spin") self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.start_delay_spin) - self.music_text = QtWidgets.QLabel(self.centralwidget) - self.music_text.setLayoutDirection(QtCore.Qt.RightToLeft) - self.music_text.setObjectName("music_text") - self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.music_text) self.music_delay_spin = QtWidgets.QDoubleSpinBox(self.centralwidget) self.music_delay_spin.setDecimals(1) self.music_delay_spin.setMaximum(1000.0) self.music_delay_spin.setObjectName("music_delay_spin") self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.music_delay_spin) - self.music_play_text = QtWidgets.QLabel(self.centralwidget) - self.music_play_text.setLayoutDirection(QtCore.Qt.RightToLeft) - self.music_play_text.setObjectName("music_play_text") - self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.music_play_text) self.music_checkbox = QtWidgets.QCheckBox(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -82,6 +74,14 @@ class Ui_MainWindow(object): self.music_checkbox.setChecked(False) self.music_checkbox.setObjectName("music_checkbox") self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.music_checkbox) + self.music_text = QtWidgets.QLabel(self.centralwidget) + self.music_text.setLayoutDirection(QtCore.Qt.RightToLeft) + self.music_text.setObjectName("music_text") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.music_text) + self.music_play_text = QtWidgets.QLabel(self.centralwidget) + self.music_play_text.setLayoutDirection(QtCore.Qt.RightToLeft) + self.music_play_text.setObjectName("music_play_text") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.music_play_text) self.verticalLayout.addLayout(self.formLayout) self.line = QtWidgets.QFrame(self.centralwidget) self.line.setFrameShape(QtWidgets.QFrame.HLine) @@ -212,7 +212,7 @@ class Ui_MainWindow(object): self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1360, 25)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1360, 22)) self.menubar.setObjectName("menubar") self.menuOptions = QtWidgets.QMenu(self.menubar) self.menuOptions.setObjectName("menuOptions") @@ -301,6 +301,8 @@ class Ui_MainWindow(object): self.action_configure_columns.setObjectName("action_configure_columns") self.actionSomething = QtWidgets.QAction(MainWindow) self.actionSomething.setObjectName("actionSomething") + self.action_send_animation = QtWidgets.QAction(MainWindow) + self.action_send_animation.setObjectName("action_send_animation") self.menuMusic_2.addAction(self.action_select_music_file) self.menuMusic_2.addAction(self.action_play_music) self.menuMusic_2.addAction(self.action_stop_music) @@ -319,10 +321,12 @@ class Ui_MainWindow(object): self.menuTable.addSeparator() self.menuTable.addAction(self.action_configure_columns) self.menuSend.addAction(self.action_send_animations) + self.menuSend.addAction(self.action_send_calibrations) + self.menuSend.addSeparator() + self.menuSend.addAction(self.action_send_aruco_map) + self.menuSend.addAction(self.action_send_animation) self.menuSend.addAction(self.action_send_configurations) self.menuSend.addAction(self.action_send_launch_file) - self.menuSend.addAction(self.action_send_aruco_map) - self.menuSend.addAction(self.action_send_calibrations) self.menuSend.addAction(self.action_send_fcu_parameters) self.menuSend.addSeparator() self.menuSend.addAction(self.action_send_any_file) @@ -357,8 +361,8 @@ class Ui_MainWindow(object): MainWindow.setWindowTitle(_translate("MainWindow", "Clever Drone Show")) self.start_text.setText(_translate("MainWindow", " Start after")) self.start_delay_spin.setSuffix(_translate("MainWindow", " s")) - self.music_text.setText(_translate("MainWindow", " Music after")) self.music_delay_spin.setSuffix(_translate("MainWindow", " s")) + self.music_text.setText(_translate("MainWindow", " Music after")) self.music_play_text.setText(_translate("MainWindow", " Play music")) self.check_button.setText(_translate("MainWindow", "Preflight check")) self.start_button.setText(_translate("MainWindow", "Start animation")) @@ -389,7 +393,7 @@ class Ui_MainWindow(object): self.action_send_aruco_map.setText(_translate("MainWindow", "Aruco map")) self.action_update_client_repo.setText(_translate("MainWindow", "Update clever-show git")) self.actionSend_launch_file_for_clever.setText(_translate("MainWindow", "Send launch file for clever")) - self.action_send_launch_file.setText(_translate("MainWindow", "Launch files")) + self.action_send_launch_file.setText(_translate("MainWindow", "Launch files folder")) self.action_restart_clever.setText(_translate("MainWindow", "clever")) self.action_restart_clever_show.setText(_translate("MainWindow", "clever-show")) self.action_select_all_rows.setText(_translate("MainWindow", "Select all drones")) @@ -410,7 +414,7 @@ class Ui_MainWindow(object): self.action_send_calibrations.setText(_translate("MainWindow", "Camera calibrations")) self.action_reboot_all.setText(_translate("MainWindow", "Reboot")) self.action_restart_chrony.setText(_translate("MainWindow", "chrony")) - self.action_send_fcu_parameters.setText(_translate("MainWindow", "FCU parameters")) + self.action_send_fcu_parameters.setText(_translate("MainWindow", "FCU parameters file")) self.action_toggle_select.setText(_translate("MainWindow", "Toggle select")) self.action_toggle_select.setShortcut(_translate("MainWindow", "Ctrl+A")) self.action_select_all.setText(_translate("MainWindow", "Select all")) @@ -424,3 +428,4 @@ class Ui_MainWindow(object): self.action_restart_server.setText(_translate("MainWindow", "Restart server")) self.action_configure_columns.setText(_translate("MainWindow", "Configure columns")) self.actionSomething.setText(_translate("MainWindow", "something")) + self.action_send_animation.setText(_translate("MainWindow", "Animation")) diff --git a/Server/server_gui.ui b/Server/server_gui.ui index e00e6f4..f6c70c1 100644 --- a/Server/server_gui.ui +++ b/Server/server_gui.ui @@ -7,7 +7,7 @@ 0 0 1360 - 816 + 869 @@ -71,7 +71,7 @@ - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Qt::AlignHCenter|Qt::AlignTop @@ -85,7 +85,7 @@ Start after - Qt::AlignCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -96,16 +96,6 @@ - - - - Qt::RightToLeft - - - Music after - - - @@ -119,16 +109,6 @@ - - - - Qt::RightToLeft - - - Play music - - - @@ -157,6 +137,26 @@ + + + + Qt::RightToLeft + + + Music after + + + + + + + Qt::RightToLeft + + + Play music + + + @@ -426,7 +426,7 @@ 0 0 1360 - 25 + 22 @@ -470,10 +470,12 @@ Send + + + + - - @@ -538,7 +540,7 @@ - Launch files + Launch files folder @@ -639,7 +641,7 @@ - FCU parameters + FCU parameters file @@ -707,6 +709,11 @@ something + + + Animation + + start_delay_spin diff --git a/Server/server_qt.py b/Server/server_qt.py index 2c315a8..bb0cb4a 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -112,10 +112,6 @@ class MainWindow(QtWidgets.QMainWindow): self.init_model() - # self.statusBar = QStatusBar() - # self.setStatusBar(self.statusBar) - # self.statusBar.showMessage("Hey", 2000) - self.register_callbacks() self.player = QtMultimedia.QMediaPlayer() @@ -161,6 +157,7 @@ class MainWindow(QtWidgets.QMainWindow): self.ui.action_send_animations.triggered.connect(self.send_animations) self.ui.action_send_calibrations.triggered.connect(self.send_calibrations) + self.ui.action_send_animation.triggered.connect(self.send_animation) self.ui.action_send_configurations.triggered.connect(self.send_config) self.ui.action_send_aruco_map.triggered.connect(self.send_aruco) self.ui.action_send_launch_file.triggered.connect(self.send_launch) @@ -171,7 +168,7 @@ class MainWindow(QtWidgets.QMainWindow): self.ui.action_retrive_any_file.triggered.connect(b_partial(self.request_any_file, client_path=None)) self.ui.action_restart_clever.triggered.connect( - b_partial(self.send_to_selected, "service_restart", command_kwargs={"name": "clever"})) + b_partial(self.send_to_selected, "service_restart", command_kwargs={"name": "clover"})) self.ui.action_restart_clever_show.triggered.connect(self.restart_clever_show) self.ui.action_restart_chrony.triggered.connect(self.restart_chrony) self.ui.action_reboot_all.triggered.connect(b_partial(self.send_to_selected, "reboot_all")) @@ -291,7 +288,7 @@ class MainWindow(QtWidgets.QMainWindow): try: col = self.model.columns.index(key) except ValueError: - logging.error(f"No column {key} present!") + logging.debug(f"No column {key} present!") else: row_data = self.model.get_row_by_attr("client", client) row_num = self.model.get_row_index(row_data) @@ -481,6 +478,10 @@ class MainWindow(QtWidgets.QMainWindow): self.send_directory_files("Select directory with animations", ('.csv', '.txt'), match_id=True, client_path="", client_filename="animation.csv") + @pyqtSlot() + def send_animation(self): + self.send_files("Select animation file", "Animation files (*.csv)", onefile=True, client_filename="animation.csv") + @pyqtSlot() def send_calibrations(self): self.send_directory_files("Select directory with calibrations", ('.yaml', ), match_id=True, @@ -646,7 +647,7 @@ if __name__ == "__main__": msgbox_handler.setLevel(logging.CRITICAL) logging.basicConfig( - level=logging.DEBUG, + level=logging.INFO, format="%(asctime)s [%(name)-7.7s] [%(threadName)-19.19s] [%(levelname)-7.7s] %(message)s", handlers=[ logging.FileHandler("server_logs/{}.log".format(now)), diff --git a/blender-addon/addon.py b/blender-addon/addon.py index 221e6cf..e0cb41f 100644 --- a/blender-addon/addon.py +++ b/blender-addon/addon.py @@ -8,13 +8,13 @@ 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), + "name": "clever-show animation (.csv)", + "author": "Artem Vasiunik & Arthur Golubtsov", + "version": (0, 5, 0), "blender": (2, 80, 0), #"api": 36079, - "location": "File > Export > CSV Drone Swarm Animation Exporter (.csv)", - "description": "Export > CSV Drone Swarm Animation Exporter (.csv)", + "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", @@ -23,20 +23,20 @@ bl_info = { class ExportCsv(Operator, ExportHelper): - bl_idname = "export_swarm_anim.folder" - bl_label = "Export Drone Swarm animation" + 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=True, + default=False, ) drones_name: bpy.props.StringProperty( name="Name identifier", description="Name identifier for all drone objects", - default="copter" + default="clever" ) show_warnings: bpy.props.BoolProperty( @@ -61,7 +61,7 @@ class ExportCsv(Operator, ExportHelper): filepath: StringProperty( name="File Path", - description="File path used for exporting CSV files", + description="File path used for exporting csv files", maxlen=1024, subtype='DIR_PATH', default="" @@ -96,11 +96,11 @@ class ExportCsv(Operator, ExportHelper): 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) @@ -135,9 +135,7 @@ class ExportCsv(Operator, ExportHelper): round(rot_z, 5), *rgb, ]) - - - + if speed_exeeded: self.report({'WARNING'}, "Drone '%s' speed limits exeeded" % drone_obj.name) if distance_exeeded: @@ -195,7 +193,7 @@ def calc_distance(start_point, end_point): def menu_func(self, context): self.layout.operator( ExportCsv.bl_idname, - text="CSV Drone Swarm Animation Exporter (.csv)" + text="clever-show animation (.csv)" ) diff --git a/blender-addon/examples/basic.blend b/blender-addon/examples/basic.blend new file mode 100644 index 0000000..39d58b0 Binary files /dev/null and b/blender-addon/examples/basic.blend differ diff --git a/blender-addon/examples/basic/clever-1.csv b/blender-addon/examples/basic/clever-1.csv new file mode 100644 index 0000000..e7e37cb --- /dev/null +++ b/blender-addon/examples/basic/clever-1.csv @@ -0,0 +1,51 @@ +basic +1,0.0,0.0,0.0,0.0,204,2,0 +2,0.0,0.0,0.0,0.0,204,9,1 +3,0.0,0.0,0.0,0.0,204,21,2 +4,0.0,0.0,0.0,0.0,204,37,3 +5,0.0,0.0,0.0,0.0,204,56,4 +6,0.0,0.0,0.0,0.0,204,77,5 +7,0.0,0.0,0.0,0.0,204,97,6 +8,0.0,0.0,0.0,0.0,204,116,6 +9,0.0,0.0,0.0,0.0,204,131,7 +10,0.0,0.0,0.0,0.0,204,143,7 +11,0.0,0.0,0.1,0.0,199,153,7 +12,0.0,0.0,0.2,0.0,185,163,6 +13,0.0,0.0,0.3,0.0,163,172,6 +14,0.0,0.0,0.4,0.0,134,181,5 +15,0.0,0.0,0.5,0.0,102,188,5 +16,0.0,0.0,0.6,0.0,69,194,4 +17,0.0,0.0,0.7,0.0,40,199,3 +18,0.0,0.0,0.8,0.0,18,201,3 +19,0.0,0.0,0.9,0.0,4,203,2 +20,0.0,0.0,1.0,0.0,0,204,2 +21,0.02217,0.0,1.0,0.0,0,204,2 +22,0.08889,0.0,1.0,0.0,0,204,2 +23,0.19737,0.0,1.0,0.0,0,204,2 +24,0.33926,0.0,1.0,0.0,0,204,2 +25,0.5,0.0,1.0,0.0,0,204,2 +26,0.66074,0.0,1.0,0.0,0,204,2 +27,0.80263,0.0,1.0,0.0,0,204,2 +28,0.91111,0.0,1.0,0.0,0,204,2 +29,0.97783,0.0,1.0,0.0,0,204,2 +30,1.0,0.0,1.0,0.0,0,204,2 +31,1.0,0.0,0.9,0.0,3,204,2 +32,1.0,0.0,0.8,0.0,14,204,2 +33,1.0,0.0,0.7,0.0,32,204,2 +34,1.0,0.0,0.6,0.0,56,204,1 +35,1.0,0.0,0.5,0.0,84,204,1 +36,1.0,0.0,0.4,0.0,112,204,0 +37,1.0,0.0,0.3,0.0,138,204,0 +38,1.0,0.0,0.2,0.0,159,204,0 +39,1.0,0.0,0.1,0.0,175,204,0 +40,1.0,0.0,0.0,0.0,183,204,0 +41,1.0,0.0,0.0,0.0,188,199,0 +42,1.0,0.0,0.0,0.0,192,185,0 +43,1.0,0.0,0.0,0.0,196,163,0 +44,1.0,0.0,0.0,0.0,199,135,0 +45,1.0,0.0,0.0,0.0,201,102,0 +46,1.0,0.0,0.0,0.0,202,69,0 +47,1.0,0.0,0.0,0.0,203,40,0 +48,1.0,0.0,0.0,0.0,203,18,0 +49,1.0,0.0,0.0,0.0,203,5,0 +50,1.0,0.0,0.0,0.0,204,0,0 diff --git a/blender-addon/examples/two_drones_test.blend b/blender-addon/examples/two_drones_test.blend deleted file mode 100644 index 7a8d5c9..0000000 Binary files a/blender-addon/examples/two_drones_test.blend and /dev/null differ diff --git a/tests/animation_1.csv b/tests/animation_1.csv new file mode 100644 index 0000000..e7e37cb --- /dev/null +++ b/tests/animation_1.csv @@ -0,0 +1,51 @@ +basic +1,0.0,0.0,0.0,0.0,204,2,0 +2,0.0,0.0,0.0,0.0,204,9,1 +3,0.0,0.0,0.0,0.0,204,21,2 +4,0.0,0.0,0.0,0.0,204,37,3 +5,0.0,0.0,0.0,0.0,204,56,4 +6,0.0,0.0,0.0,0.0,204,77,5 +7,0.0,0.0,0.0,0.0,204,97,6 +8,0.0,0.0,0.0,0.0,204,116,6 +9,0.0,0.0,0.0,0.0,204,131,7 +10,0.0,0.0,0.0,0.0,204,143,7 +11,0.0,0.0,0.1,0.0,199,153,7 +12,0.0,0.0,0.2,0.0,185,163,6 +13,0.0,0.0,0.3,0.0,163,172,6 +14,0.0,0.0,0.4,0.0,134,181,5 +15,0.0,0.0,0.5,0.0,102,188,5 +16,0.0,0.0,0.6,0.0,69,194,4 +17,0.0,0.0,0.7,0.0,40,199,3 +18,0.0,0.0,0.8,0.0,18,201,3 +19,0.0,0.0,0.9,0.0,4,203,2 +20,0.0,0.0,1.0,0.0,0,204,2 +21,0.02217,0.0,1.0,0.0,0,204,2 +22,0.08889,0.0,1.0,0.0,0,204,2 +23,0.19737,0.0,1.0,0.0,0,204,2 +24,0.33926,0.0,1.0,0.0,0,204,2 +25,0.5,0.0,1.0,0.0,0,204,2 +26,0.66074,0.0,1.0,0.0,0,204,2 +27,0.80263,0.0,1.0,0.0,0,204,2 +28,0.91111,0.0,1.0,0.0,0,204,2 +29,0.97783,0.0,1.0,0.0,0,204,2 +30,1.0,0.0,1.0,0.0,0,204,2 +31,1.0,0.0,0.9,0.0,3,204,2 +32,1.0,0.0,0.8,0.0,14,204,2 +33,1.0,0.0,0.7,0.0,32,204,2 +34,1.0,0.0,0.6,0.0,56,204,1 +35,1.0,0.0,0.5,0.0,84,204,1 +36,1.0,0.0,0.4,0.0,112,204,0 +37,1.0,0.0,0.3,0.0,138,204,0 +38,1.0,0.0,0.2,0.0,159,204,0 +39,1.0,0.0,0.1,0.0,175,204,0 +40,1.0,0.0,0.0,0.0,183,204,0 +41,1.0,0.0,0.0,0.0,188,199,0 +42,1.0,0.0,0.0,0.0,192,185,0 +43,1.0,0.0,0.0,0.0,196,163,0 +44,1.0,0.0,0.0,0.0,199,135,0 +45,1.0,0.0,0.0,0.0,201,102,0 +46,1.0,0.0,0.0,0.0,202,69,0 +47,1.0,0.0,0.0,0.0,203,40,0 +48,1.0,0.0,0.0,0.0,203,18,0 +49,1.0,0.0,0.0,0.0,203,5,0 +50,1.0,0.0,0.0,0.0,204,0,0 diff --git a/tests/animation_2.csv b/tests/animation_2.csv new file mode 100755 index 0000000..addc6f0 --- /dev/null +++ b/tests/animation_2.csv @@ -0,0 +1,1066 @@ +parad +0,-1.00519,2.65699,0.21,-2.76315,7,255,0 +1,-1.00519,2.65699,0.21,-2.76315,7,255,0 +2,-1.00519,2.65699,0.21,-2.76315,7,255,0 +3,-1.00519,2.65699,0.21,-2.76315,7,255,0 +4,-1.00519,2.65699,0.21,-2.76315,7,255,0 +5,-1.00519,2.65699,0.21,-2.76315,7,255,0 +6,-1.00519,2.65699,0.21,-2.76315,7,255,0 +7,-1.00519,2.65699,0.21,-2.76315,7,255,0 +8,-1.00519,2.65699,0.21,-2.76315,7,255,0 +9,-1.00519,2.65699,0.21,-2.76315,7,255,0 +10,-1.00519,2.65699,0.21,-2.76315,7,255,0 +11,-1.00519,2.65699,0.21,-2.76315,7,255,0 +12,-1.00519,2.65699,0.21,-2.76315,7,255,0 +13,-1.00519,2.65699,0.21,-2.76315,7,255,0 +14,-1.00519,2.65699,0.21,-2.76315,7,255,0 +15,-1.00519,2.65699,0.21,-2.76315,7,255,0 +16,-1.00519,2.65699,0.21,-2.76315,7,255,0 +17,-1.00519,2.65699,0.21,-2.76315,7,255,0 +18,-1.00519,2.65699,0.21,-2.76315,7,255,0 +19,-1.00519,2.65699,0.21,-2.76315,7,255,0 +20,-1.00519,2.65699,0.21,-2.76315,7,255,0 +21,-1.00519,2.65699,0.21,-2.76315,7,255,0 +22,-1.00519,2.65699,0.21,-2.76315,7,255,0 +23,-1.00519,2.65699,0.21,-2.76315,7,255,0 +24,-1.00519,2.65699,0.21,-2.76315,7,255,0 +25,-1.00519,2.65699,0.21,-2.76315,7,255,0 +26,-1.00519,2.65699,0.21,-2.76315,7,255,0 +27,-1.00519,2.65699,0.21,-2.76315,7,255,0 +28,-1.00519,2.65699,0.21,-2.76315,7,255,0 +29,-1.00519,2.65699,0.21,-2.76315,7,255,0 +30,-1.00519,2.65699,0.21,-2.76315,7,255,0 +31,-1.00519,2.65699,0.21,-2.76315,7,255,0 +32,-1.00519,2.65699,0.21,-2.76315,7,255,0 +33,-1.00519,2.65699,0.21,-2.76315,7,255,0 +34,-1.00519,2.65699,0.21,-2.76315,7,255,0 +35,-1.00519,2.65699,0.21,-2.76315,7,255,0 +36,-1.00519,2.65699,0.21,-2.76315,7,255,0 +37,-1.00519,2.65699,0.21,-2.76315,7,255,0 +38,-1.00519,2.65699,0.21,-2.76315,7,255,0 +39,-1.00519,2.65699,0.21,-2.76315,7,255,0 +40,-1.00519,2.65699,0.21,-2.76315,7,255,0 +41,-1.00519,2.65699,0.21,-2.76315,7,255,0 +42,-1.00519,2.65699,0.21,-2.76315,7,255,0 +43,-1.00519,2.65699,0.21,-2.76315,7,255,0 +44,-1.00519,2.65699,0.21,-2.76315,7,255,0 +45,-1.00519,2.65699,0.21,-2.76315,7,255,0 +46,-1.00519,2.65699,0.21,-2.76315,7,255,0 +47,-1.00519,2.65699,0.21,-2.76315,7,255,0 +48,-1.00519,2.65699,0.21,-2.76315,7,255,0 +49,-1.00519,2.65699,0.21,-2.76315,7,255,0 +50,-1.00519,2.65699,0.21,-2.76315,7,255,0 +51,-1.00519,2.65699,0.21,-2.76315,7,255,0 +52,-1.00519,2.65699,0.21,-2.76315,7,255,0 +53,-1.00519,2.65699,0.21,-2.76315,7,255,0 +54,-1.00519,2.65699,0.21,-2.76315,7,255,0 +55,-1.00519,2.65699,0.21,-2.76315,7,255,0 +56,-1.00519,2.65699,0.21,-2.76315,7,255,0 +57,-1.00519,2.65699,0.21,-2.76315,7,255,0 +58,-1.00519,2.65699,0.21,-2.76315,7,255,0 +59,-1.00519,2.65699,0.21,-2.76315,7,255,0 +60,-1.00519,2.65699,0.21,-2.76315,7,255,0 +61,-1.00519,2.65699,0.21,-2.76315,7,255,0 +62,-1.00519,2.65699,0.21,-2.76315,7,255,0 +63,-1.00519,2.65699,0.21,-2.76315,7,255,0 +64,-1.00519,2.65699,0.21,-2.76315,7,255,0 +65,-1.00519,2.65699,0.21,-2.76315,7,255,0 +66,-1.00519,2.65699,0.21,-2.76315,7,255,0 +67,-1.00519,2.65699,0.21,-2.76315,7,255,0 +68,-1.00519,2.65699,0.21,-2.76315,7,255,0 +69,-1.00519,2.65699,0.21,-2.76315,7,255,0 +70,-1.00519,2.65699,0.21,-2.76315,7,255,0 +71,-1.00519,2.65699,0.21,-2.76315,7,255,0 +72,-1.00519,2.65699,0.21,-2.76315,7,255,0 +73,-1.00519,2.65699,0.21,-2.76315,7,255,0 +74,-1.00519,2.65699,0.21,-2.76315,7,255,0 +75,-1.00519,2.65699,0.21,-2.76315,7,255,0 +76,-1.00519,2.65699,0.21,-2.76315,7,255,0 +77,-1.00519,2.65699,0.21,-2.76315,7,255,0 +78,-1.00519,2.65699,0.21,-2.76315,7,255,0 +79,-1.00519,2.65699,0.21,-2.76315,7,255,0 +80,-1.00519,2.65699,0.21,-2.76315,7,255,0 +81,-1.00519,2.65699,0.21,-2.76315,7,255,0 +82,-1.00519,2.65699,0.21,-2.76315,7,255,0 +83,-1.00519,2.65699,0.21,-2.76315,7,255,0 +84,-1.00519,2.65699,0.21,-2.76315,7,255,0 +85,-1.00519,2.65699,0.21,-2.76315,7,255,0 +86,-1.00519,2.65699,0.21,-2.76315,7,255,0 +87,-1.00519,2.65699,0.21,-2.76315,7,255,0 +88,-1.00519,2.65699,0.21,-2.76315,7,255,0 +89,-1.00519,2.65699,0.21,-2.76315,7,255,0 +90,-1.00519,2.65699,0.21,-2.76315,7,255,0 +91,-1.00519,2.65699,0.21,-2.76315,7,255,0 +92,-1.00519,2.65699,0.21,-2.76315,7,255,0 +93,-1.00519,2.65699,0.21,-2.76315,7,255,0 +94,-1.00519,2.65699,0.21,-2.76315,7,255,0 +95,-1.00519,2.65699,0.21,-2.76315,7,255,0 +96,-1.00519,2.65699,0.21,-2.76315,7,255,0 +97,-1.00519,2.65699,0.21,-2.76315,7,255,0 +98,-1.00519,2.65699,0.21,-2.76315,7,255,0 +99,-1.00519,2.65699,0.21,-2.76315,7,255,0 +100,-1.00519,2.65699,0.21,-2.76315,7,255,0 +101,-1.00519,2.65699,0.21,-2.76315,7,255,0 +102,-1.00519,2.65699,0.21,-2.76315,7,255,0 +103,-1.00519,2.65699,0.21,-2.76315,7,255,0 +104,-1.00519,2.65699,0.21,-2.76315,7,255,0 +105,-1.00519,2.65699,0.21,-2.76315,7,255,0 +106,-1.00519,2.65699,0.21,-2.76315,7,255,0 +107,-1.00519,2.65699,0.21,-2.76315,7,255,0 +108,-1.00519,2.65699,0.21,-2.76315,7,255,0 +109,-1.00519,2.65699,0.21,-2.76315,7,255,0 +110,-1.00519,2.65699,0.21,-2.76315,7,255,0 +111,-1.00519,2.65699,0.21,-2.76315,7,255,0 +112,-1.00519,2.65699,0.21,-2.76315,7,255,0 +113,-1.00519,2.65699,0.21,-2.76315,7,255,0 +114,-1.00519,2.65699,0.21,-2.76315,7,255,0 +115,-1.00519,2.65699,0.21,-2.76315,7,255,0 +116,-1.00519,2.65699,0.21,-2.76315,7,255,0 +117,-1.00519,2.65699,0.21,-2.76315,7,255,0 +118,-1.00519,2.65699,0.21,-2.76315,7,255,0 +119,-1.00519,2.65699,0.21,-2.76315,7,255,0 +120,-1.00519,2.65699,0.21,-2.76315,7,255,0 +121,-1.00519,2.65699,0.21,-2.76315,7,255,0 +122,-1.00519,2.65699,0.21,-2.76315,7,255,0 +123,-1.00519,2.65699,0.21,-2.76315,7,255,0 +124,-1.00519,2.65699,0.21,-2.76315,7,255,0 +125,-1.00519,2.65699,0.21,-2.76315,7,255,0 +126,-1.00519,2.65699,0.21,-2.76315,7,255,0 +127,-1.00519,2.65699,0.21,-2.76315,7,255,0 +128,-1.00519,2.65699,0.21,-2.76315,7,255,0 +129,-1.00519,2.65699,0.21,-2.76315,7,255,0 +130,-1.00519,2.65699,0.21,-2.76315,7,255,0 +131,-1.00519,2.65699,0.21,-2.76315,7,255,0 +132,-1.00519,2.65699,0.21,-2.76315,7,255,0 +133,-1.00519,2.65699,0.21,-2.76315,7,255,0 +134,-1.00519,2.65699,0.21,-2.76315,7,255,0 +135,-1.00519,2.65699,0.21,-2.76315,7,255,0 +136,-1.00519,2.65699,0.21,-2.76315,7,255,0 +137,-1.00519,2.65699,0.21,-2.76315,7,255,0 +138,-1.00519,2.65699,0.21,-2.76315,7,255,0 +139,-1.00519,2.65699,0.21,-2.76315,7,255,0 +140,-1.00519,2.65699,0.21,-2.76315,7,255,0 +141,-1.00519,2.65699,0.21,-2.76315,7,255,0 +142,-1.00519,2.65699,0.21,-2.76315,7,255,0 +143,-1.00519,2.65699,0.21,-2.76315,7,255,0 +144,-1.00519,2.65699,0.21,-2.76315,7,255,0 +145,-1.00519,2.65699,0.21,-2.76315,7,255,0 +146,-1.00519,2.65699,0.21,-2.76315,7,255,0 +147,-1.00519,2.65699,0.21,-2.76315,7,255,0 +148,-1.00519,2.65699,0.21,-2.76315,7,255,0 +149,-1.00519,2.65699,0.21,-2.76315,7,255,0 +150,-1.00519,2.65699,0.21,-2.76315,7,255,0 +151,-1.00519,2.65699,0.21,-2.76315,7,255,0 +152,-1.00519,2.65699,0.21,-2.76315,7,255,0 +153,-1.00519,2.65699,0.21,-2.76315,7,255,0 +154,-1.00519,2.65699,0.21,-2.76315,7,255,0 +155,-1.00519,2.65699,0.21,-2.76315,7,255,0 +156,-1.00519,2.65699,0.21,-2.76315,7,255,0 +157,-1.00519,2.65699,0.21,-2.76315,7,255,0 +158,-1.00519,2.65699,0.21,-2.76315,7,255,0 +159,-1.00519,2.65699,0.21,-2.76315,7,255,0 +160,-1.00519,2.65699,0.21,-2.76315,7,255,0 +161,-1.00519,2.65699,0.21,-2.76315,7,255,0 +162,-1.00519,2.65699,0.21,-2.76315,7,255,0 +163,-1.00519,2.65699,0.21,-2.76315,7,255,0 +164,-1.00519,2.65699,0.21,-2.76315,7,255,0 +165,-1.00519,2.65699,0.21,-2.76315,7,255,0 +166,-1.00519,2.65699,0.21,-2.76315,7,255,0 +167,-1.00519,2.65699,0.21,-2.76315,7,255,0 +168,-1.00519,2.65699,0.21,-2.76315,7,255,0 +169,-1.00519,2.65699,0.21,-2.76315,7,255,0 +170,-1.00519,2.65699,0.21,-2.76315,7,255,0 +171,-1.00519,2.65699,0.21,-2.76315,7,255,0 +172,-1.00519,2.65699,0.21,-2.76315,7,255,0 +173,-1.00519,2.65699,0.21,-2.76315,7,255,0 +174,-1.00519,2.65699,0.21,-2.76315,7,255,0 +175,-1.00519,2.65699,0.21,-2.76315,7,255,0 +176,-1.00519,2.65699,0.21,-2.76315,7,255,0 +177,-1.00519,2.65699,0.21,-2.76315,7,255,0 +178,-1.00519,2.65699,0.21,-2.76315,7,255,0 +179,-1.00519,2.65699,0.21,-2.76315,7,255,0 +180,-1.00519,2.65699,0.21,-2.76315,7,255,0 +181,-1.00519,2.65699,0.21,-2.76315,7,255,0 +182,-1.00519,2.65699,0.21,-2.76315,7,255,0 +183,-1.00519,2.65699,0.21,-2.76315,7,255,0 +184,-1.00519,2.65699,0.21,-2.76315,7,255,0 +185,-1.00519,2.65699,0.21,-2.76315,7,255,0 +186,-1.00519,2.65699,0.21,-2.76315,7,255,0 +187,-1.00519,2.65699,0.21,-2.76315,7,255,0 +188,-1.00519,2.65699,0.21,-2.76315,7,255,0 +189,-1.00519,2.65699,0.21,-2.76315,7,255,0 +190,-1.00519,2.65699,0.21,-2.76315,7,255,0 +191,-1.00519,2.65699,0.21,-2.76315,7,255,0 +192,-1.00519,2.65699,0.21,-2.76315,7,255,0 +193,-1.00519,2.65699,0.21,-2.76315,7,255,0 +194,-1.00519,2.65699,0.21,-2.76315,7,255,0 +195,-1.00519,2.65699,0.21,-2.76315,7,255,0 +196,-1.00519,2.65699,0.21,-2.76315,7,255,0 +197,-1.00519,2.65699,0.21,-2.76315,7,255,0 +198,-1.00519,2.65699,0.21,-2.76315,7,255,0 +199,-1.00519,2.65699,0.21,-2.76315,7,255,0 +200,-1.00519,2.65699,0.21,-2.76315,7,255,0 +201,-1.00519,2.65699,0.21,-2.76315,7,255,0 +202,-1.00519,2.65699,0.21,-2.76315,7,255,0 +203,-1.00519,2.65699,0.21,-2.76315,7,255,0 +204,-1.00519,2.65699,0.21,-2.76315,7,255,0 +205,-1.00519,2.65699,0.21,-2.76315,7,255,0 +206,-1.00519,2.65699,0.21,-2.76315,7,255,0 +207,-1.00519,2.65699,0.21,-2.76315,7,255,0 +208,-1.00519,2.65699,0.21,-2.76315,7,255,0 +209,-1.00519,2.65699,0.21,-2.76315,7,255,0 +210,-1.00519,2.65699,0.21,-2.76315,7,255,0 +211,-1.00519,2.65699,0.21,-2.76315,7,255,0 +212,-1.00519,2.65699,0.21,-2.76315,7,255,0 +213,-1.00519,2.65699,0.21,-2.76315,7,255,0 +214,-1.00519,2.65699,0.21,-2.76315,7,255,0 +215,-1.00519,2.65699,0.21,-2.76315,7,255,0 +216,-1.00519,2.65699,0.21,-2.76315,7,255,0 +217,-1.00519,2.65699,0.21,-2.76315,7,255,0 +218,-1.00519,2.65699,0.21,-2.76315,7,255,0 +219,-1.00519,2.65699,0.21,-2.76315,7,255,0 +220,-1.00519,2.65699,0.21,-2.76315,7,255,0 +221,-1.00519,2.65699,0.21,-2.76315,7,255,0 +222,-1.00519,2.65699,0.21,-2.76315,7,255,0 +223,-1.00519,2.65699,0.21,-2.76315,7,255,0 +224,-1.00519,2.65699,0.21,-2.76315,7,255,0 +225,-1.00519,2.65699,0.21,-2.76315,7,255,0 +226,-1.00519,2.65699,0.21,-2.76315,7,255,0 +227,-1.00519,2.65699,0.21,-2.76315,7,255,0 +228,-1.00519,2.65699,0.21,-2.76315,7,255,0 +229,-1.00519,2.65699,0.21,-2.76315,7,255,0 +230,-1.00519,2.65699,0.21,-2.76315,7,255,0 +231,-1.00519,2.65699,0.21,-2.76315,7,255,0 +232,-1.00519,2.65699,0.21,-2.76315,7,255,0 +233,-1.00519,2.65699,0.21,-2.76315,7,255,0 +234,-1.00519,2.65699,0.21,-2.76315,7,255,0 +235,-1.00519,2.65699,0.21,-2.76315,7,255,0 +236,-1.00519,2.65699,0.21,-2.76315,7,255,0 +237,-1.00519,2.65699,0.21,-2.76315,7,255,0 +238,-1.00519,2.65699,0.21,-2.76315,7,255,0 +239,-1.00519,2.65699,0.21,-2.76315,7,255,0 +240,-1.00519,2.65699,0.21,-2.76315,7,255,0 +241,-1.00519,2.65699,0.21,-2.76315,7,255,0 +242,-1.00519,2.65699,0.21,-2.76315,7,255,0 +243,-1.00519,2.65699,0.21,-2.76315,7,255,0 +244,-1.00519,2.65699,0.21,-2.76315,7,255,0 +245,-1.00519,2.65699,0.21,-2.76315,7,255,0 +246,-1.00519,2.65699,0.21,-2.76315,7,255,0 +247,-1.00519,2.65699,0.21,-2.76315,7,255,0 +248,-1.00519,2.65699,0.21,-2.76315,7,255,0 +249,-1.00519,2.65699,0.21,-2.76315,7,255,0 +250,-1.00519,2.65699,0.21,-2.76315,7,255,0 +251,-1.00519,2.65699,0.21,-2.76315,7,255,0 +252,-1.00519,2.65699,0.21,-2.76315,7,255,0 +253,-1.00519,2.65699,0.21,-2.76315,7,255,0 +254,-1.00519,2.65699,0.21,-2.76315,7,255,0 +255,-1.00519,2.65699,0.21,-2.76315,7,255,0 +256,-1.00519,2.65699,0.21,-2.76315,7,255,0 +257,-1.00519,2.65699,0.21,-2.76315,7,255,0 +258,-1.00519,2.65699,0.21,-2.76315,7,255,0 +259,-1.00519,2.65699,0.21,-2.76315,7,255,0 +260,-1.00519,2.65699,0.21,-2.76315,7,255,0 +261,-1.00519,2.65699,0.21,-2.76315,7,255,0 +262,-1.00519,2.65699,0.21,-2.76315,7,255,0 +263,-1.00519,2.65699,0.21,-2.76315,7,255,0 +264,-1.00519,2.65699,0.21,-2.76315,7,255,0 +265,-1.00519,2.65699,0.21,-2.76315,7,255,0 +266,-1.00519,2.65699,0.21,-2.76315,7,255,0 +267,-1.00519,2.65699,0.21,-2.76315,7,255,0 +268,-1.00519,2.65699,0.21,-2.76315,7,255,0 +269,-1.00519,2.65699,0.21,-2.76315,7,255,0 +270,-1.00519,2.65699,0.21,-2.76315,7,255,0 +271,-1.00519,2.65699,0.24386,-2.76315,7,255,0 +272,-1.00519,2.65699,0.30495,-2.76315,7,255,0 +273,-1.00519,2.65699,0.36315,-2.76315,7,255,0 +274,-1.00519,2.65699,0.41951,-2.76315,7,255,0 +275,-1.00519,2.65699,0.47568,-2.76315,7,255,0 +276,-1.00519,2.65699,0.53229,-2.76315,7,255,0 +277,-1.00519,2.65699,0.58898,-2.76315,7,255,0 +278,-1.00519,2.65699,0.64513,-2.76315,7,255,0 +279,-1.00519,2.65699,0.70179,-2.76315,7,255,0 +280,-1.00519,2.65699,0.75979,-2.76315,7,255,0 +281,-1.00519,2.65699,0.81297,-2.76315,7,255,0 +282,-1.00519,2.65699,0.86881,-2.76315,7,255,0 +283,-1.00519,2.65699,0.93843,-2.76315,7,255,0 +284,-1.00453,2.65334,0.9872,-2.76315,7,255,0 +285,-1.00118,2.61466,1.0,-2.76315,7,255,0 +286,-0.99839,2.52817,1.0,-2.76315,7,255,0 +287,-0.99588,2.44199,1.0,-2.76315,7,255,0 +288,-0.99316,2.37257,1.0,-2.76315,7,255,0 +289,-0.99048,2.297,1.0,-2.76315,7,255,0 +290,-0.98782,2.21961,1.0,-2.76315,7,255,0 +291,-0.98515,2.14444,1.0,-2.76315,7,255,0 +292,-0.98247,2.06873,1.0,-2.76315,7,255,0 +293,-0.9798,1.9928,1.0,-2.76315,7,255,0 +294,-0.97713,1.91706,1.0,-2.76315,7,255,0 +295,-0.97446,1.84129,1.0,-2.76315,7,255,0 +296,-0.97178,1.7655,1.0,-2.76315,7,255,0 +297,-0.96911,1.68972,1.0,-2.76315,7,255,0 +298,-0.96644,1.61394,1.0,-2.76315,7,255,0 +299,-0.96377,1.53816,1.0,-2.76315,7,255,0 +300,-0.96109,1.46238,1.0,-2.76315,7,255,0 +301,-0.95842,1.3866,1.0,-2.76315,7,255,0 +302,-0.95575,1.31083,1.0,-2.76315,7,255,0 +303,-0.95308,1.23504,1.0,-2.76315,7,255,0 +304,-0.9504,1.15926,1.0,-2.76315,7,255,0 +305,-0.94773,1.08349,1.0,-2.76315,7,255,0 +306,-0.94506,1.00771,1.0,-2.76315,7,255,0 +307,-0.94239,0.93192,1.0,-2.76315,7,255,0 +308,-0.93971,0.85615,1.0,-2.76315,7,255,0 +309,-0.93704,0.78037,1.0,-2.76315,7,255,0 +310,-0.93437,0.70458,1.0,-2.76315,7,255,0 +311,-0.9317,0.62881,1.0,-2.76315,7,255,0 +312,-0.92902,0.55304,1.0,-2.76315,7,255,0 +313,-0.92636,0.47725,1.0,-2.76315,7,255,0 +314,-0.92366,0.40136,1.0,-2.76315,7,255,0 +315,-0.92101,0.32571,1.0,-2.76315,7,255,0 +316,-0.91843,0.2505,1.0,-2.76315,7,255,0 +317,-0.91542,0.17384,1.0,-2.76315,7,255,0 +318,-0.91299,0.09409,1.0,-2.76315,7,255,0 +319,-0.90963,0.02185,1.0,-2.76315,7,255,0 +320,-0.87383,0.00163,1.0,-2.76315,7,255,0 +321,-0.77046,0.0,1.0,-2.76315,7,255,0 +322,-0.69735,0.0,1.0,-2.76315,7,255,0 +323,-0.63239,0.0,1.0,-2.76315,7,255,0 +324,-0.55624,0.0,1.0,-2.76315,7,255,0 +325,-0.48324,0.0,1.0,-2.76315,7,255,0 +326,-0.41137,0.0,1.0,-2.76315,7,255,0 +327,-0.33828,0.0,1.0,-2.76315,7,255,0 +328,-0.26549,0.0,1.0,-2.76315,7,255,0 +329,-0.19282,0.0,1.0,-2.76315,7,255,0 +330,-0.12004,0.0,1.0,-2.76315,7,255,0 +331,-0.04728,0.0,1.0,-2.76315,7,255,0 +332,0.02546,0.0,1.0,-2.76315,7,255,0 +333,0.09822,0.0,1.0,-2.76315,7,255,0 +334,0.17097,0.0,1.0,-2.76315,7,255,0 +335,0.24373,0.0,1.0,-2.76315,7,255,0 +336,0.31648,0.0,1.0,-2.76315,7,255,0 +337,0.38924,0.0,1.0,-2.76315,7,255,0 +338,0.46199,0.0,1.0,-2.76315,7,255,0 +339,0.53475,0.0,1.0,-2.76315,7,255,0 +340,0.6075,0.0,1.0,-2.76315,7,255,0 +341,0.68025,0.0,1.0,-2.76315,7,255,0 +342,0.75301,0.0,1.0,-2.76315,7,255,0 +343,0.82576,0.0,1.0,-2.76315,7,255,0 +344,0.89852,0.0,1.0,-2.76315,7,255,0 +345,0.97127,0.0,1.0,-2.76315,7,255,0 +346,1.04403,0.0,1.0,-2.76315,7,255,0 +347,1.11678,0.0,1.0,-2.76315,7,255,0 +348,1.18953,0.0,1.0,-2.76315,7,255,0 +349,1.26229,0.0,1.0,-2.76315,7,255,0 +350,1.33504,0.0,1.0,-2.76315,7,255,0 +351,1.4078,0.0,1.0,-2.76315,7,255,0 +352,1.48055,0.0,1.0,-2.76315,7,255,0 +353,1.55331,0.0,1.0,-2.76315,7,255,0 +354,1.62606,0.0,1.0,-2.76315,7,255,0 +355,1.69881,0.0,1.0,-2.76315,7,255,0 +356,1.77157,0.0,1.0,-2.76315,7,255,0 +357,1.84432,0.0,1.0,-2.76315,7,255,0 +358,1.91707,0.0,1.0,-2.76315,7,255,0 +359,1.98983,0.0,1.0,-2.76315,7,255,0 +360,2.06258,0.0,1.0,-2.76315,7,255,0 +361,2.13534,0.0,1.0,-2.76315,7,255,0 +362,2.20809,0.0,1.0,-2.76315,7,255,0 +363,2.28084,0.0,1.0,-2.76315,7,255,0 +364,2.3536,0.0,1.0,-2.76315,7,255,0 +365,2.42635,0.0,1.0,-2.76315,7,255,0 +366,2.49911,0.0,1.0,-2.76315,7,255,0 +367,2.57186,0.0,1.0,-2.76315,7,255,0 +368,2.64461,0.0,1.0,-2.76315,7,255,0 +369,2.71737,0.0,1.0,-2.76315,7,255,0 +370,2.79012,0.0,1.0,-2.76315,7,255,0 +371,2.86288,0.0,1.0,-2.76315,7,255,0 +372,2.93563,0.0,1.0,-2.76315,7,255,0 +373,3.00839,0.0,1.0,-2.76315,7,255,0 +374,3.08114,0.0,1.0,-2.76315,7,255,0 +375,3.15389,0.0,1.0,-2.76315,7,255,0 +376,3.22665,0.0,1.0,-2.76315,7,255,0 +377,3.2994,0.0,1.0,-2.76315,7,255,0 +378,3.37216,0.0,1.0,-2.76315,7,255,0 +379,3.44492,0.0,1.0,-2.76315,7,255,0 +380,3.51765,0.0,1.0,-2.76315,7,255,0 +381,3.59042,0.0,1.0,-2.76315,7,255,0 +382,3.66323,0.0,1.0,-2.76315,7,255,0 +383,3.73581,0.0,1.0,-2.76315,7,255,0 +384,3.80863,0.0,1.0,-2.76315,7,255,0 +385,3.88208,0.0,1.0,-2.76315,7,255,0 +386,3.95309,0.0,1.0,-2.76315,7,255,0 +387,4.02606,0.0,1.0,-2.76315,7,255,0 +388,4.10654,-0.02619,1.03815,-2.766,7,255,0 +389,4.17222,-0.07,1.10188,-2.76964,7,255,0 +390,4.21934,-0.10777,1.15672,-2.77183,7,255,0 +391,4.26563,-0.144,1.20926,-2.77475,7,255,0 +392,4.31231,-0.18027,1.26178,-2.77798,7,255,0 +393,4.3582,-0.21679,1.31456,-2.78112,7,255,0 +394,4.40386,-0.25346,1.36747,-2.78438,7,255,0 +395,4.44921,-0.29032,1.42055,-2.78777,7,255,0 +396,4.49413,-0.3274,1.47382,-2.79127,7,255,0 +397,4.53865,-0.36469,1.52728,-2.7949,7,255,0 +398,4.5828,-0.40218,1.58092,-2.79865,7,255,0 +399,4.62655,-0.43987,1.63474,-2.80252,7,255,0 +400,4.66991,-0.47776,1.68872,-2.80652,7,255,0 +401,4.71287,-0.51586,1.74289,-2.81066,7,255,0 +402,4.75539,-0.55417,1.79725,-2.81494,7,255,0 +403,4.79746,-0.59271,1.8518,-2.81938,7,255,0 +404,4.83905,-0.63148,1.90655,-2.82397,7,255,0 +405,4.88015,-0.67049,1.9615,-2.82875,7,255,0 +406,4.92073,-0.70975,2.01666,-2.83371,7,255,0 +407,4.96076,-0.74927,2.07204,-2.83887,7,255,0 +408,5.00021,-0.78905,2.12764,-2.84425,7,255,0 +409,5.03906,-0.82911,2.18347,-2.84985,7,255,0 +410,5.07727,-0.86945,2.23954,-2.8557,7,255,0 +411,5.11481,-0.91009,2.29584,-2.86182,7,255,0 +412,5.15163,-0.95103,2.35239,-2.86822,7,255,0 +413,5.18772,-0.99229,2.40919,-2.87493,7,255,0 +414,5.22301,-1.03387,2.46625,-2.88196,7,255,0 +415,5.25747,-1.07579,2.52357,-2.88936,7,255,0 +416,5.29105,-1.11805,2.58116,-2.89714,7,255,0 +417,5.32371,-1.16067,2.63901,-2.90533,7,255,0 +418,5.35538,-1.20365,2.69714,-2.91397,7,255,0 +419,5.38603,-1.24701,2.75554,-2.9231,7,255,0 +420,5.41557,-1.29075,2.81421,-2.93275,7,255,0 +421,5.44396,-1.33489,2.87316,-2.94297,7,255,0 +422,5.47112,-1.37943,2.93238,-2.95382,7,255,0 +423,5.49698,-1.42437,2.99186,-2.96534,7,255,0 +424,5.52148,-1.46973,3.05161,-2.97757,7,255,0 +425,5.54449,-1.51552,3.11161,-2.99065,7,255,0 +426,5.56591,-1.56175,3.17188,-3.00459,7,255,0 +427,5.58555,-1.60846,3.23241,-3.0195,7,255,0 +428,5.60301,-1.65558,3.29306,-3.03606,7,255,0 +429,5.61879,-1.70323,3.35405,-3.05318,7,255,0 +430,5.63399,-1.75332,3.41792,-3.07028,7,255,0 +431,5.64777,-1.81252,3.49266,-3.0953,7,255,0 +432,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +433,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +434,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +435,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +436,5.65455,-1.84833,3.53759,-3.11273,7,255,0 +437,5.65455,-1.79891,3.53759,-3.11273,7,255,0 +438,5.65455,-1.72513,3.53759,-3.11273,7,255,0 +439,5.65455,-1.66957,3.53759,-3.11273,7,255,0 +440,5.65455,-1.61072,3.53759,-3.11273,7,255,0 +441,5.65455,-1.55011,3.53759,-3.11273,7,255,0 +442,5.65455,-1.49082,3.53759,-3.11273,7,255,0 +443,5.65456,-1.43129,3.53759,-3.11273,7,255,0 +444,5.65456,-1.37163,3.53759,-3.11273,7,255,0 +445,5.65456,-1.31206,3.53759,-3.11273,7,255,0 +446,5.65456,-1.25248,3.53759,-3.11273,7,255,0 +447,5.65456,-1.19289,3.53759,-3.11273,7,255,0 +448,5.65456,-1.13331,3.53759,-3.11273,7,255,0 +449,5.65456,-1.07373,3.53758,-3.11273,7,255,0 +450,5.65456,-1.01414,3.53758,-3.11273,7,255,0 +451,5.65456,-0.95456,3.53758,-3.11273,7,255,0 +452,5.65456,-0.89497,3.53758,-3.11273,7,255,0 +453,5.65456,-0.83539,3.53758,-3.11273,7,255,0 +454,5.65456,-0.7758,3.53758,-3.11273,7,255,0 +455,5.65456,-0.71622,3.53758,-3.11273,7,255,0 +456,5.65456,-0.65663,3.53758,-3.11273,7,255,0 +457,5.65456,-0.59705,3.53758,-3.11273,7,255,0 +458,5.65456,-0.53746,3.53758,-3.11273,7,255,0 +459,5.65456,-0.47788,3.53758,-3.11273,7,255,0 +460,5.65456,-0.41829,3.53758,-3.11273,7,255,0 +461,5.65456,-0.35871,3.53758,-3.11273,7,255,0 +462,5.65456,-0.29912,3.53758,-3.11273,7,255,0 +463,5.65456,-0.23954,3.53758,-3.11273,7,255,0 +464,5.65456,-0.17996,3.53758,-3.11273,7,255,0 +465,5.65456,-0.12037,3.53758,-3.11273,7,255,0 +466,5.65456,-0.06079,3.53758,-3.11273,7,255,0 +467,5.65456,-0.0012,3.53758,-3.11273,7,255,0 +468,5.65456,0.05838,3.53758,-3.11273,7,255,0 +469,5.65456,0.11797,3.53758,-3.11273,7,255,0 +470,5.65456,0.17755,3.53758,-3.11273,7,255,0 +471,5.65456,0.23714,3.53758,-3.11273,7,255,0 +472,5.65456,0.29672,3.53758,-3.11273,7,255,0 +473,5.65456,0.35631,3.53758,-3.11273,7,255,0 +474,5.65456,0.41589,3.53758,-3.11273,7,255,0 +475,5.65456,0.47548,3.53758,-3.11273,7,255,0 +476,5.65456,0.53506,3.53757,-3.11273,7,255,0 +477,5.65456,0.59465,3.53757,-3.11273,7,255,0 +478,5.65457,0.65423,3.53757,-3.11273,7,255,0 +479,5.65457,0.71381,3.53757,-3.11273,7,255,0 +480,5.65457,0.7734,3.53757,-3.11273,7,255,0 +481,5.65457,0.83298,3.53757,-3.11273,7,255,0 +482,5.65457,0.89257,3.53757,-3.11273,7,255,0 +483,5.65457,0.95215,3.53757,-3.11273,7,255,0 +484,5.65457,1.01174,3.53757,-3.11273,7,255,0 +485,5.65457,1.07132,3.53757,-3.11273,7,255,0 +486,5.65457,1.13091,3.53757,-3.11273,7,255,0 +487,5.65457,1.19049,3.53756,-3.11273,7,255,0 +488,5.65457,1.25008,3.53756,-3.11273,7,255,0 +489,5.65458,1.30966,3.53756,-3.11273,7,255,0 +490,5.65458,1.36925,3.53756,-3.11273,7,255,0 +491,5.65458,1.42883,3.53756,-3.11273,7,255,0 +492,5.65458,1.48841,3.53756,-3.11273,7,255,0 +493,5.65458,1.548,3.53756,-3.11273,7,255,0 +494,5.65458,1.60764,3.53756,-3.11273,7,255,0 +495,5.65459,1.66702,3.53755,-3.11486,7,255,0 +496,5.65574,1.72639,3.53755,3.11304,7,255,0 +497,5.66228,1.78358,3.53755,2.83459,7,255,0 +498,5.6874,1.82462,3.53755,2.39285,7,255,0 +499,5.73331,1.84438,3.53755,1.98516,7,255,0 +500,5.78976,1.85001,3.53755,1.7291,7,255,0 +501,5.84936,1.85028,3.53754,1.61928,7,255,0 +502,5.9089,1.85028,3.53754,1.59963,7,255,0 +503,5.96848,1.85028,3.53754,1.59962,7,255,0 +504,6.02807,1.85027,3.53754,1.59962,7,255,0 +505,6.08765,1.85027,3.53754,1.59962,7,255,0 +506,6.14723,1.85026,3.53753,1.59962,7,255,0 +507,6.20682,1.85025,3.53753,1.59962,7,255,0 +508,6.26641,1.85024,3.53753,1.59962,7,255,0 +509,6.32599,1.85023,3.53753,1.59961,7,255,0 +510,6.38558,1.85022,3.53753,1.59961,7,255,0 +511,6.44516,1.85021,3.53752,1.59961,7,255,0 +512,6.50474,1.8502,3.53752,1.59961,7,255,0 +513,6.56433,1.85019,3.53752,1.59961,7,255,0 +514,6.62391,1.85018,3.53752,1.59961,7,255,0 +515,6.6835,1.85017,3.53751,1.59961,7,255,0 +516,6.74308,1.85017,3.53751,1.59961,7,255,0 +517,6.80267,1.85016,3.53751,1.59961,7,255,0 +518,6.86225,1.85015,3.53751,1.5996,7,255,0 +519,6.92184,1.85014,3.5375,1.5996,7,255,0 +520,6.98142,1.85013,3.5375,1.5996,7,255,0 +521,7.04101,1.85013,3.5375,1.5996,7,255,0 +522,7.10059,1.85012,3.53749,1.5996,7,255,0 +523,7.16018,1.85011,3.53749,1.5996,7,255,0 +524,7.21976,1.8501,3.53749,1.5996,7,255,0 +525,7.27935,1.8501,3.53748,1.5996,7,255,0 +526,7.33893,1.85009,3.53748,1.5996,7,255,0 +527,7.39851,1.85008,3.53748,1.5996,7,255,0 +528,7.4581,1.85007,3.53747,1.5996,7,255,0 +529,7.51768,1.85007,3.53747,1.5996,7,255,0 +530,7.57727,1.85006,3.53747,1.5996,7,255,0 +531,7.63685,1.85005,3.53746,1.5996,7,255,0 +532,7.69644,1.85004,3.53746,1.5996,7,255,0 +533,7.75602,1.85004,3.53746,1.59959,7,255,0 +534,7.81561,1.85003,3.53745,1.59959,7,255,0 +535,7.87519,1.85002,3.53745,1.59959,7,255,0 +536,7.93478,1.85001,3.53745,1.59959,7,255,0 +537,7.99436,1.85,3.53744,1.59959,7,255,0 +538,8.05395,1.85,3.53744,1.59959,7,255,0 +539,8.11353,1.84999,3.53743,1.59959,7,255,0 +540,8.17312,1.84998,3.53743,1.59959,7,255,0 +541,8.2327,1.84997,3.53743,1.59959,7,255,0 +542,8.29228,1.84996,3.53742,1.59959,7,255,0 +543,8.35187,1.84996,3.53742,1.59959,7,255,0 +544,8.41145,1.84995,3.53741,1.59959,7,255,0 +545,8.47104,1.84994,3.53741,1.59959,7,255,0 +546,8.53063,1.84993,3.53741,1.59959,7,255,0 +547,8.59021,1.84992,3.5374,1.59959,7,255,0 +548,8.64979,1.84992,3.5374,1.59959,7,255,0 +549,8.70938,1.84991,3.5374,1.59959,7,255,0 +550,8.76896,1.8499,3.53739,1.59959,7,255,0 +551,8.82855,1.84989,3.53739,1.59959,7,255,0 +552,8.88813,1.84988,3.53738,1.59959,7,255,0 +553,8.94772,1.84988,3.53738,1.59959,7,255,0 +554,9.0073,1.84987,3.53738,1.59959,7,255,0 +555,9.06689,1.84986,3.53737,1.59959,7,255,0 +556,9.12647,1.84985,3.53737,1.59959,7,255,0 +557,9.18606,1.84985,3.53737,1.59959,7,255,0 +558,9.24564,1.84984,3.53736,1.59959,7,255,0 +559,9.30522,1.84983,3.53736,1.59959,7,255,0 +560,9.36481,1.84982,3.53735,1.59959,7,255,0 +561,9.42439,1.84981,3.53735,1.59959,7,255,0 +562,9.48398,1.84981,3.53735,1.59959,7,255,0 +563,9.54356,1.8498,3.53734,1.59959,7,255,0 +564,9.60315,1.84979,3.53734,1.59959,7,255,0 +565,9.66273,1.84978,3.53734,1.59959,7,255,0 +566,9.72232,1.84978,3.53733,1.59959,7,255,0 +567,9.7819,1.84977,3.53733,1.59959,7,255,0 +568,9.84149,1.84976,3.53733,1.59959,7,255,0 +569,9.90107,1.84975,3.53732,1.59959,7,255,0 +570,9.96066,1.84974,3.53732,1.59959,7,255,0 +571,10.02024,1.84974,3.53731,1.59959,7,255,0 +572,10.07983,1.84973,3.53731,1.59959,7,255,0 +573,10.13941,1.84972,3.53731,1.59959,7,255,0 +574,10.199,1.84971,3.5373,1.59959,7,255,0 +575,10.25858,1.8497,3.5373,1.59959,7,255,0 +576,10.31816,1.8497,3.53729,1.59959,7,255,0 +577,10.37775,1.84969,3.53729,1.59959,7,255,0 +578,10.43733,1.84968,3.53729,1.59959,7,255,0 +579,10.49692,1.84967,3.53728,1.59959,7,255,0 +580,10.5565,1.84967,3.53728,1.59959,7,255,0 +581,10.61609,1.84966,3.53727,1.59959,7,255,0 +582,10.67567,1.84965,3.53727,1.59959,7,255,0 +583,10.73526,1.84964,3.53727,1.59959,7,255,0 +584,10.79484,1.84963,3.53726,1.59959,7,255,0 +585,10.85443,1.84963,3.53726,1.59959,7,255,0 +586,10.91401,1.84962,3.53725,1.59959,7,255,0 +587,10.9736,1.84961,3.53725,1.59959,7,255,0 +588,11.03318,1.8496,3.53724,1.59959,7,255,0 +589,11.09277,1.8496,3.53724,1.59959,7,255,0 +590,11.15235,1.84959,3.53724,1.59959,7,255,0 +591,11.21194,1.84958,3.53723,1.59959,7,255,0 +592,11.27152,1.84957,3.53723,1.59959,7,255,0 +593,11.33111,1.84956,3.53723,1.59958,7,255,0 +594,11.39069,1.84956,3.53722,1.59958,7,255,0 +595,11.45028,1.84955,3.53722,1.59958,7,255,0 +596,11.50986,1.84954,3.53722,1.59958,7,255,0 +597,11.56944,1.84953,3.53721,1.59958,7,255,0 +598,11.62903,1.84952,3.53721,1.59958,7,255,0 +599,11.68862,1.84952,3.53721,1.59958,7,255,0 +600,11.7482,1.84951,3.53721,1.59958,7,255,0 +601,11.80779,1.8495,3.5372,1.59958,7,255,0 +602,11.86737,1.84949,3.5372,1.59958,7,255,0 +603,11.92696,1.84948,3.5372,1.59958,7,255,0 +604,11.98654,1.84947,3.5372,1.59957,7,255,0 +605,12.04613,1.84946,3.5372,1.59957,7,255,0 +606,12.10571,1.84946,3.5372,1.59957,7,255,0 +607,12.16529,1.84945,3.53719,1.59957,7,255,0 +608,12.22488,1.84944,3.53719,1.59957,7,255,0 +609,12.28446,1.84944,3.53719,1.59957,7,255,0 +610,12.34405,1.84943,3.53719,1.59957,7,255,0 +611,12.40363,1.84943,3.53719,1.59957,7,255,0 +612,12.46322,1.84943,3.53719,1.59956,7,255,0 +613,12.5228,1.84942,3.53719,1.59956,7,255,0 +614,12.58239,1.84942,3.53719,1.59956,7,255,0 +615,12.64197,1.84942,3.53719,1.59956,7,255,0 +616,12.70156,1.84941,3.53719,1.59956,7,255,0 +617,12.76113,1.8494,3.53718,1.59956,7,255,0 +618,12.82075,1.84938,3.53718,1.59952,7,255,0 +619,12.88031,1.84936,3.53718,1.59853,7,255,0 +620,12.93977,1.84932,3.53718,1.57955,7,255,0 +621,12.99975,1.8492,3.53718,1.53822,7,255,0 +622,13.05807,1.84683,3.53718,1.39467,7,255,0 +623,13.10874,1.8321,3.53718,1.02962,7,255,0 +624,13.14285,1.79679,3.53718,0.55984,7,255,0 +625,13.15555,1.74517,3.53718,0.18196,7,255,0 +626,13.15648,1.6865,3.53718,0.04504,7,255,0 +627,13.15648,1.62648,3.53718,0.0302,7,255,0 +628,13.15648,1.56702,3.53718,0.02887,7,255,0 +629,13.15648,1.50746,3.53717,0.02886,7,255,0 +630,13.15648,1.44785,3.53717,0.02886,7,255,0 +631,13.15648,1.38827,3.53717,0.02886,7,255,0 +632,13.15648,1.32869,3.53717,0.02886,7,255,0 +633,13.15648,1.2691,3.53717,0.02885,7,255,0 +634,13.15648,1.20952,3.53717,0.02885,7,255,0 +635,13.15648,1.14993,3.53717,0.02885,7,255,0 +636,13.15649,1.09035,3.53717,0.02885,7,255,0 +637,13.15648,1.03076,3.53717,0.02885,7,255,0 +638,13.15649,0.97118,3.53717,0.02885,7,255,0 +639,13.15649,0.9116,3.53717,0.02884,7,255,0 +640,13.15649,0.85201,3.53717,0.02884,7,255,0 +641,13.15649,0.79243,3.53717,0.02884,7,255,0 +642,13.15649,0.73284,3.53716,0.02884,7,255,0 +643,13.15649,0.67326,3.53716,0.02884,7,255,0 +644,13.15649,0.61367,3.53716,0.02884,7,255,0 +645,13.15649,0.55409,3.53716,0.02884,7,255,0 +646,13.15649,0.4945,3.53716,0.02884,7,255,0 +647,13.15649,0.43492,3.53716,0.02883,7,255,0 +648,13.15649,0.37533,3.53716,0.02883,7,255,0 +649,13.1565,0.31575,3.53716,0.02883,7,255,0 +650,13.1565,0.25616,3.53716,0.02883,7,255,0 +651,13.1565,0.19658,3.53716,0.02883,7,255,0 +652,13.1565,0.137,3.53716,0.02883,7,255,0 +653,13.1565,0.07741,3.53716,0.02883,7,255,0 +654,13.1565,0.01783,3.53716,0.02883,7,255,0 +655,13.1565,-0.04176,3.53716,0.02883,7,255,0 +656,13.1565,-0.10134,3.53716,0.02883,7,255,0 +657,13.1565,-0.16093,3.53715,0.02883,7,255,0 +658,13.1565,-0.22051,3.53715,0.02883,7,255,0 +659,13.1565,-0.2801,3.53715,0.02883,7,255,0 +660,13.1565,-0.33968,3.53715,0.02883,7,255,0 +661,13.15651,-0.39926,3.53715,0.02883,7,255,0 +662,13.15651,-0.45885,3.53715,0.02883,7,255,0 +663,13.15651,-0.51843,3.53715,0.02883,7,255,0 +664,13.15651,-0.57802,3.53715,0.02883,7,255,0 +665,13.15651,-0.6376,3.53715,0.02883,7,255,0 +666,13.15651,-0.69719,3.53715,0.02883,7,255,0 +667,13.15651,-0.75677,3.53715,0.02882,7,255,0 +668,13.15651,-0.81636,3.53715,0.02882,7,255,0 +669,13.15651,-0.87594,3.53715,0.02882,7,255,0 +670,13.15651,-0.93553,3.53715,0.02882,7,255,0 +671,13.15651,-0.99511,3.53715,0.02882,7,255,0 +672,13.15651,-1.05469,3.53715,0.02882,7,255,0 +673,13.15651,-1.11428,3.53715,0.02882,7,255,0 +674,13.15652,-1.17386,3.53715,0.02882,7,255,0 +675,13.15652,-1.23344,3.53714,0.02882,7,255,0 +676,13.15652,-1.29303,3.53714,0.02882,7,255,0 +677,13.15652,-1.35262,3.53714,0.02882,7,255,0 +678,13.15652,-1.41219,3.53714,0.02882,7,255,0 +679,13.15652,-1.47179,3.53714,0.02882,7,255,0 +680,13.15652,-1.53141,3.53714,0.02882,7,255,0 +681,13.15652,-1.59085,3.53714,0.02882,7,255,0 +682,13.15652,-1.65054,3.53714,0.02774,7,255,0 +683,13.15644,-1.71064,3.53714,-0.01199,7,255,0 +684,13.15416,-1.76717,3.53714,-0.20779,7,255,0 +685,13.13611,-1.81192,3.53714,-0.58791,7,255,0 +686,13.09665,-1.83671,3.53714,-1.05263,7,255,0 +687,13.04295,-1.84623,3.53714,-1.37746,7,255,0 +688,12.98346,-1.84808,3.53714,-1.50019,7,255,0 +689,12.9238,-1.84831,3.53714,-1.53617,7,255,0 +690,12.86434,-1.84831,3.53714,-1.54141,7,255,0 +691,12.80472,-1.84831,3.53714,-1.54193,7,255,0 +692,12.74513,-1.84831,3.53714,-1.54193,7,255,0 +693,12.68556,-1.84831,3.53714,-1.54193,7,255,0 +694,12.62597,-1.84831,3.53714,-1.54193,7,255,0 +695,12.56638,-1.84831,3.53714,-1.54193,7,255,0 +696,12.5068,-1.84831,3.53714,-1.54193,7,255,0 +697,12.44722,-1.84831,3.53714,-1.54193,7,255,0 +698,12.38763,-1.84831,3.53713,-1.54193,7,255,0 +699,12.32805,-1.84831,3.53713,-1.54193,7,255,0 +700,12.26846,-1.84831,3.53713,-1.54193,7,255,0 +701,12.20887,-1.84831,3.53713,-1.54193,7,255,0 +702,12.14929,-1.84831,3.53713,-1.54193,7,255,0 +703,12.08971,-1.84831,3.53713,-1.54193,7,255,0 +704,12.03012,-1.84831,3.53713,-1.54193,7,255,0 +705,11.97054,-1.84831,3.53713,-1.54193,7,255,0 +706,11.91095,-1.84831,3.53713,-1.54193,7,255,0 +707,11.85137,-1.84831,3.53713,-1.54193,7,255,0 +708,11.79178,-1.84831,3.53713,-1.54193,7,255,0 +709,11.7322,-1.84831,3.53713,-1.54193,7,255,0 +710,11.67261,-1.84831,3.53713,-1.54193,7,255,0 +711,11.61303,-1.84831,3.53713,-1.54193,7,255,0 +712,11.55344,-1.84831,3.53713,-1.54193,7,255,0 +713,11.49385,-1.84831,3.53713,-1.54193,7,255,0 +714,11.43427,-1.84831,3.53713,-1.54193,7,255,0 +715,11.37469,-1.84831,3.53713,-1.54193,7,255,0 +716,11.3151,-1.84831,3.53713,-1.54193,7,255,0 +717,11.25552,-1.84831,3.53713,-1.54193,7,255,0 +718,11.19593,-1.84832,3.53713,-1.54193,7,255,0 +719,11.13635,-1.84832,3.53713,-1.54193,7,255,0 +720,11.07676,-1.84832,3.53713,-1.54193,7,255,0 +721,11.01718,-1.84832,3.53713,-1.54193,7,255,0 +722,10.95759,-1.84832,3.53713,-1.54193,7,255,0 +723,10.89801,-1.84832,3.53713,-1.54193,7,255,0 +724,10.83842,-1.84832,3.53713,-1.54193,7,255,0 +725,10.77884,-1.84832,3.53713,-1.54193,7,255,0 +726,10.71925,-1.84832,3.53713,-1.54193,7,255,0 +727,10.65967,-1.84832,3.53713,-1.54193,7,255,0 +728,10.60008,-1.84832,3.53713,-1.54193,7,255,0 +729,10.5405,-1.84832,3.53713,-1.54193,7,255,0 +730,10.48091,-1.84832,3.53713,-1.54193,7,255,0 +731,10.42133,-1.84832,3.53713,-1.54193,7,255,0 +732,10.36174,-1.84832,3.53712,-1.54193,7,255,0 +733,10.30216,-1.84832,3.53712,-1.54193,7,255,0 +734,10.24257,-1.84832,3.53712,-1.54193,7,255,0 +735,10.18299,-1.84832,3.53712,-1.54193,7,255,0 +736,10.1234,-1.84832,3.53712,-1.54193,7,255,0 +737,10.06382,-1.84832,3.53712,-1.54193,7,255,0 +738,10.00423,-1.84832,3.53712,-1.54193,7,255,0 +739,9.94465,-1.84832,3.53712,-1.54193,7,255,0 +740,9.88506,-1.84832,3.53712,-1.54193,7,255,0 +741,9.82548,-1.84832,3.53712,-1.54193,7,255,0 +742,9.76589,-1.84832,3.53712,-1.54193,7,255,0 +743,9.70631,-1.84832,3.53712,-1.54193,7,255,0 +744,9.64673,-1.84832,3.53712,-1.54193,7,255,0 +745,9.58714,-1.84832,3.53712,-1.54193,7,255,0 +746,9.52755,-1.84832,3.53712,-1.54193,7,255,0 +747,9.46797,-1.84832,3.53712,-1.54193,7,255,0 +748,9.40839,-1.84832,3.53712,-1.54193,7,255,0 +749,9.3488,-1.84832,3.53712,-1.54193,7,255,0 +750,9.28922,-1.84832,3.53712,-1.54193,7,255,0 +751,9.22963,-1.84832,3.53712,-1.54193,7,255,0 +752,9.17004,-1.84832,3.53712,-1.54193,7,255,0 +753,9.11046,-1.84832,3.53712,-1.54193,7,255,0 +754,9.05088,-1.84832,3.53712,-1.54193,7,255,0 +755,8.99129,-1.84832,3.53712,-1.54193,7,255,0 +756,8.93171,-1.84832,3.53712,-1.54193,7,255,0 +757,8.87212,-1.84832,3.53712,-1.54193,7,255,0 +758,8.81254,-1.84832,3.53712,-1.54193,7,255,0 +759,8.75295,-1.84832,3.53712,-1.54193,7,255,0 +760,8.69337,-1.84832,3.53712,-1.54193,7,255,0 +761,8.63378,-1.84832,3.53712,-1.54193,7,255,0 +762,8.5742,-1.84832,3.53712,-1.54193,7,255,0 +763,8.51461,-1.84832,3.53712,-1.54193,7,255,0 +764,8.45503,-1.84832,3.53712,-1.54193,7,255,0 +765,8.39544,-1.84832,3.53712,-1.54193,7,255,0 +766,8.33586,-1.84832,3.53712,-1.54193,7,255,0 +767,8.27627,-1.84832,3.53712,-1.54193,7,255,0 +768,8.21669,-1.84832,3.53712,-1.54193,7,255,0 +769,8.1571,-1.84832,3.53712,-1.54193,7,255,0 +770,8.09752,-1.84832,3.53712,-1.54193,7,255,0 +771,8.03793,-1.84832,3.53712,-1.54193,7,255,0 +772,7.97835,-1.84832,3.53712,-1.54193,7,255,0 +773,7.91876,-1.84832,3.53712,-1.54193,7,255,0 +774,7.85918,-1.84832,3.53712,-1.54193,7,255,0 +775,7.79959,-1.84832,3.53712,-1.54193,7,255,0 +776,7.74001,-1.84832,3.53712,-1.54193,7,255,0 +777,7.68043,-1.84832,3.53712,-1.54193,7,255,0 +778,7.62084,-1.84832,3.53712,-1.54193,7,255,0 +779,7.56125,-1.84832,3.53712,-1.54193,7,255,0 +780,7.50167,-1.84832,3.53712,-1.54193,7,255,0 +781,7.44208,-1.84832,3.53712,-1.54193,7,255,0 +782,7.3825,-1.84832,3.53712,-1.54193,7,255,0 +783,7.32291,-1.84832,3.53712,-1.54193,7,255,0 +784,7.26333,-1.84832,3.53712,-1.54193,7,255,0 +785,7.20374,-1.84832,3.53712,-1.54193,7,255,0 +786,7.14416,-1.84832,3.53712,-1.54193,7,255,0 +787,7.08457,-1.84832,3.53712,-1.54193,7,255,0 +788,7.02499,-1.84832,3.53712,-1.54193,7,255,0 +789,6.96541,-1.84832,3.53712,-1.54193,7,255,0 +790,6.90582,-1.84832,3.53712,-1.54193,7,255,0 +791,6.84623,-1.84832,3.53712,-1.54193,7,255,0 +792,6.78665,-1.84833,3.53712,-1.54193,7,255,0 +793,6.72706,-1.84833,3.53712,-1.54193,7,255,0 +794,6.66748,-1.84833,3.53712,-1.54193,7,255,0 +795,6.6079,-1.84833,3.53712,-1.54193,7,255,0 +796,6.54831,-1.84833,3.53712,-1.54193,7,255,0 +797,6.48873,-1.84833,3.53712,-1.54193,7,255,0 +798,6.42914,-1.84833,3.53712,-1.54193,7,255,0 +799,6.36956,-1.84833,3.53712,-1.54193,7,255,0 +800,6.30997,-1.84833,3.53712,-1.54193,7,255,0 +801,6.25038,-1.84833,3.53712,-1.54193,7,255,0 +802,6.1908,-1.84833,3.53712,-1.54193,7,255,0 +803,6.13123,-1.84833,3.53712,-1.54193,7,255,0 +804,6.07158,-1.84833,3.53712,-1.54193,7,255,0 +805,6.01204,-1.84833,3.53712,-1.54193,7,255,0 +806,5.95275,-1.84833,3.53712,-1.54193,7,255,0 +807,5.89215,-1.84833,3.53712,-1.54193,7,255,0 +808,5.83329,-1.84833,3.53712,-1.54193,7,255,0 +809,5.77773,-1.84833,3.53712,-1.54193,7,255,0 +810,5.70396,-1.84833,3.53712,-1.54193,7,255,0 +811,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +812,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +813,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +814,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +815,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +816,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +817,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +818,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +819,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +820,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +821,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +822,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +823,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +824,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +825,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +826,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +827,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +828,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +829,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +830,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +831,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +832,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +833,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +834,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +835,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +836,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +837,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +838,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +839,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +840,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +841,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +842,5.65454,-1.84833,3.53715,-1.54193,7,255,0 +843,5.65454,-1.84833,3.53721,-1.54193,7,255,0 +844,5.65454,-1.84833,3.53726,-1.54193,7,255,0 +845,5.65454,-1.84833,3.5373,-1.54193,7,255,0 +846,5.65454,-1.84833,3.53735,-1.54193,7,255,0 +847,5.65454,-1.84833,3.5374,-1.54193,7,255,0 +848,5.65454,-1.84833,3.53746,-1.54193,7,255,0 +849,5.65454,-1.84833,3.53749,-1.54193,7,255,0 +850,5.65454,-1.84833,3.53751,-1.54193,7,255,0 +851,5.65454,-1.84833,3.53752,-1.54193,7,255,0 +852,5.65454,-1.84833,3.53753,-3.11273,7,255,0 +853,5.65454,-1.84833,3.53754,-3.11273,7,255,0 +854,5.65454,-1.84833,3.53755,-3.11273,7,255,0 +855,5.65454,-1.84833,3.53756,-3.11273,7,255,0 +856,5.65454,-1.84833,3.53756,-3.11273,7,255,0 +857,5.65454,-1.84833,3.53757,-3.11273,7,255,0 +858,5.65454,-1.84833,3.53757,-3.11273,7,255,0 +859,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +860,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +861,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +862,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +863,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +864,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +865,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +866,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +867,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +868,5.65049,-1.82643,3.51012,-3.10193,7,255,0 +869,5.6412,-1.78182,3.454,-3.0815,7,255,0 +870,5.63027,-1.74017,3.4012,-3.06561,7,255,0 +871,5.61879,-1.70323,3.35405,-3.05319,7,255,0 +872,5.60705,-1.66743,3.30825,-3.0404,7,255,0 +873,5.59457,-1.63197,3.26272,-3.02754,7,255,0 +874,5.58083,-1.59674,3.21726,-3.01564,7,255,0 +875,5.56591,-1.56175,3.17188,-3.00459,7,255,0 +876,5.55,-1.52703,3.12665,-2.99406,7,255,0 +877,5.53318,-1.49257,3.08158,-2.984,7,255,0 +878,5.51549,-1.45835,3.03665,-2.97444,7,255,0 +879,5.49698,-1.42437,2.99186,-2.96534,7,255,0 +880,5.47771,-1.39062,2.94722,-2.95664,7,255,0 +881,5.45769,-1.35711,2.90273,-2.94832,7,255,0 +882,5.43697,-1.32382,2.85839,-2.94036,7,255,0 +883,5.41557,-1.29075,2.81421,-2.93275,7,255,0 +884,5.39352,-1.25791,2.77018,-2.92546,7,255,0 +885,5.37084,-1.22528,2.7263,-2.91847,7,255,0 +886,5.34756,-1.19287,2.68258,-2.91176,7,255,0 +887,5.32371,-1.16067,2.63901,-2.90533,7,255,0 +888,5.29931,-1.12867,2.5956,-2.89914,7,255,0 +889,5.27437,-1.09687,2.55233,-2.8932,7,255,0 +890,5.24894,-1.06527,2.50922,-2.88748,7,255,0 +891,5.22301,-1.03387,2.46625,-2.88196,7,255,0 +892,5.19661,-1.00265,2.42343,-2.87665,7,255,0 +893,5.16977,-0.97162,2.38076,-2.87153,7,255,0 +894,5.1425,-0.94076,2.33823,-2.86659,7,255,0 +895,5.11481,-0.91009,2.29584,-2.86182,7,255,0 +896,5.08672,-0.87958,2.25359,-2.85721,7,255,0 +897,5.05825,-0.84924,2.21147,-2.85275,7,255,0 +898,5.02941,-0.81907,2.16949,-2.84843,7,255,0 +899,5.00021,-0.78905,2.12764,-2.84425,7,255,0 +900,4.97068,-0.75919,2.08592,-2.84019,7,255,0 +901,4.94082,-0.72948,2.04432,-2.83626,7,255,0 +902,4.91064,-0.69991,2.00285,-2.83245,7,255,0 +903,4.88015,-0.67049,1.9615,-2.82875,7,255,0 +904,4.84938,-0.64121,1.92026,-2.82515,7,255,0 +905,4.81832,-0.61207,1.87915,-2.82165,7,255,0 +906,4.78698,-0.58306,1.83814,-2.81825,7,255,0 +907,4.75539,-0.55417,1.79725,-2.81494,7,255,0 +908,4.72354,-0.52542,1.75647,-2.81171,7,255,0 +909,4.69144,-0.49678,1.71579,-2.80857,7,255,0 +910,4.65911,-0.46827,1.67521,-2.80551,7,255,0 +911,4.62655,-0.43987,1.63474,-2.80252,7,255,0 +912,4.59377,-0.41158,1.59436,-2.79961,7,255,0 +913,4.56077,-0.38341,1.55408,-2.79676,7,255,0 +914,4.52756,-0.35535,1.5139,-2.79398,7,255,0 +915,4.49413,-0.3274,1.47382,-2.79127,7,255,0 +916,4.46048,-0.29957,1.43385,-2.78864,7,255,0 +917,4.4266,-0.27186,1.39398,-2.78607,7,255,0 +918,4.39247,-0.24428,1.35423,-2.78355,7,255,0 +919,4.3582,-0.21679,1.31456,-2.78112,7,255,0 +920,4.32383,-0.18938,1.27495,-2.77877,7,255,0 +921,4.28909,-0.16211,1.23548,-2.77639,7,255,0 +922,4.2539,-0.13496,1.19615,-2.77395,7,255,0 +923,4.21934,-0.10777,1.15672,-2.77183,7,255,0 +924,4.18494,-0.07986,1.11621,-2.77023,7,255,0 +925,4.14273,-0.04879,1.07106,-2.76811,7,255,0 +926,4.08671,-0.016,1.02331,-2.76493,7,255,0 +927,4.02606,0.0,1.0,-2.76315,7,255,0 +928,3.97066,0.0,1.0,-2.76315,7,255,0 +929,3.91803,0.0,1.0,-2.76315,7,255,0 +930,3.86381,0.0,1.0,-2.76315,7,255,0 +931,3.80863,0.0,1.0,-2.76315,7,255,0 +932,3.75394,0.0,1.0,-2.76315,7,255,0 +933,3.69956,0.0,1.0,-2.76315,7,255,0 +934,3.64504,0.0,1.0,-2.76315,7,255,0 +935,3.59042,0.0,1.0,-2.76315,7,255,0 +936,3.53584,0.0,1.0,-2.76315,7,255,0 +937,3.48129,0.0,1.0,-2.76315,7,255,0 +938,3.42673,0.0,1.0,-2.76315,7,255,0 +939,3.37216,0.0,1.0,-2.76315,7,255,0 +940,3.31759,0.0,1.0,-2.76315,7,255,0 +941,3.26303,0.0,1.0,-2.76315,7,255,0 +942,3.20846,0.0,1.0,-2.76315,7,255,0 +943,3.15389,0.0,1.0,-2.76315,7,255,0 +944,3.09933,0.0,1.0,-2.76315,7,255,0 +945,3.04476,0.0,1.0,-2.76315,7,255,0 +946,2.9902,0.0,1.0,-2.76315,7,255,0 +947,2.93563,0.0,1.0,-2.76315,7,255,0 +948,2.88107,0.0,1.0,-2.76315,7,255,0 +949,2.8265,0.0,1.0,-2.76315,7,255,0 +950,2.77194,0.0,1.0,-2.76315,7,255,0 +951,2.71737,0.0,1.0,-2.76315,7,255,0 +952,2.6628,0.0,1.0,-2.76315,7,255,0 +953,2.60824,0.0,1.0,-2.76315,7,255,0 +954,2.55367,0.0,1.0,-2.76315,7,255,0 +955,2.49911,0.0,1.0,-2.76315,7,255,0 +956,2.44454,0.0,1.0,-2.76315,7,255,0 +957,2.38998,0.0,1.0,-2.76315,7,255,0 +958,2.33541,0.0,1.0,-2.76315,7,255,0 +959,2.28084,0.0,1.0,-2.76315,7,255,0 +960,2.22628,0.0,1.0,-2.76315,7,255,0 +961,2.17171,0.0,1.0,-2.76315,7,255,0 +962,2.11715,0.0,1.0,-2.76315,7,255,0 +963,2.06258,0.0,1.0,-2.76315,7,255,0 +964,2.00802,0.0,1.0,-2.76315,7,255,0 +965,1.95345,0.0,1.0,-2.76315,7,255,0 +966,1.89888,0.0,1.0,-2.76315,7,255,0 +967,1.84432,0.0,1.0,-2.76315,7,255,0 +968,1.78975,0.0,1.0,-2.76315,7,255,0 +969,1.73519,0.0,1.0,-2.76315,7,255,0 +970,1.68063,0.0,1.0,-2.76315,7,255,0 +971,1.62606,0.0,1.0,-2.76315,7,255,0 +972,1.57149,0.0,1.0,-2.76315,7,255,0 +973,1.51693,0.0,1.0,-2.76315,7,255,0 +974,1.46236,0.0,1.0,-2.76315,7,255,0 +975,1.4078,0.0,1.0,-2.76315,7,255,0 +976,1.35323,0.0,1.0,-2.76315,7,255,0 +977,1.29867,0.0,1.0,-2.76315,7,255,0 +978,1.2441,0.0,1.0,-2.76315,7,255,0 +979,1.18953,0.0,1.0,-2.76315,7,255,0 +980,1.13497,0.0,1.0,-2.76315,7,255,0 +981,1.0804,0.0,1.0,-2.76315,7,255,0 +982,1.02584,0.0,1.0,-2.76315,7,255,0 +983,0.97127,0.0,1.0,-2.76315,7,255,0 +984,0.9167,0.0,1.0,-2.76315,7,255,0 +985,0.86214,0.0,1.0,-2.76315,7,255,0 +986,0.80757,0.0,1.0,-2.76315,7,255,0 +987,0.75301,0.0,1.0,-2.76315,7,255,0 +988,0.69844,0.0,1.0,-2.76315,7,255,0 +989,0.64388,0.0,1.0,-2.76315,7,255,0 +990,0.58931,0.0,1.0,-2.76315,7,255,0 +991,0.53475,0.0,1.0,-2.76315,7,255,0 +992,0.48018,0.0,1.0,-2.76315,7,255,0 +993,0.42561,0.0,1.0,-2.76315,7,255,0 +994,0.37105,0.0,1.0,-2.76315,7,255,0 +995,0.31648,0.0,1.0,-2.76315,7,255,0 +996,0.26192,0.0,1.0,-2.76315,7,255,0 +997,0.20735,0.0,1.0,-2.76315,7,255,0 +998,0.15279,0.0,1.0,-2.76315,7,255,0 +999,0.09822,0.0,1.0,-2.76315,7,255,0 +1000,0.04365,0.0,1.0,-2.76315,7,255,0 +1001,-0.01091,0.0,1.0,-2.76315,7,255,0 +1002,-0.06547,0.0,1.0,-2.76315,7,255,0 +1003,-0.12004,0.0,1.0,-2.76315,7,255,0 +1004,-0.17463,0.0,1.0,-2.76315,7,255,0 +1005,-0.22918,0.0,1.0,-2.76315,7,255,0 +1006,-0.28366,0.0,1.0,-2.76315,7,255,0 +1007,-0.33828,0.0,1.0,-2.76315,7,255,0 +1008,-0.39314,0.0,1.0,-2.76315,7,255,0 +1009,-0.44752,0.0,1.0,-2.76315,7,255,0 +1010,-0.50114,0.0,1.0,-2.76315,7,255,0 +1011,-0.55624,0.0,1.0,-2.76315,7,255,0 +1012,-0.61376,0.0,1.0,-2.76315,7,255,0 +1013,-0.66693,0.0,1.0,-2.76315,7,255,0 +1014,-0.7127,0.0,1.0,-2.76315,7,255,0 +1015,-0.77046,0.0,1.0,-2.76315,7,255,0 +1016,-0.85023,0.00069,1.0,-2.76315,7,255,0 +1017,-0.90212,0.0055,1.0,-2.76315,7,255,0 +1018,-0.9114,0.03656,1.0,-2.76315,7,255,0 +1019,-0.91299,0.09409,1.0,-2.76315,7,255,0 +1020,-0.91465,0.15423,1.0,-2.76315,7,255,0 +1021,-0.917,0.21249,1.0,-2.76315,7,255,0 +1022,-0.91909,0.26934,1.0,-2.76315,7,255,0 +1023,-0.92101,0.32571,1.0,-2.76315,7,255,0 +1024,-0.92299,0.3824,1.0,-2.76315,7,255,0 +1025,-0.92502,0.43932,1.0,-2.76315,7,255,0 +1026,-0.92703,0.4962,1.0,-2.76315,7,255,0 +1027,-0.92902,0.55304,1.0,-2.76315,7,255,0 +1028,-0.93103,0.60986,1.0,-2.76315,7,255,0 +1029,-0.93303,0.66669,1.0,-2.76315,7,255,0 +1030,-0.93504,0.72353,1.0,-2.76315,7,255,0 +1031,-0.93704,0.78037,1.0,-2.76315,7,255,0 +1032,-0.93905,0.8372,1.0,-2.76315,7,255,0 +1033,-0.94105,0.89403,1.0,-2.76315,7,255,0 +1034,-0.94306,0.95087,1.0,-2.76315,7,255,0 +1035,-0.94506,1.00771,1.0,-2.76315,7,255,0 +1036,-0.94706,1.06454,1.0,-2.76315,7,255,0 +1037,-0.94907,1.12137,1.0,-2.76315,7,255,0 +1038,-0.95107,1.17821,1.0,-2.76315,7,255,0 +1039,-0.95308,1.23504,1.0,-2.76315,7,255,0 +1040,-0.95508,1.29188,1.0,-2.76315,7,255,0 +1041,-0.95709,1.34871,1.0,-2.76315,7,255,0 +1042,-0.95909,1.40555,1.0,-2.76315,7,255,0 +1043,-0.96109,1.46238,1.0,-2.76315,7,255,0 +1044,-0.9631,1.51922,1.0,-2.76315,7,255,0 +1045,-0.9651,1.57605,1.0,-2.76315,7,255,0 +1046,-0.96711,1.63289,1.0,-2.76315,7,255,0 +1047,-0.96911,1.68972,1.0,-2.76315,7,255,0 +1048,-0.97112,1.74656,1.0,-2.76315,7,255,0 +1049,-0.97312,1.80339,1.0,-2.76315,7,255,0 +1050,-0.97512,1.86024,1.0,-2.76315,7,255,0 +1051,-0.97713,1.91706,1.0,-2.76315,7,255,0 +1052,-0.97913,1.97386,1.0,-2.76315,7,255,0 +1053,-0.98114,2.03073,1.0,-2.76315,7,255,0 +1054,-0.98314,2.08772,1.0,-2.76315,7,255,0 +1055,-0.98515,2.14444,1.0,-2.76315,7,255,0 +1056,-0.98715,2.20073,1.0,-2.76315,7,255,0 +1057,-0.98915,2.2579,1.0,-2.76315,7,255,0 +1058,-0.99115,2.3165,1.0,-2.76315,7,255,0 +1059,-0.99316,2.37257,1.0,-2.76315,7,255,0 +1060,-0.99521,2.42414,1.0,-2.76315,7,255,0 +1061,-0.99521000000000004,2.42414,1.0,0,0,0,0 +1062,-1.0301400000000001,2.4494266666666666,1.0,0,0,0,0 +1063,-1.06507,2.4747133333333333,1.0,0,0,0,0 +1064,-1.1000000000000001,2.5,1.0,0,0,0,0 diff --git a/tests/animation_3.csv b/tests/animation_3.csv new file mode 100644 index 0000000..1f725db --- /dev/null +++ b/tests/animation_3.csv @@ -0,0 +1,12 @@ +route +20,0.0,0.0,1.0,0.0,0,204,2 +21,0.02217,0.0,1.0,0.0,0,204,2 +22,0.08889,0.0,1.0,0.0,0,204,2 +23,0.19737,0.0,1.0,0.0,0,204,2 +24,0.33926,0.0,1.0,0.0,0,204,2 +25,0.5,0.0,1.0,0.0,0,204,2 +26,0.66074,0.0,1.0,0.0,0,204,2 +27,0.80263,0.0,1.0,0.0,0,204,2 +28,0.91111,0.0,1.0,0.0,0,204,2 +29,0.97783,0.0,1.0,0.0,0,204,2 +30,1.0,0.0,1.0,0.0,0,204,2 diff --git a/tests/animation_4.csv b/tests/animation_4.csv new file mode 100644 index 0000000..530caf5 --- /dev/null +++ b/tests/animation_4.csv @@ -0,0 +1,161 @@ +two_drones_test +1,0.2,1.4,1.0,0.15708,0,0,0 +2,0.2,1.4,1.0,0.15708,0,0,0 +3,0.2,1.4,1.0,0.15708,0,0,0 +4,0.2,1.4,1.0,0.15708,0,0,0 +5,0.2,1.4,1.0,0.15708,0,0,0 +6,0.2,1.4,1.0,0.15708,0,0,0 +7,0.2,1.4,1.0,0.15708,0,0,0 +8,0.2,1.4,1.0,0.15708,0,0,0 +9,0.2,1.4,1.0,0.15708,0,0,0 +10,0.2,1.4,1.0,0.15708,0,0,0 +11,0.20441,1.4,1.0,0.15708,0,0,0 +12,0.21774,1.4,1.0,0.15708,0,0,0 +13,0.24002,1.4,1.0,0.15708,0,0,0 +14,0.27111,1.4,1.0,0.15708,0,0,0 +15,0.31063,1.4,1.0,0.15708,0,0,0 +16,0.3579,1.4,1.0,0.15708,0,0,0 +17,0.41193,1.4,1.0,0.15708,0,0,0 +18,0.47141,1.4,1.0,0.15708,0,0,0 +19,0.53472,1.4,1.0,0.15708,0,0,0 +20,0.6,1.4,1.0,0.15708,0,0,0 +21,0.66528,1.4,1.0,0.15708,0,0,0 +22,0.72859,1.4,1.0,0.15708,0,0,0 +23,0.78807,1.4,1.0,0.15708,0,0,0 +24,0.8421,1.4,1.0,0.15708,0,0,0 +25,0.88937,1.4,1.0,0.15708,0,0,0 +26,0.92889,1.4,1.0,0.15708,0,0,0 +27,0.95998,1.4,1.0,0.15708,0,0,0 +28,0.98226,1.4,1.0,0.15708,0,0,0 +29,0.99559,1.4,1.0,0.15708,0,0,0 +30,1.0,1.4,1.0,0.15708,0,0,0 +31,1.0,1.40441,1.0,0.15708,0,0,0 +32,1.0,1.41774,1.0,0.15708,0,0,0 +33,1.0,1.44002,1.0,0.15708,0,0,0 +34,1.0,1.47111,1.0,0.15708,0,0,0 +35,1.0,1.51063,1.0,0.15708,0,0,0 +36,1.0,1.5579,1.0,0.15708,0,0,0 +37,1.0,1.61193,1.0,0.15708,0,0,0 +38,1.0,1.67141,1.0,0.15708,0,0,0 +39,1.0,1.73472,1.0,0.15708,0,0,0 +40,1.0,1.8,1.0,0.15708,0,0,0 +41,1.0,1.86528,1.0,0.15708,0,0,0 +42,1.0,1.92859,1.0,0.15708,0,0,0 +43,1.0,1.98807,1.0,0.15708,0,0,0 +44,1.0,2.0421,1.0,0.15708,0,0,0 +45,1.0,2.08937,1.0,0.15708,0,0,0 +46,1.0,2.12889,1.0,0.15708,0,0,0 +47,1.0,2.15998,1.0,0.15708,0,0,0 +48,1.0,2.18226,1.0,0.15708,0,0,0 +49,1.0,2.19559,1.0,0.15708,0,0,0 +50,1.0,2.2,1.0,0.15708,0,0,0 +51,0.99559,2.2,1.0,0.15708,0,0,0 +52,0.98226,2.2,1.0,0.15708,0,0,0 +53,0.95998,2.2,1.0,0.15708,0,0,0 +54,0.92889,2.2,1.0,0.15708,0,0,0 +55,0.88937,2.2,1.0,0.15708,0,0,0 +56,0.8421,2.2,1.0,0.15708,0,0,0 +57,0.78807,2.2,1.0,0.15708,0,0,0 +58,0.72859,2.2,1.0,0.15708,0,0,0 +59,0.66528,2.2,1.0,0.15708,0,0,0 +60,0.6,2.2,1.0,0.15708,0,0,0 +61,0.53472,2.2,1.0,0.15708,0,0,0 +62,0.47141,2.2,1.0,0.15708,0,0,0 +63,0.41193,2.2,1.0,0.15708,0,0,0 +64,0.3579,2.2,1.0,0.15708,0,0,0 +65,0.31063,2.2,1.0,0.15708,0,0,0 +66,0.27111,2.2,1.0,0.15708,0,0,0 +67,0.24002,2.2,1.0,0.15708,0,0,0 +68,0.21774,2.2,1.0,0.15708,0,0,0 +69,0.20441,2.2,1.0,0.15708,0,0,0 +70,0.2,2.2,1.0,0.15708,0,0,0 +71,0.2,2.19559,1.0,0.15708,0,0,0 +72,0.2,2.18226,1.0,0.15708,0,0,0 +73,0.2,2.15998,1.0,0.15708,0,0,0 +74,0.2,2.12889,1.0,0.15708,0,0,0 +75,0.2,2.08937,1.0,0.15708,0,0,0 +76,0.2,2.0421,1.0,0.15708,0,0,0 +77,0.2,1.98807,1.0,0.15708,0,0,0 +78,0.2,1.92859,1.0,0.15708,0,0,0 +79,0.2,1.86528,1.0,0.15708,0,0,0 +80,0.2,1.8,1.0,0.15708,0,0,0 +81,0.2,1.73472,1.0,0.15708,0,0,0 +82,0.2,1.67141,1.0,0.15708,0,0,0 +83,0.2,1.61193,1.0,0.15708,0,0,0 +84,0.2,1.5579,1.0,0.15708,0,0,0 +85,0.2,1.51063,1.0,0.15708,0,0,0 +86,0.2,1.47111,1.0,0.15708,0,0,0 +87,0.2,1.44002,1.0,0.15708,0,0,0 +88,0.2,1.41774,1.0,0.15708,0,0,0 +89,0.2,1.40441,1.0,0.15708,0,0,0 +90,0.2,1.4,1.0,0.15708,0,0,0 +91,0.2062,1.4,1.0,0.15708,0,0,0 +92,0.22355,1.4,1.0,0.15708,0,0,0 +93,0.25043,1.4,1.0,0.15708,0,0,0 +94,0.28553,1.4,1.0,0.15708,0,0,0 +95,0.32771,1.4,1.0,0.15708,0,0,0 +96,0.3759,1.4,1.0,0.15708,0,0,0 +97,0.42903,1.4,1.0,0.15708,0,0,0 +98,0.48579,1.4,1.0,0.15708,0,0,0 +99,0.54421,1.4,1.0,0.15708,0,0,0 +100,0.6,1.4,1.0,0.15708,0,0,0 +101,0.66257,1.40492,1.025,0.15708,0,0,0 +102,0.72361,1.41958,1.05,0.31416,0,0,0 +103,0.7816,1.4436,1.075,0.47124,0,0,0 +104,0.83511,1.47639,1.1,0.62832,0,0,0 +105,0.88284,1.51716,1.125,0.7854,0,0,0 +106,0.92361,1.56489,1.15,0.94248,0,0,0 +107,0.9564,1.6184,1.175,1.09956,0,0,0 +108,0.98042,1.67639,1.2,1.25664,0,0,0 +109,0.99508,1.73743,1.225,1.41372,0,0,0 +110,1.0,1.8,1.25,1.5708,0,0,0 +111,0.99508,1.86257,1.275,1.72788,0,0,0 +112,0.98042,1.92361,1.3,1.88496,0,0,0 +113,0.9564,1.9816,1.325,2.04204,0,0,0 +114,0.92361,2.03511,1.35,2.19912,0,0,0 +115,0.88284,2.08284,1.375,2.35619,0,0,0 +116,0.83511,2.12361,1.4,2.51327,0,0,0 +117,0.7816,2.1564,1.425,2.67035,0,0,0 +118,0.72361,2.18042,1.45,2.82743,0,0,0 +119,0.66257,2.19508,1.475,2.98451,0,0,0 +120,0.6,2.2,1.5,-3.14159,0,0,0 +121,0.53743,2.19508,1.525,-2.98451,0,0,0 +122,0.47639,2.18042,1.55,-2.82743,0,0,0 +123,0.4184,2.1564,1.575,-2.67035,0,0,0 +124,0.36489,2.12361,1.6,-2.51327,0,0,0 +125,0.31716,2.08284,1.625,-2.35619,0,0,0 +126,0.27639,2.03511,1.65,-2.19911,0,0,0 +127,0.2436,1.9816,1.675,-2.04203,0,0,0 +128,0.21958,1.92361,1.7,-1.88495,0,0,0 +129,0.20492,1.86257,1.725,-1.72788,0,0,0 +130,0.2,1.8,1.75,-1.5708,0,0,0 +131,0.20492,1.73743,1.775,-1.41372,0,0,0 +132,0.21958,1.67639,1.8,-1.25664,0,0,0 +133,0.2436,1.6184,1.825,-1.09956,0,0,0 +134,0.27639,1.56489,1.85,-0.94248,0,0,0 +135,0.31716,1.51716,1.875,-0.7854,0,0,0 +136,0.36489,1.47639,1.9,-0.62832,0,0,0 +137,0.4184,1.4436,1.925,-0.47124,0,0,0 +138,0.47639,1.41958,1.95,-0.31416,0,0,0 +139,0.53743,1.40492,1.975,-0.15708,0,0,0 +140,0.6,1.4,2.0,0.0,0,0,0 +141,0.6,1.4,2.0,0.0,0,0,0 +142,0.6,1.4,2.0,0.0,0,0,0 +143,0.6,1.4,2.0,0.0,0,0,0 +144,0.6,1.4,2.0,0.0,0,0,0 +145,0.6,1.4,2.0,0.0,0,0,0 +146,0.6,1.4,2.0,0.0,0,0,0 +147,0.6,1.4,2.0,0.0,0,0,0 +148,0.6,1.4,2.0,0.0,0,0,0 +149,0.6,1.4,2.0,0.0,0,0,0 +150,0.6,1.4,2.0,0.0,0,0,0 +151,0.6,1.4,2.0,0.0,0,0,0 +152,0.6,1.4,2.0,0.0,0,0,0 +153,0.6,1.4,2.0,0.0,0,0,0 +154,0.6,1.4,2.0,0.0,0,0,0 +155,0.6,1.4,2.0,0.0,0,0,0 +156,0.6,1.4,2.0,0.0,0,0,0 +157,0.6,1.4,2.0,0.0,0,0,0 +158,0.6,1.4,2.0,0.0,0,0,0 +159,0.6,1.4,2.0,0.0,0,0,0 +160,0.6,1.4,2.0,0.0,0,0,0 diff --git a/tests/animation_test.py b/tests/animation_test.py new file mode 100644 index 0000000..ce0abd7 --- /dev/null +++ b/tests/animation_test.py @@ -0,0 +1,140 @@ +import os +import sys +import shutil +from pytest import approx +import pytest + +# Add parent dir to PATH to import config +import inspect +current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) +parent_dir = os.path.dirname(current_dir) +sys.path.insert(0, parent_dir) +sys.path.insert(0, os.path.join(parent_dir,"Drone")) + +from config import ConfigManager + +config_path = 'animation_config/config' +spec_path = os.path.join(config_path,'spec') +if not os.path.exists(spec_path): + try: + os.makedirs(spec_path) + except OSError: + print("Creation of the directory {} failed".format(spec_path)) + else: + print("Successfully created the directory {}".format(spec_path)) + +shutil.copy("../Drone/config/spec/configspec_client.ini", spec_path) + +config = ConfigManager() +config.load_config_and_spec(os.path.join(config_path,'client.ini')) + +assert config.config_name == "client" + +import animation_lib + +a = animation_lib.Animation() + +def test_animation_1(): + a.update_frames(config, "animation_1.csv") + assert a.id == 'basic' + assert approx(a.original_frames[0].get_pos()) == [0,0,0] + assert a.original_frames[0].get_color() == [204,2,0] + assert a.original_frames[0].pose_is_valid() + assert animation_lib.get_numbers(a.static_begin_frames) == range(1,11) + assert animation_lib.get_numbers(a.takeoff_frames) == range(11,21) + assert animation_lib.get_numbers(a.route_frames) == range(21,31) + assert animation_lib.get_numbers(a.land_frames) == range(31, 41) + assert animation_lib.get_numbers(a.static_end_frames) == range(41, 51) + assert animation_lib.get_numbers(a.output_frames) == range(11,31) + assert approx(a.static_begin_time) == 1 + assert approx(a.takeoff_time) == 1 + assert approx(a.output_frames_min_z) == 0.1 + assert approx(a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6])[0].get_pos()) == [4.,5.,6.3] + assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 6.3 + assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4.,5.,6.3] + +def test_animation_2(): + a.update_frames(config, "animation_2.csv") + assert a.id == 'parad' + assert approx(a.original_frames[271].get_pos()) == [-1.00519,2.65699,0.24386] + assert a.original_frames[271].get_color() == [7,255,0] + assert a.original_frames[271].pose_is_valid() + assert animation_lib.get_numbers(a.static_begin_frames) == range(271) + assert animation_lib.get_numbers(a.takeoff_frames) == range(271,285) + assert animation_lib.get_numbers(a.route_frames) == range(285,1065) + assert animation_lib.get_numbers(a.land_frames) == [] + assert animation_lib.get_numbers(a.static_end_frames) == [] + assert animation_lib.get_numbers(a.output_frames) == range(271, 1065) + assert approx(a.static_begin_time) == 27.1 + assert approx(a.takeoff_time) == 1.4 + assert approx(a.output_frames_min_z) == 0.24386 + assert approx(a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6])[0].get_pos()) == [2.99481, 10.31398, 6.73158] + assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 6.73158 + assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [2.99481, 10.31398, 6.73158] + +def test_animation_3(): + a.update_frames(config, "animation_3.csv") + assert a.id == 'route' + assert approx(a.original_frames[9].get_pos()) == [0.97783,0.0,1.0] + assert a.original_frames[9].get_color() == [0,204,2] + assert a.original_frames[9].pose_is_valid() + assert animation_lib.get_numbers(a.static_begin_frames) == [] + assert animation_lib.get_numbers(a.takeoff_frames) == [] + assert animation_lib.get_numbers(a.route_frames) == range(20,31) + assert animation_lib.get_numbers(a.land_frames) == [] + assert animation_lib.get_numbers(a.static_end_frames) == [] + assert approx(a.static_begin_time) == 0 + assert approx(a.takeoff_time) == 0 + assert approx(a.output_frames_min_z) == 1 + assert approx(a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6])[0].get_pos()) == [4,5,9] + assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 9 + assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4,5,9] + +def test_animation_4(): + a.update_frames(config, "animation_4.csv") + assert a.id == 'two_drones_test' + assert approx(a.original_frames[11].get_pos()) == [0.21774,1.4,1.0] + assert a.original_frames[11].get_color() == [0,0,0] + assert a.original_frames[11].pose_is_valid() + assert animation_lib.get_numbers(a.static_begin_frames) == range(1,12) + assert animation_lib.get_numbers(a.takeoff_frames) == [] + assert animation_lib.get_numbers(a.route_frames) == range(12,141) + assert animation_lib.get_numbers(a.land_frames) == [] + assert animation_lib.get_numbers(a.static_end_frames) == range(141,161) + assert animation_lib.get_numbers(a.output_frames) == range(12,141) + assert approx(a.static_begin_time) == 1.1 + assert approx(a.takeoff_time) == 0 + assert approx(a.output_frames_min_z) == 1 + assert approx(a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6])[0].get_pos()) == [4.21774,7.8,9] + assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 9 + assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4.21774,7.8,9] + +def test_animation_no_file(): + a.update_frames(config, "zzz.csv") + assert a.id == None + assert a.original_frames == [] + assert a.output_frames == [] + assert animation_lib.get_numbers(a.static_begin_frames) == [] + assert animation_lib.get_numbers(a.takeoff_frames) == [] + assert animation_lib.get_numbers(a.route_frames) == [] + assert animation_lib.get_numbers(a.land_frames) == [] + assert animation_lib.get_numbers(a.static_end_frames) == [] + assert a.static_begin_time == 0 + assert a.takeoff_time == 0 + assert a.output_frames_min_z is None + assert a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6]) == [] + assert a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6]) is None + assert a.get_start_point(ratio=[1,2,3], offset=[4,5,6]) == [] + + +# print animation_lib.get_numbers(a.static_begin_frames) +# print animation_lib.get_numbers(a.takeoff_frames) +# print animation_lib.get_numbers(a.route_frames) +# print animation_lib.get_numbers(a.land_frames) +# print animation_lib.get_numbers(a.static_end_frames) +# print animation_lib.get_numbers(a.output_frames) +# print a.static_begin_time +# print a.takeoff_time +# print a.output_frames_min_z + +shutil.rmtree('animation_config') diff --git a/update_configspec.py b/update_configspec.py index 590b32d..e1d2bf2 100644 --- a/update_configspec.py +++ b/update_configspec.py @@ -1,14 +1,34 @@ +import os +import shutil import config from Server.copter_table_models import CopterDataModel -cfg_server = config.ConfigObj('SERVER/config/spec/configspec_server.ini', list_values=False) -widths = {"copter_id": 150} -default_width = 100 +from config import ConfigManager, ConfigObj -default = {key: f"preset_param(default=list(True, {widths.get(key, default_width)}))" +config_path = 'temp_config/config' +spec_path = os.path.join(config_path,'spec') +if not os.path.exists(spec_path): + try: + os.makedirs(spec_path) + except OSError: + print("Creation of the directory {} failed".format(spec_path)) + else: + print("Successfully created the directory {}".format(spec_path)) + +shutil.copy("Server/config/spec/configspec_server.ini", spec_path) + +config = ConfigManager() +config.load_config_and_spec(os.path.join(config_path,'server.ini')) + +preset_params = config.table_presets_default +default_param = (True, 100) + +default = {key: f"preset_param(default=list{preset_params.get(key, default_param)})" for key in CopterDataModel.columns} +cfg_server = ConfigObj('Server/config/spec/configspec_server.ini', list_values=False) cfg_server['TABLE']['PRESETS']['DEFAULT'] = default - cfg_server.write() -print('Server configspec updated') + +print('Server configspec updated!') +shutil.rmtree('temp_config')