From e8569ee73adb7eab1e7eb64619762649c2057f1d Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Thu, 14 Jul 2022 16:38:29 +0530 Subject: [PATCH 01/59] adding polyline class --- docs/tutorials/02_ui/viz_polyline.py | 35 ++++++++++ fury/ui/elements.py | 95 +++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 docs/tutorials/02_ui/viz_polyline.py diff --git a/docs/tutorials/02_ui/viz_polyline.py b/docs/tutorials/02_ui/viz_polyline.py new file mode 100644 index 000000000..9205453ad --- /dev/null +++ b/docs/tutorials/02_ui/viz_polyline.py @@ -0,0 +1,35 @@ +""" +=========== +PolyLine UI +=========== + +This example shows how to use the Polyline UI. + +First, some imports. +""" +from fury import ui, window + +######################################################################### +# We then create a Polyline Object. + +polyline = ui.PolyLine(points_data=[(100, 100), (100, 150), + (200, 200), (590, 230), (230, 50), (100, 100)]) + +############################################################################### +# Show Manager +# ============ +# +# Now we add DrawPanel to the scene. + +current_size = (600, 600) +showm = window.ShowManager(size=current_size, title="PolyLine UI Example") + +showm.scene.add(polyline) + +interactive = 1 + +if interactive: + showm.start() + +window.record(showm.scene, size=current_size, + out_path="viz_polyline.png") diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 37a9b5ec5..4f2682f07 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -2,7 +2,7 @@ __all__ = ["TextBox2D", "LineSlider2D", "LineDoubleSlider2D", "RingSlider2D", "RangeSlider", "Checkbox", "Option", "RadioButton", - "ComboBox2D", "ListBox2D", "ListBoxItem2D", "FileMenu2D", + "ComboBox2D", "ListBox2D", "ListBoxItem2D", "FileMenu2D", "PolyLine", "DrawShape", "DrawPanel"] import os @@ -3060,6 +3060,99 @@ def directory_click_callback(self, i_ren, _obj, listboxitem): i_ren.event.abort() +class PolyLine(UI): + """Create a Polyline. + """ + + def __init__(self, points_data=None, line_width=1, color=(1, 1, 1)): + """Init this UI element. + + Parameters + ---------- + position : (float, float), optional + (x, y) in pixels. + """ + if points_data is None: + self.points = [] + self.points = points_data + self.line_width = line_width + self.lines = [] + self.previous_point = None + self.current_point = None + self.in_process = False + self.color = color + super(PolyLine, self).__init__() + + def _setup(self): + """Setup this UI component. + + Create a Polyline. + """ + for i in range(len(self.points) - 1): + self.add_point(self.points[i], initialize=True) + self.add_point(self.points[-1], initialize=True) + self.lines.pop() + + def _get_actors(self): + """Get the actors composing this UI component.""" + pass + + def _add_to_scene(self, scene): + """Add all subcomponents or VTK props that compose this UI component. + + Parameters + ---------- + scene : scene + + """ + self._scene = scene + scene.add(*self.lines) + + def _get_size(self): + pass + + def _set_position(self, coords): + """Set the lower-left corner position of this UI component. + + Parameters + ---------- + coords: (float, float) + Absolute pixel coordinates (x, y). + """ + pass + + def resize(self, size): + hyp = np.hypot(size[0], size[1]) + self.lines[-1].resize((hyp, self.line_width)) + self.rotate(angle=np.arctan2(size[1], size[0])) + + def rotate(self, angle): + """Rotate the vertices of the UI component using specific angle. + + Parameters + ---------- + angle: float + Value by which the vertices are rotated in radian. + """ + points_arr = vertices_from_actor(self.lines[-1].actor) + rotation_matrix = np.array( + [[np.cos(angle), np.sin(angle), 0], + [-np.sin(angle), np.cos(angle), 0], [0, 0, 1]]) + new_points_arr = np.matmul(points_arr, rotation_matrix) + set_polydata_vertices(self.lines[-1]._polygonPolyData, new_points_arr) + update_actor(self.lines[-1].actor) + + def add_point(self, point, initialize=False): + if self.previous_point is not None: + self.resize(np.asarray(point) - self.current_point) + line = Rectangle2D((self.line_width, self.line_width), position=point, color=self.color) + self.lines.append(line) + if not initialize: + self._scene.add(line) + self.previous_point = self.current_point if self.current_point is not None else point + self.current_point = point + + class DrawShape(UI): """Create and Manage 2D Shapes. """ From 72011f6c61154b712f3b3df4456470baaf823812 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 15 Jul 2022 14:57:28 +0530 Subject: [PATCH 02/59] adding tutorial file --- docs/tutorials/02_ui/viz_polyline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/02_ui/viz_polyline.py b/docs/tutorials/02_ui/viz_polyline.py index 9205453ad..1e35b52b0 100644 --- a/docs/tutorials/02_ui/viz_polyline.py +++ b/docs/tutorials/02_ui/viz_polyline.py @@ -13,7 +13,7 @@ # We then create a Polyline Object. polyline = ui.PolyLine(points_data=[(100, 100), (100, 150), - (200, 200), (590, 230), (230, 50), (100, 100)]) + (200, 200), (590, 230), (230, 50), (100, 100)], line_width=3) ############################################################################### # Show Manager From 54cedb7da8e28a650f2191e270eb8d718d2d4b5c Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 15 Jul 2022 14:57:48 +0530 Subject: [PATCH 03/59] adding poluline to the DrawShape --- fury/ui/elements.py | 127 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 101 insertions(+), 26 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 4f2682f07..d7c4459a9 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3064,7 +3064,7 @@ class PolyLine(UI): """Create a Polyline. """ - def __init__(self, points_data=None, line_width=1, color=(1, 1, 1)): + def __init__(self, points_data=None, line_width=3, color=(1, 1, 1)): """Init this UI element. Parameters @@ -3072,15 +3072,17 @@ def __init__(self, points_data=None, line_width=1, color=(1, 1, 1)): position : (float, float), optional (x, y) in pixels. """ - if points_data is None: - self.points = [] + print("create Polyline") self.points = points_data + if points_data is None: + self.points = [(0, 0)] self.line_width = line_width self.lines = [] self.previous_point = None self.current_point = None self.in_process = False self.color = color + self.current_line = None super(PolyLine, self).__init__() def _setup(self): @@ -3088,10 +3090,18 @@ def _setup(self): Create a Polyline. """ - for i in range(len(self.points) - 1): - self.add_point(self.points[i], initialize=True) - self.add_point(self.points[-1], initialize=True) - self.lines.pop() + line = Rectangle2D((self.line_width, self.line_width), position=self.points[0]) + + line.on_left_mouse_button_pressed = self.left_button_pressed + line.on_left_mouse_button_dragged = self.left_button_dragged + + self.current_line = line + self.lines.append(line) + + if len(self.points) > 2: + for point in self.points[1:]: + self.add_point(point) + self.lines.pop() def _get_actors(self): """Get the actors composing this UI component.""" @@ -3119,11 +3129,12 @@ def _set_position(self, coords): coords: (float, float) Absolute pixel coordinates (x, y). """ - pass + self.lines[0].position = coords + # update other points def resize(self, size): hyp = np.hypot(size[0], size[1]) - self.lines[-1].resize((hyp, self.line_width)) + self.current_line.resize((hyp, self.line_width)) self.rotate(angle=np.arctan2(size[1], size[0])) def rotate(self, angle): @@ -3134,23 +3145,54 @@ def rotate(self, angle): angle: float Value by which the vertices are rotated in radian. """ - points_arr = vertices_from_actor(self.lines[-1].actor) + points_arr = vertices_from_actor(self.current_line.actor) rotation_matrix = np.array( [[np.cos(angle), np.sin(angle), 0], [-np.sin(angle), np.cos(angle), 0], [0, 0, 1]]) new_points_arr = np.matmul(points_arr, rotation_matrix) - set_polydata_vertices(self.lines[-1]._polygonPolyData, new_points_arr) - update_actor(self.lines[-1].actor) - - def add_point(self, point, initialize=False): - if self.previous_point is not None: - self.resize(np.asarray(point) - self.current_point) - line = Rectangle2D((self.line_width, self.line_width), position=point, color=self.color) - self.lines.append(line) - if not initialize: - self._scene.add(line) - self.previous_point = self.current_point if self.current_point is not None else point - self.current_point = point + set_polydata_vertices(self.current_line._polygonPolyData, new_points_arr) + update_actor(self.current_line.actor) + + # def delete(self): + # self.current_line = None + # self.lines.pop() + # self._scene.rm(self.current_line) + + def calculate_vertices(self): + vertices = np.empty((0, 2), int) + for line in self.lines: + vertices = np.append(vertices, line.position + + vertices_from_actor(line.actor)[:, :-1], axis=0) + return vertices + + def add_point(self, point, interactive=False): + self.resize(np.asarray(point) - self.current_line.position) + + new_line = Rectangle2D((self.line_width, self.line_width), position=point, color=self.color) + new_line.on_left_mouse_button_pressed = self.left_button_pressed + new_line.on_left_mouse_button_dragged = self.left_button_dragged + + self.current_line = new_line + self.lines.append(new_line) + if interactive: + self._scene.add(new_line) + + def left_button_pressed(self, i_ren, _obj, line): + # click_pos = np.array(i_ren.event.position) + # self._drag_offset = click_pos - self.position + # i_ren.event.abort() # Stop propagating the event. + self.on_left_mouse_button_pressed(i_ren, _obj, line) + + def left_button_dragged(self, i_ren, _obj, line): + # if self._drag_offset is not None: + # click_position = np.array(i_ren.event.position) + # new_position = click_position - self._drag_offset + # self.position = new_position + # i_ren.force_render() + self.on_left_mouse_button_dragged(i_ren, _obj, line) + + def left_button_released(self, i_ren, _obj, line): + self.on_left_mouse_button_released(i_ren, _obj, line) class DrawShape(UI): @@ -3183,6 +3225,8 @@ def _setup(self): """ if self.shape_type == "line": self.shape = Rectangle2D(size=(3, 3)) + elif self.shape_type == "polyline": + self.shape = PolyLine() elif self.shape_type == "quad": self.shape = Rectangle2D(size=(3, 3)) elif self.shape_type == "circle": @@ -3250,7 +3294,10 @@ def cal_bounding_box(self, position): position : (float, float) (x, y) in pixels. """ - vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] + if self.shape_type == "polyline": + vertices = self.shape.calculate_vertices() + else: + vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] min_x, min_y = vertices[0] max_x, max_y = vertices[0] @@ -3298,6 +3345,9 @@ def resize(self, size): self.shape.resize((hyp, 3)) self.rotate(angle=np.arctan2(size[1], size[0])) + elif self.shape_type == "polyline": + self.shape.resize(size) + elif self.shape_type == "quad": self.shape.resize(size) @@ -3316,7 +3366,10 @@ def left_button_pressed(self, i_ren, _obj, shape): self._drag_offset = click_pos - self.position i_ren.event.abort() elif mode == "delete": - self._scene.rm(self.shape.actor) + if self.shape_type == "polyline": + self._scene.rm(*[l.actor for l in self.shape.lines]) + else: + self._scene.rm(self.shape.actor) i_ren.force_render() else: self.drawpanel.left_button_pressed(i_ren, _obj, self.drawpanel) @@ -3351,6 +3404,7 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): Whether the background canvas will be draggble or not. """ self.panel_size = size + self.shape_types = ["line", "polyline", "quad", "circle"] super(DrawPanel, self).__init__(position) self.is_draggable = is_draggable self.current_mode = None @@ -3370,6 +3424,10 @@ def _setup(self): self.left_button_pressed self.canvas.background.on_left_mouse_button_dragged = \ self.left_button_dragged + self.canvas.background.on_left_mouse_button_released = \ + self.left_button_released + self.canvas.background.on_right_mouse_button_pressed = \ + self.right_button_pressed # Todo # Convert mode_data into a private variable and make it read-only @@ -3377,6 +3435,7 @@ def _setup(self): mode_data = { "selection": ["selection.png", "selection-pressed.png"], "line": ["line.png", "line-pressed.png"], + "polyline": ["polyline.png", "polyline-pressed.png"], "quad": ["quad.png", "quad-pressed.png"], "circle": ["circle.png", "circle-pressed.png"], "delete": ["delete.png", "delete-pressed.png"] @@ -3489,6 +3548,7 @@ def draw_shape(self, shape_type, current_position, in_process=False): Checks whether in process or not. """ if not in_process: + print("creating new") shape = DrawShape(shape_type=shape_type, drawpanel=self, position=current_position) if shape_type == "circle": @@ -3498,6 +3558,7 @@ def draw_shape(self, shape_type, current_position, in_process=False): self.canvas.add_element(shape, current_position - self.canvas.position) else: + # print("resizing") current_shape = self.shape_list[-1] size = current_position - current_shape.position current_shape.resize(size) @@ -3535,7 +3596,7 @@ def clamp_mouse_position(self, mouse_position): def handle_mouse_click(self, position): if self.is_draggable and self.current_mode == "selection": self._drag_offset = position - self.position - if self.current_mode in ["line", "quad", "circle"]: + if self.current_mode in self.shape_types: self.draw_shape(self.current_mode, position) def left_button_pressed(self, i_ren, _obj, element): @@ -3547,10 +3608,24 @@ def handle_mouse_drag(self, position): if self._drag_offset is not None: new_position = position - self._drag_offset self.position = new_position - if self.current_mode in ["line", "quad", "circle"]: + if self.current_mode in self.shape_types: self.draw_shape(self.current_mode, position, True) def left_button_dragged(self, i_ren, _obj, element): mouse_position = self.clamp_mouse_position(i_ren.event.position) self.handle_mouse_drag(mouse_position) i_ren.force_render() + + def left_button_released(self, i_ren, _obj, element): + if self.current_mode == "polyline": + self.shape_list[-1].shape.add_point(i_ren.event.position, interactive=True) + + i_ren.add_active_prop(self.canvas.background.actor) + self.canvas.background.left_button_state = "dragging" + self.mouse_move_callback(i_ren, _obj, element) + i_ren.force_render() + + def right_button_pressed(self, i_ren, _obj, element): + i_ren.remove_active_prop(self.canvas.background.actor) + self.canvas.background.left_button_state = "released" + i_ren.force_render() From 395f878f7bf8168e7698647cbeb4ba1ce5872900 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 15 Jul 2022 16:09:12 +0530 Subject: [PATCH 04/59] adding key release callback --- fury/ui/core.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fury/ui/core.py b/fury/ui/core.py index 46f34b91b..125975175 100644 --- a/fury/ui/core.py +++ b/fury/ui/core.py @@ -108,6 +108,7 @@ def __init__(self, position=(0, 0)): self.on_middle_mouse_double_clicked = lambda i_ren, obj, element: None self.on_middle_mouse_button_dragged = lambda i_ren, obj, element: None self.on_key_press = lambda i_ren, obj, element: None + self.on_key_release = lambda i_ren, obj, element: None @abc.abstractmethod def _setup(self): @@ -261,7 +262,8 @@ def handle_events(self, actor): self.add_callback(actor, "MiddleButtonReleaseEvent", self.middle_button_release_callback) self.add_callback(actor, "MouseMoveEvent", self.mouse_move_callback) - self.add_callback(actor, "KeyPressEvent", self.key_press_callback) + self.add_callback(actor, "KeyPressEvent", self.on_key_press_callback) + self.add_callback(actor, "KeyReleaseEvent", self.on_key_release_callback) @staticmethod def left_button_click_callback(i_ren, obj, self): @@ -325,9 +327,13 @@ def mouse_move_callback(i_ren, obj, self): self.on_middle_mouse_button_dragged(i_ren, obj, self) @staticmethod - def key_press_callback(i_ren, obj, self): + def on_key_press_callback(i_ren, obj, self): self.on_key_press(i_ren, obj, self) + @staticmethod + def on_key_release_callback(i_ren, obj, self): + self.on_key_release(i_ren, obj, self) + class Rectangle2D(UI): """A 2D rectangle sub-classed from UI.""" From d527745fc3b91c30e615e13b9cd808a21b427f85 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 15 Jul 2022 16:15:22 +0530 Subject: [PATCH 05/59] adding interface to identity key status --- fury/ui/elements.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 37a9b5ec5..dd289a95f 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3147,7 +3147,7 @@ def rotate(self, angle): set_polydata_vertices(self.shape._polygonPolyData, new_points_arr) update_actor(self.shape.actor) - self.cal_bounding_box(self.position) + self.cal_bounding_box(self.position) def cal_bounding_box(self, position): """Calculates the min and max position of the bounding box. @@ -3219,6 +3219,8 @@ def resize(self, size): def left_button_pressed(self, i_ren, _obj, shape): mode = self.drawpanel.current_mode if mode == "selection": + if self.drawpanel.key_status["Control_L"]: + self.drawpanel.shape_group.add(self) click_pos = np.array(i_ren.event.position) self._drag_offset = click_pos - self.position i_ren.event.abort() @@ -3266,6 +3268,12 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): self.current_mode = "selection" self.shape_list = [] + self.shape_group = DrawShapeGroup() + self.key_status = { + "Control_L": False, + "Shift_L": False, + "Alt_L": False + } def _setup(self): """Setup this UI component. @@ -3277,6 +3285,10 @@ def _setup(self): self.left_button_pressed self.canvas.background.on_left_mouse_button_dragged = \ self.left_button_dragged + self.canvas.background.on_key_press = \ + self.key_press + self.canvas.background.on_key_release = \ + self.key_release # Todo # Convert mode_data into a private variable and make it read-only @@ -3331,6 +3343,8 @@ def _add_to_scene(self, scene): """ self.current_scene = scene + iren = scene.GetRenderWindow().GetInteractor().GetInteractorStyle() + iren.add_active_prop(self.canvas.actors[0]) self.canvas.add_to_scene(scene) def _get_size(self): @@ -3461,3 +3475,16 @@ def left_button_dragged(self, i_ren, _obj, element): mouse_position = self.clamp_mouse_position(i_ren.event.position) self.handle_mouse_drag(mouse_position) i_ren.force_render() + + def key_press(self, i_ren, _obj, _drawpanel): + self.key_status[i_ren.event.key] = True + + def key_release(self, i_ren, _obj, _drawpanel): + # key = i_ren.event.key + # if key == "Shift_L": + # self.key_status[key] = False + # elif key == "Control_L": + # self.key_status[key] = False + # elif key == "Alt_L": + # self.key_status[key] = False + self.key_status[i_ren.event.key] = False From c4fd598fe442c64244bb9555d41248a4ba00174d Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 15 Jul 2022 16:16:22 +0530 Subject: [PATCH 06/59] adding DrawShapeGroup --- fury/ui/elements.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index dd289a95f..821490d6a 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3060,6 +3060,43 @@ def directory_click_callback(self, i_ren, _obj, listboxitem): i_ren.event.abort() +class DrawShapeGroup: + def __init__(self): + self.grouped_shapes = [] + self._position = None + + def add(self, shape): + if shape in self.grouped_shapes: + self.remove(shape) + else: + self.grouped_shapes.append(shape) + print(self.grouped_shapes) + + def remove(self, shape): + self.grouped_shapes.remove(shape) + print(self.grouped_shapes) + + def clear(self, shape): + self.grouped_shapes = [] + print(self.grouped_shapes) + + def is_empty(self): + return bool(len(self.grouped_shapes)) + print(self.grouped_shapes) + + @property + def position(self): + return self._position + print(self.grouped_shapes) + + @property + def position(self, value): + offset = np.asarray(self._position) - value + for shape in self.grouped_shapes: + shape.position += offset + print(self.grouped_shapes) + + class DrawShape(UI): """Create and Manage 2D Shapes. """ From f054c142afd4824cd2089d0cd71dc56fc326eec2 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 15 Jul 2022 16:47:18 +0530 Subject: [PATCH 07/59] adding bb border --- fury/ui/elements.py | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 821490d6a..4886aadc1 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3070,10 +3070,12 @@ def add(self, shape): self.remove(shape) else: self.grouped_shapes.append(shape) + shape.set_bb_border_visibility(True) print(self.grouped_shapes) def remove(self, shape): self.grouped_shapes.remove(shape) + shape.set_bb_border_visibility(False) print(self.grouped_shapes) def clear(self, shape): @@ -3134,8 +3136,14 @@ def _setup(self): else: raise IOError("Unknown shape type: {}.".format(self.shape_type)) + self.bb_border = Panel2D(size=(1, 1), has_border=True, border_width=3, opacity=0) + self.bb_border.background.on_left_mouse_button_dragged = lambda i_ren, _ob, _ele: self.left_button_dragged( + i_ren, _ob, self) + self.set_bb_border_visibility(False) + self.shape.on_left_mouse_button_pressed = self.left_button_pressed self.shape.on_left_mouse_button_dragged = self.left_button_dragged + # self.shape.on_left_mouse_button_released = self.left_button_released def _get_actors(self): """Get the actors composing this UI component.""" @@ -3151,6 +3159,7 @@ def _add_to_scene(self, scene): """ self._scene = scene self.shape.add_to_scene(scene) + self.bb_border.add_to_scene(scene) def _get_size(self): return self.shape.size @@ -3168,6 +3177,15 @@ def _set_position(self, coords): else: self.shape.position = coords + def update_bb_border(self): + self.cal_bounding_box(self.position) + self.bb_border.position = self._bounding_box_min + self.bb_border.resize(self._bounding_box_size) + + def set_bb_border_visibility(self, value): + self.update_bb_border() + self.bb_border.set_visibility(value) + def rotate(self, angle): """Rotate the vertices of the UI component using specific angle. @@ -3184,7 +3202,12 @@ def rotate(self, angle): set_polydata_vertices(self.shape._polygonPolyData, new_points_arr) update_actor(self.shape.actor) - self.cal_bounding_box(self.position) + self.update_bb_border() + + def remove(self): + self._scene.rm(self.shape.actor) + for actor in self.bb_border.actors: + self._scene.rm(actor) def cal_bounding_box(self, position): """Calculates the min and max position of the bounding box. @@ -3256,16 +3279,17 @@ def resize(self, size): def left_button_pressed(self, i_ren, _obj, shape): mode = self.drawpanel.current_mode if mode == "selection": + self.set_bb_border_visibility(True) if self.drawpanel.key_status["Control_L"]: self.drawpanel.shape_group.add(self) click_pos = np.array(i_ren.event.position) self._drag_offset = click_pos - self.position i_ren.event.abort() elif mode == "delete": - self._scene.rm(self.shape.actor) - i_ren.force_render() + self.remove() else: self.drawpanel.left_button_pressed(i_ren, _obj, self.drawpanel) + i_ren.force_render() def left_button_dragged(self, i_ren, _obj, shape): if self.drawpanel.current_mode == "selection": @@ -3275,10 +3299,15 @@ def left_button_dragged(self, i_ren, _obj, shape): self._drag_offset - self.drawpanel.position new_position = self.clamp_position(relative_canvas_position) self.drawpanel.canvas.update_element(self, new_position) + self.update_bb_border() i_ren.force_render() else: self.drawpanel.left_button_dragged(i_ren, _obj, self.drawpanel) + # def left_button_released(self, i_ren, _obj, shape): + # self.set_bb_border_visibility(False) + # i_ren.force_render() + class DrawPanel(UI): """The main Canvas(Panel2D) on which everything would be drawn. @@ -3491,8 +3520,11 @@ def clamp_mouse_position(self, mouse_position): self.canvas.position + self.canvas.size) def handle_mouse_click(self, position): - if self.is_draggable and self.current_mode == "selection": - self._drag_offset = position - self.position + if self.current_mode == "selection": + for shape in self.shape_list: + shape.set_bb_border_visibility(False) + if self.draggable: + self._drag_offset = position - self.position if self.current_mode in ["line", "quad", "circle"]: self.draw_shape(self.current_mode, position) From a02f5c54448bd6ba43f17549a2cc5eafe168d906 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 19 Jul 2022 17:54:35 +0530 Subject: [PATCH 08/59] adding borders to the shape --- fury/ui/elements.py | 49 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index bb6f56511..ef370f270 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3080,7 +3080,6 @@ def __init__(self, shape_type, drawpanel=None, position=(0, 0)): self.shape_type = shape_type.lower() self.drawpanel = drawpanel self.max_size = None - self.is_selected = True super(DrawShape, self).__init__(position) self.shape.color = np.random.random(3) @@ -3098,6 +3097,9 @@ def _setup(self): else: raise IOError("Unknown shape type: {}.".format(self.shape_type)) + self.bb_box = [Rectangle2D(size=(3, 3)) for i in range(4)] + self.set_bb_box_visibility(False) + self.shape.on_left_mouse_button_pressed = self.left_button_pressed self.shape.on_left_mouse_button_dragged = self.left_button_dragged self.shape.on_left_mouse_button_released = self.left_button_released @@ -3132,6 +3134,7 @@ def _add_to_scene(self, scene): """ self._scene = scene self.shape.add_to_scene(scene) + scene.add(*[border.actor for border in self.bb_box]) self.rotation_slider.add_to_scene(scene) def _get_size(self): @@ -3161,6 +3164,7 @@ def update_shape_position(self, center_position): new_center = self.clamp_position(center=center_position) self.drawpanel.canvas.update_element(self, new_center, "center") self.cal_bounding_box(update_value=True) + self.set_bb_box_visibility(True) @property def center(self): @@ -3193,8 +3197,30 @@ def is_selected(self, value): self.selection_change() def selection_change(self): - if not self.is_selected: + if self.is_selected: + self.show_rotation_slider() + self.set_bb_box_visibility(True) + else: self.rotation_slider.set_visibility(False) + self.set_bb_box_visibility(False) + + def set_bb_box_visibility(self, value): + if value: + border_width = 3 + points = [self._bounding_box_min-(0, border_width), + [self._bounding_box_max[0], self._bounding_box_min[1]], + self._bounding_box_min - border_width, + [self._bounding_box_min[0]-border_width, self._bounding_box_max[1]]] + size = [(self._bounding_box_size[0]+border_width, border_width), + (border_width, self._bounding_box_size[1]+border_width), + (border_width, self._bounding_box_size[1] + border_width), + (self._bounding_box_size[0]+border_width, border_width)] + for i in range(4): + self.bb_box[i].position = points[i] + self.bb_box[i].resize(size[i]) + + for border in self.bb_box: + border.set_visibility(value) def rotate(self, angle): """Rotate the vertices of the UI component using specific angle. @@ -3293,22 +3319,32 @@ def resize(self, size): self.cal_bounding_box(update_value=True) + def bring_to_top(self): + self.remove() + self._add_to_scene(self._scene) + self.drawpanel.shape_list.append(self) + def remove(self): """Removes the Shape and all related actors. """ + print(self.drawpanel.shape_list) + self.drawpanel.shape_list.remove(self) self._scene.rm(self.shape.actor) + self._scene.rm(*[border.actor for border in self.bb_box]) self._scene.rm(*self.rotation_slider.actors) def left_button_pressed(self, i_ren, _obj, shape): mode = self.drawpanel.current_mode if mode == "selection": + self.bring_to_top() + self.drawpanel.update_shape_selection(self) click_pos = np.array(i_ren.event.position) self._drag_offset = click_pos - self.center - self.show_rotation_slider() i_ren.event.abort() elif mode == "delete": + # print(self.drawpanel.shape_list) self.remove() else: self.drawpanel.left_button_pressed(i_ren, _obj, self.drawpanel) @@ -3329,7 +3365,7 @@ def left_button_dragged(self, i_ren, _obj, shape): def left_button_released(self, i_ren, _obj, shape): if self.drawpanel.current_mode == "selection": self.show_rotation_slider() - i_ren.force_render() + i_ren.force_render() class DrawPanel(UI): @@ -3493,7 +3529,7 @@ def draw_shape(self, shape_type, current_position, in_process=False): if shape_type == "circle": shape.max_size = self.cal_min_boundary_distance(current_position) self.shape_list.append(shape) - self.update_shape_selection(shape) + self.current_shape = shape self.current_scene.add(shape) self.canvas.add_element(shape, current_position - self.canvas.position) @@ -3540,10 +3576,11 @@ def clamp_mouse_position(self, mouse_position): self.canvas.position + self.canvas.size) def handle_mouse_click(self, position): + if self.current_shape: + self.current_shape.is_selected = False if self.current_mode == "selection": if self.is_draggable: self._drag_offset = position - self.position - self.current_shape.is_selected = False if self.current_mode in ["line", "quad", "circle"]: self.draw_shape(self.current_mode, position) From 53e9f1532165e3d104dd7836553b16e1276e2551 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 19 Jul 2022 17:54:35 +0530 Subject: [PATCH 09/59] adding borders to the shape --- fury/ui/elements.py | 49 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 5161186ce..5925d7b8f 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3080,7 +3080,6 @@ def __init__(self, shape_type, drawpanel=None, position=(0, 0)): self.shape_type = shape_type.lower() self.drawpanel = drawpanel self.max_size = None - self.is_selected = True super(DrawShape, self).__init__(position) self.shape.color = np.random.random(3) @@ -3098,6 +3097,9 @@ def _setup(self): else: raise IOError("Unknown shape type: {}.".format(self.shape_type)) + self.bb_box = [Rectangle2D(size=(3, 3)) for i in range(4)] + self.set_bb_box_visibility(False) + self.shape.on_left_mouse_button_pressed = self.left_button_pressed self.shape.on_left_mouse_button_dragged = self.left_button_dragged self.shape.on_left_mouse_button_released = self.left_button_released @@ -3133,6 +3135,7 @@ def _add_to_scene(self, scene): """ self._scene = scene self.shape.add_to_scene(scene) + scene.add(*[border.actor for border in self.bb_box]) self.rotation_slider.add_to_scene(scene) def _get_size(self): @@ -3162,6 +3165,7 @@ def update_shape_position(self, center_position): new_center = self.clamp_position(center=center_position) self.drawpanel.canvas.update_element(self, new_center, "center") self.cal_bounding_box(update_value=True) + self.set_bb_box_visibility(True) @property def center(self): @@ -3194,8 +3198,30 @@ def is_selected(self, value): self.selection_change() def selection_change(self): - if not self.is_selected: + if self.is_selected: + self.show_rotation_slider() + self.set_bb_box_visibility(True) + else: self.rotation_slider.set_visibility(False) + self.set_bb_box_visibility(False) + + def set_bb_box_visibility(self, value): + if value: + border_width = 3 + points = [self._bounding_box_min-(0, border_width), + [self._bounding_box_max[0], self._bounding_box_min[1]], + self._bounding_box_min - border_width, + [self._bounding_box_min[0]-border_width, self._bounding_box_max[1]]] + size = [(self._bounding_box_size[0]+border_width, border_width), + (border_width, self._bounding_box_size[1]+border_width), + (border_width, self._bounding_box_size[1] + border_width), + (self._bounding_box_size[0]+border_width, border_width)] + for i in range(4): + self.bb_box[i].position = points[i] + self.bb_box[i].resize(size[i]) + + for border in self.bb_box: + border.set_visibility(value) def rotate(self, angle): """Rotate the vertices of the UI component using specific angle. @@ -3294,22 +3320,32 @@ def resize(self, size): self.cal_bounding_box(update_value=True) + def bring_to_top(self): + self.remove() + self._add_to_scene(self._scene) + self.drawpanel.shape_list.append(self) + def remove(self): """Removes the Shape and all related actors. """ + print(self.drawpanel.shape_list) + self.drawpanel.shape_list.remove(self) self._scene.rm(self.shape.actor) + self._scene.rm(*[border.actor for border in self.bb_box]) self._scene.rm(*self.rotation_slider.actors) def left_button_pressed(self, i_ren, _obj, shape): mode = self.drawpanel.current_mode if mode == "selection": + self.bring_to_top() + self.drawpanel.update_shape_selection(self) click_pos = np.array(i_ren.event.position) self._drag_offset = click_pos - self.center - self.show_rotation_slider() i_ren.event.abort() elif mode == "delete": + # print(self.drawpanel.shape_list) self.remove() else: self.drawpanel.left_button_pressed(i_ren, _obj, self.drawpanel) @@ -3330,7 +3366,7 @@ def left_button_dragged(self, i_ren, _obj, shape): def left_button_released(self, i_ren, _obj, shape): if self.drawpanel.current_mode == "selection": self.show_rotation_slider() - i_ren.force_render() + i_ren.force_render() class DrawPanel(UI): @@ -3494,7 +3530,7 @@ def draw_shape(self, shape_type, current_position, in_process=False): if shape_type == "circle": shape.max_size = self.cal_min_boundary_distance(current_position) self.shape_list.append(shape) - self.update_shape_selection(shape) + self.current_shape = shape self.current_scene.add(shape) self.canvas.add_element(shape, current_position - self.canvas.position) @@ -3541,10 +3577,11 @@ def clamp_mouse_position(self, mouse_position): self.canvas.position + self.canvas.size) def handle_mouse_click(self, position): + if self.current_shape: + self.current_shape.is_selected = False if self.current_mode == "selection": if self.is_draggable: self._drag_offset = position - self.position - self.current_shape.is_selected = False if self.current_mode in ["line", "quad", "circle"]: self.draw_shape(self.current_mode, position) From e81ee055eb01d74e106045bb56a7549837bbd8fa Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 24 Jul 2022 15:44:58 +0530 Subject: [PATCH 10/59] rebasing --- fury/ui/elements.py | 172 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 136 insertions(+), 36 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index d7c4459a9..64bd930ec 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3215,6 +3215,7 @@ def __init__(self, shape_type, drawpanel=None, position=(0, 0)): self.shape_type = shape_type.lower() self.drawpanel = drawpanel self.max_size = None + self.is_selected = True super(DrawShape, self).__init__(position) self.shape.color = np.random.random(3) @@ -3236,6 +3237,22 @@ def _setup(self): self.shape.on_left_mouse_button_pressed = self.left_button_pressed self.shape.on_left_mouse_button_dragged = self.left_button_dragged + self.shape.on_left_mouse_button_released = self.left_button_released + + self.rotation_slider = RingSlider2D(initial_value=0, + text_template="{angle:5.1f}°") + self.rotation_slider.set_visibility(False) + + def rotate_shape(slider): + angle = slider.value + previous_angle = slider.previous_value + rotation_angle = angle - previous_angle + + current_center = self.center + self.rotate(np.deg2rad(rotation_angle)) + self.update_shape_position(current_center - self.drawpanel.position) + + self.rotation_slider.on_change = rotate_shape def _get_actors(self): """Get the actors composing this UI component.""" @@ -3251,6 +3268,7 @@ def _add_to_scene(self, scene): """ self._scene = scene self.shape.add_to_scene(scene) + self.rotation_slider.add_to_scene(scene) def _get_size(self): return self.shape.size @@ -3268,6 +3286,52 @@ def _set_position(self, coords): else: self.shape.position = coords + def update_shape_position(self, center_position): + """Updates the center position of this shape on the canvas. + + Parameters + ---------- + center_position: (float, float) + Absolute pixel coordinates (x, y). + """ + new_center = self.clamp_position(center=center_position) + self.drawpanel.canvas.update_element(self, new_center, "center") + self.cal_bounding_box(update_value=True) + + @property + def center(self): + return self._bounding_box_min + self._bounding_box_size//2 + + @center.setter + def center(self, coords): + """Position the center of this UI component. + + Parameters + ---------- + coords: (float, float) + Absolute pixel coordinates (x, y). + + """ + new_center = np.array(coords) + new_lower_left_corner = new_center - self._bounding_box_size // 2 + self.position = new_lower_left_corner + self._bounding_box_offset + self.cal_bounding_box(update_value=True) + + @property + def is_selected(self): + return self._is_selected + + @is_selected.setter + def is_selected(self, value): + if self.drawpanel and value: + self.drawpanel.current_shape = self + self._is_selected = value + self.selection_change() + + def selection_change(self): + if not self.is_selected: + self.rotation_slider.set_visibility(False) + def rotate(self, angle): """Rotate the vertices of the UI component using specific angle. @@ -3276,6 +3340,8 @@ def rotate(self, angle): angle: float Value by which the vertices are rotated in radian. """ + if self.shape_type == "circle": + return points_arr = vertices_from_actor(self.shape.actor) rotation_matrix = np.array( [[np.cos(angle), np.sin(angle), 0], @@ -3284,10 +3350,18 @@ def rotate(self, angle): set_polydata_vertices(self.shape._polygonPolyData, new_points_arr) update_actor(self.shape.actor) - self.cal_bounding_box(self.position) + self.cal_bounding_box(update_value=True) - def cal_bounding_box(self, position): - """Calculates the min and max position of the bounding box. + def show_rotation_slider(self): + """Display the RingSlider2D to allow rotation of shape from the center. + """ + offset = (self._bounding_box_size[0] + self.rotation_slider.size[0])/2 + self.rotation_slider.center = self.center + \ + [offset if self.center[0] < self.drawpanel.center[0] else -offset, 0] + self.rotation_slider.set_visibility(True) + + def cal_bounding_box(self, update_value=False, position=None): + """Calculates the min, max position and the size of the bounding box. Parameters ---------- @@ -3296,8 +3370,9 @@ def cal_bounding_box(self, position): """ if self.shape_type == "polyline": vertices = self.shape.calculate_vertices() - else: - vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] + elif position is None: + position = self.position + vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] min_x, min_y = vertices[0] max_x, max_y = vertices[0] @@ -3312,30 +3387,31 @@ def cal_bounding_box(self, position): if y > max_y: max_y = y - self._bounding_box_min = [min_x, min_y] - self._bounding_box_max = [max_x, max_y] - self._bounding_box_size = [max_x-min_x, max_y-min_y] + if update_value: + self._bounding_box_min = np.asarray([min_x, min_y], dtype="int") + self._bounding_box_max = np.asarray([max_x, max_y], dtype="int") + self._bounding_box_size = np.asarray([max_x-min_x, max_y-min_y], dtype="int") - self._bounding_box_offset = position - self._bounding_box_min + self._bounding_box_offset = position - self._bounding_box_min - def clamp_position(self, position): - """Clamps the given position according to the DrawPanel canvas. + def clamp_position(self, center=None): + """Clamps the given center according to the DrawPanel canvas. Parameters ---------- - position : (float, float) + center : (float, float) (x, y) in pixels. Returns ------- - new_position: ndarray(int) - New position for the shape. + new_center: ndarray(int) + New center for the shape. """ - self.cal_bounding_box(position) - new_position = np.clip(self._bounding_box_min, [0, 0], - self.drawpanel.size - self._bounding_box_size) - new_position = new_position + self._bounding_box_offset - return new_position.astype(int) + if center is None: + center = self.center + new_center = np.clip(center, self._bounding_box_size//2, + self.drawpanel.size - self._bounding_box_size//2) + return new_center.astype(int) def resize(self, size): """Resize the UI. @@ -3357,35 +3433,49 @@ def resize(self, size): hyp = self.max_size self.shape.outer_radius = hyp - self.cal_bounding_box(self.position) + self.cal_bounding_box(update_value=True) + + def remove(self): + """Removes the Shape and all related actors. + """ + if self.shape_type == "polyline": + self._scene.rm(*[l.actor for l in self.shape.lines]) + else: + self._scene.rm(self.shape.actor) + self._scene.rm(*self.rotation_slider.actors) def left_button_pressed(self, i_ren, _obj, shape): mode = self.drawpanel.current_mode if mode == "selection": + self.drawpanel.update_shape_selection(self) + click_pos = np.array(i_ren.event.position) - self._drag_offset = click_pos - self.position + self._drag_offset = click_pos - self.center + self.show_rotation_slider() i_ren.event.abort() elif mode == "delete": - if self.shape_type == "polyline": - self._scene.rm(*[l.actor for l in self.shape.lines]) - else: - self._scene.rm(self.shape.actor) - i_ren.force_render() + self.remove() else: self.drawpanel.left_button_pressed(i_ren, _obj, self.drawpanel) + i_ren.force_render() def left_button_dragged(self, i_ren, _obj, shape): if self.drawpanel.current_mode == "selection": + self.rotation_slider.set_visibility(False) if self._drag_offset is not None: click_position = i_ren.event.position - relative_canvas_position = click_position - \ + relative_center_position = click_position - \ self._drag_offset - self.drawpanel.position - new_position = self.clamp_position(relative_canvas_position) - self.drawpanel.canvas.update_element(self, new_position) + self.update_shape_position(relative_center_position) i_ren.force_render() else: self.drawpanel.left_button_dragged(i_ren, _obj, self.drawpanel) + def left_button_released(self, i_ren, _obj, shape): + if self.drawpanel.current_mode == "selection": + self.show_rotation_slider() + i_ren.force_render() + class DrawPanel(UI): """The main Canvas(Panel2D) on which everything would be drawn. @@ -3413,6 +3503,7 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): self.current_mode = "selection" self.shape_list = [] + self.current_shape = None def _setup(self): """Setup this UI component. @@ -3554,14 +3645,21 @@ def draw_shape(self, shape_type, current_position, in_process=False): if shape_type == "circle": shape.max_size = self.cal_min_boundary_distance(current_position) self.shape_list.append(shape) + self.update_shape_selection(shape) self.current_scene.add(shape) self.canvas.add_element(shape, current_position - self.canvas.position) else: - # print("resizing") - current_shape = self.shape_list[-1] - size = current_position - current_shape.position - current_shape.resize(size) + self.current_shape = self.shape_list[-1] + size = current_position - self.current_shape.position + self.current_shape.resize(size) + + def update_shape_selection(self, selected_shape): + for shape in self.shape_list: + if selected_shape == shape: + shape.is_selected = True + else: + shape.is_selected = False def update_button_icons(self, current_mode): """Updates the button icon. @@ -3594,9 +3692,11 @@ def clamp_mouse_position(self, mouse_position): self.canvas.position + self.canvas.size) def handle_mouse_click(self, position): - if self.is_draggable and self.current_mode == "selection": - self._drag_offset = position - self.position - if self.current_mode in self.shape_types: + if self.current_mode == "selection": + if self.is_draggable: + self._drag_offset = position - self.position + self.current_shape.is_selected = False + if self.current_mode in ["line", "quad", "circle"]: self.draw_shape(self.current_mode, position) def left_button_pressed(self, i_ren, _obj, element): From b27ff81dcdf6d25c77f5dfcb2efce02bc0e43a2f Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 24 Jul 2022 15:46:45 +0530 Subject: [PATCH 11/59] removing prints --- fury/ui/elements.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 64bd930ec..3c0972fe6 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3072,7 +3072,6 @@ def __init__(self, points_data=None, line_width=3, color=(1, 1, 1)): position : (float, float), optional (x, y) in pixels. """ - print("create Polyline") self.points = points_data if points_data is None: self.points = [(0, 0)] @@ -3639,7 +3638,6 @@ def draw_shape(self, shape_type, current_position, in_process=False): Checks whether in process or not. """ if not in_process: - print("creating new") shape = DrawShape(shape_type=shape_type, drawpanel=self, position=current_position) if shape_type == "circle": @@ -3650,7 +3648,6 @@ def draw_shape(self, shape_type, current_position, in_process=False): self.canvas.add_element(shape, current_position - self.canvas.position) else: - self.current_shape = self.shape_list[-1] size = current_position - self.current_shape.position self.current_shape.resize(size) From 39ab852aca1559ddd798929032c933506695cb99 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 27 Jul 2022 06:44:39 +0530 Subject: [PATCH 12/59] adding on_mouse_move callback --- fury/ui/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fury/ui/core.py b/fury/ui/core.py index 46f34b91b..35150551c 100644 --- a/fury/ui/core.py +++ b/fury/ui/core.py @@ -108,6 +108,7 @@ def __init__(self, position=(0, 0)): self.on_middle_mouse_double_clicked = lambda i_ren, obj, element: None self.on_middle_mouse_button_dragged = lambda i_ren, obj, element: None self.on_key_press = lambda i_ren, obj, element: None + self.on_mouse_move = lambda i_ren, obj, element: None @abc.abstractmethod def _setup(self): @@ -304,6 +305,7 @@ def middle_button_release_callback(i_ren, obj, self): @staticmethod def mouse_move_callback(i_ren, obj, self): + self.on_mouse_move(i_ren, obj, self) left_pressing_or_dragging = (self.left_button_state == "pressing" or self.left_button_state == "dragging") From bd738e897be3151209f25d879c3f9bf6589d56ee Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 27 Jul 2022 06:46:23 +0530 Subject: [PATCH 13/59] integrating polyline --- fury/ui/elements.py | 79 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 3c0972fe6..bd9bc0979 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3132,8 +3132,9 @@ def _set_position(self, coords): # update other points def resize(self, size): + offset_from_mouse = 2 hyp = np.hypot(size[0], size[1]) - self.current_line.resize((hyp, self.line_width)) + self.current_line.resize((hyp - offset_from_mouse, self.line_width)) self.rotate(angle=np.arctan2(size[1], size[0])) def rotate(self, angle): @@ -3173,9 +3174,20 @@ def add_point(self, point, interactive=False): self.current_line = new_line self.lines.append(new_line) + self.points.append(point) if interactive: self._scene.add(new_line) + @property + def color(self): + color = self.current_line.color + return np.asarray(color) + + @color.setter + def color(self, color): + for line in self.lines: + line.actor.GetProperty().SetColor(*color) + def left_button_pressed(self, i_ren, _obj, line): # click_pos = np.array(i_ren.event.position) # self._drag_offset = click_pos - self.position @@ -3234,6 +3246,8 @@ def _setup(self): else: raise IOError("Unknown shape type: {}.".format(self.shape_type)) + self.cal_bounding_box(update_value=True) + self.shape.on_left_mouse_button_pressed = self.left_button_pressed self.shape.on_left_mouse_button_dragged = self.left_button_dragged self.shape.on_left_mouse_button_released = self.left_button_released @@ -3367,11 +3381,13 @@ def cal_bounding_box(self, update_value=False, position=None): position : (float, float) (x, y) in pixels. """ + if position is None: + position = self.position + if self.shape_type == "polyline": vertices = self.shape.calculate_vertices() - elif position is None: - position = self.position - vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] + else: + vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] min_x, min_y = vertices[0] max_x, max_y = vertices[0] @@ -3503,6 +3519,7 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): self.shape_list = [] self.current_shape = None + self.is_creating_polyline = False def _setup(self): """Setup this UI component. @@ -3516,8 +3533,9 @@ def _setup(self): self.left_button_dragged self.canvas.background.on_left_mouse_button_released = \ self.left_button_released - self.canvas.background.on_right_mouse_button_pressed = \ - self.right_button_pressed + self.canvas.background.on_right_mouse_button_released = \ + self.right_button_released + self.canvas.background.on_mouse_move = self.mouse_move # Todo # Convert mode_data into a private variable and make it read-only @@ -3535,7 +3553,7 @@ def _setup(self): # Todo # Add this size to __init__ mode_panel_size = (len(mode_data) * 35 + 2 * padding, 40) - self.mode_panel = Panel2D(size=mode_panel_size, color=(0.5, 0.5, 0.5)) + self.mode_panel = Panel2D(size=mode_panel_size, color=(0.7, 0.7, 0.7)) btn_pos = np.array([0, 0]) for mode, fname in mode_data.items(): @@ -3573,6 +3591,8 @@ def _add_to_scene(self, scene): """ self.current_scene = scene + iren = scene.GetRenderWindow().GetInteractor().GetInteractorStyle() + iren.add_active_prop(self.canvas.background.actor) self.canvas.add_to_scene(scene) def _get_size(self): @@ -3693,14 +3713,22 @@ def handle_mouse_click(self, position): if self.is_draggable: self._drag_offset = position - self.position self.current_shape.is_selected = False - if self.current_mode in ["line", "quad", "circle"]: - self.draw_shape(self.current_mode, position) + if self.current_mode in self.shape_types: + # if self.current_mode == "polyline" and not self.is_creating_polyline: + # self.is_creating_polyline = True + # self.draw_shape(self.current_mode, position) + if not self.is_creating_polyline: + if self.current_mode == "polyline": + self.is_creating_polyline = True + self.draw_shape(self.current_mode, position) def left_button_pressed(self, i_ren, _obj, element): self.handle_mouse_click(i_ren.event.position) i_ren.force_render() def handle_mouse_drag(self, position): + if self.current_mode == "polyline": + return if self.is_draggable and self.current_mode == "selection": if self._drag_offset is not None: new_position = position - self._drag_offset @@ -3713,16 +3741,31 @@ def left_button_dragged(self, i_ren, _obj, element): self.handle_mouse_drag(mouse_position) i_ren.force_render() - def left_button_released(self, i_ren, _obj, element): - if self.current_mode == "polyline": - self.shape_list[-1].shape.add_point(i_ren.event.position, interactive=True) + def mouse_move(self, i_ren, _obj, element): + if self.is_creating_polyline: + current_line = self.current_shape.shape.current_line + if np.linalg.norm(self.clamp_mouse_position(i_ren.event.position) + - self.current_shape.shape.lines[0].position) < 10: + self.current_shape.shape.resize( + self.current_shape.shape.lines[0].position - current_line.position) + else: + self.current_shape.shape.resize(self.clamp_mouse_position( + i_ren.event.position) - current_line.position) + i_ren.force_render() - i_ren.add_active_prop(self.canvas.background.actor) - self.canvas.background.left_button_state = "dragging" - self.mouse_move_callback(i_ren, _obj, element) + def left_button_released(self, i_ren, _obj, element): + if self.is_creating_polyline: + self.current_shape.shape.add_point(i_ren.event.position, True) + # if self.current_mode == "polyline": + # self.shape_list[-1].shape.add_point(i_ren.event.position, interactive=True) + + # i_ren.add_active_prop(self.canvas.background.actor) + # self.canvas.background.left_button_state = "dragging" + # self.mouse_move_callback(i_ren, _obj, element) i_ren.force_render() - def right_button_pressed(self, i_ren, _obj, element): - i_ren.remove_active_prop(self.canvas.background.actor) - self.canvas.background.left_button_state = "released" + def right_button_released(self, i_ren, _obj, element): + # i_ren.remove_active_prop(self.canvas.background.actor) + # i_ren.force_render() + self.is_creating_polyline = False i_ren.force_render() From 23e07f28292d2ba2c7540696e533d3c25d2d1bd8 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 27 Jul 2022 18:55:37 +0530 Subject: [PATCH 14/59] updating method name --- fury/ui/elements.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 77e9b9c18..10aead097 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3070,12 +3070,12 @@ def add(self, shape): self.remove(shape) else: self.grouped_shapes.append(shape) - shape.set_bb_border_visibility(True) + shape.set_bb_box_visibility(True) print(self.grouped_shapes) def remove(self, shape): self.grouped_shapes.remove(shape) - shape.set_bb_border_visibility(False) + shape.set_bb_box_visibility(False) print(self.grouped_shapes) def clear(self, shape): @@ -3381,6 +3381,7 @@ def remove(self): def left_button_pressed(self, i_ren, _obj, shape): mode = self.drawpanel.current_mode if mode == "selection": + self.set_bb_box_visibility(True) if self.drawpanel.key_status["Control_L"]: self.drawpanel.shape_group.add(self) self.bring_to_top() From 222138893892b66c7d92aac18029a8353eb94f8c Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Mon, 1 Aug 2022 22:44:10 +0530 Subject: [PATCH 15/59] updating selection of shape --- fury/ui/elements.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 10aead097..28979bb9a 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3070,12 +3070,12 @@ def add(self, shape): self.remove(shape) else: self.grouped_shapes.append(shape) - shape.set_bb_box_visibility(True) + shape.is_selected = True print(self.grouped_shapes) def remove(self, shape): self.grouped_shapes.remove(shape) - shape.set_bb_box_visibility(False) + shape.is_selected = False print(self.grouped_shapes) def clear(self, shape): @@ -3365,9 +3365,10 @@ def resize(self, size): self.cal_bounding_box(update_value=True) def bring_to_top(self): - self.remove() - self._add_to_scene(self._scene) - self.drawpanel.shape_list.append(self) + # self.remove() + # self._add_to_scene(self._scene) + # self.drawpanel.shape_list.append(self) + pass def remove(self): """Removes the Shape and all related actors. @@ -3384,9 +3385,10 @@ def left_button_pressed(self, i_ren, _obj, shape): self.set_bb_box_visibility(True) if self.drawpanel.key_status["Control_L"]: self.drawpanel.shape_group.add(self) - self.bring_to_top() + else: + self.bring_to_top() - self.drawpanel.update_shape_selection(self) + self.drawpanel.update_shape_selection(self) click_pos = np.array(i_ren.event.position) self._drag_offset = click_pos - self.center From 2e324cf537e959dccb61faa3c6e3b9577a8e6176 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Mon, 1 Aug 2022 22:49:41 +0530 Subject: [PATCH 16/59] removing position property and bring_to_top --- fury/ui/elements.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 4e0c64a2f..f07adbd15 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3086,18 +3086,6 @@ def is_empty(self): return bool(len(self.grouped_shapes)) print(self.grouped_shapes) - @property - def position(self): - return self._position - print(self.grouped_shapes) - - @property - def position(self, value): - offset = np.asarray(self._position) - value - for shape in self.grouped_shapes: - shape.position += offset - print(self.grouped_shapes) - class DrawShape(UI): """Create and Manage 2D Shapes. @@ -3365,12 +3353,6 @@ def resize(self, size): self.cal_bounding_box(update_value=True) - def bring_to_top(self): - # self.remove() - # self._add_to_scene(self._scene) - # self.drawpanel.shape_list.append(self) - pass - def remove(self): """Removes the Shape and all related actors. """ @@ -3387,8 +3369,6 @@ def left_button_pressed(self, i_ren, _obj, shape): if self.drawpanel.key_status["Control_L"]: self.drawpanel.shape_group.add(self) else: - self.bring_to_top() - self.drawpanel.update_shape_selection(self) click_pos = np.array(i_ren.event.position) From b7a4d7472742ca39543b6b6ccbfe8386db18432f Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Mon, 1 Aug 2022 23:17:44 +0530 Subject: [PATCH 17/59] updating position of all grouped shapes --- fury/ui/elements.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index f07adbd15..ef3d8fc0c 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3086,6 +3086,10 @@ def is_empty(self): return bool(len(self.grouped_shapes)) print(self.grouped_shapes) + def update_position(self, offset): + for shape in self.grouped_shapes: + shape.update_shape_position(shape.center + offset) + class DrawShape(UI): """Create and Manage 2D Shapes. @@ -3388,6 +3392,10 @@ def left_button_dragged(self, i_ren, _obj, shape): click_position = i_ren.event.position relative_center_position = click_position - \ self._drag_offset - self.drawpanel.position + + self.drawpanel.shape_group.update_position( + relative_center_position - self.center) + self.update_shape_position(relative_center_position) i_ren.force_render() else: From 41239856aa5f1e7421a5b0201c2a0adf6c8ebd2f Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 2 Aug 2022 06:40:44 +0530 Subject: [PATCH 18/59] adding group rotation --- fury/ui/elements.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index ef3d8fc0c..b00c86ceb 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3065,12 +3065,36 @@ def __init__(self): self.grouped_shapes = [] self._position = None + # Group rotation slider + self.group_rotation_slider = RingSlider2D(initial_value=0, + text_template="{angle:5.1f}°") + + self.group_rotation_slider.set_visibility(False) + + def update_rotation(slider): + print("update rotation") + angle = slider.value + previous_angle = slider.previous_value + rotation_angle = angle - previous_angle + + for shape in self.grouped_shapes: + current_center = shape.center + shape.rotate(np.deg2rad(rotation_angle)) + shape.update_shape_position(current_center - shape.drawpanel.position) + + self.group_rotation_slider.on_change = update_rotation + def add(self, shape): if shape in self.grouped_shapes: self.remove(shape) else: self.grouped_shapes.append(shape) shape.is_selected = True + shape.rotation_slider.set_visibility(False) + + self.group_rotation_slider.center = shape.rotation_slider.center + self.group_rotation_slider.set_visibility(True) + print(self.grouped_shapes) def remove(self, shape): @@ -3083,13 +3107,15 @@ def clear(self, shape): print(self.grouped_shapes) def is_empty(self): - return bool(len(self.grouped_shapes)) - print(self.grouped_shapes) + return not bool(len(self.grouped_shapes)) def update_position(self, offset): for shape in self.grouped_shapes: shape.update_shape_position(shape.center + offset) + def add_rotation_slider(self, scene): + scene.add(self.group_rotation_slider) + class DrawShape(UI): """Create and Manage 2D Shapes. @@ -3137,6 +3163,9 @@ def _setup(self): self.rotation_slider = RingSlider2D(initial_value=0, text_template="{angle:5.1f}°") + slider_position = self.drawpanel.position + \ + [self.drawpanel.size[0] - self.rotation_slider.size[0]/2, self.rotation_slider.size[1]/2] + self.rotation_slider.center = slider_position self.rotation_slider.set_visibility(False) def rotate_shape(slider): @@ -3282,9 +3311,6 @@ def show_rotation_slider(self): """ self._scene.rm(*self.rotation_slider.actors) self.rotation_slider.add_to_scene(self._scene) - slider_position = self.drawpanel.position + \ - [self.drawpanel.size[0] - self.rotation_slider.size[0]/2, self.rotation_slider.size[1]/2] - self.rotation_slider.center = slider_position self.rotation_slider.set_visibility(True) def cal_bounding_box(self, update_value=False, position=None): @@ -3393,7 +3419,8 @@ def left_button_dragged(self, i_ren, _obj, shape): relative_center_position = click_position - \ self._drag_offset - self.drawpanel.position - self.drawpanel.shape_group.update_position( + if not self.drawpanel.shape_group.is_empty(): + self.drawpanel.shape_group.update_position( relative_center_position - self.center) self.update_shape_position(relative_center_position) @@ -3402,7 +3429,7 @@ def left_button_dragged(self, i_ren, _obj, shape): self.drawpanel.left_button_dragged(i_ren, _obj, self.drawpanel) def left_button_released(self, i_ren, _obj, shape): - if self.drawpanel.current_mode == "selection": + if self.drawpanel.current_mode == "selection" and self.drawpanel.shape_group.is_empty(): self.show_rotation_slider() i_ren.force_render() @@ -3511,6 +3538,7 @@ def _add_to_scene(self, scene): iren = scene.GetRenderWindow().GetInteractor().GetInteractorStyle() iren.add_active_prop(self.canvas.actors[0]) self.canvas.add_to_scene(scene) + self.shape_group.add_rotation_slider(scene) def _get_size(self): return self.canvas.size From 471a90cb499ad320f2764fd8765eed38be364131 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 3 Aug 2022 18:52:47 +0530 Subject: [PATCH 19/59] removing extra point --- fury/ui/elements.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index bd9bc0979..12a81d029 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3089,18 +3089,18 @@ def _setup(self): Create a Polyline. """ - line = Rectangle2D((self.line_width, self.line_width), position=self.points[0]) + # line = Rectangle2D((.05, .05), position=self.points[0]) - line.on_left_mouse_button_pressed = self.left_button_pressed - line.on_left_mouse_button_dragged = self.left_button_dragged + # line.on_left_mouse_button_pressed = self.left_button_pressed + # line.on_left_mouse_button_dragged = self.left_button_dragged - self.current_line = line - self.lines.append(line) + # self.current_line = line + # self.lines.append(line) - if len(self.points) > 2: - for point in self.points[1:]: - self.add_point(point) - self.lines.pop() + # if len(self.points) > 2: + # for point in self.points[1:]: + # self.add_point(point) + # self.lines.pop() def _get_actors(self): """Get the actors composing this UI component.""" @@ -3115,7 +3115,7 @@ def _add_to_scene(self, scene): """ self._scene = scene - scene.add(*self.lines) + # scene.add(*self.lines) def _get_size(self): pass @@ -3128,7 +3128,7 @@ def _set_position(self, coords): coords: (float, float) Absolute pixel coordinates (x, y). """ - self.lines[0].position = coords + # self.lines[0].position = coords # update other points def resize(self, size): @@ -3166,7 +3166,8 @@ def calculate_vertices(self): return vertices def add_point(self, point, interactive=False): - self.resize(np.asarray(point) - self.current_line.position) + if self.current_line: + self.resize(np.asarray(point) - self.current_line.position) new_line = Rectangle2D((self.line_width, self.line_width), position=point, color=self.color) new_line.on_left_mouse_button_pressed = self.left_button_pressed @@ -3180,11 +3181,11 @@ def add_point(self, point, interactive=False): @property def color(self): - color = self.current_line.color - return np.asarray(color) + return np.asarray(self._color) @color.setter def color(self, color): + self._color = color for line in self.lines: line.actor.GetProperty().SetColor(*color) @@ -3386,6 +3387,8 @@ def cal_bounding_box(self, update_value=False, position=None): if self.shape_type == "polyline": vertices = self.shape.calculate_vertices() + if not vertices: + return else: vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] @@ -3744,6 +3747,8 @@ def left_button_dragged(self, i_ren, _obj, element): def mouse_move(self, i_ren, _obj, element): if self.is_creating_polyline: current_line = self.current_shape.shape.current_line + if not current_line: + return if np.linalg.norm(self.clamp_mouse_position(i_ren.event.position) - self.current_shape.shape.lines[0].position) < 10: self.current_shape.shape.resize( From 6ba3587d9ae49432c2af0ce3ba42f3c1051ab9e6 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 3 Aug 2022 18:57:33 +0530 Subject: [PATCH 20/59] remove print statement --- fury/ui/elements.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index b00c86ceb..a1d874047 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3072,7 +3072,6 @@ def __init__(self): self.group_rotation_slider.set_visibility(False) def update_rotation(slider): - print("update rotation") angle = slider.value previous_angle = slider.previous_value rotation_angle = angle - previous_angle From c1825b40adf85648d3fef3266187113762897d12 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 3 Aug 2022 19:27:46 +0530 Subject: [PATCH 21/59] adding pass statement --- fury/ui/elements.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 12a81d029..56da78bb2 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3101,6 +3101,7 @@ def _setup(self): # for point in self.points[1:]: # self.add_point(point) # self.lines.pop() + pass def _get_actors(self): """Get the actors composing this UI component.""" @@ -3190,8 +3191,8 @@ def color(self, color): line.actor.GetProperty().SetColor(*color) def left_button_pressed(self, i_ren, _obj, line): - # click_pos = np.array(i_ren.event.position) - # self._drag_offset = click_pos - self.position + click_pos = np.array(i_ren.event.position) + self._drag_offset = click_pos - self.position # i_ren.event.abort() # Stop propagating the event. self.on_left_mouse_button_pressed(i_ren, _obj, line) From babb57cd3bcfd39672458457dc3e8fedc546fee8 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sat, 6 Aug 2022 17:59:38 +0530 Subject: [PATCH 22/59] adding support for shortcut keys --- fury/ui/elements.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index a1d874047..0f15f456f 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3679,8 +3679,21 @@ def left_button_dragged(self, i_ren, _obj, element): self.handle_mouse_drag(mouse_position) i_ren.force_render() + def handle_keys(self, key, key_char): + mode_from_key = { + "s": "selection", + "l": "line", + "q": "quad", + "c": "circle", + "d": "delete", + } + if key.lower() in mode_from_key.keys(): + self.current_mode = mode_from_key[key.lower()] + def key_press(self, i_ren, _obj, _drawpanel): + self.handle_keys(i_ren.event.key, i_ren.event.key_char) self.key_status[i_ren.event.key] = True + i_ren.force_render() def key_release(self, i_ren, _obj, _drawpanel): # key = i_ren.event.key From b41164be03d068a9abc644bbd47c8874db75e75e Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sat, 6 Aug 2022 18:01:32 +0530 Subject: [PATCH 23/59] fixing pep --- fury/ui/elements.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index e90f5d0c3..c6c27dd55 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -874,7 +874,6 @@ def _setup(self): self.handles[1].on_left_mouse_button_released = \ self.handle_release_callback - def _get_actors(self): """Get the actors composing this UI component.""" return (self.track.actors + self.handles[0].actors + @@ -2839,6 +2838,7 @@ def left_button_clicked(self, i_ren, _obj, _list_box_item): def resize(self, size): self.background.resize(size) + class FileMenu2D(UI): """A menu to select files in the current folder. @@ -3186,7 +3186,8 @@ def _setup(self): self.rotation_slider = RingSlider2D(initial_value=0, text_template="{angle:5.1f}°") slider_position = self.drawpanel.position + \ - [self.drawpanel.size[0] - self.rotation_slider.size[0]/2, self.rotation_slider.size[1]/2] + [self.drawpanel.size[0] - self.rotation_slider.size[0]/2, + self.rotation_slider.size[1]/2] self.rotation_slider.center = slider_position self.rotation_slider.set_visibility(False) From e14e573e19745edde9630398506056cbd76bb7c6 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 7 Aug 2022 21:50:48 +0530 Subject: [PATCH 24/59] fixing selection issue --- fury/ui/elements.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index c6c27dd55..587cd7631 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3107,9 +3107,11 @@ def update_rotation(slider): self.group_rotation_slider.on_change = update_rotation def add(self, shape): - if shape in self.grouped_shapes: + if self.is_present(shape): self.remove(shape) else: + if self.is_empty(): + shape.drawpanel.update_shape_selection(shape) self.grouped_shapes.append(shape) shape.is_selected = True shape.rotation_slider.set_visibility(False) @@ -3124,10 +3126,18 @@ def remove(self, shape): shape.is_selected = False print(self.grouped_shapes) - def clear(self, shape): + def clear(self): + self.group_rotation_slider.set_visibility(False) + for shape in self.grouped_shapes: + shape.is_selected = False self.grouped_shapes = [] print(self.grouped_shapes) + def is_present(self, shape): + if shape in self.grouped_shapes: + return True + return False + def is_empty(self): return not bool(len(self.grouped_shapes)) @@ -3442,11 +3452,13 @@ def left_button_dragged(self, i_ren, _obj, shape): relative_center_position = click_position - \ self._drag_offset - self.drawpanel.position - if not self.drawpanel.shape_group.is_empty(): + if self.drawpanel.shape_group.is_present(self): self.drawpanel.shape_group.update_position( relative_center_position - self.center) - - self.update_shape_position(relative_center_position) + else: + if not self.drawpanel.shape_group.is_empty(): + self.drawpanel.shape_group.clear() + self.update_shape_position(relative_center_position) i_ren.force_render() else: self.drawpanel.left_button_dragged(i_ren, _obj, self.drawpanel) @@ -3680,6 +3692,8 @@ def clamp_mouse_position(self, mouse_position): def handle_mouse_click(self, position): if self.current_shape: self.current_shape.is_selected = False + if not self.shape_group.is_empty(): + self.shape_group.clear() if self.current_mode == "selection": if self.is_draggable: self._drag_offset = position - self.position From b3266e614e72b14f82dc77c951c12ea4be033b48 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 7 Aug 2022 21:54:22 +0530 Subject: [PATCH 25/59] updating clear method --- fury/ui/elements.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 587cd7631..c85f26975 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3127,6 +3127,8 @@ def remove(self, shape): print(self.grouped_shapes) def clear(self): + if self.is_empty(): + return self.group_rotation_slider.set_visibility(False) for shape in self.grouped_shapes: shape.is_selected = False @@ -3432,6 +3434,7 @@ def left_button_pressed(self, i_ren, _obj, shape): if self.drawpanel.key_status["Control_L"]: self.drawpanel.shape_group.add(self) else: + self.drawpanel.shape_group.clear() self.drawpanel.update_shape_selection(self) click_pos = np.array(i_ren.event.position) @@ -3456,8 +3459,7 @@ def left_button_dragged(self, i_ren, _obj, shape): self.drawpanel.shape_group.update_position( relative_center_position - self.center) else: - if not self.drawpanel.shape_group.is_empty(): - self.drawpanel.shape_group.clear() + self.drawpanel.shape_group.clear() self.update_shape_position(relative_center_position) i_ren.force_render() else: From b11045df64b70d9d3db9274d6246ca4c9b583d3e Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 7 Aug 2022 22:00:54 +0530 Subject: [PATCH 26/59] updating individual selection --- fury/ui/elements.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index c85f26975..84943bde6 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3433,8 +3433,7 @@ def left_button_pressed(self, i_ren, _obj, shape): self.set_bb_box_visibility(True) if self.drawpanel.key_status["Control_L"]: self.drawpanel.shape_group.add(self) - else: - self.drawpanel.shape_group.clear() + elif not self.drawpanel.shape_group.is_present(self): self.drawpanel.update_shape_selection(self) click_pos = np.array(i_ren.event.position) From 1abada77ff24ffdba9da1a43df7b69a78adad4f4 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 7 Aug 2022 22:07:30 +0530 Subject: [PATCH 27/59] deleting extra remove function --- fury/ui/elements.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 84943bde6..a9db61445 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3336,11 +3336,6 @@ def rotate(self, angle): self.cal_bounding_box(update_value=True) - def remove(self): - self._scene.rm(self.shape.actor) - for actor in self.bb_border.actors: - self._scene.rm(actor) - def show_rotation_slider(self): """Display the RingSlider2D to allow rotation of shape from the center. """ From 4b43a9710af66283ca5a2a2c76f43843b0f6d167 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 7 Aug 2022 22:18:02 +0530 Subject: [PATCH 28/59] keeping rotation slider on the top --- fury/ui/elements.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index a9db61445..c932d1c91 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3112,12 +3112,13 @@ def add(self, shape): else: if self.is_empty(): shape.drawpanel.update_shape_selection(shape) + self.add_rotation_slider(self._scene) + self.group_rotation_slider.set_visibility(True) self.grouped_shapes.append(shape) shape.is_selected = True shape.rotation_slider.set_visibility(False) self.group_rotation_slider.center = shape.rotation_slider.center - self.group_rotation_slider.set_visibility(True) print(self.grouped_shapes) @@ -3129,7 +3130,7 @@ def remove(self, shape): def clear(self): if self.is_empty(): return - self.group_rotation_slider.set_visibility(False) + self._scene.rm(*self.group_rotation_slider.actors) for shape in self.grouped_shapes: shape.is_selected = False self.grouped_shapes = [] @@ -3148,6 +3149,7 @@ def update_position(self, offset): shape.update_shape_position(shape.center + offset) def add_rotation_slider(self, scene): + self._scene = scene scene.add(self.group_rotation_slider) From d0ac73cf94bdc20aee7adbdb199c0e3e9ba839b9 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 10 Aug 2022 21:31:42 +0530 Subject: [PATCH 29/59] adding docs --- fury/ui/elements.py | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index c932d1c91..6f8cdaf87 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3086,7 +3086,6 @@ def directory_click_callback(self, i_ren, _obj, listboxitem): class DrawShapeGroup: def __init__(self): self.grouped_shapes = [] - self._position = None # Group rotation slider self.group_rotation_slider = RingSlider2D(initial_value=0, @@ -3107,6 +3106,13 @@ def update_rotation(slider): self.group_rotation_slider.on_change = update_rotation def add(self, shape): + """Add shape to the group. + + Parameters + ---------- + shape : DrawShape + + """ if self.is_present(shape): self.remove(shape) else: @@ -3123,11 +3129,21 @@ def add(self, shape): print(self.grouped_shapes) def remove(self, shape): + """Remove shape from the group. + + Parameters + ---------- + shape : DrawShape + + """ self.grouped_shapes.remove(shape) shape.is_selected = False print(self.grouped_shapes) def clear(self): + """Remove all the shapes from the group. + + """ if self.is_empty(): return self._scene.rm(*self.group_rotation_slider.actors) @@ -3137,18 +3153,43 @@ def clear(self): print(self.grouped_shapes) def is_present(self, shape): + """Check whether the shape is present in the group. + + Parameters + ---------- + shape : DrawShape + + """ if shape in self.grouped_shapes: return True return False def is_empty(self): + """Return whether the group is empty or not. + + """ return not bool(len(self.grouped_shapes)) def update_position(self, offset): + """Update the position of all the shapes in the group. + + Parameters + ---------- + offset : (float, float) + Distance by which each shape is to be translated. + + """ for shape in self.grouped_shapes: shape.update_shape_position(shape.center + offset) def add_rotation_slider(self, scene): + """Add rotation slider to the scene. + + Parameters + ---------- + scene : scene + + """ self._scene = scene scene.add(self.group_rotation_slider) From 8771333eb298c87b9a778fe8525580c8cf7f95ba Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 10 Aug 2022 21:32:33 +0530 Subject: [PATCH 30/59] removing print stmts --- fury/ui/elements.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 6f8cdaf87..7e21a0612 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3126,8 +3126,6 @@ def add(self, shape): self.group_rotation_slider.center = shape.rotation_slider.center - print(self.grouped_shapes) - def remove(self, shape): """Remove shape from the group. @@ -3138,7 +3136,6 @@ def remove(self, shape): """ self.grouped_shapes.remove(shape) shape.is_selected = False - print(self.grouped_shapes) def clear(self): """Remove all the shapes from the group. @@ -3150,7 +3147,6 @@ def clear(self): for shape in self.grouped_shapes: shape.is_selected = False self.grouped_shapes = [] - print(self.grouped_shapes) def is_present(self, shape): """Check whether the shape is present in the group. From e4c6a6be66403bd6645a12ed8e55fa5d46e85b31 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sat, 13 Aug 2022 11:23:38 +0530 Subject: [PATCH 31/59] removing grouped shapes on mode change --- fury/ui/elements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 7e21a0612..1926c1adb 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3522,6 +3522,7 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): """ self.panel_size = size super(DrawPanel, self).__init__(position) + self.shape_group = DrawShapeGroup() self.is_draggable = is_draggable self.current_mode = None @@ -3529,7 +3530,6 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): self.current_mode = "selection" self.shape_list = [] - self.shape_group = DrawShapeGroup() self.key_status = { "Control_L": False, "Shift_L": False, @@ -3638,6 +3638,7 @@ def current_mode(self, mode): self._current_mode = mode if mode is not None: self.mode_text.message = f"Mode: {mode}" + self.shape_group.clear() def cal_min_boundary_distance(self, position): """Calculate the minimum distance between the current position and canvas boundary. From d192694200739134e16993ad6c9bc3eae1eaa5bd Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 14 Aug 2022 22:23:32 +0530 Subject: [PATCH 32/59] updating polyline to use points_data --- fury/ui/elements.py | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 56da78bb2..c255f83f3 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3064,7 +3064,7 @@ class PolyLine(UI): """Create a Polyline. """ - def __init__(self, points_data=None, line_width=3, color=(1, 1, 1)): + def __init__(self, points_data=[], line_width=3, color=(1, 1, 1)): """Init this UI element. Parameters @@ -3072,16 +3072,13 @@ def __init__(self, points_data=None, line_width=3, color=(1, 1, 1)): position : (float, float), optional (x, y) in pixels. """ - self.points = points_data - if points_data is None: - self.points = [(0, 0)] + self.points_data = points_data + self.points = [] self.line_width = line_width self.lines = [] self.previous_point = None - self.current_point = None - self.in_process = False - self.color = color self.current_line = None + self.color = color super(PolyLine, self).__init__() def _setup(self): @@ -3089,23 +3086,16 @@ def _setup(self): Create a Polyline. """ - # line = Rectangle2D((.05, .05), position=self.points[0]) - - # line.on_left_mouse_button_pressed = self.left_button_pressed - # line.on_left_mouse_button_dragged = self.left_button_dragged + if len(self.points_data) < 2: + return - # self.current_line = line - # self.lines.append(line) - - # if len(self.points) > 2: - # for point in self.points[1:]: - # self.add_point(point) - # self.lines.pop() - pass + for ptn in self.points_data: + self.add_point(ptn) + self.add_point(self.points_data[0]) def _get_actors(self): """Get the actors composing this UI component.""" - pass + return self.lines def _add_to_scene(self, scene): """Add all subcomponents or VTK props that compose this UI component. @@ -3116,7 +3106,7 @@ def _add_to_scene(self, scene): """ self._scene = scene - # scene.add(*self.lines) + scene.add(*self.lines) def _get_size(self): pass @@ -3129,8 +3119,7 @@ def _set_position(self, coords): coords: (float, float) Absolute pixel coordinates (x, y). """ - # self.lines[0].position = coords - # update other points + pass def resize(self, size): offset_from_mouse = 2 @@ -3177,6 +3166,7 @@ def add_point(self, point, interactive=False): self.current_line = new_line self.lines.append(new_line) self.points.append(point) + self.previous_point = point if interactive: self._scene.add(new_line) @@ -3191,8 +3181,8 @@ def color(self, color): line.actor.GetProperty().SetColor(*color) def left_button_pressed(self, i_ren, _obj, line): - click_pos = np.array(i_ren.event.position) - self._drag_offset = click_pos - self.position + # click_pos = np.array(i_ren.event.position) + # self._drag_offset = click_pos - self.position # i_ren.event.abort() # Stop propagating the event. self.on_left_mouse_button_pressed(i_ren, _obj, line) @@ -3388,7 +3378,7 @@ def cal_bounding_box(self, update_value=False, position=None): if self.shape_type == "polyline": vertices = self.shape.calculate_vertices() - if not vertices: + if not vertices.any(): return else: vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] @@ -3762,6 +3752,7 @@ def mouse_move(self, i_ren, _obj, element): def left_button_released(self, i_ren, _obj, element): if self.is_creating_polyline: self.current_shape.shape.add_point(i_ren.event.position, True) + self.current_shape.cal_bounding_box(update_value=True) # if self.current_mode == "polyline": # self.shape_list[-1].shape.add_point(i_ren.event.position, interactive=True) From b0f06688bd2343fda3ef991374c131832835abaf Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 19 Aug 2022 20:59:49 +0530 Subject: [PATCH 33/59] removing extra comments --- fury/ui/elements.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 9b840aec5..c65d32267 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3771,13 +3771,6 @@ def key_press(self, i_ren, _obj, _drawpanel): i_ren.force_render() def key_release(self, i_ren, _obj, _drawpanel): - # key = i_ren.event.key - # if key == "Shift_L": - # self.key_status[key] = False - # elif key == "Control_L": - # self.key_status[key] = False - # elif key == "Alt_L": - # self.key_status[key] = False self.key_status[i_ren.event.key] = False From c5960ba94faeac18e10f58b32a86855395b1f089 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 19 Aug 2022 21:15:02 +0530 Subject: [PATCH 34/59] maintaining consistency in callback names --- fury/ui/containers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fury/ui/containers.py b/fury/ui/containers.py index aa285606a..bce56be4e 100644 --- a/fury/ui/containers.py +++ b/fury/ui/containers.py @@ -963,7 +963,7 @@ def mouse_move_callback2(istyle, obj, self): ANTICLOCKWISE_ROTATION_X = np.array([-10, 1, 0, 0]) CLOCKWISE_ROTATION_X = np.array([10, 1, 0, 0]) - def key_press_callback(self, istyle, obj, _what): + def on_key_press_callback(self, istyle, obj, _what): has_changed = False if istyle.event.key == "Left": has_changed = True @@ -1009,8 +1009,8 @@ def _setup(self): # TODO: this is currently not running self.add_callback(actor, "KeyPressEvent", - self.key_press_callback) - # self.on_key_press = self.key_press_callback2 + self.on_key_press_callback) + # self.on_key_press = self.on_key_press_callback2 def _get_actors(self): """Get the actors composing this UI component.""" From 66cf81028106446e05457dd139e9709aa72618b8 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sat, 20 Aug 2022 20:27:30 +0530 Subject: [PATCH 35/59] removing prints --- fury/ui/elements.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index eed76c277..2022a7104 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3453,7 +3453,6 @@ def resize(self, size): def remove(self): """Remove the Shape and all related actors. """ - print(self.drawpanel.shape_list) self.drawpanel.shape_list.remove(self) self._scene.rm(self.shape.actor) self._scene.rm(*[border.actor for border in self.bb_box]) @@ -3472,7 +3471,6 @@ def left_button_pressed(self, i_ren, _obj, shape): self._drag_offset = click_pos - self.center i_ren.event.abort() elif mode == "delete": - # print(self.drawpanel.shape_list) self.remove() else: self.drawpanel.left_button_pressed(i_ren, _obj, self.drawpanel) From 1ae116c8e97a05519d94ff8257c831579e1c9672 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sat, 20 Aug 2022 20:34:04 +0530 Subject: [PATCH 36/59] initializing `DrawShapeGroup`'s scene --- fury/ui/elements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 2022a7104..0d599b7b8 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3087,6 +3087,7 @@ def directory_click_callback(self, i_ren, _obj, listboxitem): class DrawShapeGroup: def __init__(self): self.grouped_shapes = [] + self._scene = None # Group rotation slider self.group_rotation_slider = RingSlider2D(initial_value=0, @@ -3187,7 +3188,6 @@ def add_rotation_slider(self, scene): scene : scene """ - self._scene = scene scene.add(self.group_rotation_slider) @@ -3604,7 +3604,7 @@ def _add_to_scene(self, scene): iren = scene.GetRenderWindow().GetInteractor().GetInteractorStyle() iren.add_active_prop(self.canvas.actors[0]) self.canvas.add_to_scene(scene) - self.shape_group.add_rotation_slider(scene) + self.shape_group._scene = scene def _get_size(self): return self.canvas.size From a76f5de431f3487ffa95dec743eaac793ea9f53d Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 21 Aug 2022 21:42:50 +0530 Subject: [PATCH 37/59] renaming interactive parameter --- fury/ui/elements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index c255f83f3..bccb449ff 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3155,7 +3155,7 @@ def calculate_vertices(self): vertices_from_actor(line.actor)[:, :-1], axis=0) return vertices - def add_point(self, point, interactive=False): + def add_point(self, point, add_to_scene=False): if self.current_line: self.resize(np.asarray(point) - self.current_line.position) @@ -3167,7 +3167,7 @@ def add_point(self, point, interactive=False): self.lines.append(new_line) self.points.append(point) self.previous_point = point - if interactive: + if add_to_scene: self._scene.add(new_line) @property From ce0c2e3c8534989123985e0f9d67930e47743b28 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 21 Aug 2022 21:43:09 +0530 Subject: [PATCH 38/59] adding control points --- fury/ui/elements.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index bccb449ff..6a8b92b08 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3163,12 +3163,14 @@ def add_point(self, point, add_to_scene=False): new_line.on_left_mouse_button_pressed = self.left_button_pressed new_line.on_left_mouse_button_dragged = self.left_button_dragged + control_point = Disk2D(5, center=point) + self.current_line = new_line self.lines.append(new_line) self.points.append(point) self.previous_point = point if add_to_scene: - self._scene.add(new_line) + self._scene.add(new_line, control_point) @property def color(self): From e57ac1ca8a2085977f64cda31baf2b2010b20db7 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 23 Aug 2022 21:54:24 +0530 Subject: [PATCH 39/59] removing redundant position param --- fury/ui/elements.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 0d599b7b8..b114f3ca3 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3383,7 +3383,7 @@ def show_rotation_slider(self): self.rotation_slider.add_to_scene(self._scene) self.rotation_slider.set_visibility(True) - def cal_bounding_box(self, position=None): + def cal_bounding_box(self): """Calculate the min, max position and the size of the bounding box. Parameters @@ -3391,8 +3391,7 @@ def cal_bounding_box(self, position=None): position : (float, float) (x, y) in pixels. """ - position = self.position if position is None else position - vertices = position + vertices_from_actor(self.shape.actor)[:, :-1] + vertices = self.position + vertices_from_actor(self.shape.actor)[:, :-1] min_x, min_y = vertices[0] max_x, max_y = vertices[0] @@ -3411,7 +3410,7 @@ def cal_bounding_box(self, position=None): self._bounding_box_max = np.asarray([max_x, max_y], dtype="int") self._bounding_box_size = np.asarray([max_x-min_x, max_y-min_y], dtype="int") - self._bounding_box_offset = position - self._bounding_box_min + self._bounding_box_offset = self.position - self._bounding_box_min def clamp_position(self, center=None): """Clamp the given center according to the DrawPanel canvas. From 36adf8201ec0c106b58cc66bcdbe6fa999cbaa89 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 28 Aug 2022 10:23:28 +0530 Subject: [PATCH 40/59] fix overlapping of the shapes when translated near the borders --- fury/ui/elements.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index b114f3ca3..e4e0acd7c 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3085,9 +3085,10 @@ def directory_click_callback(self, i_ren, _obj, listboxitem): class DrawShapeGroup: - def __init__(self): + def __init__(self, drawpanel): self.grouped_shapes = [] self._scene = None + self.drawpanel = drawpanel # Group rotation slider self.group_rotation_slider = RingSlider2D(initial_value=0, @@ -3177,8 +3178,38 @@ def update_position(self, offset): Distance by which each shape is to be translated. """ + vertices = [] + for shape in self.grouped_shapes: + vertices.extend(shape.position + vertices_from_actor(shape.shape.actor)[:, :-1]) + + min_x, min_y = vertices[0] + max_x, max_y = vertices[0] + + for x, y in vertices: + if x < min_x: + min_x = x + if y < min_y: + min_y = y + if x > max_x: + max_x = x + if y > max_y: + max_y = y + + _bounding_box_min = np.asarray([min_x, min_y], dtype="int") + _bounding_box_max = np.asarray([max_x, max_y], dtype="int") + _bounding_box_size = np.asarray([max_x-min_x, max_y-min_y], dtype="int") + + group_center = _bounding_box_min + _bounding_box_size//2 + + shape_offset = [] for shape in self.grouped_shapes: - shape.update_shape_position(shape.center + offset) + shape_offset.append(shape.center - group_center) + + new_center = np.clip(group_center + offset, self.drawpanel.position + _bounding_box_size//2, + self.drawpanel.position + self.drawpanel.size - _bounding_box_size//2) + + for shape, soffset in zip(self.grouped_shapes, shape_offset): + shape.update_shape_position(new_center + soffset - self.drawpanel.position) def add_rotation_slider(self, scene): """Add rotation slider to the scene. @@ -3517,7 +3548,7 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): """ self.panel_size = size super(DrawPanel, self).__init__(position) - self.shape_group = DrawShapeGroup() + self.shape_group = DrawShapeGroup(self) self.is_draggable = is_draggable self.current_mode = None From a3d277da54881047982d03e9804f0e7bb7ccb7f9 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 30 Aug 2022 07:09:30 +0530 Subject: [PATCH 41/59] making adjustment to support merging --- fury/ui/elements.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 3dae94274..7918340f9 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3376,7 +3376,6 @@ def __init__(self, shape_type, drawpanel=None, position=(0, 0)): self.shape_type = shape_type.lower() self.drawpanel = drawpanel self.max_size = None - self.is_selected = True super(DrawShape, self).__init__(position) self.shape.color = np.random.random(3) @@ -3550,10 +3549,6 @@ def show_rotation_slider(self): """ self._scene.rm(*self.rotation_slider.actors) self.rotation_slider.add_to_scene(self._scene) - slider_position = self.drawpanel.position + \ - [self.drawpanel.size[0] - self.rotation_slider.size[0]/2, - self.rotation_slider.size[1]/2] - self.rotation_slider.center = slider_position self.rotation_slider.set_visibility(True) def cal_bounding_box(self): @@ -3867,8 +3862,8 @@ def draw_shape(self, shape_type, current_position, in_process=False): if shape_type == "circle": shape.max_size = self.cal_min_boundary_distance(current_position) self.shape_list.append(shape) - self.update_shape_selection(shape) self.current_scene.add(shape) + self.update_shape_selection(shape) self.canvas.add_element(shape, current_position - self.canvas.position) else: @@ -3981,7 +3976,7 @@ def mouse_move(self, i_ren, _obj, element): def left_button_released(self, i_ren, _obj, element): if self.is_creating_polyline: self.current_shape.shape.add_point(i_ren.event.position, True) - self.current_shape.cal_bounding_box(update_value=True) + self.current_shape.cal_bounding_box() # if self.current_mode == "polyline": # self.shape_list[-1].shape.add_point(i_ren.event.position, interactive=True) From 4fe5d3bc9860e5ef8db9a00edf853a9546502051 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 9 Sep 2022 21:00:45 +0530 Subject: [PATCH 42/59] using cal_bounding_box from helper --- fury/ui/elements.py | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 3a079b3dc..6f520fb63 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3183,31 +3183,17 @@ def update_position(self, offset): for shape in self.grouped_shapes: vertices.extend(shape.position + vertices_from_actor(shape.shape.actor)[:, :-1]) - min_x, min_y = vertices[0] - max_x, max_y = vertices[0] + bounding_box_min, bounding_box_max, \ + bounding_box_size = cal_bounding_box_2d(vertices) - for x, y in vertices: - if x < min_x: - min_x = x - if y < min_y: - min_y = y - if x > max_x: - max_x = x - if y > max_y: - max_y = y - - _bounding_box_min = np.asarray([min_x, min_y], dtype="int") - _bounding_box_max = np.asarray([max_x, max_y], dtype="int") - _bounding_box_size = np.asarray([max_x-min_x, max_y-min_y], dtype="int") - - group_center = _bounding_box_min + _bounding_box_size//2 + group_center = bounding_box_min + bounding_box_size//2 shape_offset = [] for shape in self.grouped_shapes: shape_offset.append(shape.center - group_center) - new_center = np.clip(group_center + offset, self.drawpanel.position + _bounding_box_size//2, - self.drawpanel.position + self.drawpanel.size - _bounding_box_size//2) + new_center = np.clip(group_center + offset, self.drawpanel.position + bounding_box_size//2, + self.drawpanel.position + self.drawpanel.size - bounding_box_size//2) for shape, soffset in zip(self.grouped_shapes, shape_offset): shape.update_shape_position(new_center + soffset - self.drawpanel.position) @@ -3260,6 +3246,8 @@ def _setup(self): else: raise IOError("Unknown shape type: {}.".format(self.shape_type)) + self.cal_bounding_box() + self.bb_box = [Rectangle2D(size=(3, 3)) for i in range(4)] self.set_bb_box_visibility(False) @@ -3410,10 +3398,6 @@ def show_rotation_slider(self): """ self._scene.rm(*self.rotation_slider.actors) self.rotation_slider.add_to_scene(self._scene) - slider_position = self.drawpanel.position + \ - [self.drawpanel.size[0] - self.rotation_slider.size[0]/2, - self.rotation_slider.size[1]/2] - self.rotation_slider.center = slider_position self.rotation_slider.set_visibility(True) def cal_bounding_box(self): @@ -3684,10 +3668,10 @@ def draw_shape(self, shape_type, current_position): position=current_position) if shape_type == "circle": shape.max_size = self.cal_min_boundary_distance(current_position) - self.shape_list.append(shape) - self.update_shape_selection(shape) self.current_scene.add(shape) self.canvas.add_element(shape, current_position - self.canvas.position) + self.shape_list.append(shape) + self.update_shape_selection(shape) def resize_shape(self, current_position): """Resize the shape. From bace0bb5c18c4dc2e60f544ac68d14f7a1a323db Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Mon, 12 Sep 2022 19:26:58 +0530 Subject: [PATCH 43/59] fixing integrating issues --- fury/ui/elements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 9223c479f..3a0659284 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3105,7 +3105,7 @@ def update_rotation(slider): for shape in self.grouped_shapes: current_center = shape.center shape.rotate(np.deg2rad(rotation_angle)) - shape.update_shape_position(current_center - shape.drawpanel.position) + shape.update_shape_position(current_center - shape.drawpanel.canvas.position) self.group_rotation_slider.on_change = update_rotation @@ -3184,7 +3184,7 @@ def update_position(self, offset): vertices.extend(shape.position + vertices_from_actor(shape.shape.actor)[:, :-1]) bounding_box_min, bounding_box_max, \ - bounding_box_size = cal_bounding_box_2d(vertices) + bounding_box_size = cal_bounding_box_2d(np.asarray(vertices)) group_center = bounding_box_min + bounding_box_size//2 From 674c5290d35d57827235400f17cd05ea3ded0465 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Mon, 12 Sep 2022 20:39:15 +0530 Subject: [PATCH 44/59] adding debug param --- fury/ui/elements.py | 57 ++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 3a0659284..b2eca88b8 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3213,7 +3213,7 @@ class DrawShape(UI): """Create and Manage 2D Shapes. """ - def __init__(self, shape_type, drawpanel=None, position=(0, 0)): + def __init__(self, shape_type, drawpanel=None, position=(0, 0), debug=False): """Init this UI element. Parameters @@ -3224,11 +3224,14 @@ def __init__(self, shape_type, drawpanel=None, position=(0, 0)): Reference to the main canvas on which it is drawn. position : (float, float), optional (x, y) in pixels. + debug : bool, optional + Set visibility of the bounding box around the shapes. """ self.shape = None self.shape_type = shape_type.lower() self.drawpanel = drawpanel self.max_size = None + self.debug = debug super(DrawShape, self).__init__(position) self.shape.color = np.random.random(3) @@ -3248,8 +3251,8 @@ def _setup(self): self.cal_bounding_box() - self.bb_box = [Rectangle2D(size=(3, 3)) for i in range(4)] - self.set_bb_box_visibility(False) + if self.debug: + self.bb_box = [Rectangle2D(size=(3, 3)) for i in range(4)] self.shape.on_left_mouse_button_pressed = self.left_button_pressed self.shape.on_left_mouse_button_dragged = self.left_button_dragged @@ -3257,10 +3260,6 @@ def _setup(self): self.rotation_slider = RingSlider2D(initial_value=0, text_template="{angle:5.1f}°") - slider_position = self.drawpanel.position + \ - [self.drawpanel.size[0] - self.rotation_slider.size[0]/2, - self.rotation_slider.size[1]/2] - self.rotation_slider.center = slider_position self.rotation_slider.set_visibility(False) if self.drawpanel: @@ -3294,8 +3293,9 @@ def _add_to_scene(self, scene): """ self._scene = scene self.shape.add_to_scene(scene) - scene.add(*[border.actor for border in self.bb_box]) self.rotation_slider.add_to_scene(scene) + if self.debug: + scene.add(*[border.actor for border in self.bb_box]) def _get_size(self): return self.shape.size @@ -3365,22 +3365,23 @@ def selection_change(self): self.set_bb_box_visibility(False) def set_bb_box_visibility(self, value): - if value: - border_width = 3 - points = [self._bounding_box_min-(0, border_width), - [self._bounding_box_max[0], self._bounding_box_min[1]], - self._bounding_box_min - border_width, - [self._bounding_box_min[0]-border_width, self._bounding_box_max[1]]] - size = [(self._bounding_box_size[0]+border_width, border_width), - (border_width, self._bounding_box_size[1]+border_width), - (border_width, self._bounding_box_size[1] + border_width), - (self._bounding_box_size[0]+border_width, border_width)] - for i in range(4): - self.bb_box[i].position = points[i] - self.bb_box[i].resize(size[i]) - - for border in self.bb_box: - border.set_visibility(value) + if self.debug: + if value: + border_width = 3 + points = [self._bounding_box_min-(0, border_width), + [self._bounding_box_max[0], self._bounding_box_min[1]], + self._bounding_box_min - border_width, + [self._bounding_box_min[0]-border_width, self._bounding_box_max[1]]] + size = [(self._bounding_box_size[0]+border_width, border_width), + (border_width, self._bounding_box_size[1]+border_width), + (border_width, self._bounding_box_size[1] + border_width), + (self._bounding_box_size[0]+border_width, border_width)] + for i in range(4): + self.bb_box[i].position = points[i] + self.bb_box[i].resize(size[i]) + + for border in self.bb_box: + border.set_visibility(value) def rotate(self, angle): """Rotate the vertices of the UI component using specific angle. @@ -3452,6 +3453,7 @@ def resize(self, size): self.shape.outer_radius = hyp self.cal_bounding_box() + self.set_bb_box_visibility(True) def remove(self): """Remove the Shape and all related actors. @@ -3507,7 +3509,7 @@ class DrawPanel(UI): """The main Canvas(Panel2D) on which everything would be drawn. """ - def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): + def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False, debug=False): """Init this UI element. Parameters @@ -3518,12 +3520,15 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): (x, y) in pixels. is_draggable : bool, optional Whether the background canvas will be draggble or not. + debug : bool, optional + Set visibility of the bounding box around the shapes. """ self.panel_size = size super(DrawPanel, self).__init__(position) self.shape_group = DrawShapeGroup(self) self.is_draggable = is_draggable self.current_mode = None + self.debug = debug if is_draggable: self.current_mode = "selection" @@ -3671,7 +3676,7 @@ def draw_shape(self, shape_type, current_position): Lower left corner position for the shape. """ shape = DrawShape(shape_type=shape_type, drawpanel=self, - position=current_position) + position=current_position, debug=self.debug) if shape_type == "circle": shape.max_size = self.cal_min_boundary_distance(current_position) self.current_scene.add(shape) From 924c730727c367ed247c5c5c0f3f6a2acd50dd6b Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 14 Sep 2022 22:01:39 +0530 Subject: [PATCH 45/59] updating tests --- fury/data/files/test_ui_draw_panel_basic.json | 2 +- .../files/test_ui_draw_panel_basic.log.gz | Bin 12221 -> 11303 bytes .../files/test_ui_draw_panel_rotation.json | 2 +- .../files/test_ui_draw_panel_rotation.log.gz | Bin 12733 -> 11845 bytes 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fury/data/files/test_ui_draw_panel_basic.json b/fury/data/files/test_ui_draw_panel_basic.json index 4e1cd457a..c806a95b5 100644 --- a/fury/data/files/test_ui_draw_panel_basic.json +++ b/fury/data/files/test_ui_draw_panel_basic.json @@ -1 +1 @@ -{"CharEvent": 0, "MouseMoveEvent": 993, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 17, "LeftButtonReleaseEvent": 17, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file +{"CharEvent": 0, "MouseMoveEvent": 2917, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 17, "LeftButtonReleaseEvent": 17, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_draw_panel_basic.log.gz b/fury/data/files/test_ui_draw_panel_basic.log.gz index bcb63eef94d264c1679938d9de46d9ac0322494e..e5302cb4df071c1ac79121ae2ef772411203c386 100644 GIT binary patch literal 11303 zcmV+?EZEZ@iwFoP0U~1p|8!+@bYFF8Uu1G&cVBQ}Ze?s=VqtS>V=ioOX8_fmORHT; za)sypD-L+PO+F%DQ$r0Jy1{gV=b}r70$T+tb>pA!SRruhUY+>1>fVu&G|@Wyl{

