diff --git a/armstron/config/test_profiles/simple_pull_test2.yaml b/armstron/config/test_profiles/simple_pull_test2.yaml new file mode 100644 index 0000000..ebfcb26 --- /dev/null +++ b/armstron/config/test_profiles/simple_pull_test2.yaml @@ -0,0 +1,16 @@ +params: + preload: + - {balance: pose} + - jog: + angular: [0.0, 0.0, 0.0] + linear: [0.0, 0.0, -0.003] + stop_conditions: {min_force_z: -60.0} + - pose: + orientation: [0.0, 0.0, 0.0] + position: [0.0, 0.0, 0.0] + test: + - jog: + angular: [0.0, 0.0, 0.0] + linear: [0.0, 0.0, 0.001] + stop_conditions: {max_position_z: 0.02} +type: sequence diff --git a/armstron/scripts/gui.py b/armstron/scripts/gui.py index 6c928c2..31fda38 100755 --- a/armstron/scripts/gui.py +++ b/armstron/scripts/gui.py @@ -208,6 +208,7 @@ def init_profile_handler(self, parent): ) self.profile_handler.set_callback('open_after',self.update_config) self.profile_handler.set_callback('saveas_before',self.get_config_from_gui) + self.profile_handler.set_callback('saveas_after',lambda : self.profile_handler.open_file(direct=True)) self.save_handler = ProfileHandler( diff --git a/armstron/src/armstron/gui/profile_editor.py b/armstron/src/armstron/gui/profile_editor.py index 8812747..7e8accc 100644 --- a/armstron/src/armstron/gui/profile_editor.py +++ b/armstron/src/armstron/gui/profile_editor.py @@ -14,7 +14,7 @@ from ttkthemes import ThemedTk -from armstron.gui.utils import Spinbox, OptionSwitcher +from armstron.gui.utils import Spinbox, OptionSwitcher, ScrollbarLabelFrame class ProfileEditor: @@ -347,6 +347,13 @@ def _make_input_group(self, parent, config, vars, index): condition['signal'], sorted(self.stop_values.keys())) + def cb (*args): + cond.update_options(self.stop_values[var['signal'].get()]) + fr_stop_inner.configure(bg=self.colors[var['signal'].get()][1]) + + var['signal'].trace("w", cb) + # cond.update_options(self.stop_values[value]) + box = Spinbox(fr_stop_inner, textvariable=var['value']) box.set(condition['value']) cond.pack(expand=False, fill="y", side='left') @@ -491,7 +498,11 @@ def _init_inputs(self, parent, profile, var_tree): preload_vars = var_tree['params'].get('preload') test_vars = var_tree['params'].get('test') - fr_preload = tk.LabelFrame(self.fr_buttons, text="Preload", font=('Arial', 12, 'bold'), bd=2) + sbf = ScrollbarLabelFrame(self.fr_buttons, text="Preload", font=('Arial', 12, 'bold'), bd=2) + sbf.pack(expand=True, fill="both", padx=5, pady=5, side='left') + #canvas = Scrollable(self.fr_buttons) + + fr_preload = sbf.scrolled_frame #tk.LabelFrame(sbf.scrolled_frame, text="Preload", font=('Arial', 12, 'bold'), bd=2) for idx,seg in enumerate(preload): fr_step=tk.Frame(fr_preload) fr_step.pack(expand=False, fill='both', side='top') @@ -509,9 +520,15 @@ def _init_inputs(self, parent, profile, var_tree): fr_ctrl.pack(expand=False, fill="both", padx=5, pady=5, side='left') fr.pack(expand=False, fill="both", padx=5, pady=5, side='left') - fr_preload.pack(expand=False, fill="both", padx=5, pady=5, side='left') + #fr_preload.pack(expand=False, fill="both", padx=5, pady=5, side='left') + + sbf2 = ScrollbarLabelFrame(self.fr_buttons, text="Preload", font=('Arial', 12, 'bold'), bd=2) + sbf2.pack(expand=True, fill="both", padx=5, pady=5, side='left') + #canvas = Scrollable(self.fr_buttons) + + fr_test = sbf2.scrolled_frame - fr_test = tk.LabelFrame(self.fr_buttons ,text="Main Test", font=('Arial', 12, 'bold'), bd=2) + #fr_test = tk.LabelFrame(self.fr_buttons ,text="Main Test", font=('Arial', 12, 'bold'), bd=2) for idx,seg in enumerate(test): fr_step=tk.Frame(fr_test) fr_step.pack(expand=False, fill='both', side='top') @@ -529,9 +546,9 @@ def _init_inputs(self, parent, profile, var_tree): fr_ctrl.pack(expand=False, fill="both", padx=5, pady=5, side='left') fr.pack(expand=False, fill="both", padx=5, pady=5, side='left') - fr_test.pack(expand=False, fill="both", padx=5, pady=5, side='left') + #fr_test.pack(expand=False, fill="both", padx=5, pady=5, side='left') - self.fr_buttons.pack(expand=False, fill="x", side='top') + self.fr_buttons.pack(expand=True, fill="both", side='top') def __del__(self): diff --git a/armstron/src/armstron/gui/profile_handler.py b/armstron/src/armstron/gui/profile_handler.py index 4a9aaca..5cebaf6 100644 --- a/armstron/src/armstron/gui/profile_handler.py +++ b/armstron/src/armstron/gui/profile_handler.py @@ -107,19 +107,21 @@ def _check_enable_buttons(self): self.buttons['saveas'].configure(state='disabled') - def open_file(self): + def open_file(self, direct=False): self.callbacks['open_before']() - filepath = fdialog.askopenfilename( - filetypes=self.file_types, - initialdir=self.curr_config_file['dirname'], - initialfile=self.curr_config_file['basename'] - ) - if not filepath: - return None - self.curr_config_file['basename'] = os.path.basename(filepath) - self.curr_config_file['dirname'] = os.path.dirname(filepath) + if not direct: + filepath = fdialog.askopenfilename( + filetypes=self.file_types, + initialdir=self.curr_config_file['dirname'], + initialfile=self.curr_config_file['basename'] + ) + if not filepath: + return None + self.curr_config_file['basename'] = os.path.basename(filepath) + self.curr_config_file['dirname'] = os.path.dirname(filepath) + self.load_file() self._check_enable_buttons() diff --git a/armstron/src/armstron/gui/utils.py b/armstron/src/armstron/gui/utils.py index 8354d07..3aa987a 100644 --- a/armstron/src/armstron/gui/utils.py +++ b/armstron/src/armstron/gui/utils.py @@ -3,6 +3,8 @@ import os import sys import copy +import time +import numpy as np if sys.version_info[0] == 3: import tkinter as tk import tkinter.ttk as ttk @@ -35,10 +37,162 @@ def set(self, value): class OptionSwitcher(ttk.OptionMenu): def __init__(self, container, variable, default=None, *values, **kwargs): - fill_option = "< Choose >" - values_cp = copy.deepcopy(*values) - values_cp.insert(0, fill_option) + self.container=container + self.variable=variable + self.default=default + self.fill_option = "< Choose >" + self.values=self._add_fill_option(*values) + if default is None: - default = values_cp[0] + default = self.values[0] + + ttk.OptionMenu.__init__(self,container,variable, default, *self.values, **kwargs) + + + def _add_fill_option(self,options): + values_cp = copy.deepcopy(options) + values_cp.insert(0, self.fill_option) + return values_cp + + + def update_options(self, options): + self.values=self._add_fill_option(options) + + self['menu'].delete(0, "end") + for string in self.values: + self['menu'].add_command(label=string, + command=lambda value=string: self.variable.set(value)) + + self.variable.set(self.fill_option) + + def get_options(self): + return self.values[1:] + + +class Scrollable(tk.Frame): + """ + Make a frame scrollable with scrollbar on the right. + After adding or removing widgets to the scrollable frame, + call the update() method to refresh the scrollable area. + """ + + def __init__(self, frame, width=16): + + scrollbar = tk.Scrollbar(frame, width=width) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False) + + self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set) + self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + scrollbar.config(command=self.canvas.yview) + + self.canvas.bind('', self.__fill_canvas) + + # base class initialization + tk.Frame.__init__(self, frame) + + # assign this obj (the inner frame) to the windows item of the canvas + self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW) + + + def __fill_canvas(self, event): + "Enlarge the windows item to the canvas width" + + canvas_width = event.width + self.canvas.itemconfig(self.windows_item, width = canvas_width) + + def update(self): + "Update the canvas and the scrollregion" + + self.update_idletasks() + + +class ScrollbarFrame(tk.Frame): + """ + Extends class tk.Frame to support a scrollable Frame + This class is independent from the widgets to be scrolled and + can be used to replace a standard tk.Frame + """ + def __init__(self, parent, **kwargs): + tk.Frame.__init__(self, parent, **kwargs) + + # The Scrollbar, layout to the right + vsb = tk.Scrollbar(self, orient="vertical") + vsb.pack(side="right", fill="y") + + # The Canvas which supports the Scrollbar Interface, layout to the left + self.canvas = tk.Canvas(self, borderwidth=0) + self.canvas.pack(side="left", fill="both", expand=True) + + # Bind the Scrollbar to the self.canvas Scrollbar Interface + def cb(*args): + print(args) + val=float(list(args)[1]) + for i in range(20): + self.canvas.yview(args[0],np.ceil(val/20.0), args[2]) + time.sleep(0.05) + + #self.canvas.configure(yscrollcommand=cb) + self.canvas.configure(yscrollcommand=vsb.set) + #self.canvas.configure(scrollregion=self.canvas.bbox("all")) + vsb.configure(command=self.canvas.yview) + + # The Frame to be scrolled, layout into the canvas + # All widgets to be scrolled have to use this Frame as parent + self.scrolled_frame = tk.Frame(self.canvas, background=self.canvas.cget('bg')) + self.canvas.create_window((4, 4), window=self.scrolled_frame, anchor="nw") + + # Configures the scrollregion of the Canvas dynamically + self.scrolled_frame.bind("", self.on_configure) + + def on_configure(self, event): + """Set the scroll region to encompass the scrolled frame""" + self.canvas.configure(scrollregion=self.canvas.bbox("all")) + + +class ScrollbarLabelFrame(tk.LabelFrame): + """ + Extends class tk.Frame to support a scrollable Frame + This class is independent from the widgets to be scrolled and + can be used to replace a standard tk.Frame + """ + def __init__(self, parent, **kwargs): + tk.LabelFrame.__init__(self, parent, **kwargs) + + # The Scrollbar, layout to the right + vsb = tk.Scrollbar(self, orient="vertical") + vsb.pack(side="right", fill="y") + + # The Canvas which supports the Scrollbar Interface, layout to the left + self.canvas = tk.Canvas(self, borderwidth=0) + self.canvas.pack(side="left", fill="both", expand=True) + + # Bind the Scrollbar to the self.canvas Scrollbar Interface + def cb(*args): + if args[0] =='moveto': + self.canvas.yview(*args) + + else: + val=float(args[1]) + args = list(args) + args[1] = int(np.sign(val)*np.ceil(abs(val)/5.0)) + self.canvas.yview(*tuple(args)) + + + #self.canvas.configure(yscrollcommand=cb) + self.canvas.configure(yscrollcommand=vsb.set) + #self.canvas.configure(scrollregion=self.canvas.bbox("all")) + vsb.configure(command=cb) + #vsb.configure(command=self.canvas.yview) + + # The Frame to be scrolled, layout into the canvas + # All widgets to be scrolled have to use this Frame as parent + self.scrolled_frame = tk.Frame(self.canvas, background=self.canvas.cget('bg')) + self.canvas.create_window((4, 4), window=self.scrolled_frame, anchor="nw") + + # Configures the scrollregion of the Canvas dynamically + self.scrolled_frame.bind("", self.on_configure) - ttk.OptionMenu.__init__(self, container,variable, default, *values_cp, **kwargs) \ No newline at end of file + def on_configure(self, event): + """Set the scroll region to encompass the scrolled frame""" + self.canvas.configure(scrollregion=self.canvas.bbox("all")) \ No newline at end of file