--- a +++ b/app/LeapGui.py @@ -0,0 +1,515 @@ +import json +from multiprocessing import Process +import os + +import LeapRecord +from AnyPy import AnyPy +from config.Configuration import env +from BVHAnimation import bvh_animation +from resources.pymo.pymo.parsers import BVHParser as Pymo_BVHParser +from gooey.gui import application +from gooey.gui import processor +from gooey.gui.containers import application as containers_application +from gooey.python_bindings import gooey_decorator, gooey_parser + +# strings for the actions in the Gooey side-menu +ACTION_RECORD = 'Record' +ACTION_ANYBODY = 'AnyBody' +ACTION_CONVERTER = 'Converter' +ACTION_ANIMATION = 'Animation' +EXECUTED_COMMAND = 'command' + + +class GooeyModification: + # overwrite the Gooey default stop method to cancel Leap Motion recording + def on_stop(self): + """Overload the stop method to allow stopping of Leap Motion Recording""" + executed_command = LeapGui.StoredArgs().load().stored_args[EXECUTED_COMMAND] + if executed_command == ACTION_RECORD: + return self.clientRunner.stop_leap() + self.clientRunner.stop() + + def stop_leap(self): + if not self._process.stdin.closed: + """Send Keyboard Interrupt to stop Leap Motion recording and parse bvh / interpolation""" + self._process.stdin.write(b"\n") + self._process.stdin.close() + return + self.stop() + + # overwrite classes and methods of the stop button + containers_application.GooeyApplication.onStopExecution = on_stop + processor.ProcessController.stop_leap = stop_leap + + containers_application.ProcessController = processor.ProcessController + application.GooeyApplication = containers_application.GooeyApplication + gooey_decorator.application = application + + # create alias + Gooey = gooey_decorator.Gooey + GooeyParser = gooey_parser.GooeyParser + + +class LeapGui: + """Implementation of the Gooey including some adaptions""" + + Gooey = GooeyModification.Gooey + GooeyParser = GooeyModification.GooeyParser + + @staticmethod + @Gooey(program_name="Leap Motion Recorder (c) Robin, Sean", + sidebar_title='Actions', + # return_to_config=True, + image_dir='config/gooey', + tabbed_groups=True, + default_size=(1000, 700)) + def parse_args(): + """ Use GooeyParser to build up the arguments we will use in our script + Save the arguments in a default json file so that we can retrieve them + every time we run the script. + """ + + # load default values from json (saved from last run) + stored_args = LeapGui.StoredArgs().load() + + parser = LeapGui.GooeyParser(description='Record Leap Motion data and export to BVH or AnyBody') + env.add_parser(parser) + subs = parser.add_subparsers(help='Tools', dest='command') + + # === record === # + record_parser = subs.add_parser(ACTION_RECORD, help='Leap Recording') + + # settings Group + settings_group = record_parser.add_argument_group( + "Settings", + gooey_options={ + 'show_border': True, + 'columns': 1 + } + ) + + basis_group = settings_group.add_mutually_exclusive_group( + required=True, + gooey_options={ + 'initial_selection': 0 + }) + + settings_group.add_argument('frames_per_second', + metavar='Frames per second', + action='store', + default=stored_args.get(ACTION_RECORD, 'frames_per_second', '30'), + gooey_options={ + 'validator': { + 'test': '1 <= int(user_input) <= 150', + 'message': 'Must be between 1 and 150' + } + } + ) + + settings_group.add_argument('-show_animation', + metavar='Animate', + help='Show motion animation after recording', + action='store_true') + + basis_group.add_argument('-anybody_basis', + metavar='Calculate joint angles to AnyBody basis', + action='store_true') + + basis_group.add_argument('-firstframe_basis', + metavar='Calculate joint angles to initial recorded basis (first frame)', + action='store_true') + + # bvh Group + bvh_group = record_parser.add_argument_group( + "BVH Export", + gooey_options={ + 'show_border': True, + 'columns': 1 + } + ) + + bvh_group.add_argument('-bvh', + metavar='Write BVH-File', + action='store_true') + + bvh_group.add_argument('-bvh_path', + metavar='BVH File', + action='store', + default=stored_args.get( + ACTION_RECORD, 'bvh_path', LeapGui.StoredArgs.path('../output/BVH/RightHand.bvh')), + widget='FileSaver', + help='Choose location, where to save the BVH File') + + bvh_group.add_argument('channels', + metavar='BVH Channels', + action='store', + default=stored_args.get(ACTION_RECORD, 'channels', 'rotation'), + widget='Dropdown', + help='Rotation: (X,Y,Z) rotation only (default)\n' + 'Position: (X,Y,Z) rotation and position for all channels', + choices=['rotation', 'position'], + gooey_options={ + 'validator': { + 'test': 'user_input != "Select Option"', + 'message': 'Choose a channel setting' + } + }) + + # interpol Group + interpol_group = record_parser.add_argument_group( + "Interpolation Vector", + gooey_options={ + 'show_border': True, + 'columns': 1 + } + ) + interpol_group.add_argument('-anybody', + metavar='Write interpolation files for AnyBody', + action='store_true') + + interpol_group.add_argument('-anybody_template_path', + metavar='AnyBody templates', + action='store', + default=stored_args.get( + ACTION_RECORD, 'anybody_template_path', + LeapGui.StoredArgs.path('config/anybody_templates')), + widget='DirChooser', + help='Source directory that contains *.template files for AnyBody') + + interpol_group.add_argument('-anybody_output_path', + metavar='Output directory', + action='store', + default=stored_args.get( + ACTION_RECORD, 'anybody_output_path', + LeapGui.StoredArgs.path('../output/Anybody')), + widget='DirChooser', + help='Output directory for interpolation files') + + # # c3d Group + # c3d_group = record_parser.add_argument_group( + # "C3D", + # gooey_options={ + # 'show_border': True, + # 'columns': 2 + # } + # ) + # c3d_group.add_argument('-c3d', + # metavar='Write C3D-File', + # action='store_true') + # + # c3d_group.add_argument('-c3d_filename', + # metavar=' ', + # action='store', + # default=stored_args.get(ACTION_RECORD, 'c3d_filename', 'RightHand'), + # help='Filename') + # + # c3d_group.add_argument('-c3d_path', + # metavar=' ', + # action='store', + # default=stored_args.get( + # ACTION_RECORD, 'c3d_path', LeapGui.StoredArgs.path('../output/C3D')), + # widget='DirChooser', + # help='Output directory for c3d file') + + # === anybody === # + anybody_parser = subs.add_parser(ACTION_ANYBODY, help='Anybody Simulation') + anybody_group = anybody_parser.add_argument_group( + "Source files", + "Make a selection for the source files used for the AnyBody analysis", + gooey_options={ + 'show_border': True, + 'columns': 1 + } + ) + anybody_file_group = anybody_group.add_mutually_exclusive_group( + required=True, + gooey_options={ + 'initial_selection': 0 + }) + + anybody_file_group.add_argument('-any_interpol_files', + metavar='Use existing vector files (in {})'.format(AnyPy.INTERPOL_DIR), + help='Use interpolation vector files from AnyBody project default directory', + action='store_true') + + anybody_file_group.add_argument('-any_bvh_file', + metavar='Source of the *.bvh file', + action='store', + default=stored_args.get( + ACTION_ANYBODY, 'any_bvh_file', + LeapGui.StoredArgs.path('../output/BVH/RightHand.bvh')), + widget='FileChooser', + help='Choose a bvh file to be converted to the interpolation vector files') + + anybody_file_group.add_argument('-any_files_dir', + metavar='Source (.any)', + action='store', + default=stored_args.get( + ACTION_ANYBODY, 'any_files_dir', + LeapGui.StoredArgs.path('../output/Anybody')), + widget='DirChooser', + help='Source directory that contains interpolation *.any files for AnyBody') + + anybody_group.add_argument('any_main_file', + metavar='Source of HAND.Main.any', + action='store', + default=stored_args.get(ACTION_ANYBODY, 'any_main_file', ''), + widget='FileChooser', + help='Choose the main AnyBody file for the calculation') + + anybody_group.add_argument('-start_frame', + metavar='Start Frame', + help='default: 1', + action='store', + # default=stored_args.get(ACTION_ANYBODY, 'start_frame', '1'), + gooey_options={ + 'validator': { + 'test': '1 <= int(user_input)', + 'message': 'Must be greater or equal than 1' + } + }, + type=int) + + anybody_group.add_argument('-end_frame', + metavar='End Frame', + help='default: end', + action='store', + # default=stored_args.get(ACTION_ANYBODY, 'end_frame', 'end'), + gooey_options={ + 'validator': { + 'test': '("end" in user_input.lower()) or (1 <= int(user_input))', + 'message': 'Must be a positive value and greater than the start frame' + } + }) + + operation_group = anybody_parser.add_argument_group( + "Operations", + "Select which operations should be executed by AnyBody", + gooey_options={ + 'show_border': True, + 'columns': 1 + } + ) + + operation_group.add_argument('-load', + metavar='Load AnyBody model', + action='store_true') + + operation_group.add_argument('-initial_conditions', + metavar='Calc initial conditions', + action='store_true') + + operation_group.add_argument('-kinematic', + metavar='Calc kinematic analysis', + action='store_true') + + operation_group.add_argument('-inverse_dynamics', + metavar='Calc inverse dynamics', + action='store_true') + + operation_group.add_argument('-nstep', + metavar='Time steps', + help='Number of equally spaced time steps\n' + '(leave empty for not changing the setting)', + action='store', + # default=stored_args.get(ACTION_ANYBODY, 'nstep', '50'), + gooey_options={ + 'validator': { + 'test': '1 <= int(user_input)', + 'message': 'Must be greater or equal than 1' + } + }, + type=int) + + # operation_group.add_argument('-order', + # metavar='Order of B-spline interpolation', + # help='Interpolates between the data points with a B-spline using this order' + # '\n(leave empty for using the default value)', + # action='store', + # # default=stored_args.get(ACTION_ANYBODY, 'order', '4'), + # gooey_options={ + # 'validator': { + # 'test': '1 <= int(user_input)', + # 'message': 'Must be greater or equal than 1' + # } + # }, + # type=int) + + result_group = anybody_parser.add_argument_group( + "Results", + gooey_options={ + 'show_border': True, + 'columns': 1 + } + ) + + result_group.add_argument('-plot', + metavar='Open the plot after the analysis', + action='store_true') + + result_group.add_argument('-output_file_path', + metavar='Save .anydata.h5 file', + action='store', + default=stored_args.get( + ACTION_ANYBODY, 'output_file_path', + LeapGui.StoredArgs.path('../output/Anybody/FreeHand.anydata.h5')), + widget='FileSaver', + help='Save .anydata.h5 file to save the analysis results') + + result_group.add_argument('-replay_output', + metavar='Open AnyBody and load the results', + action='store_true') + + # === converter === # + converter_parser = subs.add_parser(ACTION_CONVERTER, help='Convert a BVH-File in .any-Files') + converter_group = converter_parser.add_argument_group( + "Converter", + "Convert a BVH-File in .any-Files", + gooey_options={ + 'show_border': True, + 'columns': 1 + } + ) + + converter_group.add_argument('bvh_file', + metavar='Source: *.bvh', + action='store', + default=stored_args.get( + ACTION_CONVERTER, 'bvh_file', + LeapGui.StoredArgs.path('../output/BVH/RightHand.bvh')), + widget='FileChooser', + help='Source bvh-file to convert') + + # converter_group.add_argument('-any_file', + # metavar='Convert to .any files', + # action='store_true') + + # converter_group.add_argument('-c3d', + # metavar='Convert to .c3d files', + # action='store_true') + + converter_group.add_argument('file_dir', + metavar='Target: store files', + action='store', + default=stored_args.get( + ACTION_CONVERTER, 'file_dir', LeapGui.StoredArgs.path('../output')), + widget='DirChooser', + help='Directory to store the converted files') + + # === bvh animation === # + animation_parser = subs.add_parser(ACTION_ANIMATION, help='Show an animation for a BVH file') + animation_group = animation_parser.add_argument_group( + "Animation", + "Select a BVH file to be animated", + gooey_options={ + 'show_border': True, + 'columns': 1 + } + ) + + animation_group.add_argument('bvh_animation', + metavar='BVH file path', + action='store', + default=stored_args.get( + ACTION_ANIMATION, 'bvh_animation', + LeapGui.StoredArgs.path('../output/BVH/RightHand.bvh')), + widget='FileChooser') + + # start the UI and save arguments to json for next run + stored_args.save(parser.parse_args()) + + class StoredArgs: + """class for loading and saving arguments from/to json, also to handle default values""" + + def __init__(self): + self.stored_args = {} + self.loaded_actions = {} + # get the script name without the extension & use it to build up the json filename + script_name = os.path.splitext(os.path.basename(__file__))[0] + self.args_file = "{}-args.json".format(script_name) + + def load(self): + # Read in the prior arguments as a dictionary + if os.path.isfile(self.args_file): + with open(self.args_file) as data_file: + self.stored_args = json.load(data_file) + self.loaded_actions = self.stored_args.keys() + return self + + def get(self, action, arg, default): + return self.stored_args[action].get(arg) if action in self.loaded_actions else default + + @staticmethod + def path(relative_path): + return os.path.normpath(os.path.join(os.getcwd(), relative_path)) + + def save(self, args): + # Store the values of the arguments to the environment to access them in the code + env.save_arguments(args) + # Store the values of the arguments so we have them next time we run + with open(self.args_file, 'w') as data_file: + # Using vars(args) returns the data as a dictionary + self.stored_args[EXECUTED_COMMAND] = args.command + self.stored_args[args.command] = vars(args) + json.dump(self.stored_args, data_file) + + @staticmethod + def run(): + """Open the Gooey GUI and then run the selected action with the chosen arguments""" + LeapGui.parse_args() + + # Record, Anybody, Converter + if env.config.command == ACTION_RECORD: + from GuiControl import GuiControl + gui = GuiControl() + gui.set_windows_record() + + import time + countdown = 5 + for ii in range(countdown): + print("Record starting in {} seconds ...".format(countdown-ii)) + time.sleep(1) + + LeapRecord.start_recording() + + gui.end_record() + print("End of recording\n") + + if env.config.show_animation: + print("Loading the animation ...") + p = Process(target=bvh_animation.animate) + p.start() + # wait for bvh_animation to be closed + p.join() + return True + + if env.config.command == ACTION_ANYBODY: + from LogWatcher import log_watcher + anypy = AnyPy(env.config.any_main_file, env.config.any_files_dir) + log_watcher.start(os.path.join(anypy.any_path, anypy.LOG_FILE)) + run_status = anypy.run() + log_watcher.stop() + if not run_status: + # AnyBody operations were not successful + return False + if env.config.replay_output: + anypy.post_operations() + if env.config.plot: + anypy.plot() + return True + + if env.config.command == ACTION_CONVERTER: + from AnyWriter import AnyWriter + any_writer = AnyWriter(template_directory='config/anybody_templates/', + output_directory=env.config.file_dir + '/') + any_writer.write(Pymo_BVHParser().parse(env.config.bvh_file)) + return True + + if env.config.command == ACTION_ANIMATION: + print("Loading the animation ...") + bvh_animation.bvh_data = Pymo_BVHParser().parse(env.config.bvh_animation) + bvh_animation.animate() + + +if __name__ == "__main__": + LeapGui.run()