B}OW`&i)^oTzTvrC$S&x3wYul=iK2Hi;d338O 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 d@=X}_Pai*I&`V=ioOX8@(0ON%8* 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 Date: Fri, 16 Sep 2022 08:43:28 +0530 Subject: [PATCH 46/59] updating rotation test --- .../files/test_ui_draw_panel_rotation.json | 2 +- .../files/test_ui_draw_panel_rotation.log.gz | Bin 11845 -> 10870 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/fury/data/files/test_ui_draw_panel_rotation.json b/fury/data/files/test_ui_draw_panel_rotation.json index ab4999e90..99bf9b9c4 100644 --- a/fury/data/files/test_ui_draw_panel_rotation.json +++ b/fury/data/files/test_ui_draw_panel_rotation.json @@ -1 +1 @@ -{"CharEvent": 0, "MouseMoveEvent": 3055, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 12, "LeftButtonReleaseEvent": 12, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file +{"CharEvent": 0, "MouseMoveEvent": 2534, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 12, "LeftButtonReleaseEvent": 12, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_draw_panel_rotation.log.gz b/fury/data/files/test_ui_draw_panel_rotation.log.gz index 8d3bf882ad297d4df68d9bf83f58ef00be568970..fe3ccb1445a3105759fd19445d1adcf7542ab9f2 100644 GIT binary patch literal 10870 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 M1DY|{IWkoN01k=L3jhEB literal 11845 zcmV-LF1pbliwFp<0U~1p|8!+@bYFF8Uu1G&cVBQ}Ze?s=a&L5DbZKvHE^KdS0HvKx ztE9*8QqW{@a>3JO6DTegl@b z<1ezTeC0g8Hf~%xTmI#}$D@I%XFVVO)qI?#pRQ;7@23y7?(Q=r|Floqnvc-xW4q=r zwECJ~d;Z(L71fsiwicxPr+op=pIP-?f36%gSYoxz!9|ztFDplVbG96vu(4O4ux)$- zHALGd(3+#&hft0h1W?rnZH3?Uh%X&kJ}%O?>jOT05@&uFe15m&z5D;~);nL%(kr~c z24Ly6?+rICuo9R?l)h8cboud^Fn@&#au0xcjH`vE5|}?cy8zSI>k9&amB8|0@b_Q- z``f?$?c2AXzW&G0Uw--J1E2TZA)mSeCtUM)vHm}Q`Hx@z=gW`3JpH3zKd*ebVsJvd z?~&qIFw0h>C1bf_ahG?>+1PtH)Fd@w@QCguu%G%)Ox;)7rp2F zJmFJNr!zPdSb@0TUu3@Tw5~&89>n@&l{L}teJETW^ zo9lT?jEsNz9V*zO0DGiCcpBQ$F$v}{C4s@SJxVm zXS)0>*z&_yA2TvMDePaq^56F5qva=@OW1ELA5539#dt$_Fx(k#d}#%rTD}o#Wo!8? z>AgkbEFDZImaz1IzhjQ4hwU0G1g0Q!*uzkO+ZqV_)F;?O0DyI(Y_N;_aTy+J^=L|q zR~leoGD!zG7%e){z(`dHkROMAC2uP~n)r3C{G0urcSUs2H|N`guXimEdUTk|S7N_k zrML*qBRyrR^TUqLe^x6U9*s_qf{k9I)lO$#ql2q5@cHodIE~<18J-9?!%I`H2w+=y z-@cmAGxv26g3#(~0=@5}tq}s?MdPr=)|kf|zW%qpsqO(gr^axj+@L1zrp#i?^Xg_J zmdg}Av@M&qhJQ$wWaKYcsmr7+cVWCxw2m7uy|MbY8xe{$qQd0UM zPx0w@8zfv!iG+M8`oh)Vh`N;BFx=cAqo$#lFMA$Vw|oZrt8DoM)J>N>&^bPV^Ek)P z)%i)ultap2W$}|W%v?wFKtidj@j*1np8Tfo_|+jP66o~VN_R$!N{p1W6yl3<{x(W1 z)oDz6t57vk8Zmv|KBcOq8-Ran&C69DvDJLs3(bdppq0=<-<%)A1y;D9{N;LNpGRx{ zobq*ZP>t5-8N9lE%h$Y4g>ZrS*UnpqBa9zY@&n8*&v>b-l^ z3$&L(NC|jk@-mp;XM?=3wtWN#yp`5p11F@o8C*~bP*S77T*;Fy0yiyHG(YnY+h;a| zgBHNozZP%hyb9(Ieo)CmV1F4L1kR6vPcx#IpI-#hNXjR_m`&{}ig^D=zaAHP8ppSw zv0nq{Z5{~c@)YT`TYl8?qg2h0&K~gBd~sFWXWuu|Cs#5cY3{0hgtO*3?dEFGGT}5?+oUZxr!jZdc zmOx-P7A+24t3#e?96C+qFLd}C3#--?$0c7KL|Cf>*4h&+sSMV7+UqmM$|Lk0X=VDc zGOxv4p%YFgGZ=a~YPUS2BjR6P|DHaw@+Elsj}lr59r(?@Hv_8U7@$UXz-XxlYZrAzX+4D}j?}8s$HEy(aKl+r#fTjYcUqQc zhH7oOf_Y`4-0sH3BjC;OVt9TE9t?Mejp2rH;m1<=z6)PD)XKEe92zVK2janPXfK)} zu>UE7skB8c*n$f5JZk@fJ=-TGl<|DISWr9cw^e8zg-D{)`A*xzgq>*b&9Y^mKs%gI^jVnu ze$9aTkR!Aa>a#4*IVgJEz&PlH8piq_AMHV>mpy4kf;H9y6hbFtLIQ;~b|Bgjr5uZ* zHaPis=y8EI+854sRqy>@@pn84DG6PW0n&~K8EM6I1p5?$r^_FU z2GhZAKH@qz>=J4|JFQeAQX|iLFx-jK|0XtY0q6Iv<|*hT6f>USLcLXLu%s5rVCCoU zTW$SUaQ!I!yqoXpI~K!(VMA{IMK8v|*Q)X-jG4L?4M;InVK{3)pnAGE>Od6WiTGBH zXYR&E{Q_8kxa&q7^L*i4$O@5o6y)rU+7ZT~%_5or;q-`TM-*u=+GSowk(bc%egoaa zJp>fnKB9Nx5R)>0gBPgL*br$n-_AR3xN6IT0BX>V7+cx^HaqP}1DEC&EjH=eEaO`GU?h8U6AS0;b^{UsiQvc)(b*Q4(4yj!2@6~(6r`51G*wZCNFXQxF7O75X@f@tQG&(l&4w}9r3T^4! zmjqN!)9V(g)HG%pkw1cnXG|j=-T+B1k5>j97Jcvy&>E{dlA@%h2(a!O;IR;19pQst z9ujN-8-BSByl>M$h15dDOOt>J!3$w6CImNps3UF+s1inlNs4$d6lXltd{xnJ51<2{ zs5hrkY^Mple8&RGz2(c<$T#mL?ZkX-S$zh9AcHl5^Hpv6+#Y$gc?xdl(>tuLEW;2C1ZOd;k z=!W@?@z&mCy=^>)u!**T}fown?Iewv;hG2h`B;SciOkZXOS+x7&PB=1rI4mvetdKgTLd$&O`elLCs?wG~c|9aD?=Klryngoxhr)7|6u@*eGG+65NP^DrDsh93s z?SN2gSIVtOOm`3~+=6=rB<4{jvE-<%py&h`VZ8=53!0ufa4e-j0d&>S`ZWrzLF-q^ z$=;EZ+My{#rbE#Euz1b$=&8dqm)7qY2ygy0k!^QG-*97~Gpwg(beyR~b{WCDTGToP zin*EstwX;&3Y~X@!4KT3bN7OzT<&4;Xd%KrK?_MH|F7gYZ?3`Wg&Af0Hz8b;^gO7WS}TzTjy z9_fHDmrFo?IkHhR3)HzqFoR5q*l8UcqXuewU%`{%g*s1jGkk&#omTBD!;N8Mcm`2Bg*U>I zdIzq9sEFWqwK0PraaMvgkbx;7Nm_#Sz7Lz10qM1naF;SRkoyP~dN2kIRj|c=E*r&^ zfC>?!VdYYr_TkSizU3KA*HHNJ41Vt?DE6TC4Xa-aDiDA75ew^mm5>}LvPl|=XDSrI zNkC2+4{Q+CG7H(Eq(c4ZITLcMb2TV8;SJNvQPrt^zKMTAE zY*fHRTyN-AC%E%(!{T`Gm>KZmPcNJUpt>LM;V*}doq+MNv{D&1hUceX?2>BKW7RRH z3Akb;d<8d#dC3D?n1K1dE3cTX;F3w}QjLa3G+mqwFAOsq?(|nMunn%@i6{Ikc%UzH z!1glSF}v^)_`*?oGQ1expMnp;AHRJ4^FNecAF+!YdJ+ax9w^{~@YSo}*#Xf|oDZ=@q7{t)DzIZzf`Eqk zf>~58(r~&|GO-%u1tymu7AZ@qxuX^|k%bmL|88iP_ouxvm9PfoLRO;_1Y{vf!RT#( z#AHN%jfNPZ4Mh^%Xr2IRQ2mc=}*YxO`|AT-DO6~;c$vq+fSngZU|Im zVE~LhLljT(gHFV%V{UBvW^3=T>Gj5dz-BN^857-nd$|M zjvMUfLI<_kK_np-mQfW0P<0$Pq9j3ZB@@L$m0G(nO?VaE-=ojti}!!?Miei8P2@_t0(3KKrOw3#W)6I*2(81`PEL0S&ffRHa2Q+9OOH9E3_?B19el2 z4wNqkHA%OIUD7)I0INvxTL*7Gb?{ulhct5V(;eN*ZN9s)=ZY&2fAlL7dk;QYN)Bw{ z&DA6F9%@Oh+f z0Gah3we3cI7sM#nbjT-@PDf--F2-$skm!=U&vTcy@my)qP(R4zzV^j*^-792U}<5> zQ^Ub=Z(?>!p#a$I3T46KAR2r@ZnC4su*yS}r}oD}`le(vpiDFfulmb|W-(ve4l+(Q zGEO$;sW+A@HZmhNvNIuu^(b^3@#HqrZaCA zoSSIRP-&3m-k@A0)Dom#QHZP!8Gkj?4dQ|48^R%1_e0t7O$lys=kl0rlh}qq{(2su zd-;pWv)8jhT60j&(+3lIK#}Wndl{@RgJd%w-O?BC*!4yhM;BjCy01n))4kIxKUYD~ zBqHY~n>Yl;!iA;6w;)(nrIS;TKLMsn%0oj?(4qwhWX-XEPSG{hwq=D20^K@Uz^*@$ zj3c%<5JPn60HuN!X9J8^h>kB^BuGmrTLUWhXJS^ea86T^b)MjlWNm?5?j~_EpkEB@YHU?0_ekJwW>H5q`SZUVewgn+HE^ z`gHG{qM!KeRwKP-W3~4l76AEa%+T0fa&n-!u5t`Ws>dmG^BEq@f~58i+UJt625TiX zO@nKbk`rX<)u|sy4f?RU$xsOL;1bz{;=+jX06KLKRC0lWCTUV2 zkxAGTc#KlXZKNGg!Yl)&15k@liK?La*fBg9NsKP18EH4{jdEd7gw#o3Qd|mwr4I}sFLrW? z6UAYpQ4i5V)Lb%&R8onOGFhwQkf&@MU<})hsWrvN0Gw~sW(}*8l0zV`Ag&=SeM$gP zW6LPCu2$I_gVvM;PWK@K9yPWoet_0bAN|x^qT&WHo>l6Gim+-QK*JNM4En30G&0a7 z2aeJeV>UI*f=HaE0Gu)8WlNQCQLo5hpizZT9GpD$ukv9YEehTE+5Dc*CY3j#`N2VG zPVG?(Da>mvqCoT%Ob@dVALL~p7ta#;f;eD@;nNg`y!9$fcDToWlK+bP0 zMhmbu8;zD3!=aE#7>v{ze)9mg>rXF+bKbF!AB9_&c6(qX{j* zAsdr=+2!CjJ5>ks)1DmoR;9%n-iAmth4j$Dg4Or81CBbpISwQ0t6aw_)DpC<4{(wk zBo86J1{D6J$_FY1OS z(oJSQF@f{tP=;Ed(4rhRl*i+!1m+hygL8s>uO)My;=)%cguNs6UB6MoZ6Oo~c$vaCvxupGyN=eFxT4)p~<) z!|PY0Ioy4)Y%B2>nCAOY~5uN-G_eTpP)uL_%$( zEQ#%L7R-cnf zg>Mqys)&Z0H8F<(RTqqr_)y351&L(SXSI^|N!&F!9Y`)N-(_}(7q8$TA2X7sfRJbr z-%e&;3wVvxX4xmTcPa!0e>MNzv#GTl5{2^0gB+&Vr$m`mlvfK9ZYeR0zEY7GZYWy; ziIk~aM;>cTIVFLFg3OHaFlgGL<#|SHbs-T1$ni{Nh@f;TmSIN$2B0v`XbRt?<^dj0 zQ-|Apt)B9q$ZL>t2ZtlWTqxZ=Rl)!rk~Ju|HG>k5w&u`#Hhmz2eD;j^FMn#15XghL zsgN56GON)}79|e2^Op~XC&Tc*1#gCrp-c(n@dG|Aax%%|-oOn$REjE!+Q2iHfD+7a zDI~!J7I~fmmr5L_scz(akJ9u9&Y9L0BXO5XYJpRM2GqkOAezUeGd#}o8*7WC2qg^z zR-M``fJv#kPKQ1Uu{1$BMqv_#sQ=e=@{o5w4OKl{EkB&!|G5OKzHdan&Q$(uZPJgRCq?dL2HJL ze5{FDJ0QCCkvax6$kb>z0znyq^~?d505sL6F5rT&@r|{Kk465*AcZ5-XwXHE$5N^WRq1nK`6sX~PN_rl3rWVIa#PnK=GEvQl}D7;0XPlB$PFapCG*C~FE0 zpoI#IvNncL#jtj{2_1;)G@k!%v{L7}N)T$QkCKV=OU)m=YoBI)YWqIP6^y zm!_!)HzvJ4V9iINIfp01;$R=(+~aSnyOZFFe1A1)5xnur)kcT#LO5K>0XBv^!;LR( zGt95#@GIH#@|go`LFrGZfQh2LLA$=w^fxW_(BC2I4;0w6$*fI+Mx-RFB0-+lwF{<5 zMMFo4IKhG~g{TTgH;q*2ObHm8QQ!M1C*0V_KfCyrr!eqM25onuqd-^^u)*U&r_Rt} z&12^#YJNe1d=NX8_~)TRGpJ8^*bB-PK2j&H*#m{%K*&uJ@cBk1F{1pA6f&dQ2DE

4*VeRz3-wB3{jA?NwE)dMJKsUE{eF&^^mOA+Z9?7MTa#IFcI26 zfeu6`DdtudMq%`j=_aF%?GP*bl%iLnp^XLGt4<^@Gs;fc8__E#tW(jGn#4|Pq#S69 z_HylKZRk(lKj8z9ioj=+aH&+P8-13nmp8Ry1x!Z1n~uV9Fe8_FGKF1c%4YZ=T=};_hFvZ9^}-ka@sFN6);|8##dUt~Li>n4`R@0B zkW9G8KYQke`&+K#+4!YCj|;d{swa{fwE7=vW7GnF(Cm4mi|=A1QzHp@=v`T)i>DkB zF!59ZC?pXEN)dCB@uSd27OAi;$Y-aZhv4}Hx-T}gNxFO2*Y=dt{(j4vXvXO%RW#WT()E4`*Yn5bOMv^Y}zb$aOG@yDB+mLV_tQTfoG0 zV0xxSkJTb=chELzp%`h@5QFge6g(MT3~#=%O+}hFcrv^g-k*Zc%kZMb?-J(0q&GHr zI1C<4p!OOnJn&cw6-LEF0q$;01MqIpx(qW^tYh%29F?-uF@ZPC??P#J(OaqVrNx5^ zL=CG?s@;Ortw%j{z`BAbH6^{lji^Db*vqh?+SC&->2SMo z!>WrntPx(N;MGO5C2ATS5A{GMDA>C2mh0G*AUc?EgA%mQQLvd>nB4K@XOvfC@B(>v zxzxd_Ny^;${dTmKm$ktr(PSpiK?EL_3uHP^s3D5U$%a?e@OK;ju4ZG7YBDZVXk%cyw0nP!Jmh_9b^x;$oJu0`7~3Dq8h8ehB;pYJE$%xUov2b z!=5PVl05w%$|dY-G)i2PD=FhIG%Khm z4x-Hh8UeM_kk!Ez+^C=;iA5se2?p;_2}DB1y0HsG+eGovAcSL(+PHyLTbXpL`W8As zj7t~{W<@W8pz{hwO4l0<-R+o|x`Km*ca@fV_@ig$8U#1IdvycDdkfRV^0I3P9{S%L zOAM3HR0%=uQE+L=sl~7+t5vz8yciyW2DP=TqDy>LgPN5NZ;*_rWO=MKC>UT?(#X(> z6f&rFf~m0%yKtEGw*a}DMwZ7CWP{c0QFG|=)})@umI?ugdRfGalF7) zvcQv+>tg;E)MWzvO%G8)Y|AeRh^2la$@3s$0)z5|?^p*KArEP(c9E;WtZ}nCgm@F6 zXk4*CHVm5|LaMo==^E&+$@;I7S}UfOLQVM?$t6RPM*_8=z%`}W5AqKZ?FVND8AXBw zgIu0LbXaGS{Dn?&FG1%(CqL-m)~PWsILs&?PlTy!euO<7{Wd(7X~8>B;fihXI;=rk zyu5gFj+|QZJII{$70NRytj@cG9s6LB?OzOC^V4JHPKQW`LA^=pL9=tu{h=uc&5-Km z?IizuLUSiwxOzxVLiczFmswx9MqU$;GZ(bvN1Jq>QP&OxaC9=01BD?RSKf*1L-1f& z9o(g&$yQ#)@qHEEh%Y=H zx>inTylZxV(UBf!p7XeifZxF|>yYEwJ*ZjGGRvLB8V9`~oG*9xwZbBrdcUvW#qj(T z439+JPA;rJac*)$-)sT`@xk8G=LM z%zbnUhBO7q(X9@#9c6FGr(#3G#V_ZbZXJ8PnDTx56-g+W5DPKh4ji#`y+d`xHDF zUI-6gO5f07lP{rjPJYg3{iTjK})?(!vs%;_q&xYqsTn` z*~Pa!p+?aS#+d6w%W9A{)EWWlyAUAhWJuSjakr3*pNWfLhq|FbDqM})Aoi^ua$xc* zM1;1OE+QQmyvS`Je`Dc5mgjMYN(~D#Pn)ROBM5{!tUkW{nTJsqWWD72W}8&-pp8WC zN2I74a6vdPg}=d|z<1DEExN-R!u2rBj|Ked0yc&#!v*2OSDq8XKLqppCb%-(7#`@I z7XD?%E@O=?z#0k*6WkddpMvM7;KlHM2PgJv`}#WyJse1s`fG!w&sQffNb{WzP~Qu{ z3d|o{UbG+aO5m#t7a9(c(dBOjjPIcm2`GyO+q{ypD3I{qb_2+YZC>sRN>7EUPGz{f z3^#`D`qPbnyYpko-JT^_Q(P#pIG`4R)^}*?pF=P|`6@dzR zARxbSw-s$6(V5& zkLjIVGC6LP@IK02%o-GZTjcw|4{jVpsgm8s`UE-PDkicyer*_U;8Tr5aFh10tlJ6TL zSx(T(&od0Z0_6+IA!|G>tJH;YNN&q0pQ36NNPvuu+B=odLzaCKl$sbk04h6p=)G@* z`X({KGIoK{ymiVhNyitYRX1_IMSaLa5vkrEw)f2kx{4f-8}2%9Kv=E=Roi5s0;vXZ zk03GCL!M@nc?*becgY~+%U!r!=iGn6AQ2OA9_9pWhb|3DV!AP?x9PD!F|SVxbK(>( zA+-zM;MRb&IEk(za|Z}5yXYzd#~KiAL@|YsT8u4-*L$2+Ce0FH-^z^#*ta5U0{gay zX(zazF5;VMX#gsm{B(e4CkrvKe)!ZsbWg8D(BGa$$+`3jy@ ziVFai6G@JW;dg*Uj%Hc-*&zKc;+Vo1MZ$1A4%oRIHHhE9NkU~Q)ex!EeDZ+jm@aEkxB7sPzeW8ZPlpUx;i9x)q?a+FGt#%K z2`Y9z_a>-)dQdk(u(?~*7*hCzQDb_l(5Le&A37SK!psCAn5AP!BaR!vdmw!9(-P4o zxK)G=f4lj5NANO)RaLi!cGv(LK2#eF&x7#Br`F@47K+?ptm*hOA##hMSjmrjQqjVK z$&uzK{GsOQPW7Tph}1RKAiYrpluv*FuM%PAK_qpfOI4zRK!D zKWM{(gG|(|SA^Muoh+IK0|iVvSf|j9wRPyRv^CU5KbzFS9nN&|X|TmgZH zAmwxf@)@dnDU@<0vjc~mkoGk?=996LEQ<28idu{aF6a(#2hGzg{4XjQ z-=b?&qx-gr0wr>vMA#_xAD&ZZlIbqIF?CGW1D?E%DllovwL+d#t)b^qYn>|lV67R%_qIL?r06#&BES|kb}+O8jR9pDiMsDI zwLw}-OK)Bfw9q$%qSB&vDw9J!j5&$cQ9!yPn-8{k(C4pAA0z|SuG{l^O!%Ec*{U3j zU(`JfBst#|#$F_MF=1Dbq#N+#eIK2myccDeK^+Z~_>?N!l$Sq^=MzPnaM+ch_yl&n z?`H!@js8dmh=lmkH{f*RF&l{pD5EkKIn?(Gje{rlv~;uVG0$K>Yp~^LR{$e-36!*o zUU~<#dp0X%kLWKXjcf3`@x_B$tw=go0o@qXrq&w?8CxYKVqg?VLU5oCaUo|SXxnSI zK|OuwMgxhIMgDXBx!2u$;Crt-f8mYzPynu5N9!2^H<>J4xX!)oD`)5pCkp55DCx9y z^mps%?{k)6VJ#(dSBp*WZACc^@#^dH+{W$Q$GwXJnjo zfvy|aE@-(qM4Oz8ME4eU5K{y3B%wfYm|(+@iWs=74(uFi%%KQtYVzkeXhflna-zyz zotw@Fpx+vny&y*?VcHExHxw#0KIn_7gO=RywM3szvd2GqZY%frR~NtZy`9@5cKO}e zA@JePp4rO%mg{(u-EUwXd<75g;PxXZ&==&jJJD(zdhbQhrgPpoagYRqa*%FOCJIQ} zqHNg4ELhKT#ht}=l>G0I#;z@RK(}+BNQ`H{o55 zGT=!Pnln$-NN{z+;t??DbJ`^gJ*pn7Pgt1(&WD5Vf^!kLe9|{vz#ehM@)?dj2xmJJXYcjk)1)pabl1J!=|NYCi=g9v7(ZFLZ Date: Fri, 16 Sep 2022 21:50:37 +0530 Subject: [PATCH 47/59] adding highlight_color param --- fury/ui/elements.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index b2eca88b8..b4e6d90d0 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3213,7 +3213,8 @@ class DrawShape(UI): """Create and Manage 2D Shapes. """ - def __init__(self, shape_type, drawpanel=None, position=(0, 0), debug=False): + def __init__(self, shape_type, drawpanel=None, position=(0, 0), color=None, + highlight_color=(.8, 0, 0), debug=False): """Init this UI element. Parameters @@ -3232,8 +3233,9 @@ def __init__(self, shape_type, drawpanel=None, position=(0, 0), debug=False): self.drawpanel = drawpanel self.max_size = None self.debug = debug + self.color = np.random.random(3) if color is None else color + self.highlight_color = highlight_color super(DrawShape, self).__init__(position) - self.shape.color = np.random.random(3) def _setup(self): """Setup this UI component. @@ -3249,6 +3251,8 @@ def _setup(self): else: raise IOError("Unknown shape type: {}.".format(self.shape_type)) + self.shape.color = self.color + self.cal_bounding_box() if self.debug: @@ -3358,12 +3362,17 @@ def is_selected(self, value): def selection_change(self): if self.is_selected: + self.highlight(True) self.show_rotation_slider() self.set_bb_box_visibility(True) else: + self.highlight(False) self.rotation_slider.set_visibility(False) self.set_bb_box_visibility(False) + def highlight(self, value): + self.shape.color = self.highlight_color if value else self.color + def set_bb_box_visibility(self, value): if self.debug: if value: @@ -3460,8 +3469,9 @@ def remove(self): """ self.drawpanel.shape_list.remove(self) self._scene.rm(self.shape.actor) - self._scene.rm(*[border.actor for border in self.bb_box]) self._scene.rm(*self.rotation_slider.actors) + if self.debug: + self._scene.rm(*[border.actor for border in self.bb_box]) def left_button_pressed(self, i_ren, _obj, shape): mode = self.drawpanel.current_mode @@ -3509,7 +3519,8 @@ class DrawPanel(UI): """The main Canvas(Panel2D) on which everything would be drawn. """ - def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False, debug=False): + def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False, + highlight_color=(1, .0, .0), debug=False): """Init this UI element. Parameters @@ -3529,6 +3540,7 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False, debug=F self.is_draggable = is_draggable self.current_mode = None self.debug = debug + self.highlight_color = highlight_color if is_draggable: self.current_mode = "selection" @@ -3676,7 +3688,9 @@ def draw_shape(self, shape_type, current_position): Lower left corner position for the shape. """ shape = DrawShape(shape_type=shape_type, drawpanel=self, - position=current_position, debug=self.debug) + position=current_position, + highlight_color=self.highlight_color, + debug=self.debug) if shape_type == "circle": shape.max_size = self.cal_min_boundary_distance(current_position) self.current_scene.add(shape) From cc6d974e4677b45f122fabb573a288d832e5cac2 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 16 Sep 2022 21:55:07 +0530 Subject: [PATCH 48/59] adding test for grouping feature --- .../files/test_ui_draw_panel_grouping.json | 1 + .../files/test_ui_draw_panel_grouping.log.gz | Bin 0 -> 13588 bytes fury/ui/tests/test_elements.py | 31 ++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 fury/data/files/test_ui_draw_panel_grouping.json create mode 100644 fury/data/files/test_ui_draw_panel_grouping.log.gz diff --git a/fury/data/files/test_ui_draw_panel_grouping.json b/fury/data/files/test_ui_draw_panel_grouping.json new file mode 100644 index 000000000..c6e18d663 --- /dev/null +++ b/fury/data/files/test_ui_draw_panel_grouping.json @@ -0,0 +1 @@ +{"CharEvent": 0, "MouseMoveEvent": 2920, "KeyPressEvent": 6, "KeyReleaseEvent": 6, "LeftButtonPressEvent": 7, "LeftButtonReleaseEvent": 7, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_draw_panel_grouping.log.gz b/fury/data/files/test_ui_draw_panel_grouping.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..e8f6dae387c0cd4004464bc34dd6566d09ef6dd8 GIT binary patch literal 13588 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 a#w}8Cg8b$G{`1dw#Qy`PbE{jd@BsjK)1vGE literal 0 HcmV?d00001 diff --git a/fury/ui/tests/test_elements.py b/fury/ui/tests/test_elements.py index ce34adc1e..84e56a939 100644 --- a/fury/ui/tests/test_elements.py +++ b/fury/ui/tests/test_elements.py @@ -1127,6 +1127,37 @@ def test_ui_draw_panel_rotation(interactive=False): event_counter.check_counts(expected) +def test_ui_draw_panel_grouping(interactive=False): + filename = "test_ui_draw_panel_grouping" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") + + drawpanel = ui.DrawPanel(size=(600, 600), position=(30, 10)) + + # Assign the counter callback to every possible event. + event_counter = EventCounter() + event_counter.monitor(drawpanel) + + current_size = (680, 680) + show_manager = window.ShowManager( + size=current_size, title="DrawPanel Grouping UI Example") + show_manager.scene.add(drawpanel) + + # Recorded events: + # 1. Grouping/Ungrouping Shapes + # 2. Translation/Rotation of Grouped Shapes + + if interactive: + show_manager.record_events_to_file(recording_filename) + print(list(event_counter.events_counts.items())) + event_counter.save(expected_events_counts_filename) + + else: + show_manager.play_events_from_file(recording_filename) + expected = EventCounter.load(expected_events_counts_filename) + event_counter.check_counts(expected) + + def test_playback_panel(interactive=False): global playing, paused, stopped, loop, ts From 56d4154cc94b4c089f46a18cf6943c0865165f06 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Wed, 21 Sep 2022 07:25:50 +0530 Subject: [PATCH 49/59] adding rotation --- fury/ui/elements.py | 78 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index be81c9a16..2e40e052b 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3213,14 +3213,14 @@ class PolyLine(UI): """Create a Polyline. """ - def __init__(self, points_data=[], line_width=3, color=(1, 1, 1)): + def __init__(self, line_width=3, color=(1, 1, 1)): """Init this UI element. Parameters ---------- position : (float, float), optional (x, y) in pixels. """ - self.points_data = points_data + # self.points_data = points_data self.points = [] self.line_width = line_width self.lines = [] @@ -3233,12 +3233,13 @@ def _setup(self): """Setup this UI component. Create a Polyline. """ - if len(self.points_data) < 2: - return + pass + # if len(self.points_data) < 2: + # return - for ptn in self.points_data: - self.add_point(ptn) - self.add_point(self.points_data[0]) + # for ptn in self.points_data: + # self.add_point(ptn) + # self.add_point(self.points_data[0]) def _get_actors(self): """Get the actors composing this UI component.""" @@ -3269,23 +3270,63 @@ def resize(self, size): offset_from_mouse = 2 hyp = np.hypot(size[0], size[1]) self.current_line.resize((hyp - offset_from_mouse, self.line_width)) - self.rotate(angle=np.arctan2(size[1], size[0])) + self.rotate_line(angle=np.arctan2(size[1], size[0])) - def rotate(self, angle): - """Rotate the vertices of the UI component using specific angle. + def rotate_line(self, angle): + """Rotate a single line using specific angle. Parameters ---------- angle: float Value by which the vertices are rotated in radian. """ points_arr = vertices_from_actor(self.current_line.actor) - rotation_matrix = np.array( - [[np.cos(angle), np.sin(angle), 0], - [-np.sin(angle), np.cos(angle), 0], [0, 0, 1]]) - new_points_arr = np.matmul(points_arr, rotation_matrix) + new_points_arr = rotate_2d(points_arr, angle) set_polydata_vertices(self.current_line._polygonPolyData, new_points_arr) update_actor(self.current_line.actor) + def rotate(self, angle): + """Rotate the vertices of the UI component using specific angle. + Parameters + ---------- + angle: float + Value by which the vertices are rotated in radian. + """ + points_arr = [] + for ele in self.points: + points_arr.append([*ele, 0]) + + bb_min, bb_max, bb_size = cal_bounding_box_2d(self.calculate_vertices()) + center = bb_min + bb_size//2 + + for val in points_arr: + val[0] -= center[0] + val[1] -= center[1] + + new_points_arr = rotate_2d(np.asarray(points_arr), angle) + + for val in new_points_arr: + val[0] += center[0] + val[1] += center[1] + + self.update_line(new_points_arr.astype("int")) + + def update_line(self, points): + self.remove() + + if points.shape[1] == 3: + points = points[:, :-1] + + for val in points: + self.add_point(val, add_to_scene=True) + self.resize(np.asarray(points[0]) - self.current_line.position) + + def remove(self): + self.points = [] + self._scene.rm(*[l.actor for l in self.lines]) + self.lines = [] + self.previous_point = None + self.current_line = None + # def delete(self): # self.current_line = None # self.lines.pop() @@ -3306,14 +3347,14 @@ def add_point(self, point, add_to_scene=False): new_line.on_left_mouse_button_pressed = self.left_button_pressed new_line.on_left_mouse_button_dragged = self.left_button_dragged - control_point = Disk2D(5, center=point) + # control_point = Disk2D(5, center=point) self.current_line = new_line self.lines.append(new_line) self.points.append(point) self.previous_point = point if add_to_scene: - self._scene.add(new_line, control_point) + self._scene.add(new_line) @property def color(self): @@ -3538,6 +3579,11 @@ def rotate(self, angle): """ if self.shape_type == "circle": return + + if self.shape_type == "polyline": + self.shape.rotate(angle) + return + points_arr = vertices_from_actor(self.shape.actor) new_points_arr = rotate_2d(points_arr, angle) set_polydata_vertices(self.shape._polygonPolyData, new_points_arr) From 8f31701f479a63546682ed9be8d11b25b9a4332b Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Sun, 25 Sep 2022 13:42:11 +0530 Subject: [PATCH 50/59] implementing set_position function --- fury/ui/elements.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 2e40e052b..be27ed6cd 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3264,7 +3264,14 @@ def _set_position(self, coords): coords: (float, float) Absolute pixel coordinates (x, y). """ - pass + if len(self.lines) > 0: + offset = coords - self.position + + new_points = [] + for val in self.points: + new_points.append(val + offset) + + self.update_line(np.asarray(new_points)) def resize(self, size): offset_from_mouse = 2 @@ -3311,7 +3318,8 @@ def rotate(self, angle): self.update_line(new_points_arr.astype("int")) def update_line(self, points): - self.remove() + if len(self.lines) > 0: + self.remove() if points.shape[1] == 3: points = points[:, :-1] From 793281f9059d7456ce42e08042c3c8ff21ea368e Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 27 Sep 2022 21:33:41 +0530 Subject: [PATCH 51/59] fixing rotation issue --- fury/ui/elements.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index be27ed6cd..c65e3bf00 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3317,6 +3317,11 @@ def rotate(self, angle): self.update_line(new_points_arr.astype("int")) + bb_min, bb_max, bb_size = cal_bounding_box_2d(self.calculate_vertices()) + new_center = bb_min + bb_size//2 + + self.position += (new_center - center) + def update_line(self, points): if len(self.lines) > 0: self.remove() From a5cea7bb565f2c387a1e626d4e79f38e2dcb33ae Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 27 Sep 2022 21:38:25 +0530 Subject: [PATCH 52/59] removing polyline tutorial --- docs/tutorials/02_ui/viz_polyline.py | 35 ---------------------------- 1 file changed, 35 deletions(-) delete mode 100644 docs/tutorials/02_ui/viz_polyline.py diff --git a/docs/tutorials/02_ui/viz_polyline.py b/docs/tutorials/02_ui/viz_polyline.py deleted file mode 100644 index 1e35b52b0..000000000 --- a/docs/tutorials/02_ui/viz_polyline.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -=========== -PolyLine UI -=========== - -This example shows how to use the Polyline UI. - -First, some imports. -""" -from fury import ui, window - -######################################################################### -# We then create a Polyline Object. - -polyline = ui.PolyLine(points_data=[(100, 100), (100, 150), - (200, 200), (590, 230), (230, 50), (100, 100)], line_width=3) - -############################################################################### -# Show Manager -# ============ -# -# Now we add DrawPanel to the scene. - -current_size = (600, 600) -showm = window.ShowManager(size=current_size, title="PolyLine UI Example") - -showm.scene.add(polyline) - -interactive = 1 - -if interactive: - showm.start() - -window.record(showm.scene, size=current_size, - out_path="viz_polyline.png") From 357d4a8dc2f4ba9d2d7fdb21a35e350e4f09bd58 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 27 Sep 2022 21:44:14 +0530 Subject: [PATCH 53/59] removing commented code --- fury/ui/elements.py | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index c65e3bf00..e4647ac3a 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3220,7 +3220,6 @@ def __init__(self, line_width=3, color=(1, 1, 1)): position : (float, float), optional (x, y) in pixels. """ - # self.points_data = points_data self.points = [] self.line_width = line_width self.lines = [] @@ -3234,12 +3233,6 @@ def _setup(self): Create a Polyline. """ pass - # if len(self.points_data) < 2: - # return - - # for ptn in self.points_data: - # self.add_point(ptn) - # self.add_point(self.points_data[0]) def _get_actors(self): """Get the actors composing this UI component.""" @@ -3340,11 +3333,6 @@ def remove(self): self.previous_point = None self.current_line = None - # def delete(self): - # self.current_line = None - # self.lines.pop() - # self._scene.rm(self.current_line) - def calculate_vertices(self): vertices = np.empty((0, 2), int) for line in self.lines: @@ -3357,10 +3345,8 @@ def add_point(self, point, add_to_scene=False): self.resize(np.asarray(point) - self.current_line.position) new_line = Rectangle2D((self.line_width, self.line_width), position=point, color=self.color) - new_line.on_left_mouse_button_pressed = self.left_button_pressed - new_line.on_left_mouse_button_dragged = self.left_button_dragged - - # control_point = Disk2D(5, center=point) + new_line.on_left_mouse_button_pressed = self.on_left_mouse_button_pressed + new_line.on_left_mouse_button_dragged = self.on_left_mouse_button_dragged self.current_line = new_line self.lines.append(new_line) @@ -3379,23 +3365,6 @@ def color(self, color): for line in self.lines: line.actor.GetProperty().SetColor(*color) - def left_button_pressed(self, i_ren, _obj, line): - # click_pos = np.array(i_ren.event.position) - # self._drag_offset = click_pos - self.position - # i_ren.event.abort() # Stop propagating the event. - self.on_left_mouse_button_pressed(i_ren, _obj, line) - - def left_button_dragged(self, i_ren, _obj, line): - # if self._drag_offset is not None: - # click_position = np.array(i_ren.event.position) - # new_position = click_position - self._drag_offset - # self.position = new_position - # i_ren.force_render() - self.on_left_mouse_button_dragged(i_ren, _obj, line) - - def left_button_released(self, i_ren, _obj, line): - self.on_left_mouse_button_released(i_ren, _obj, line) - class DrawShape(UI): """Create and Manage 2D Shapes. @@ -4034,17 +4003,9 @@ def left_button_released(self, i_ren, _obj, element): if self.is_creating_polyline: self.current_shape.shape.add_point(i_ren.event.position, True) self.current_shape.cal_bounding_box() - # if self.current_mode == "polyline": - # self.shape_list[-1].shape.add_point(i_ren.event.position, interactive=True) - - # i_ren.add_active_prop(self.canvas.background.actor) - # self.canvas.background.left_button_state = "dragging" - # self.mouse_move_callback(i_ren, _obj, element) i_ren.force_render() def right_button_released(self, i_ren, _obj, element): - # i_ren.remove_active_prop(self.canvas.background.actor) - # i_ren.force_render() self.is_creating_polyline = False i_ren.force_render() From 03d9089c5690543866eeeee10b6c62b00d168ecb Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 27 Sep 2022 21:53:51 +0530 Subject: [PATCH 54/59] renaming resize function --- fury/ui/elements.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index e4647ac3a..24c93c0c3 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3217,8 +3217,8 @@ def __init__(self, line_width=3, color=(1, 1, 1)): """Init this UI element. Parameters ---------- - position : (float, float), optional - (x, y) in pixels. + line_width : int, optional + Width of the individual line. """ self.points = [] self.line_width = line_width @@ -3266,7 +3266,7 @@ def _set_position(self, coords): self.update_line(np.asarray(new_points)) - def resize(self, size): + def resize_line(self, size): offset_from_mouse = 2 hyp = np.hypot(size[0], size[1]) self.current_line.resize((hyp - offset_from_mouse, self.line_width)) @@ -3324,7 +3324,7 @@ def update_line(self, points): for val in points: self.add_point(val, add_to_scene=True) - self.resize(np.asarray(points[0]) - self.current_line.position) + self.resize_line(np.asarray(points[0]) - self.current_line.position) def remove(self): self.points = [] @@ -3342,7 +3342,7 @@ def calculate_vertices(self): def add_point(self, point, add_to_scene=False): if self.current_line: - self.resize(np.asarray(point) - self.current_line.position) + self.resize_line(np.asarray(point) - self.current_line.position) new_line = Rectangle2D((self.line_width, self.line_width), position=point, color=self.color) new_line.on_left_mouse_button_pressed = self.on_left_mouse_button_pressed @@ -3622,7 +3622,7 @@ def resize(self, size): self.rotate(angle=np.arctan2(size[1], size[0])) elif self.shape_type == "polyline": - self.shape.resize(size) + self.shape.resize_line(size) elif self.shape_type == "quad": self.shape.resize(size) @@ -3992,10 +3992,10 @@ def mouse_move(self, i_ren, _obj, element): return if np.linalg.norm(self.clamp_mouse_position(i_ren.event.position) - self.current_shape.shape.lines[0].position) < 10: - self.current_shape.shape.resize( + self.current_shape.shape.resize_line( self.current_shape.shape.lines[0].position - current_line.position) else: - self.current_shape.shape.resize(self.clamp_mouse_position( + self.current_shape.shape.resize_line(self.clamp_mouse_position( i_ren.event.position) - current_line.position) i_ren.force_render() From 32a1520e77bf27f44187e84de72c8e037ed5679d Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 27 Sep 2022 21:57:21 +0530 Subject: [PATCH 55/59] simplifying polyline reference in drawane; --- fury/ui/elements.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 24c93c0c3..0516dede5 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3987,15 +3987,16 @@ def key_release(self, i_ren, _obj, _drawpanel): def mouse_move(self, i_ren, _obj, element): if self.is_creating_polyline: - current_line = self.current_shape.shape.current_line + polyline = self.current_shape.shape + current_line = polyline.current_line if not current_line: return if np.linalg.norm(self.clamp_mouse_position(i_ren.event.position) - - self.current_shape.shape.lines[0].position) < 10: - self.current_shape.shape.resize_line( - self.current_shape.shape.lines[0].position - current_line.position) + - polyline.lines[0].position) < 10: + polyline.resize_line( + polyline.lines[0].position - current_line.position) else: - self.current_shape.shape.resize_line(self.clamp_mouse_position( + polyline.resize_line(self.clamp_mouse_position( i_ren.event.position) - current_line.position) i_ren.force_render() From b9456b963b03ba5462500e550b2a0141f24f7f6e Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 27 Sep 2022 23:13:00 +0530 Subject: [PATCH 56/59] fixing looping issue --- fury/ui/elements.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 0516dede5..f2d75f089 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3225,6 +3225,7 @@ def __init__(self, line_width=3, color=(1, 1, 1)): self.lines = [] self.previous_point = None self.current_line = None + self.closed = False self.color = color super(PolyLine, self).__init__() @@ -3324,7 +3325,17 @@ def update_line(self, points): for val in points: self.add_point(val, add_to_scene=True) - self.resize_line(np.asarray(points[0]) - self.current_line.position) + + if self.closed: + self.resize_line(np.asarray(points[0]) - self.current_line.position) + else: + self.remove_last_line() + + def remove_last_line(self): + self._scene.rm(self.current_line.actor) + + self.lines.pop() + self.current_line = self.lines[-1] def remove(self): self.points = [] @@ -3995,9 +4006,11 @@ def mouse_move(self, i_ren, _obj, element): - polyline.lines[0].position) < 10: polyline.resize_line( polyline.lines[0].position - current_line.position) + polyline.closed = True else: polyline.resize_line(self.clamp_mouse_position( i_ren.event.position) - current_line.position) + polyline.closed = False i_ren.force_render() def left_button_released(self, i_ren, _obj, element): @@ -4007,7 +4020,11 @@ def left_button_released(self, i_ren, _obj, element): i_ren.force_render() def right_button_released(self, i_ren, _obj, element): - self.is_creating_polyline = False + if self.is_creating_polyline: + self.is_creating_polyline = False + polyline = self.current_shape.shape + if not polyline.closed: + polyline.remove_last_line() i_ren.force_render() From 1c8315e2f18898eaafbe53c2d976964441dbb48a Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 30 Sep 2022 16:41:23 +0530 Subject: [PATCH 57/59] adding docs to the functions --- fury/ui/elements.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index f2d75f089..cb5afd18a 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3219,6 +3219,8 @@ def __init__(self, line_width=3, color=(1, 1, 1)): ---------- line_width : int, optional Width of the individual line. + color : (float, float, float), optional + RGB: Values must be between 0-1. """ self.points = [] self.line_width = line_width @@ -3268,6 +3270,12 @@ def _set_position(self, coords): self.update_line(np.asarray(new_points)) def resize_line(self, size): + """Resize the current line. + Parameters + ---------- + size: (int, int) + Size to resize the line. + """ offset_from_mouse = 2 hyp = np.hypot(size[0], size[1]) self.current_line.resize((hyp - offset_from_mouse, self.line_width)) @@ -3317,6 +3325,12 @@ def rotate(self, angle): self.position += (new_center - center) def update_line(self, points): + """Redraws all the individual lines from the given points. + Parameters + ---------- + points: ndarray + Set of points to create a polyline. + """ if len(self.lines) > 0: self.remove() @@ -3332,12 +3346,16 @@ def update_line(self, points): self.remove_last_line() def remove_last_line(self): + """Removes the last added line from the polyline. + """ self._scene.rm(self.current_line.actor) self.lines.pop() self.current_line = self.lines[-1] def remove(self): + """Resets all data and removes actor from scene. + """ self.points = [] self._scene.rm(*[l.actor for l in self.lines]) self.lines = [] @@ -3345,6 +3363,8 @@ def remove(self): self.current_line = None def calculate_vertices(self): + """Calculate the vertices of the polyline. + """ vertices = np.empty((0, 2), int) for line in self.lines: vertices = np.append(vertices, line.position + @@ -3352,6 +3372,14 @@ def calculate_vertices(self): return vertices def add_point(self, point, add_to_scene=False): + """Add a new point and create a new line. + Parameters + ---------- + point: (int, int) + Position for the new line. + add_to_scene: bool, optional + Add current line to the scene. + """ if self.current_line: self.resize_line(np.asarray(point) - self.current_line.position) From 63c3effb88f02434ef8035279bb835a73932c800 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Fri, 30 Sep 2022 19:17:58 +0530 Subject: [PATCH 58/59] adding and updating old tests --- fury/data/files/test_ui_draw_panel_basic.json | 2 +- .../files/test_ui_draw_panel_basic.log.gz | Bin 11303 -> 17991 bytes .../files/test_ui_draw_panel_grouping.json | 2 +- .../files/test_ui_draw_panel_grouping.log.gz | Bin 13588 -> 15522 bytes .../files/test_ui_draw_panel_polyline.json | 1 + .../files/test_ui_draw_panel_polyline.log.gz | Bin 0 -> 12364 bytes .../files/test_ui_draw_panel_rotation.json | 2 +- .../files/test_ui_draw_panel_rotation.log.gz | Bin 10870 -> 15069 bytes fury/ui/tests/test_elements.py | 31 ++++++++++++++++++ 9 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 fury/data/files/test_ui_draw_panel_polyline.json create mode 100644 fury/data/files/test_ui_draw_panel_polyline.log.gz diff --git a/fury/data/files/test_ui_draw_panel_basic.json b/fury/data/files/test_ui_draw_panel_basic.json index c806a95b5..f883938a9 100644 --- a/fury/data/files/test_ui_draw_panel_basic.json +++ b/fury/data/files/test_ui_draw_panel_basic.json @@ -1 +1 @@ -{"CharEvent": 0, "MouseMoveEvent": 2917, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 17, "LeftButtonReleaseEvent": 17, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file +{"CharEvent": 0, "MouseMoveEvent": 4726, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 24, "LeftButtonReleaseEvent": 24, "RightButtonPressEvent": 1, "RightButtonReleaseEvent": 1, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_draw_panel_basic.log.gz b/fury/data/files/test_ui_draw_panel_basic.log.gz index e5302cb4df071c1ac79121ae2ef772411203c386..2a3799bb523f5c3e3ec041d08c107b033d8d0174 100644 GIT binary patch literal 17991 zcmV)fK&8JQiwFp&>NaBn|8!+@bYFF8Uu1G&cVBQ}Ze?s=VqtS>V=ioOX8^^WON)I+ zmfh$5DK2)$>8>DOO={Pe&6%a33F_Lsl@{HXuF{==Vt{QS>9e)-eC{`mRVNBQlafB4hS zzy9C1@_Y@xM(O1ji`tY(-*yqD7zqp@YkNo1j zKYWcGu8&djU)y|)x^mdhfADKNH^`sd%6aG z4f?Ef(K~pY3y+e*zTf9qP?Bi#?`S+4cfynY7W&1!$Y>3z5wVdt}Yz7)F-2%clx|yVK@_ii4lF=r!sJK&IkE_)ovfY{jnMUZ^yM^2|K@2P z{Gt2&4&AWZ)ibDsHbf0k99P3cw1y#Q^+5Z#1UvMC#$iLLQ(@xE3O#rv9l9(_5>(!Y6{ zWq;^CzeD>Cn(vaja_$6cE;7f7!@01#rTOUjAjUe;G6a+k5ck?wkF|)8_m9M|hJz1d_XolHs#w)8MBh^NOt@XuQP|o!yCZ+;d!uBnH2ANs${kU=TCmz4bP*$Z*c$e zuW#;M@X3d=lz9f}Ob54uW8UD4DSOrVgJOEB=Lse|L(=mMX-p<&+^ z9B<3vGiv!nj?*B|O|>;*ou#mn3Hg&pQXu~%3YO$~ffo6O{KXWexeLXA{G*q*$l-|B z@Zf;o7E$~`tK14~K$~*&nyYQWfiup_8Py~-&vSf=;O6tS&lb*bnq@l|&)p6`yL!ua zH$P8ZH>A^g1n8i!r9$P3j|d{C#YD$1wH%ha3^h0Mwet|bRE`P%ESpJ?bJ-c!X!2!ZXrC1m`7K^9+d`7<)Hl7aFM2A+k#wi8 zAfFb{vt{I8-_NlD=y5cz+keza zXN(f{AlWW>t_<B3E|(8Jb9gYk5!N0|uti-(3OmE&9^M4E++Dne9EO&|D{4M6 z_$-~4d$QN8aWJN^x)$>9ebz?HQ$p8NNG6{SXYI6n7rmz3sD*HMguNgPo}qkJgpr%P+20!#u*G!@_%h z@$*Y)V7tyFc37e6jvH|CGino#x%1?CzDb9q=bprM%udgvaM!VE9X{yU5giVTX4Ig0 z9b10DetuTSfJJothyi;`!1)S2P6{+EUgyFCCexXv16Jrcg@(c2@))_@dO0;(9)Ex2 zcIol=2OJdjpbgQU8+p*}+Ng8%JW_7Zs^>J1UmY+v!4J4|dlfoeYmLZQ>TE^Bbn5;_ z4kI_B$ASvRi1nok>lp^@d6oSwBNpK(ZK~*0!q0@8rNbICx)<#pY$23X8?Ni7W7_!yC$k34v6eKc2AzY_p+dI;L@B`ABMU4kGHJn+6Dy3vB09T+!!kI#19-678KAyacCF_o zl}uN!m6GQWHIQiwAqi#lyF*TYM>Q4{8gwceQr!g>&lo(Ky3BHqOD3!tR)iST^{62V z>SxY{A?zm$7h;M5LrIC?#=nM=BaYh#Jn_jZRp`{nLK57uHVE=ravLht!?cM-VoGOl zsny0x9QfMjuB%Z+OzscMUV)8aS4eS>rG5k24*Q@(0uCUfZdRSzc{Emu&X}T)RZWVWQ2Q>UiUPTiv~5=Eb*$9Rct#zHOQ`XA5pNh#j6fsyMJzTK5d~btV5G1( zguTUKGbmnf5U$^FRwz?IXoK2W=t}0u2Yd=2bCsKr-HdAs$1|Nt4Na| zP<9ae(WLDYIw}w9Ml5iH_}W_jDTY($j9OpvbmdZ04t9*^36^c-5UC%3V@jdro!1i4o+;%l&c`pAy3JS6eP{{zY zx+XPcu1)pw`ADi26dJ3K8v&?qXeJW@03^uIp^@+2D&&Hx)5R`RfSDGfIz9U9fWLQ! zvzO*Bje7gK)+10EcdW+m-l!U1Bo$C@FT!xBi0{z}3VG8-fwq1&IH(L)_eupm;Ei3V ziZxfD8s11u()WsuE`{Y{Lsw3Df)kRMkAHS^m6xzFT<{GbU#`^x8Sm)f%?Ec)YIW_= zoRY(n`d-9QpC=i+5?)wCkcY!9xDE2X2fL4StmXTb;tj6|(Vv0$gy;}hL(CqK^nOs0 zPwmvMkcV(949g&@(LtQu0wHV+-n>A_mjbK#+3WiJ8rTUmijOyXAYxMY4exo5{(OYI zLlDM^W?jMj4&I~@;Xcr;-!Z9gV&h+*17CBn`)4@VKYx=KLt^xe@=TEGILMFPNJ>m? zY6=hMxifK2)ZBG%;w8?_s3t-Uh!U4UgkBa2Nr*99ka_{ZQv<4^bLv1W#6*sl^B{FK zhL1|cYY;5y%4AK<{Q!}fD3V$p_38?BPcT0H{?|A6Hhl7YqV{?-EHh}>!02vyb4QPe zI$HGjLC=f1Q^Kv~S$&lYG%_W`K$!baqHye!v~F654!})~L>7#T=p{8EQHv&PfQ^D4 zpMvYR;PzSg<$nLEYw#1m{uD4@ybXjaU%c|r7K9J8(57x@U4qr{@ zN`ty78=+ktqEQw})=H}!qET-lg-ZU!#sg(LqEISbA%@aRyF)AOir{_V08`M#mr zP~iP=)r~+=y*5)Q9np4}7jle(i2)Vd5-prUsNnLqLBTMUDOak&-jN(g-S-g^9I5MH z!fS#`W=Kkp;5aT+i(EneG8*|S6&piRdZ=5z7^#A`d^(J9=JGLDJA5moNDAhTiV7R4 zdR5ALkD$qJ4D+v@f9?G1fi>9R>%GAX-+N6Qwe2+KM&DaenEbt`6JfSyi;=`^BNkIN zZ&xVU-#pXxIu#!GN8N4xVtkv7~Tjw-`hLGd~aU}L#0x{#xUP|@VzJB zd-AYM9+t_EHhDU%$}rEhwQ;8IhVvZy6gQGa=$Ft#^#%`Gp+Fj;`;N+OjL_8@@In|G z)6{1H39+J*&&lv&cw+;maepQl;Gcnw;m)u#JQ$u|h7Fq_OAr5me%xRme*vnv!#Cd%VM zoK;pE@a2e^2J&WeqOhOR$6#!#_!FYEQRJL|tc`-oA&#{d32Pa!Y)LW|IJzXb>I@UT z(TGw&fIE;fiCqj-%nBx9Lr{!ftY8vWylC`E#N?h&2W>@Qxy0#Ni14iwo7@>Suqu~D zrx(;BPzqP<`QT;9i~-k2k&Uvc9sn}DgGuJKcL2$}GBA+g74M+Kp|uxDU@Ii&`R1V< zDdkB`p`=DyVi5n5{Q#*XN$p>!*`nGmNccx|I^Vd|^A3vW5q>72E-9ir`UFHt(P;TA z+ya8>ii$wAZfoB%@Pdela!{miwn%NI{C&~An)_|Ya7m|x(2iH`l~8MhlKN-VNav#g z@+s3OvZ+$EJpG_Lw%jF`oOV7|%Sk3&D&au46(&w23K%eulr#Zm8#S{-qn0`>pjoP_ z0R+?=YN6&hF-bt-1;TK>S)bj)x8j` z@pk0qp;ldW5j)c75UkD02Wc52+`a|d{jcH4_1+6#4EmpSq(di|_EBguQ(HJSc6V$G z=lvwQr^(q{w8e+LkZ+VGeMx9_6=4aGNVy03RSH+K@Z@L$MWCsZ0Gf1Y z_E|Wg!uW$%`5oN(5se48;&%`88FnbGmPRz#)HTm+e$uz3=ImyqBWr4XgZW-j)YmFf zrixs#=7*V#63{WW(u1-ug^{941&ct@CCO3X|B|%>VRl(Npr@@31XxhoYWMJ9*ctBp zXkY(zCB`;c)(B7P;nbcEWO6NZ;~|*k#FD4wSE+_{L-B~rraZteFKjtgr!HU$pHRo3 zO<^{o!-3#li1$#{Yi@%^(Q7_Gr3acnvjUoM-wH{_W+e6yD6mzo8x$fCNTeO;M0Ajx zKu0w`C?OEqDEtcBTchN4p#BZCQ&EJqZ=e-XUUuje3w!uhp`OCU~f*z85mSti4a;r$)B!lN0b_htTr z{!a^P%zU>Ib0|!cfH%UhJU0@mzQLVgXL#Uy-@%jN#qj>}uNVDVE(b8Bs_O>oHAG%c z2DGg6JR_t!GN9A5;Q0nv#UP8gl%di(ywTj43pzcZPEtU~kf|Bs?#jp#8K1&)d0@mQ z<~mde0@3TBYNhzW!~lW93&0~C6brydRf%d}d2C1^y}?dITSgRN9=_yvU?lm%U*Evx zJMdf#BX5Of3ys*(QoEy7_PU3*N z+!iV+m?EpB$_}Y}H4W$B*9dhaU8e#8P|g5Ny?QKxoGZ3IIk?{1*v(K;+NhnjML`uVU`#bPEbME!L{qRRGuTk+huD4|_AOGxyH_$8kJ)Mrg5=vAstvlow z)XH{~N`ff8D)uQ-?$oPhHmJ`)CHb8+Gt|T2M7G|c+O1(DoZcB)FH|MoK^UwLhUXLf zT1x1{AN}qcE7bH59t>}Up>r$XiSW9Oq<#FeS6A8i2GG$HFi6G)ybx}DagZqt*r^N? zNLN)r3Y0`$y@nvceAbH-Z@y7O-y7T+W>S7oC2}VcFEuMsJ7B;d+%<{1vp(aW5|3NL{Cs)$b) zbNI@F6bHz56~n%ps7pzzXa>v&`@?@XbEd8nD9Y# zWPsX97d(iGWdMntb|#>~*766n6wNhgM?1Umm_|(12rq(z;Fn&mFxABV^e^BH1wn_6`TRp6jfT5Kx);Flw6k6XBq8P+R zR%veu7$|}QUc{&YJMuW*85|ZPM4jQ{9#=rPAPnT;9ci!w21fRdX3ZOXb8&w1%1;g) zC;?Z53y*o>ix+%y--BU%G+%gc@Jj+7n_<4U;}-(~j(}}3%%j#C!t}i*nF5a{?;YSB z79SAm5Rs5%$%*R_<3=*4IxOG_r)^^6f>@o1ggOk%n4#7REJX&SoGK0@s;~Yq;C7l~ z!~%Fd2`arSOZf=BD@=J)o!;~)LguRs0r>wnZr{OP~``j@}{{Q9q6-RWHv?ax1c{^!3h zFRookIy&I7^J|ZNbM1j7fbSclpRWA@`~9^){K3m>54iTYyY}E~J6zlEt_^|^>q+VK z`P$Fei)$~INfq4S+U@Szjjvtd+V$?*mF&y+4L;A;e#Ty0ds27M8@%A!>+af%ukCPc zzq_{o+g;n>+IDwsLu%f4HuLkfpRpI$9w;I#o!_2)%V+GxwV(gs<+TS~d)!?c_LKh9 z{`P$BXY9qbJN3@J!40n6?yh}XjPdc${`B)d-(HHIkErzopK9V$ z4SXsxR*V?$h2lUvj&sM^*Evvz?`!zXhW9mmBVLY%?`!ydd8ragbPRWBg^K<_9nntW z01&fhRE%zwVz3nMr```me<>GWh*s!VQPzo2hfWx!Myio=1fc4Z0WFLQoj)p}*(6Q8 zNcm%uXvm}tcn%J_?0_aZNp2$%t5Z2ZUJRfj;=ImaKA;HU?;qtk>ii76i#hk-rtK>x zK?H05AgV)#V$)4v18_Nj>i(xr@=lG$oyZiNI0pw&KultnSj0NFku|O&?HdX!<;%J) zTJ%9rW=KEA^kPl|wuI6asD@@hZ6S(J)drz;Biacq_`ZeSSLp^q@wW?qyJ$2N^d-~? z9UDXjGha1;0YU z_bunjIotCFt80@vrzN(5jvGJ+8&VnpIamOkWST0kTR;T@Oy2#VY%_tK?Cb&tycm9S zi>iQAAzphRYk3f_7ty~}5Z9AxU*6;Po-5-$FU9-+|IQKR^K(Xq8XRFBQkx)mIiqDN4P+Lw zLln*g<4JY~Sc5VLz+(1bN{1Nhjz%-kzyjq}4M`^jEu&^}w@$^{`W)Q>US~RX688eN zLCut2H-)=9465W+HYld8pecp+I3vZh9dwTbJO3J%fgX*9WfDoHhQ%t&vlsXzjRbMt zVlLW6qUk|n($wq<3yEwl}OL?vw|r*d=gSwlU!8M0tSN&_@P zHWrMBFv@DAPEtu?q);Wclm(!eT3ruN53Sw-f)$VlSfpqG5B{}ED<=?(I>Qwy)f65l zcrrW~c7{8{Mw(1-7w`ONo!`4-#(`jzi+cN@z>&xB;z1Qp{~Tn#*M;o9w8nxf-$Q zSCz)Rk$Q;}g`Tuepqs2k9~|VVN62kLvbjOHP)chw}Ach_p@cmm~KC(+2@ z)C1B{)@7*bg9Bzd2{$e##s&t>fCp`RZB2$3!yDmU8SV@Zd@-V96(zdSm>XnnLcXAq zabS>qU|oh9st%pz8fmSs6$LEmL~fz!n8G^<^kJ14ETXeB8K{wohTc&$%t*Vfg)#t0 zu|rp|YEg_LwVb05cY-C3S`(yrRqr^!;q0rU1f~da2{rpwkzy;gb`5hGljV zA2hIfBCvpJ2{w5;fX!qUnlq$3MsR1iAbd_Dg!$rS;fra7g3DA4Sx)z&K@Hso@Jn{3 z=@2{9thlcMKdTgifY$rW6xbbVkOFi%fnnoPC#Lm^hXs@(gjNckV-SW?=O73t)zD=w z~>v zE#S^D-`n`t#=ma--cTnX)io$z2)G~&WfcJrEU$x75fk~o5j8Y%^UH%SNDU2|j2f{D zWZt30F|d9QILoDb0mRZ#?%ydV2~w5nih|B41uX!@uBwH~7pQ0%TYrWDU_xg%C@@#uAYCtEkzcoq>c0 zCBwHiHl{$BUY$n}Sn7(+oY@Knb6K7sH!3>`Fo3TR1;UWC20=GGM1T z9m16*d^QUJO_mQD4D)+Ovf9Xxzm+cfp0d&ZQ{kiU=_0+ORroHKT$s5IcxeKSZ;6`9xAx15S{?s-2k{~kslc)XB{nI!3UEv;dCPuju zz1Rk@0gpeCZcv+>Xm~rg=)l#oVNW>@6|j763K}WGlE(-o2AO+`I+pT`51Sp)z?olp zkm$Fn7V?a${8OWrQ*e#X^R|3*R#8FjyM_bk5PfTT(T{w~+-jCvo4Xo;)^DH<(cHF^ zJH4@InDYIiwE*48v%^z4KsAz_53yR(?~|k+ zbN@O2J+JhfleC1?cALi!)uNrJ#m>$R?ZT^4OZr)V(1{!1k9s4!$=H0fX`SouEhN=d2Qs;9tD*G;3Sf=1)O7AoetuE zj;F7dzO#gtle$nRCL--hV7mp}88*}}bNJp$N^I0G1DcJ1L3Kv3@x@uTkaP(J4^)mZ zo@?Oz2TeK17OL(EUQ8i7Sl_CD&Leu-Pf0FKeB%Q}iz{&RtK<@Vo3mk7_$2CHE+`ZY zN_uDC05!ELq38UhkgJfR^8t#a>arO`qYnAJDAme+%ndB-zkT6X;82hU*xN5i%5HmH z4bsc)alx57uYhT+N2paKueS&#&u ziGAf0RP<<<13CT~HHTbvL)a_A#`mtBUaap*W3C_>!Dkv?9hg+kCo!NKJA<0ro{FDd zKu4?Z4K>)u_PZe8r9}h@ExkA7i_QYNHhH}l1VlBYko~?WesodY!c`%%SRkGWgcJ{O zut~iFicYQ$l+srvuH~y0*v&Ota!ctthlIgH*(3L&v<4snBhxg0sv-9-M<>VwU1#5c z6vk=~$Y7{)G*3%jDocfr6rCUWgGF&4Bq>znobL=*kd8ayohlQr=scNpf8=4gY7~GI zjvbGe1hN{8o{TKu*1FVk(+sjts(z=8pm55&oqI#a!W&N6uV%!#@P^lVgLiy>s8+b5 z$fMEtw;;>PzrQNA7Of4?CD)=fNLwnSKVk6H6`n`M$?FkmVxDQ!E|jn6vyJq8M?TBF zL!?$FHrIL5ug^?`TPqw2oQEpMg$$x9$$IXT$Pt4f3-$nL1ks@YjUdM_hqS1Y@&=tA zDl1voCPt(R@^GQ)D7Yqo!uZTlV06|DhqfVg(F65}C|7{eTa+sec<2U|A}LA?sdcgM z09j1*ziqG@D;2K2!Nzbwmig<=a@qekDGdhgU^1`Te*uq`l3 zYGE1lU`7Gz5$ag%9xDYFFqE&TmbAet5sej85g51TsfiTonA&1^KEX+G^~XQExyq{^ zJYbY>PK8Hra7UQP`<+ar7@oqKR6v!d7#yd$fVm;WvVi>TOi4Jx&xC<(TV)M}T7#)^ zxI?r`i*&qdsCWk6HB`Lo-F!9vH(({O5oiQ<0tbQf%itn#;~?-5;TYBS4b;AY;t*5A z?k$Snr_OJni;IiXzxl9PQn>dXZH2;3tsy#*iVbHOWc$x9xsh25bfSaOmrmXp-N;~{ zB8`Eu%`l0MP`;}bG-uF+6fK{leF|ze0k?0##-?+{_io4yVW99c*zgv874++?yQwif zT}7q=Xl{6eQQ8vX1#hq+sb9qt8FGc;hn0=gc)+822UqGH2p19Nxp?;qEZ~K{P&6jA zFT;cS7T#bqp~w$YhLvEW2BNU)!Hetl9(LLlf$CJIBe{s7q1PC$6pkPVQ~n5Snuba5 zTeuPX>@8d)C4+EBSLq%6`ixKi==qTU@Mkx-dJ!)oCtY3%vEX;4nNd5=Ks8)qf{~m9 z$c~9M8P26pdjL+8hHTM_*KdZ25v3t%x?l)Sgg~hw3_iSI@L^Q}V2e0-iKI_tEjKI$ z(9^b=jcy}BT5uc;N^nBkm{8;;CZd-1SeRH{_*4t&C>B05ut=WQVK!7CLp?h#a5|_g zbrQn{aF|80IidqmN3{Rcfm+PT2=P;drp5AB%?Imv(pKZwg6X`>8+X=P)O zLgqX9SIpGt%$7JCm7IkB&R_){P;Ni|70A%2>OU{E!{b7qD!=U+?kgWpNrGMmLTn3b z6OSu)k)Y^^3aGT{rai9QPghUQ1sZYZAV7F9yuJgUS%7e5xG`)|+m*t4v3yX^=eY6( z(x*NN$go;ew57GE=gITTnPgZmpp>XwntYD+D|h51QGG#&^m!>LB3}<`L~6(xH6D2x z#t@xce&w^^C-7$%aENAWKyo+Fc96$%<{9OD4$QNl^9eN1?9BJ6d1hZ8WSD32<%iM? z!tnf+Bz{P6MfQ2lZJt>Q zs>}iB`&_{JksI(_1doCj9}S-znx+GWPa>FyWx(!mjNO?VD)YS51UiYcDNX1H(?$%W zLuUcayhJJByx4Rc8)oKmSL5up6WZhy+5-5@afBEgdc)SqB!K`@Yik~s2 z2(Spfx+eGV&Pm);7YWoaid?Fw-~t8PvjCU0sN z&y!e+1p|A4X;AYmN?eY>To8dxASwK9mb~H_q;_M`t2F4|ACT1(r$Kk(fN+oq=tS#Y zCWY^cPXC-QLxu;jX-p?#B_MUFRKFo~4?>}0MUPc0zEcH8Cka0QcZmcNdm$PSkn$7{ zNcDw8AOqMYQH#!1sgtL)JOqLo(PBWav8X8;={q=S(YX}q4MY^M3CY!b1x}q3VtKsZ zqaPoZbfE~I=u9mSi@IoE43Y`h;MkAB0{Z64R6uiOpV*q&Rm44=axBpIsRRzlLTeKR zN$ReoIb_A|;K8u-y*tTaca=gz`X@BTfke-7%sWyaD_IDV)IId<^Y>P?xI>~+&E}Bi z9Mp}_E2;u?&GXzFQWhWRs>9;$3MHd%J1Mx{WDrfEjP8akXr8j2b@nTZ9j12sH_H*{Is{#e1B8* z2Gr|HSH6-3YNVQNQSFpJdEX=tE#deLMHCHm!L)=I)MqFMkQ?Ru0r5>Sn>xv+f;SMv z!6ZFO(xSw=rbZ^mJ|+k>O6~|6jTWL4J7lSLRUNW~8lv?F2Kv}`yB z6|sW3arSoTN+|_U1sQT>KZ-i;P9XKGMC1baBLN%)(uuyPDP&Sl$fTAK)saKt3xNZ6 zMs>jhzcTE{YJLGyUpNIATm()ym?TwtLw%9`$+HIQxVg#xDv09+g|B3qsyo3*ucm-5 zsgnRG6l@f7?anrx^;g4QkV^SR#gtx*12Nzlu>5e`Yk)j1D*hC5g zUti^G8wFwU)Bz=pLTUqhXozV}9uW!5Ok%82s-jU@U&C0bcm-x9c^V)NkRBkg`-#dF zbUYRKrYNtdc=CjU>61X+hw!3%D=rT&rerpZnhLz3QmFGM zQIe-YvSW6lYwU`wLoQ?^`L7yjfb3CuH<-xkvuThi6|3LClpyKCilQV>8K{W|c0~#E zfoz9|sz-kBKIpKMavzAAs{(;?yskcr{Lw<`-Vuf@?HRGk8c!etuM{wlR0K@#JsyoFl&mDwgUCCEwL@G2E?*KB^@jMBQqB$9_8cN|YG%yi zgvy-e)4p2)TE8yA7|3iAVW1j}AwzXMS;}8I#6?Bj-3lI-IVy^!iidnd0|Mpks5{sw zPN>ZKAd3ug1d8@-iq}B{|1Pk~EhOv{TIs2PcT#A_`wqPFBD5EZ zSN42U+>-dFC~41q!L1gA1v96EWUi6aw$R+15}MnflIqQaIYI}bxks@b<%LlMcrVgz z)ba+U%}@|-7+wbfQf*9www6KCsWob7amNKs*ibK?m3I39Mcx4qN_LF7=%g7GZ=BJ6 znZoCbElkpkJWl%tuQJ;%z6}R1rsR_ zG?Ix4Y=Vl((yx&r5q_=02DrC!y_2X~ceF4=-3tt=f*b?rQWZ9bwh}3X=lvbxcN%$v zhdQ-jri$rjQ0xhppt==ceak%oNodi}KzCl3L~tHsRiW*G`s4FhBM)Dz{&?i4h>Dv@ zwOKD_*h&Gq%kqw4yLT2C7Lf`_1WO7LG=SCBJs{YiVJRqH>3dO?zVAtbo}?gLN?aj@ zTL^bzqe!FX1>co=;Srvp>j`}ozBa>)VODNk&TyxW5T{m^8vPB>jpEcKb4)Q1`K=x? zrW9Ue$f@(z8_M*0zF}*1cF+J;Q3)_cnDV2Q)Qh2iR=X0E;Y-BZKy6no00m{ZA%_xh z!(Z3jl?b>S!8Zcod}+KgkVgf~Qq|NS$y{RNrn{0`Gj+xgY|gEleD8vduAyVRp)0RY zW2i?*DkL$^y%(IX4VlQ)bqr}I2^B-qst<%qC0MCdf~#TYsVLTE z*ctBMf-AxcQM_~^5MPNNnQE+s8veH9cN%!*iND>5l1gs8F!FyuTJH*Zo{jK=N5cl8 zi!-g_T>v2pEh%Ve)5J7TL{^pzSW^b+eO{BKMlT7GPC{WwR``5?f?%&u{B6hkcD!=O zH-d@3qO0=DXl@-}`y1%_eBF)V&agAQz6{skS4G%JySxt&O}6m+5$+5xT5{k~yupoO z7*s#-LYOv-Pr&eK<2^IPYq)}0v59Via_{+6P=2Q&gPv4TPWd8MluoV6+DWm48x9At zV8h9xBdJ5x$g#QM)Y5$+!KT`eo@b49Xx+m8E!Y^Y2-n4M$M=S&dW~9XRQd<}_dVJ! zH~F5#v7pwh2^2dMz&=?-NYL1n^c__j{b0ELBIuohwd}3*@YL*E$s$tdVcwfVs z(I9;Ivzx2Dgq>kSc<>uI-#hOT2-ph36(0>MLIZ~PF6bLdGm_*WRIrnKNm?l&=5`>E z2^1f(Q<2+%5;%K+ocful&?Y~rO|PL`k_wWkxNcKpxEi1uStSj%>+42+-Gv(Dlk_tQ zHAEYtsKuaI6}>QNuTC1J$OTW{$}405wG0osKo5 zm2|8LU9^sLjv2Q0qvLRc_D`a(bw}^7;J2cr&wusm8UxWqV*`7e?7@c0-Kc1);}A7|Py*{=^y1jj)1!H^#cPZ*XULd<&ioFNQb5o!=XrEzzg|MVWfrIvBo% z8bsgh;+s}Suxy=Lz}|ck@MtRfBx)ebgGeU%W+*o^)D;(DB#`MT3cC6^cwin&%gfo<+qZKO7$KHcGB6zV&=RyeQw~| z4zv=ZL`8AWe2u8n2O6Mm&<_2Acjy2|Ezm_28H6H3+uAqiyEXlqWMUz=+zm<&S)dzL zSSO(!e>?7VnxFsb#WkQiLZe>r#VfygCF+n$A|5rYcxE*L??r?LM6K7TEiG6NS|=7M-}N}+)5+9>0TMKJNu zC4$h67BG}9h@NL7OD{O3LDQQzy(l(f)EZXErG0~Me(ikkMo$jp^E!*##_R|U>Zp_$ z6a<++#R!0bK9GBn4{BFdVHYZ0s8IOc2dz-C7FFOp z^2%_dqvj1>cyFL%x_R=O_f{SUYBCxsw5Z^;Zs;>)HhocG6$qbf(swaO4n9S7K>j7@ zOkk#}%-1RwA3#j1qP9%L0SO*N5BT=$&T!*L;}?TijzW~J0$q9yy65O{$73v8lRDQ3fLdx<>qSND@~- zsR2MEkTid!7?*=|&q)=9gD$I05O_-fbQ&S2%Q}sa(+D|LKgL-~pnVzaUj|9gyA1;8 zm%&Bg2GDMjo!Or}3tbdaq__hs%!@qOAkbjC0VD8$3QOtHIV`LE)KKQX0jVOKb4m{Y z!I}!5^C?#qqk{u!D7gRy3xy&8@}Z%8GE)4&I~V(HrL-;NWg~4{9n-d8t?9@kNwkKx z?gu!@jow^?-DR%tjKncLKUN<_+Lp#{aYh`!n*`$^z@els#FUk7g5;;d6OatkJJ-%- z__neBpS9mARMV?#p>~R3)WUrlFsR2(2lwDOl`!IGGhEnALm3~^tyhZ=xE*KDo0U@N zlq+grzN@l&R4TOfO>t%@vpu`18wKZ}mhntHD#wySefy-b>Sr>88bC2PX#Fg~{0n+x6#|+YqMjS5RWVR(VlYEv ze&65<&C&J9E4%$HK+toT2J2Tee-n8jK|SL@`Mx23!FJ_-D(G?@oYQ5M&jad0fDBo zm3G=ok&VYC`wpAO`PL2^N|JUbHAz1wTRv)|5n-#&HTO+IMGoiQnZ!O(s>n&d$>OX4 zaVTG%VgD99z6@8Q_q~M+aR-Yl8kmP=#UN}5%RVN!A*`@Fz?I>Gk0y>Jf~Obw-hJ&5{F9f3f^bj+p+T^7X1E{--RUfEH#3n(;y?4XxRBS=vJmR5g}g=;(N9P=*- zh2QE{U6}0}M#;I}NGiPnF+No{4wMp8Z$e(Zq~t;#;#O`UkCz^I95&!dr2(P_MEN41 z)UHHZq#_FFX^il3p2n$<{HMy3<=}o?`E|zvUTNibF6^WPLTj~^T+n7*W)P@Cnt>OeeZ(v18P)+kF5rRC= zq(QwN#Ei5HwiT-U4${G^AS{hP6LQK&h0{yBD2QwwVp2ZArWXbA=*a!0;&aN>e68EOckC1NAl z5s5iPRzv6r#{FaQ^^x>SSe>^)PUoa9LJ7~ zBppk+j?(^U96<)Rp-nM2v|JAE&OhRFXiW9d}O(!rANg3Nw;(1ZM z=f*i$L3>)|o-mWdCGOZI2;I*uNL>u+)1ehjO&&m!J&;*e(J$)E9L(t?qZPKuFqkp4 z)}T0eHR4DOPA95YoydKnV)ooq4=SA1kQxP!GtrX}|G&rmfh`h*G{MpT|UNyHfr6$qPN*@n8=g%f%L?E$(VV=ioOX8_fmORHT; za)sypD-L+PO+F%DQ$r0Jy1{gV=b}r70$T+tb>pA!SRruhUY+>1>fVu&G|@Wyl{

B}OW`&i)^oTzTvrC$S&x3wYul=iK2Hi;d338O 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 RT;sjc1OU50OD0GN{{yrqvS0uJ literal 13588 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 a#w}8Cg8b$G{`1dw#Qy`PbE{jd@BsjK)1vGE diff --git a/fury/data/files/test_ui_draw_panel_polyline.json b/fury/data/files/test_ui_draw_panel_polyline.json new file mode 100644 index 000000000..392c75d28 --- /dev/null +++ b/fury/data/files/test_ui_draw_panel_polyline.json @@ -0,0 +1 @@ +{"CharEvent": 0, "MouseMoveEvent": 3289, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 21, "LeftButtonReleaseEvent": 21, "RightButtonPressEvent": 2, "RightButtonReleaseEvent": 2, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_draw_panel_polyline.log.gz b/fury/data/files/test_ui_draw_panel_polyline.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..eda8840591713d0ad8dcbda6231c13dd2b856219 GIT binary patch literal 12364 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
    |{tCy#Gr(RO_RY2b`CgLd=yX-gzy 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 literal 0 HcmV?d00001 diff --git a/fury/data/files/test_ui_draw_panel_rotation.json b/fury/data/files/test_ui_draw_panel_rotation.json index 99bf9b9c4..ab0e357ce 100644 --- a/fury/data/files/test_ui_draw_panel_rotation.json +++ b/fury/data/files/test_ui_draw_panel_rotation.json @@ -1 +1 @@ -{"CharEvent": 0, "MouseMoveEvent": 2534, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 12, "LeftButtonReleaseEvent": 12, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file +{"CharEvent": 0, "MouseMoveEvent": 3626, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 20, "LeftButtonReleaseEvent": 20, "RightButtonPressEvent": 1, "RightButtonReleaseEvent": 1, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_draw_panel_rotation.log.gz b/fury/data/files/test_ui_draw_panel_rotation.log.gz index fe3ccb1445a3105759fd19445d1adcf7542ab9f2..0afb360e05073ed92131901750cfa55166f48b25 100644 GIT binary patch literal 15069 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 literal 10870 zcmV-+Dv8w}iwFq4=p$nS|8!+@bYFF8Uu1G&cVBQ}Ze?s=a&L5DbZKvHE^KdS0HvKt zi)2}Hh4=j{9JF?EC%eN|Pz!+sq6OL)>J&6cRZx}P=;zChIqJ^vbo*Xp1VwG~c)a1h zJJ@jS*gxF<{p)XEe*T~T`10HDfByBCTm8rShd+M#h2>*PE5};`N-{SQX*E5iXP9zBpnt!{t`t(e}Fe)1~OR?fir6dRzHiSiVH^5%ZO4@VDzW0JU4bda6HCuY@-I z+tv{+gw}i{ed5-Yqs?akqWKr}^#-WVD?*L#I~zT-`IYlYYn?`892!YqXf2=6zJb6u z@|Do?nd6_n<)h&@ZTb89K(+jReNzV1x6GD~hA;no!uqz6FK%C+JspZ&0Mru7!A>Au zb{+yZf$IWnblFA^R0%Bfo;lbE?C`M{%>@4{08RoofIbuQxw&cL?oQx<<-(wS7@Y40 z|K-bnef_7ueEs_CFaP%2m*0QqpZxL5e}DP;_aDE>^J*=0GL`10SHAn$?c_U`8vwQE zo0EUyy1``huaO^nZRnkHo6u)o&+W4Auhn}x`Fhi7oj!RV{?{kHcQU*=wpuD z!RUUVjodEmZp{vuEp7{sna{Q3c4O|;^>9zp^M4e7^4wz_KF`N447VKiPm;T357<5o z`>^cA@Mie%#`*lCmk+o;{PvYbKE8UsKXrdBnsHjEx&PFCz6{vD{K6x*DEnBrKe;Qd zDV^?(ZfUK28M{&0XSz;Xc)WQaV4t{ux~uQFe+Gx;l;7JQtC#%9Fxfx_51gG}yvrHS zAFa!W%ir4#z6PV_22F#|jUN}y@=*&9A`ct+VRs4FPyKag*ce_2LtMybN}Kt`tG-zN zV(x;reCurCJYgiSo&3pVf8DeB-VVUGpKS5M=eOH#cIc57=B3V1a<4tdmXwmJTjk zehRwDQU^HX8$t{1o9X}a%YXkSneq2M$luu9Jkz9o44G~(+5t$eak1_ zI6gfxFuYp=(--m6G~e8f0+<5R_musTzNgQ_R@q$6e)4Ajd;252$qO(auL&V;KjI-E zHZ`I0;8bot_?DH2AF9ua3P-S?uhn=<%B_Rg?;2-rz^?K6L8gc7_X=jNf}uIA!Oord zrpP7Oa|$xr$>D*{!F%AwupvwVBXn!x#dc$u|N8J>gRv-YuXKiu;f*l(OaV8B`O)&j zNY?kL_Fnkqrj`Hbt=aZtxB3n)3@f6SPc1iHdY-UsXGC->b*LN0BD=#7Hgo5y=izYK zqTn$ry43Pez=-4%oY#Y#+Tm-pjr`PBG(L}HnwzVzg_=*wMu%(c;ZEU2M~BHK$#)u);gL@!lL3(*dZr9$zRQ^(;Wc+|%;%ai=qbG7;7j2h zS@P6CD3Op(;T~D#6jstWe>7#B^EGCX>K-|4k#|mErL*&UD-DtxDYh#)H?~{kty8#@ znPTB9z@zz#<$H_yG_1oI&WC0Dbm(wz+1~kkpGr=h`SPoI62*MWt$Bdd{4B0uN{bqk z+vu~%a?JC>WrSmx!tz#54*R|Hz|r(msOD*}DWf*0LQQ|_nj0F^Pjcl0Q`oNI%5cfy z37ntlU*l{ky!R`oNX_(9Mm1^NnVKu`t0=G<}79G-AC%n80YYTkIA2D{H3#huiO4+rfM1 z@j%l?G~l%hG5oA5CYL{X8+a!6h9{~&R-F?KESQ3_Lvrfkm8lT6z+qe9pe-=j7Jx^S zEkJO`_uf2e@A>P6b_hKhzgUbK!2H*~DO6bcKF{jxXw?V4p<@FNTfb!46jsp7KXw-sp64UBMGikNkr^;LdQnOl&;; z?BXilZA=RvQB-<(f7u*)O5T)B7;NuA3_z(0FsACpz=~E&9&b{VuIA^2XmKs~g~wRI zZAs&ciz-;eVQRLC)CY_l(AzXl*gPKLzu9L%uKI z`YD*dSRV}YpmrYA&V$-9>jF&Z2izF$3=f8l;mPp&Fll8H}0`q3x2c)iYJSMa-u;0{wtynmR^N?_*JTbm76n(;FB$D&QZA=;nZ{-E7W zEGejPJB>D8m2ASct*8MIrK>eM6J!pKJEyVEwbl{aoCA1G@A>fr8G8p^=5sGhA4N)Xk)Jo_k%7zga0f`E}N93{S zd#9p-`yvj?eIu%u%}wpQMk=={?;7Sj2y)jq%JWM1x~^cLZrpu=_T2lq`|JT@`QFMk zYz*h4)+$wT+#8m#-Bsj~J4g36i0<@&;oeBZzBeR6-~BKE@8{k&G0+}xM|c>*o&P%D z@dGaSXzk#=q5dS`+{@RXHhM85M_iMs!IS$c0j&tFP>3*S`2zvsj#(-UkS_{w`_2q9 z5eG7yKD&Y9xNpG55=$_TI6!?i3J53Yk zU1S6(sQGl03qQ5ZMV}uigw~UUZeR}@RJT0nEv^IVj=VpN(796;f*KqGXh=+=SS89 z=6hGZIIuecHiSF>b;luxY`4B?#Lzq5j4Xcab z&hRKSYM=q3PtqnXgSKGQRL%%5cBlqvo)!u56z*=@J;dqYh10e6Nc!}#P78-KuqVZL|ed*|-{12%>?!ksVP z`I8JDmcfrU_`Qt>b@FtWMymwmu~w)s!Fw0dpqk3}f+^&4+@L1KcL#&>+bt5SaPJXi zhzK9r*9^{3-xUqcP=9B$fiUdn0Uv}H-y6=*fcfGFpM266tKq*8TpPm+;Z_;udpCUV zNgAf8&i#P-;)9=j@V$q93g$r_5O9=eIaploBO4kf)6k@vIrU#I*fj9&{LylD;+)O@ zujG@?yx2)!3#8uVD^GILPdpyf7=p~aof*z4(2OUP`MOCnsYv+5xTgUV3u`ltw)8M3 z(@;%~DFL-w6XXI9!-}Z@ybNL0wgIf^0I;S5z@6b%5UvO-|4pzvdoboAs=^nOeLO)M z!D={sX<$~Lq>#!hbX$<{0?c!Tlc^bl_Lg}_aAJZUpzR1PffgZb9#VjrkfL;l2OB{c z?Q7Aa0#vOOJT#Q{haFkk1QW#P6xAGufUQgX1 z&p9d76326T?h~px1_ix78cz5ll3`b`9r5AQ@P>%X{yG{Hwdpr9AR;*G>aW zKHnz{=yE=V4M<1>TMPx9JFNldL5hI$L+|ce7&z-1RI8gl=r~B18*30v5NPFEL_jmX z8?gi}l%{j`D=9foqQZ?z5n*Ws8u6DKwk820o5&v2^r#tS2PAd_GC@@eM$|uOQ`#ds zDnU7ms&!$weM5rsL`ky}JovB0VW3ZqrJ+*AhN@L14nT%N@?$V+B7}fSMGJW(qne9K zRKG*I11jhMNvft&;Du#8mCE#t$}&}o0vcl~WB~y)xi%nwCJl8EPgDJ2gZ>sQ+!iqO zw-5|1CQ zUrQ`L{p{i@FZ^YEMB^9iF>6z}#d=F&l>!giL}*$pR%;bJ_~K9+swPP!?uk0Hjm2st z*bRl_ZBE>oCA-qFrmWUYI7)4}4!g31qM*q`xxr{F z+>|eHc~PB;-W8i2E91XBFRB1FRss_7&%y8_U z3bmb53pms>wsXq_!;S%sdst9A0Vw(u3Qtw2;pHJ>tHdZ8_P!w05bcO!6GxryIjY0` zz39ur<*O^~E7T}p`^_ewoc&L~;%mGZIqC&FsXbk^-=+BrtJzIh!_`c?wE^9<`C>gC z-6c}oHd3<*ufmM9K|q674L(|d-cE~p?y2;xL02jjDM6#NQOK(okqXM+RFy}1EaH^| zfj{8R@WOio4?p0>@c1x1B2ksXo#DoC!F%`3uu(TI5$6!@3|EE=!YUGlPty23o zHa_w6v!8zX)8(c9_~n;B{eQcoXYAsIUVdl8IYwWq;v7?hUy2Tuh!8<%lm^y}iqOCm za=)~JM`al9le^tOFszrz>O+}fB|mYHW}gheN+ph+^vva?9PFaE3%LW-ER7s?)oqSV zO_d=VuXZ1hftb)|sV?e{9q6P|RB$mCI!Ds!gQN--#!K#)P<5E5>RLeUAZm!>l?T4j zFhp@A`=qbXsUlRVFL2>x@<5Grr+KA@vA`ZUh;BsJC(!wczug~yBOZ;5k(}g6OUkBE z9eW3@qVg^JIt20dH{V^MUR@uXEcWXV#M@u-HNHa)0&E&}Abu%0cj#+FYs#`A$RIT5 zAL_~*h*br}Pt=wGntcjpO@s<;HNpJSkQWUYI&K!sO$H1bq+r9<*LK4NRtwMZLt7{a z7b(FGa!<;ElbW1~=27TC0&zsi?~K`qxd1gdDz_YY%t;67*qvcxczz0A#OeGNe!a0h z{pf`iflr`(16Wm!*gIj)O$^QsqUst7i8j`4v`W;VP@E)^0Cq%tbdl$nS)TAxlgOTL zx~hxp1GJP-#j8hm?VLv$Ns(M=qF^vkm5m553QRrVf^hDF#-jo0N-tJA_W}e0sgaMV zF{j|KJk*?i7h2uC}G?tW)z_`S_`-V;Y`^q*jtoz z+MM@+jW^1&0iHqWpoZU@`+BsI+5*rrdS-J$1!8kNBVHz?W!K;9|( z-os&WXd>%?gk_*D_Y%JJe4c-Hah31jK?_;RtFXsBbfh{JhXFTA8Ut-KsvT<;YsV_p zjum1zDRWjSMKnNgMrl^q)g?C7knsAg&G~bJ{G{mg--=ZV6td*Pc%X--K7D{4z$)D^ z&sM0rO$W{Dd}9QTRG(vU=+ypKHNwm%e#IDpQ2D&7|Ot1pMym5d&H6{dO2SsWJh3S1qY!?~1 z>?dyzq)s=%XGf5N%oBZjhPAUU_$#n@SwCXuwYdG1r!@KBmq~Z zF>mSNW#ukhU%@_tN~;dMSP`MBZsOJ zPs3i)pb4XX&c+>?sc~hEvkBUfLl15BK?l)A|019^^u9RG{EaTY?;s6j zH)cUhlV_c8Frdul);FOBg;5u4L479jHyRCBSw*yvIy0kw7K1k61-ePu`2t;}`|M^m z?MRcfn|;3mb!Ir&R4r~p@~3pm6i1LGNrlb@EJ$Iq6OAW&Rueow z_19SCWL8N7{4p$(rV%wl*Omh+OVJCY&@6-%ZN4b;y)e*1b7ez}i_T{?0bd}ixWOLL zr<{v=)`Klm&{}b`9W0JoVcAK%iMF>!=ar+huc{dgNb3i0{LtQ6^9Vq=E7WIxMx-s> zJ_pWS-yX@6>N~oY(#(e@_S&l;0;?`?@ZRbI*NkBZqz}s0y$gnmV><)$3)6pnH#87;rgdJR>gPZ07?BI;fgJ;A3)~rgRg-^fY zYkY^U3v?A)JuqgzS%T5$=0|=u>BNLDDc=z?h6VgT?XvmFB|{aa?of4j9^4!<%Ak+r zsI>p9nKA4KJ+!tq_CW*2r%n%0PZF^JG9CV*8zdu?SA>wh!m<#Kt`ik}-2Mm6sfU=1D|!clt2UM_g%4K znp8GAiTf92?YKi+XpJ>G?ttJ>i4Utyv!cEsXjlxQ;fA6kDDf`3Z>Zg3g$}e|rn2^K zM5I*R5s1x7vL>e^V$rZ#xefHR1~51~s2{3TOdc9nF`+?|ng;3rfwC0RAkPg~F+ji} zR)4T6@l~;ISjb>yo>|q zHh_&nJyavT0znrQR^(g&wWWi@T7eZf=}o+7NT-R#1Y>SJNRcT#$huLe7FecYF|~mt zCtz;{3>~`-3k3t-1>u7))=QyC7*uCzP=Rl!BxI*SEmW&(V^O8Ag6rVuYbt^#uZi3# zD<)@q(<%XPT5AlP7xjYxpH%C6KpL_42*<(JGa%m~r<2(7YWE4*l1)YD+Uf`F4$|bz zO~~Op{Wx%bVNcDmCN<5}V_81ehFEC%t~kU^$o(ocNVVLN(12PV-5)Yxq0Fw=$PA{& zh6JGVNAoSua-)8Pd^Ci8M=(AhDhzE|)QlJ5g0SRM2;K}E!1Bc|UR)h@^EI^fT6tk! zkMvw#Uq>x%^;8AQ(hLq6@u9#1r9DM+hz#~f8s_C^mYZy@Km|wHTbM_6M;x;{sXYesNuH+eV|9(3g0&` zZG({Rp466J$}6CDqcA>+H*E~bhM+JuZvrc}hLPJyXb_s4RF)u!93ZqOQN$K%#H7j% zVQ~388~ZDP@=DaZKz+Z;*C46eI>HiW0a#&hfG4^{A^QNZbRA&-D+l3*k2d;1c;KMQ zl5F|LJz`jlf=8c$2gB|9$@A@7Vy_e16hrxww5XsCQ_4!D@}r4t7zk`YUXYMz?q_z5 z8o&b!OeHlza6!05`-Vjwf(X_kA{d{%8UMBMy`_@+emJW{{IRRNXe892hmB#fEa1Lz z>xD4B6-s%Dasma_Dl~t1jZ`7KRn)d`+2!#g6heR|2#W(7BUoeK$%w~SDnav?lm8i9E$aX4(f`uk@ zBf36;8oh6wG?Ep+?*+Oq#3|qYhOh8y4)6@A?`(#Ywgu{+XsCpm;ZlymeWEkhXj5A& z(9A5{)XX4>t;oqTk{tkz{(+@xk$VM60};L9m6_$xVwNsy>;a|W5}<$%hN8hVZfMdP zwnKrGj660-$wXc`fA0%nNGWZCJ^Fy{Q}9J`;q#APx5s$?)yuc+FI!$bV=u1#_6Ny6 z@cgq^tv7z;IbJnp?H`79=v)vNfTJc#H8jtV>CJV5meb}*MN5?CVjwtu@z`vE73n)I-L~B{%!i-)u zhMY(<6N=L7FuSDTS1&EVx4+_Ryh2$R9=o>;)O|Npbe|~b4*si9xbguSl&WKZdbyCw zq%#Oqa1?>2h7Z!vgm0~4J?O$I2WfPM=cnNHUN~)!O4m@;6nXy@A5i8o`TvV5b`=hR zp_S!KEv(e$1A1^rVe24$IJ6%&#)yKzHqdqfg*Q>q2tIFYmn``A^s|eryii-CPOKMj zM|NOPX+eCiQ<oG%C$7$uuz z%7fADuLE+)5>tUvC>69rqYzb&6+BAoMcSZYeIXcj52LcNQE6073zcb#fTP9wHfHs@ zY_xD-OBGBMgEuyc=Vf14kl?L2@(~uRXnT=SD0L_Zs)d{##`_M+;zHKd7k%zqO7#{< zK%2Cq6v59uUU7UDc6I^O4{|^^U0EgBfV$lVs2krva&c!a9}O{N)+P>lKF(^QmuJR@ z|6$HR9c~svn-<^YpunS$wxjuUh7ziZQ}$6|)vySP>KL$AHA;yp7Kcy)mgMiCNWyS~ zHd%vpk!TIc!0BiK^K7HvE+R6?{wZDoGG016pnl_EFk}|g)|=XUK~Yx$36RCDSUNTp zuKX?Bv3nIWHDNes00hVbCOHva3$Flc#3>*j_pMx)lLTbc&7 zle<-@S)#so?GA}S2~`XX5MSp~HO0G6Xc@WvNY*1-xYNDb-@yWSLsj5|T#HJO$?-W8`P@4=ao$FDGGuNv|5f)g4|FbPc{NX64Z_v zctiqz{%)K?MXl)KW-U3%WJ8N^_;NH)34 zRrf}99ETd+%bJ9JTYYGucD+YPHy;+?#H?Y5Yy+tpU%W=0R7YrIr0}A`N~!+YgeI>o z++k?)YSbdusEAdyga)0#16~YYT)ZL-3{Szv7a#oBo$p=w#Y&;-HaHbk3`NU8=P}K- z5q0~dsRW(iLws{nlPl2Oi(b#LzLkRpy70vQ+L8kl;W&jG$)re8lp0B^lnN^T1;gH= zBqD5@hBC45f-h52&p*4k%1f9>4;Hcneiu#)?3}i_`0tQ3PjtuzpH~5tJYAwPYY-Qf z4cVyY=?(`E0=o^OkQEzfOYfi5lEkNkFoZkQt!z>aY6Ziq4dTWMuTlVIph{G5wmUYI zgJ^j)l%Ck30s3%=z5#*`irrG!sYTL~gzZ5kN|9fKdoK>{7Si{goU@x{QFn+43^xe% z4eY6*z%YavE4KiQo-ELWsdAU4$}OlW_OHSgO+qpB3cbFB7DAzmn9z;rLUbaE{j5Mm zJ<656z8@(-OWQ_5upD(t3bSZx`qULPem4y*6=;Lnlt57uAkWzxna& z^RpG~HPo?-5p0{-gTl8C>a8@PAE!K*iJE_a4s4~?7t}FIxe_{pH0tRj6wXfd0_-S> z32L1Kc^%O+405XC6jC=Eq=?74ocq_0q$w^cg4G3(vV)VvJfjEV*w|nZ4*iDI{s&yB zw<9o#9`OF?Utio?)LuxrR&JelCc=y1XlTp=sz5u8hEf$77bNb8wEPF1!^mSeh(hNY zp^a`#T4ja$`1q$L8Bgldt86LkgX(Tnsl^p!klo;beQ;Bqkh}zN5;hScg@W(1k)RfB zxquq1eGaB(bPOB3i5_I!80xK(DzUZ$(ASlbPpYL2IWs{iG*T_4rhUPfybPc`|Lo!_ zKMEImLeeb9z-sRNnJ1V=K(XjcbVO;rh2Y8^}keK)qCJN3Dp(f0%Rb)9cUo!@{OqKzW1-Dd%Gs*|z<)|pdp!-KjH8*>}-r=SL+ zR%~S+J({6q2?f*|##ioYK$D&w_h>_vzSLbMzPSl_my4T+-S3*zEg#Xl1_T<_-J_(u z!QXeZ>+cF$wcKF1N4b0MY~AH)wL#b4q-lB&t((~yU!AYN>G>=) zpzlEBd{LT#Dm96kZ-W@iqRnk&@B$IL6r%0xa2#ilo)p2V@djAbW0GxO#1U`4ihxWg z7ZL*ITD%Bm`pb+>F_HQLQ49QBm~;4$}9&cww_Qn zPvOmiu8T~kkUH(G*xD}Ij2h9}!R+ho;Jypq3}0&DKmY9IRgB^BVYr2CJi;(~HMEsU z=eG=kfi0+TOa*4H^dd;Wc!ydC!9aHoc;ll%z`0`fLQ|4prNjt6_-GPvgFQy2`!Fn> zM@`yl1a~5Es+JQfy|w+(_g2>Cg2xT?_KDt~=lM9=e#vkWsJiR+;3wNld> z@QcLw@6^E1;_)CA2Iu&Y?F0?BHg>C$l?}y(O5Q@@jci6}8Yyytk*d{FA4MB=DY-yV)mvyMjcC%(4L zBXL*gnIiVVAf-G|>Q;(-l12of*dIwQIEq@o8%=bhli$46IRBCRys8mr^pGkyD4+{P zRc-2qMa*)U)+8`|fY}(m!8Qr|LC*=LaY1ldYQ0oXUx@hF#G>vjcAe*3q8ZzS3}1`2 z3BrDL!(SirFvaqs1|{mz$DM<~+fkkfoPPN#5VlZBW+Rb;88Qddn-J8?1GWnt%BLT_ zZjqB;>aE)N)6ZVDv3dT@w{2{mv5Oaa3Fi}7A;Dfq0J6dSUdSd?J5ng1Rk8vyhLV?r zXr(A~9UN|{P4r12!4A<%HD7`7j1(U#wgnyevR57tvM`FsgF4cXZwQT}S0Tq!J~4Tg z>V>{gw$btwdN{pM0;@LN2Za|6!tQQo8c;`UM_E(~AB5Fyo#2l5zJe`^Xn}8o@M3r~ zd=OrZVZ#?&{7Dv%xf^K{Btm`p&C-bxH=#OLqSR2^>xa_>jrrE2yejoKYZPgw@QLcH zREFN7ekz4`+#uBmch@twsi*mi<=fs=c^_0i`68N7w`C`uK7-vTm{2n&7>I(t1ZOU% zFnzP0`NlPU3C`U3e883A!LTt5kLI5Q)Jxw3SB5*ogJENMeG1;MB0$g3PyhSpudk8+ M1DY|{IWkoN01k=L3jhEB diff --git a/fury/ui/tests/test_elements.py b/fury/ui/tests/test_elements.py index 84e56a939..7eb726d3d 100644 --- a/fury/ui/tests/test_elements.py +++ b/fury/ui/tests/test_elements.py @@ -1158,6 +1158,37 @@ def test_ui_draw_panel_grouping(interactive=False): event_counter.check_counts(expected) +def test_ui_draw_panel_polyline(interactive=False): + filename = "test_ui_draw_panel_polyline" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") + + drawpanel = ui.DrawPanel(size=(600, 600), position=(30, 10)) + + # Assign the counter callback to every possible event. + event_counter = EventCounter() + event_counter.monitor(drawpanel) + + current_size = (680, 680) + show_manager = window.ShowManager( + size=current_size, title="DrawPanel Polyline UI Example") + show_manager.scene.add(drawpanel) + + # Recorded events: + # 1. Creating Polyline + # 2. Translation/Rotation of Polyline + + if interactive: + show_manager.record_events_to_file(recording_filename) + print(list(event_counter.events_counts.items())) + event_counter.save(expected_events_counts_filename) + + else: + show_manager.play_events_from_file(recording_filename) + expected = EventCounter.load(expected_events_counts_filename) + event_counter.check_counts(expected) + + def test_playback_panel(interactive=False): global playing, paused, stopped, loop, ts From 34eb0d2c25af4183fa73090b354a6ddcf891f271 Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Thu, 6 Oct 2022 18:44:13 +0530 Subject: [PATCH 59/59] fixing polyline group translation issue --- fury/ui/elements.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index cb5afd18a..c15ca6ad9 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3181,7 +3181,10 @@ def update_position(self, offset): """ vertices = [] for shape in self.grouped_shapes: - vertices.extend(shape.position + vertices_from_actor(shape.shape.actor)[:, :-1]) + if shape.shape_type == "polyline": + vertices.extend(shape.shape.calculate_vertices()) + else: + vertices.extend(shape.position + vertices_from_actor(shape.shape.actor)[:, :-1]) bounding_box_min, bounding_box_max, \ bounding_box_size = cal_bounding_box_2d(np.asarray(vertices))