From dd13e22836ad69efe1056f7a1bebe7b025cc686b Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Fri, 25 Nov 2022 15:23:58 +0100 Subject: [PATCH 01/19] FBD and event connection must be separated. FBD is targeted to design the behaviour of a function. Now it is required to define the execution sequence of function blocks. --- editor/FBD_view.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 34202307..dc080e05 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -521,12 +521,14 @@ def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): ofbv = ObjectFunctionBlockView(self.reference_object, txt.set_value, "set_value", "set_value", self) self.add_fb_view(ofbv) + """ ie = InputEvent("onclicked", self.callback_test) self.add_io_widget(ie) oe = OutputEvent("onclick", self.onclick) self.add_io_widget(oe) - + """ + def callback_test(self, emitter): self.outline.set_stroke(2, 'red') @@ -541,8 +543,6 @@ class FunctionBlockView(FBD_model.FunctionBlock, gui.SvgSubcontainer, MoveableWi io_font_size = 12 io_left_right_offset = 10 - input_event = None - def __init__(self, name, container, x = 10, y = 10, *args, **kwargs): FBD_model.FunctionBlock.__init__(self, name) gui.SvgSubcontainer.__init__(self, x, y, self.calc_width(), self.calc_height(), *args, **kwargs) @@ -553,16 +553,11 @@ def __init__(self, name, container, x = 10, y = 10, *args, **kwargs): self.outline.set_stroke(2, 'black') self.append(self.outline) - self.input_event = InputEvent(self.name, self.do) - self.input_event.label.attr_text_anchor = "middle" - #self.input_event.label.attr_dominant_baseline = 'hanging' - self.input_event.label.css_font_size = gui.to_pix(self.io_font_size) - self.input_event.label.attr_x = "50%" - self.input_event.label.attr_y = "50%" - self.input_event.set_size(len(self.input_event.name) * self.io_font_size, self.io_font_size) - self.input_event.onmousedown.do(self.container.onselection_start, js_stop_propagation=True, js_prevent_default=True) - self.input_event.onmouseup.do(self.container.onselection_end, js_stop_propagation=True, js_prevent_default=True) - self.append(self.input_event) + self.label = gui.SvgText("50%", 0, self.name) + self.label.attr_text_anchor = "middle" + self.label.attr_dominant_baseline = 'hanging' + self.label.css_font_size = gui.to_pix(self.label_font_size) + self.append(self.label) self.populate_io() @@ -619,10 +614,6 @@ def adjust_geometry(self): gui._MixinSvgSize.set_size(self, self.calc_width(), self.calc_height()) w, h = self.get_size() - if not self.input_event is None: - iew, ieh = self.input_event.get_size() - self.input_event.set_position((w-iew)/2, 0) - i = 1 for inp in self.inputs.values(): inp.set_position(0, self.label_font_size + self.io_font_size*i) From 6e4b0aabf88b0e8417f77303a477fa75546e419d Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Fri, 25 Nov 2022 16:34:07 +0100 Subject: [PATCH 02/19] Shown execution sequence number on FBs. --- editor/FBD_model.py | 14 +++++++++++--- editor/FBD_view.py | 21 ++++++++++++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/editor/FBD_model.py b/editor/FBD_model.py index 3d62eb76..d1c160e4 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -20,8 +20,6 @@ def has_default(self): return not (self.default == inspect.Parameter.empty) def link(self, output): - if not issubclass(type(output), Output): - return self.source = output def is_linked(self): @@ -88,6 +86,8 @@ class FunctionBlock(): inputs = None outputs = None + execution_priority = 0 + def decorate_process(output_list): """ setup a method as a process FunctionBlock """ """ @@ -99,11 +99,15 @@ def add_annotation(method): return method return add_annotation - def __init__(self, name): + def __init__(self, name, execution_priority = 0): self.name = name + self.set_execution_priority(execution_priority) self.inputs = {} self.outputs = {} + def set_execution_priority(self, execution_priority): + self.execution_priority = execution_priority + def add_io(self, io): if issubclass(type(io), Input): self.inputs[io.name] = io @@ -147,10 +151,14 @@ def do(self): for function_block in object_block.FBs.values(): sub_function_blocks.append(function_block) + execution_priority = 0 for function_block in (*self.function_blocks.values(), *sub_function_blocks): parameters = {} all_inputs_connected = True + function_block.set_execution_priority(execution_priority) + execution_priority += 1 + for IN in function_block.inputs.values(): if (not IN.is_linked()) and (not IN.has_default()): all_inputs_connected = False diff --git a/editor/FBD_view.py b/editor/FBD_view.py index dc080e05..53c92bea 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -528,7 +528,7 @@ def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): oe = OutputEvent("onclick", self.onclick) self.add_io_widget(oe) """ - + def callback_test(self, emitter): self.outline.set_stroke(2, 'red') @@ -536,6 +536,7 @@ def callback_test(self, emitter): class FunctionBlockView(FBD_model.FunctionBlock, gui.SvgSubcontainer, MoveableWidget): label = None + label_priority = None label_font_size = 12 outline = None @@ -543,8 +544,10 @@ class FunctionBlockView(FBD_model.FunctionBlock, gui.SvgSubcontainer, MoveableWi io_font_size = 12 io_left_right_offset = 10 - def __init__(self, name, container, x = 10, y = 10, *args, **kwargs): - FBD_model.FunctionBlock.__init__(self, name) + execution_priority = 0 + + def __init__(self, name, container, x = 10, y = 10, execution_priority = 0, *args, **kwargs): + FBD_model.FunctionBlock.__init__(self, name, execution_priority) gui.SvgSubcontainer.__init__(self, x, y, self.calc_width(), self.calc_height(), *args, **kwargs) MoveableWidget.__init__(self, container, *args, **kwargs) @@ -559,10 +562,22 @@ def __init__(self, name, container, x = 10, y = 10, *args, **kwargs): self.label.css_font_size = gui.to_pix(self.label_font_size) self.append(self.label) + self.label_priority = gui.SvgText("100%", 0, str(self.execution_priority)) + self.label_priority.attr_text_anchor = "end" + self.label_priority.attr_dominant_baseline = 'hanging' + self.label_priority.set_fill("red") + self.label_priority.css_font_size = gui.to_pix(self.label_font_size) + self.append(self.label_priority) + self.populate_io() self.stop_drag.do(lambda emitter, x, y:self.adjust_geometry()) + def set_execution_priority(self, execution_priority): + if not self.label_priority is None: + self.label_priority.set_text(str(self.execution_priority)) + FBD_model.FunctionBlock.set_execution_priority(self, execution_priority) + def populate_io(self): #for all the outputs defined by decorator on FunctionBlock.do # add the related Outputs From 9ac60c6ca67c7ab8e8183f6bb538093e645ac226 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Fri, 25 Nov 2022 17:31:32 +0100 Subject: [PATCH 03/19] FB selection. --- editor/FBD_view.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 53c92bea..1638ddeb 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -446,7 +446,7 @@ def calc_width(self): xmax = max(xmax, x+w) ymax = max(ymax, y+h) - return max((len(self.name) * self.label_font_size), xmax) + return max((len(self.name) * self.label_font_size*0.6), xmax) def add_fb_view(self, fb_view_instance): self.FBs[fb_view_instance.name] = fb_view_instance @@ -605,11 +605,11 @@ def calc_width(self): for o in self.outputs.values(): max_name_len_output = max(max_name_len_output, len(o.name)) - return max((len(self.name) * self.label_font_size), (max(max_name_len_input, max_name_len_output)*self.io_font_size) * 2) + self.io_left_right_offset + return max((len(self.name) * self.label_font_size*0.6), (max(max_name_len_input, max_name_len_output)*self.io_font_size*0.6) * 2) + self.io_left_right_offset def add_io_widget(self, widget): widget.label.css_font_size = gui.to_pix(self.io_font_size) - widget.set_size(len(widget.name) * self.io_font_size, self.io_font_size) + widget.set_size(len(widget.name) * self.io_font_size*0.6, self.io_font_size) FBD_model.FunctionBlock.add_io(self, widget) self.append(widget) @@ -708,6 +708,8 @@ class ProcessView(gui.Svg, FBD_model.Process): selected_input = None selected_output = None + selected_function_block = None + def __init__(self, *args, **kwargs): gui.Svg.__init__(self, *args, **kwargs) FBD_model.Process.__init__(self) @@ -748,6 +750,10 @@ def add_object_block(self, object_block): @gui.decorate_event def onfunction_block_clicked(self, function_block): + if not self.selected_function_block is None: + self.selected_function_block.label.css_font_weight = "normal" + self.selected_function_block = function_block + self.selected_function_block.label.css_font_weight = "bolder" return (function_block,) From c9037ce68faa88bbc4780dbdf6b9d1a5a7f232d7 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Fri, 25 Nov 2022 17:56:38 +0100 Subject: [PATCH 04/19] FB added to the main ProcessView. ObjectBlock must only be a graphical indicator. --- editor/FBD_model.py | 20 +---------- editor/FBD_view.py | 82 ++++++++++++--------------------------------- 2 files changed, 23 insertions(+), 79 deletions(-) diff --git a/editor/FBD_model.py b/editor/FBD_model.py index d1c160e4..ef66b125 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -63,22 +63,9 @@ def unlink(self, destination = None): class ObjectBlock(): name = None - FBs = None #this is the list of member functions - - inputs = None - outputs = None def __init__(self, name): self.name = name - self.FBs = {} - self.inputs = {} - self.outputs = {} - - def add_io(self, io): - if issubclass(type(io), Input): - self.inputs[io.name] = io - else: - self.outputs[io.name] = io class FunctionBlock(): @@ -146,13 +133,8 @@ def add_object_block(self, object_block): self.object_blocks[object_block.name] = object_block def do(self): - sub_function_blocks = [] - for object_block in self.object_blocks.values(): - for function_block in object_block.FBs.values(): - sub_function_blocks.append(function_block) - execution_priority = 0 - for function_block in (*self.function_blocks.values(), *sub_function_blocks): + for function_block in self.function_blocks.values(): parameters = {} all_inputs_connected = True diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 1638ddeb..a7aae54c 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -382,14 +382,11 @@ class ObjectBlockView(FBD_model.ObjectBlock, gui.SvgSubcontainer, MoveableWidget reference_object = None - io_font_size = 12 - io_left_right_offset = 10 - def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): name = obj.__class__.__name__ self.reference_object = obj FBD_model.ObjectBlock.__init__(self, name) - gui.SvgSubcontainer.__init__(self, x, y, self.calc_width(), self.calc_height(), *args, **kwargs) + gui.SvgSubcontainer.__init__(self, x, y, 0, 0, *args, **kwargs) MoveableWidget.__init__(self, container, *args, **kwargs) self.outline = gui.SvgRectangle(0, 0, "100%", "100%") @@ -403,9 +400,6 @@ def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): self.label.css_font_size = gui.to_pix(self.label_font_size) self.append(self.label) - self.onselection_start = self.container.onselection_start - self.onselection_end = self.container.onselection_end - """ for (method_name, method) in inspect.getmembers(self.reference_object, inspect.ismethod): #try: @@ -413,8 +407,8 @@ def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): #setattr(c, "do", types.MethodType(getattr(self.reference_object, method_name), c)) #c.do.__dict__['_outputs'] = [] #FBD_model.FunctionBlock.decorate_process(['OUT'])(c.do) - #self.add_fb_view(c(method_name, container)) - self.add_fb_view(ObjectFunctionBlockView(self.reference_object, method, method_name, method_name, self)) + #self.add_function_block_view(c(method_name, container)) + self.add_function_block_view(ObjectFunctionBlockView(self.reference_object, method, method_name, method_name, self)) #except: # pass @@ -422,14 +416,15 @@ def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): evt = getattr(self.reference_object, class_name) if issubclass(type(_class), gui.ClassEventConnector): #self.append(ObjectBlockView(evt, self)) - self.add_fb_view(ObjectFunctionBlockView(evt, evt, "do", evt.event_method_bound.__name__ + ".do", self)) + self.add_function_block_view(ObjectFunctionBlockView(evt, evt, "do", evt.event_method_bound.__name__ + ".do", self)) """ self.stop_drag.do(lambda emitter, x, y:self.adjust_geometry()) + self.adjust_geometry() def calc_height(self): xmax = ymax = 0 - if not self.FBs is None: - for fb in self.FBs.values(): + for fb in self.children.values(): + if issubclass(type(fb), FunctionBlockView): x, y = fb.get_position() w, h = fb.get_size() xmax = max(xmax, x+w) @@ -439,8 +434,8 @@ def calc_height(self): def calc_width(self): xmax = ymax = 0 - if not self.FBs is None: - for fb in self.FBs.values(): + for fb in self.children.values(): + if issubclass(type(fb), FunctionBlockView): x, y = fb.get_position() w, h = fb.get_size() xmax = max(xmax, x+w) @@ -448,24 +443,12 @@ def calc_width(self): return max((len(self.name) * self.label_font_size*0.6), xmax) - def add_fb_view(self, fb_view_instance): - self.FBs[fb_view_instance.name] = fb_view_instance - - self.append(fb_view_instance) - for fb in self.FBs.values(): - fb.adjust_geometry() - fb.on_drag.do(self.onfunction_block_position_changed) - self.adjust_geometry() - - def add_io_widget(self, widget): - widget.label.css_font_size = gui.to_pix(self.io_font_size) - widget.set_size(len(widget.name) * self.io_font_size, self.io_font_size) - - FBD_model.FunctionBlock.add_io(self, widget) - self.append(widget) - widget.onmousedown.do(self.container.onselection_start, js_stop_propagation=True, js_prevent_default=True) - widget.onmouseup.do(self.container.onselection_end, js_stop_propagation=True, js_prevent_default=True) - + def add_function_block_view(self, fb_view_instance): + self.container.add_function_block(fb_view_instance) + for fb in self.children.values(): + if issubclass(type(fb), FunctionBlockView): + fb.adjust_geometry() + fb.on_drag.do(self.onfunction_block_position_changed) self.adjust_geometry() def onfunction_block_position_changed(self, emitter, x, y): @@ -473,34 +456,13 @@ def onfunction_block_position_changed(self, emitter, x, y): self.adjust_geometry() def adjust_geometry(self): - w, h = self.get_size() - - i = 1 - for inp in self.inputs.values(): - inp.set_position(0, self.label_font_size + self.io_font_size*i) - inp.onpositionchanged() - i += 1 - - i = 1 - for o in self.outputs.values(): - ow, oh = o.get_size() - o.set_position(w - ow, self.label_font_size + self.io_font_size*i) - o.onpositionchanged() - i += 1 - gui._MixinSvgSize.set_size(self, self.calc_width(), self.calc_height()) def set_position(self, x, y): - for fb in self.FBs.values(): - fb.onposition_changed() + for fb in self.children.values(): + if issubclass(type(fb), FunctionBlockView): + fb.onposition_changed() - for inp in self.inputs.values(): - inp.onpositionchanged() - - for o in self.outputs.values(): - o.onpositionchanged() - #w, h = self.get_size() - #self.attr_viewBox = "%s %s %s %s"%(x, y, x+w, y+h) return gui.SvgSubcontainer.set_position(self, x, y) def set_name(self, name): @@ -514,12 +476,12 @@ def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): ObjectBlockView.__init__(self, obj, container, x = 10, y = 10, *args, **kwargs) txt = gui.TextInput() - ofbv = ObjectFunctionBlockView(self.reference_object, txt.get_value, "get_value", "get_value", self) + ofbv = ObjectFunctionBlockView(self.reference_object, txt.get_value, "get_value", "get_value", self.container) ofbv.add_io_widget(OutputView("Value")) - self.add_fb_view(ofbv) + self.add_function_block_view(ofbv) - ofbv = ObjectFunctionBlockView(self.reference_object, txt.set_value, "set_value", "set_value", self) - self.add_fb_view(ofbv) + ofbv = ObjectFunctionBlockView(self.reference_object, txt.set_value, "set_value", "set_value", self.container) + self.add_function_block_view(ofbv) """ ie = InputEvent("onclicked", self.callback_test) From a341250793f045ccb121c8c3d6667be7c238f686 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Wed, 30 Nov 2022 10:05:51 +0100 Subject: [PATCH 05/19] Cleaning up code. Improving user interaction. --- editor/FBD_model.py | 12 -- editor/FBD_view.py | 277 ++++---------------------------------------- 2 files changed, 22 insertions(+), 267 deletions(-) diff --git a/editor/FBD_model.py b/editor/FBD_model.py index ef66b125..b615bc63 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -61,13 +61,6 @@ def unlink(self, destination = None): self.destinations = [] -class ObjectBlock(): - name = None - - def __init__(self, name): - self.name = name - - class FunctionBlock(): name = None inputs = None @@ -120,18 +113,13 @@ def unlink(self): class Process(): function_blocks = None - object_blocks = None def __init__(self): self.function_blocks = {} - self.object_blocks = {} def add_function_block(self, function_block): self.function_blocks[function_block.name] = function_block - def add_object_block(self, object_block): - self.object_blocks[object_block.name] = object_block - def do(self): execution_priority = 0 for function_block in self.function_blocks.values(): diff --git a/editor/FBD_view.py b/editor/FBD_view.py index a7aae54c..998bb713 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -32,6 +32,8 @@ def get_size(self): class MoveableWidget(gui.EventSource, MixinPositionSize): container = None + x_start = 0 + y_start = 0 def __init__(self, container, *args, **kwargs): gui.EventSource.__init__(self) self.container = container @@ -39,6 +41,8 @@ def __init__(self, container, *args, **kwargs): self.onmousedown.do(self.start_drag, js_stop_propagation=True, js_prevent_default=True) def start_drag(self, emitter, x, y): + self.x_start = float(x) + self.y_start = float(y) self.active = True self.container.onmousemove.do(self.on_drag, js_stop_propagation=True, js_prevent_default=True) self.onmousemove.do(None, js_stop_propagation=False, js_prevent_default=True) @@ -53,7 +57,8 @@ def stop_drag(self, emitter, x, y): @gui.decorate_event def on_drag(self, emitter, x, y): if self.active: - self.set_position(float(x) - float(self.attr_width)/2.0, float(y) - float(self.attr_height)/2.0) + #self.set_position(float(x) - float(self.attr_width)/2.0, float(y) - float(self.attr_height)/2.0) + self.set_position(float(x) - self.x_start, float(y) - self.y_start) return (x, y) @@ -85,7 +90,7 @@ def set_default_look(self): self.placeholder.set_fill("lightgray") self.placeholder.style['cursor'] = 'pointer' - self.label.attr_dominant_baseline = 'middle' + self.label.attr_dominant_baseline = 'central' self.label.attr_text_anchor = "start" self.label.style['cursor'] = 'pointer' self.label.set_fill('black') @@ -135,7 +140,7 @@ def __init__(self, name, *args, **kwargs): self.append(self.placeholder) self.label = gui.SvgText("100%", "50%", name) - self.label.attr_dominant_baseline = 'middle' + self.label.attr_dominant_baseline = 'central' self.label.attr_text_anchor = "end" self.label.style['cursor'] = 'pointer' self.append(self.label) @@ -145,7 +150,7 @@ def __init__(self, name, *args, **kwargs): def link(self, destination, container): link_view = LinkView(self, destination, container) container.append(link_view) - bt_unlink = Unlink() + bt_unlink = UnlinkButton() container.append(bt_unlink) link_view.set_unlink_button(bt_unlink) FBD_model.Output.link(self, destination) @@ -181,78 +186,7 @@ def onpositionchanged(self): return () -class InputEvent(InputView): - placeholder = None - label = None - event_callback = None - def __init__(self, name, event_callback, *args, **kwargs): - self.event_callback = event_callback - gui.SvgSubcontainer.__init__(self, 0, 0, 0, 0, *args, **kwargs) - - self.placeholder = gui.SvgRectangle(0, 0, 0, 0) - self.append(self.placeholder) - - self.label = gui.SvgText("0%", "50%", name) - self.append(self.label) - - FBD_model.Input.__init__(self, name, *args, **kwargs) - self.set_default_look() - - def set_default_look(self): - self.placeholder.set_stroke(1, 'black') - self.placeholder.set_fill("orange") - self.placeholder.style['cursor'] = 'pointer' - - self.label.attr_dominant_baseline = 'middle' - self.label.attr_text_anchor = "start" - self.label.style['cursor'] = 'pointer' - - def link(self, source, link_view): - if not issubclass(type(source), OutputEvent): - return - self.placeholder.set_fill('green') - InputView.link(self, source, link_view) - - def unlink(self, destination = None): - self.placeholder.set_fill('orange') - InputView.unlink(self) - - -class OutputEvent(OutputView): - placeholder = None - label = None - event_connector = None - def __init__(self, name, event_connector, *args, **kwargs): - self.event_connector = event_connector - gui.SvgSubcontainer.__init__(self, 0, 0, 0, 0, *args, **kwargs) - self.placeholder = gui.SvgRectangle(0, 0, 0, 0) - self.placeholder.set_stroke(1, 'black') - self.placeholder.set_fill("orange") - self.placeholder.style['cursor'] = 'pointer' - self.append(self.placeholder) - - self.label = gui.SvgText("100%", "50%", name) - self.label.attr_dominant_baseline = 'middle' - self.label.attr_text_anchor = "end" - self.label.style['cursor'] = 'pointer' - self.append(self.label) - - FBD_model.Output.__init__(self, name, *args, **kwargs) - - def link(self, destination, container): - if not issubclass(type(destination), InputEvent): - return - self.placeholder.set_fill('green') - gui.ClassEventConnector.do(self.event_connector, destination.event_callback) - OutputView.link(self, destination, container) - - def unlink(self, destination = None): - self.placeholder.set_fill('orange') - gui.ClassEventConnector.do(self.event_connector, None) - FBD_model.Output.unlink(self, destination) - - -class Unlink(gui.SvgSubcontainer): +class UnlinkButton(gui.SvgSubcontainer): def __init__(self, x=0, y=0, w=15, h=15, *args, **kwargs): gui.SvgSubcontainer.__init__(self, x, y, w, h, *args, **kwargs) self.outline = gui.SvgRectangle(0, 0, "100%", "100%") @@ -373,128 +307,6 @@ def update_path(self, emitter=None): self.bt_unlink.set_position(xdestination - offset / 2.0 - w/2, ydestination -h/2) -class ObjectBlockView(FBD_model.ObjectBlock, gui.SvgSubcontainer, MoveableWidget): - - label = None - label_font_size = 12 - - outline = None - - reference_object = None - - def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): - name = obj.__class__.__name__ - self.reference_object = obj - FBD_model.ObjectBlock.__init__(self, name) - gui.SvgSubcontainer.__init__(self, x, y, 0, 0, *args, **kwargs) - MoveableWidget.__init__(self, container, *args, **kwargs) - - self.outline = gui.SvgRectangle(0, 0, "100%", "100%") - self.outline.set_fill('white') - self.outline.set_stroke(2, 'orange') - self.append(self.outline) - - self.label = gui.SvgText("50%", 0, self.name) - self.label.attr_text_anchor = "middle" - self.label.attr_dominant_baseline = 'hanging' - self.label.css_font_size = gui.to_pix(self.label_font_size) - self.append(self.label) - - """ - for (method_name, method) in inspect.getmembers(self.reference_object, inspect.ismethod): - #try: - #c = types.new_class(method_name, (FunctionBlockView,)) - #setattr(c, "do", types.MethodType(getattr(self.reference_object, method_name), c)) - #c.do.__dict__['_outputs'] = [] - #FBD_model.FunctionBlock.decorate_process(['OUT'])(c.do) - #self.add_function_block_view(c(method_name, container)) - self.add_function_block_view(ObjectFunctionBlockView(self.reference_object, method, method_name, method_name, self)) - #except: - # pass - - for (class_name, _class) in inspect.getmembers(self.reference_object): - evt = getattr(self.reference_object, class_name) - if issubclass(type(_class), gui.ClassEventConnector): - #self.append(ObjectBlockView(evt, self)) - self.add_function_block_view(ObjectFunctionBlockView(evt, evt, "do", evt.event_method_bound.__name__ + ".do", self)) - """ - self.stop_drag.do(lambda emitter, x, y:self.adjust_geometry()) - self.adjust_geometry() - - def calc_height(self): - xmax = ymax = 0 - for fb in self.children.values(): - if issubclass(type(fb), FunctionBlockView): - x, y = fb.get_position() - w, h = fb.get_size() - xmax = max(xmax, x+w) - ymax = max(ymax, y+h) - - return self.label_font_size + ymax - - def calc_width(self): - xmax = ymax = 0 - for fb in self.children.values(): - if issubclass(type(fb), FunctionBlockView): - x, y = fb.get_position() - w, h = fb.get_size() - xmax = max(xmax, x+w) - ymax = max(ymax, y+h) - - return max((len(self.name) * self.label_font_size*0.6), xmax) - - def add_function_block_view(self, fb_view_instance): - self.container.add_function_block(fb_view_instance) - for fb in self.children.values(): - if issubclass(type(fb), FunctionBlockView): - fb.adjust_geometry() - fb.on_drag.do(self.onfunction_block_position_changed) - self.adjust_geometry() - - def onfunction_block_position_changed(self, emitter, x, y): - emitter.adjust_geometry() - self.adjust_geometry() - - def adjust_geometry(self): - gui._MixinSvgSize.set_size(self, self.calc_width(), self.calc_height()) - - def set_position(self, x, y): - for fb in self.children.values(): - if issubclass(type(fb), FunctionBlockView): - fb.onposition_changed() - - return gui.SvgSubcontainer.set_position(self, x, y) - - def set_name(self, name): - self.name = name - self.label.set_text(name) - self.adjust_geometry() - - -class TextInputAdapter(ObjectBlockView): - def __init__(self, obj, container, x = 10, y = 10, *args, **kwargs): - ObjectBlockView.__init__(self, obj, container, x = 10, y = 10, *args, **kwargs) - - txt = gui.TextInput() - ofbv = ObjectFunctionBlockView(self.reference_object, txt.get_value, "get_value", "get_value", self.container) - ofbv.add_io_widget(OutputView("Value")) - self.add_function_block_view(ofbv) - - ofbv = ObjectFunctionBlockView(self.reference_object, txt.set_value, "set_value", "set_value", self.container) - self.add_function_block_view(ofbv) - - """ - ie = InputEvent("onclicked", self.callback_test) - self.add_io_widget(ie) - - oe = OutputEvent("onclick", self.onclick) - self.add_io_widget(oe) - """ - - def callback_test(self, emitter): - self.outline.set_stroke(2, 'red') - - class FunctionBlockView(FBD_model.FunctionBlock, gui.SvgSubcontainer, MoveableWidget): label = None @@ -520,16 +332,24 @@ def __init__(self, name, container, x = 10, y = 10, execution_priority = 0, *arg self.label = gui.SvgText("50%", 0, self.name) self.label.attr_text_anchor = "middle" - self.label.attr_dominant_baseline = 'hanging' + self.label.attr_dominant_baseline = 'text-before-edge' + self.label.style['pointer-events'] = 'none' self.label.css_font_size = gui.to_pix(self.label_font_size) self.append(self.label) self.label_priority = gui.SvgText("100%", 0, str(self.execution_priority)) + self.label_priority.style['pointer-events'] = 'none' self.label_priority.attr_text_anchor = "end" - self.label_priority.attr_dominant_baseline = 'hanging' + self.label_priority.attr_dominant_baseline = 'text-before-edge' self.label_priority.set_fill("red") self.label_priority.css_font_size = gui.to_pix(self.label_font_size) - self.append(self.label_priority) + #self.append(self.label_priority) + + self.priority_container = gui.SvgSubcontainer(self.calc_width()-2, 2, 0, 0) + self.priority_container.css_overflow = "visible" + self.priority_container.style['pointer-events'] = 'none' + self.append(self.priority_container) + self.priority_container.append(self.label_priority) self.populate_io() @@ -590,6 +410,7 @@ def onposition_changed(self): def adjust_geometry(self): gui._MixinSvgSize.set_size(self, self.calc_width(), self.calc_height()) w, h = self.get_size() + self.priority_container.set_position(w-2, 2) i = 1 for inp in self.inputs.values(): @@ -619,53 +440,6 @@ def set_name(self, name): self.adjust_geometry() -class ObjectFunctionBlockView(FunctionBlockView): - reference_object = None - method = None - method_name = None - - def __init__(self, obj, method, method_name, name, container, x = 10, y = 10, *args, **kwargs): - self.reference_object = obj - self.method = method - self.method_name = method_name - FunctionBlockView.__init__(self, name, container, x, y, *args, **kwargs) - - #for all the outputs defined by decorator on FunctionBlock.do - # add the related Outputs - #if hasattr(self.do, "_outputs"): - # for o in self.do._outputs: - # self.add_io_widget(OutputView(o)) - - def populate_io(self): - signature = inspect.signature(getattr(self.reference_object, self.method_name)) - for arg in signature.parameters: - self.add_io_widget(InputView(arg, default=signature.parameters[arg].default)) - self.add_io_widget(InputView('EN', default=False)) - - def do(self, *args, **kwargs): - if kwargs.get('EN') != None: - if kwargs['EN'] == False: - return - if 'EN' in kwargs: - del kwargs['EN'] - - output = getattr(self.reference_object, self.method_name)(*args, **kwargs) - """ #this is to populate outputs automatically - if self.processed_outputs == False: - if not output is None: - self.add_io_widget(OutputView('OUT' + str(0))) - if type(output) in (tuple,): - if len(output) > 1: - i = 1 - for o in output: - self.add_io_widget(OutputView('OUT' + str(i))) - i += 1 - - self.processed_outputs = True - """ - return output - - class ProcessView(gui.Svg, FBD_model.Process): selected_input = None selected_output = None @@ -705,11 +479,6 @@ def add_function_block(self, function_block): self.append(function_block, function_block.name) FBD_model.Process.add_function_block(self, function_block) - def add_object_block(self, object_block): - object_block.onclick.do(self.onfunction_block_clicked) - self.append(object_block, object_block.name) - FBD_model.Process.add_object_block(self, object_block) - @gui.decorate_event def onfunction_block_clicked(self, function_block): if not self.selected_function_block is None: @@ -892,8 +661,6 @@ def main(self): self.process.onfunction_block_clicked.do(self.onprocessview_function_block_clicked) self.attributes_editor = EditorAttributes(self) self.toolbox = FBToolbox(self) - - self.process.add_object_block(TextInputAdapter(gui.TextInput(), self.process)) self.main_container.append(self.toolbox, 'toolbox') self.main_container.append(self.process, 'process_view') From 119a2bf219dc24e6bbc6f3942f24545a73897473 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Wed, 30 Nov 2022 10:24:20 +0100 Subject: [PATCH 06/19] Some style. --- editor/FBD_view.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 998bb713..92a02638 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -187,18 +187,18 @@ def onpositionchanged(self): class UnlinkButton(gui.SvgSubcontainer): - def __init__(self, x=0, y=0, w=15, h=15, *args, **kwargs): + def __init__(self, x=0, y=0, w=12, h=12, *args, **kwargs): gui.SvgSubcontainer.__init__(self, x, y, w, h, *args, **kwargs) self.outline = gui.SvgRectangle(0, 0, "100%", "100%") - self.outline.set_fill('white') - self.outline.set_stroke(1, 'black') + self.outline.set_fill('red') + self.outline.set_stroke(1, 'darkred') self.append(self.outline) - line = gui.SvgLine(0,0,"100%","100%") - line.set_stroke(2, 'red') + line = gui.SvgLine("20%","20%","80%","80%") + line.set_stroke(2, 'white') self.append(line) - line = gui.SvgLine("100%", 0, 0, "100%") - line.set_stroke(2, 'red') + line = gui.SvgLine("80%", "20%", "20%", "80%") + line.set_stroke(2, 'white') self.append(line) def get_size(self): @@ -450,9 +450,11 @@ def __init__(self, *args, **kwargs): gui.Svg.__init__(self, *args, **kwargs) FBD_model.Process.__init__(self) self.css_border_color = 'black' - self.css_border_width = '1' + self.css_border_width = '0px' self.css_border_style = 'solid' - self.style['background-color'] = 'lightyellow' + #self.style['background-color'] = 'lightyellow' + self.css_background_color = 'rgb(250,248,240)' + self.css_background_image = "url('/editor_resources:background.png')" def onselection_start(self, emitter, x, y): self.selected_input = self.selected_output = None From 19df3b1d54c858e28313a06736471b845800eab2 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Fri, 2 Dec 2022 11:21:13 +0100 Subject: [PATCH 07/19] Enabling input editing. --- editor/FBD_library.py | 4 +--- editor/FBD_model.py | 20 ++++++++++++++++++-- editor/FBD_view.py | 31 ++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/editor/FBD_library.py b/editor/FBD_library.py index d3db464b..03314c22 100644 --- a/editor/FBD_library.py +++ b/editor/FBD_library.py @@ -10,9 +10,7 @@ class PRINT(FBD_view.FunctionBlockView): @FBD_model.FunctionBlock.decorate_process([]) - def do(self, IN, EN = True): - if not EN: - return + def do(self, IN): print(IN) class STRING(FBD_view.FunctionBlockView): diff --git a/editor/FBD_model.py b/editor/FBD_model.py index b615bc63..0310c5db 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -68,6 +68,8 @@ class FunctionBlock(): execution_priority = 0 + has_enabling_input = False #This property gets overloaded in FBD_view.FunctionBlockView + def decorate_process(output_list): """ setup a method as a process FunctionBlock """ """ @@ -93,9 +95,16 @@ def add_io(self, io): self.inputs[io.name] = io else: self.outputs[io.name] = io - + + def add_enabling_input(self): + self.add_io(Input('EN', False)) + + def remove_enabling_input(self): + self.inputs['EN'].unlink() + del self.inputs['EN'] + @decorate_process([]) - def do(self): + def do(self, *args, **kwargs): return None @@ -137,6 +146,13 @@ def do(self): if not all_inputs_connected: continue + + if function_block.has_enabling_input: + if not parameters['EN']: + continue + else: + del parameters['EN'] + output_results = function_block.do(**parameters) if output_results is None: continue diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 92a02638..a3f6e25a 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -230,7 +230,7 @@ def set_unlink_button(self, bt_unlink): self.bt_unlink.onclick.do(self.unlink) self.update_path() - def unlink(self, emitter): + def unlink(self, emitter = None): self.get_parent().remove_child(self.bt_unlink) self.get_parent().remove_child(self) FBD_model.Link.unlink(self) @@ -309,6 +309,16 @@ def update_path(self, emitter=None): class FunctionBlockView(FBD_model.FunctionBlock, gui.SvgSubcontainer, MoveableWidget): + @property + @gui.editor_attribute_decorator("WidgetSpecific",'''Defines if the function block has to be enabled''', bool, {}) + def has_enabling_input(self): return 'EN' in self.inputs.keys() + @has_enabling_input.setter + def has_enabling_input(self, value): + if value: + self.add_enabling_input_widget() + else: + self.remove_enabling_input_widget() + label = None label_priority = None label_font_size = 12 @@ -400,6 +410,25 @@ def add_io_widget(self, widget): self.adjust_geometry() + def remove_io_widget(self, name): + io = self.inputs[name] + if io.is_linked(): + if issubclass(type(io), FBD_model.Input): + io.link_view.unlink() + else: + for dest in io.destinations: + if dest.name == name: + dest.link_view.unlink() + break + self.remove_child(io) + del self.inputs[name] + + def add_enabling_input_widget(self): + self.add_io_widget(InputView('EN', default = False)) + + def remove_enabling_input_widget(self): + self.remove_io_widget('EN') + def onposition_changed(self): for inp in self.inputs.values(): inp.onpositionchanged() From 055df11d143dc7c288693fc996cc661f02f64159 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Fri, 2 Dec 2022 14:27:04 +0100 Subject: [PATCH 08/19] Playing with object methods. --- editor/FBD_library.py | 39 +++++++++++++++++++++++++++ editor/FBD_model.py | 2 +- editor/FBD_view.py | 45 +++++++++++++++++++++++++------- editor/widgets/toolbox_opencv.py | 29 +++++++++++--------- 4 files changed, 93 insertions(+), 22 deletions(-) diff --git a/editor/FBD_library.py b/editor/FBD_library.py index 03314c22..07b4486b 100644 --- a/editor/FBD_library.py +++ b/editor/FBD_library.py @@ -8,11 +8,50 @@ import types +def FBWrapObjectMethod(obj_name, method_bound, container): + fb = FBD_view.FunctionBlockView(obj_name + "." + method_bound.__name__, container) + #if hasattr(self.do, "_outputs"): + #for o in self.do._outputs: + # self.add_io_widget(OutputView(o)) + + signature = inspect.signature(method_bound) + for arg in signature.parameters: + fb.add_io_widget(FBD_view.InputView(arg, default = signature.parameters[arg].default)) + + def do(*args, **kwargs): + return method_bound(*args, **kwargs) + + def pre_do(*args, **kwargs): + #populate outputs + results = method_bound(*args, **kwargs) + for k in fb.outputs.keys(): + fb.remove_io_widget(k) + if not results is None: + if type(results) in [tuple,]: + i = 0 + for res in results: + fb.add_io_widget(FBD_view.OutputView(f"out{i}")) + i += 1 + else: + fb.add_io_widget(FBD_view.OutputView("out")) + fb.do = do + + fb.do = pre_do + return fb + class PRINT(FBD_view.FunctionBlockView): @FBD_model.FunctionBlock.decorate_process([]) def do(self, IN): print(IN) +class NONE(FBD_view.FunctionBlockView): + def __init__(self, name, *args, **kwargs): + FBD_view.FunctionBlockView.__init__(self, name, *args, **kwargs) + + @FBD_model.FunctionBlock.decorate_process(['OUT']) + def do(self): + return None + class STRING(FBD_view.FunctionBlockView): @property @gui.editor_attribute_decorator("WidgetSpecific",'''Defines the actual value''', str, {}) diff --git a/editor/FBD_model.py b/editor/FBD_model.py index 0310c5db..8736c6e5 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -104,7 +104,7 @@ def remove_enabling_input(self): del self.inputs['EN'] @decorate_process([]) - def do(self, *args, **kwargs): + def do(self): return None diff --git a/editor/FBD_view.py b/editor/FBD_view.py index a3f6e25a..57fc114d 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -107,12 +107,17 @@ def get_value(self): v = FBD_model.Input.get_value(self) if self.is_linked() or self.has_default(): - if self.previous_value != v: - if type(v) == bool: - self.label.set_fill('white') - self.placeholder.set_fill('blue' if v else 'BLACK') - self.append(SvgTitle(str(v)), "title") - self.previous_value = v + try: + if self.previous_value == v: + return v + except: + pass + + if type(v) == bool: + self.label.set_fill('white') + self.placeholder.set_fill('blue' if v else 'BLACK') + self.append(SvgTitle(str(v)), "title") + self.previous_value = v return v @@ -167,8 +172,11 @@ def set_size(self, width, height): return gui._MixinSvgSize.set_size(self, width, height) def set_value(self, value): - if value == self.value: - return + try: + if value == self.value: + return + except: + pass if type(value) == bool: self.label.set_fill('white') self.placeholder.set_fill('blue' if value else 'BLACK') @@ -536,6 +544,7 @@ def __init__(self, appInstance, **kwargs): import FBD_library # load all widgets + self.add_widget_to_collection(FBD_library.NONE) self.add_widget_to_collection(FBD_library.BOOL) self.add_widget_to_collection(FBD_library.NOT) self.add_widget_to_collection(FBD_library.AND) @@ -684,7 +693,8 @@ def main(self): self.main_container = gui.AsciiContainer(width="100%", height="100%", margin='0px auto') self.main_container.set_from_asciiart( """ - |toolbox|process_view |attributes| + |toolbox |process_view |attributes| + |container|process_view |attributes| """, 0, 0 ) @@ -693,6 +703,23 @@ def main(self): self.attributes_editor = EditorAttributes(self) self.toolbox = FBToolbox(self) + #a container to put some widgets for debugging inside + self.container = gui.VBox() + self.main_container.append(self.container, "container") + + import widgets.toolbox_opencv + import FBD_library + imread = widgets.toolbox_opencv.OpencvImRead(r"C:\Users\davide\Documents\GIT\remi\editor\widgets\camera.png") + fb = FBD_library.FBWrapObjectMethod("imread", imread.get_image_data, self.process) + #fb.add_io_widget(OutputView("IMAGE")) + self.process.add_function_block(fb) + self.container.append(imread) + + imthreshold = widgets.toolbox_opencv.OpencvThreshold() + fb = FBD_library.FBWrapObjectMethod("imthreshold", imthreshold.set_image_data, self.process) + self.process.add_function_block(fb) + self.container.append(imthreshold) + self.main_container.append(self.toolbox, 'toolbox') self.main_container.append(self.process, 'process_view') self.main_container.append(self.attributes_editor, 'attributes') diff --git a/editor/widgets/toolbox_opencv.py b/editor/widgets/toolbox_opencv.py index 79b67a14..f6d5955a 100644 --- a/editor/widgets/toolbox_opencv.py +++ b/editor/widgets/toolbox_opencv.py @@ -79,6 +79,9 @@ def set_image_data(self, img): self.update() self.on_new_image() + def get_image_data(self): + return self.img + def search_app_instance(self, node): if issubclass(node.__class__, remi.server.App): return node @@ -90,10 +93,10 @@ def update(self, *args): if self.app_instance==None: self.app_instance = self.search_app_instance(self) if self.app_instance==None: - self.attributes['src'] = "/%s/get_image_data?index=00"%self.identifier #gui.load_resource(self.filename) + self.attributes['src'] = "/%s/get_image_encoded?index=00"%self.identifier #gui.load_resource(self.filename) return self.app_instance.execute_javascript(""" - url = '/%(id)s/get_image_data?index=%(frame_index)s'; + url = '/%(id)s/get_image_encoded?index=%(frame_index)s'; xhr = null; xhr = new XMLHttpRequest(); @@ -108,8 +111,8 @@ def update(self, *args): xhr.send(); """ % {'id': self.identifier, 'frame_index':str(time.time())}) - def get_image_data(self, index=0): - gui.Image.set_image(self, '/%(id)s/get_image_data?index=%(frame_index)s'% {'id': self.identifier, 'frame_index':str(time.time())}) + def get_image_encoded(self, index=0): + gui.Image.set_image(self, '/%(id)s/get_image_encoded?index=%(frame_index)s'% {'id': self.identifier, 'frame_index':str(time.time())}) self._set_updated() try: ret, png = cv2.imencode('.png', self.img) @@ -202,7 +205,7 @@ def update(self, *args): with self.app_instance.update_lock: self.app_instance.execute_javascript(""" - var url = '/%(id)s/get_image_data?index=%(frame_index)s'; + var url = '/%(id)s/get_image_encoded?index=%(frame_index)s'; var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'blob' @@ -216,8 +219,8 @@ def update(self, *args): """ % {'id': self.identifier, 'frame_index':str(time.time())}) - def get_image_data(self, index=0): - gui.Image.set_image(self, '/%(id)s/get_image_data?index=%(frame_index)s'% {'id': self.identifier, 'frame_index':str(time.time())}) + def get_image_encoded(self, index=0): + gui.Image.set_image(self, '/%(id)s/get_image_encoded?index=%(frame_index)s'% {'id': self.identifier, 'frame_index':str(time.time())}) self._set_updated() try: ret, frame = self.capture.read() @@ -298,15 +301,17 @@ def __init__(self, *args, **kwargs): super(OpencvThreshold, self).__init__("", *args, **kwargs) self.threshold = 125 + def set_image_data(self, img): + OpencvImage.set_image_data(self, img) + if len(img.shape)>2: + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + res, self.img = cv2.threshold(img,self.threshold,255,cv2.THRESH_BINARY) + def on_new_image_listener(self, emitter): #THRESHOLD if emitter is None or emitter.img is None: return self.image_source = emitter - img = emitter.img - if len(img.shape)>2: - img = cv2.cvtColor(emitter.img, cv2.COLOR_BGR2GRAY) - res, self.img = cv2.threshold(img,self.threshold,255,cv2.THRESH_BINARY) - self.set_image_data(self.img) + self.set_image_data(emitter.img) ''' class OpencvSimpleBlobDetector(OpencvImage): From 379860dde4e25c863b708c992eb35a1ba280e858 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Fri, 2 Dec 2022 15:51:34 +0100 Subject: [PATCH 09/19] Creating an IO interface for the Process. --- editor/FBD_view.py | 47 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 57fc114d..320f700a 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -477,15 +477,35 @@ def set_name(self, name): self.adjust_geometry() +""" + Bisogna gestire + nome processo + tipo di esecuzione + parametri in input e output + vedere i processi a più alto livello come function blocks + entrare in un function block e comporlo come un processo + +""" + class ProcessView(gui.Svg, FBD_model.Process): + name = None + selected_input = None selected_output = None selected_function_block = None - def __init__(self, *args, **kwargs): + label = None + label_font_size = 18 + + process_outputs_fb = None #this is required to route result values outside of Process + process_inputs_fb = None #this is required to route parameters inside the Process + + def __init__(self, name, *args, **kwargs): + self.name = name gui.Svg.__init__(self, *args, **kwargs) FBD_model.Process.__init__(self) + self.css_border_color = 'black' self.css_border_width = '0px' self.css_border_style = 'solid' @@ -493,6 +513,29 @@ def __init__(self, *args, **kwargs): self.css_background_color = 'rgb(250,248,240)' self.css_background_image = "url('/editor_resources:background.png')" + self.label = gui.SvgText("50%", 0, self.name) + self.label.attr_text_anchor = "middle" + self.label.attr_dominant_baseline = 'text-before-edge' + #self.label.set_stroke(1, "gray") + self.label.set_fill("darkgray") + self.label.style['pointer-events'] = 'none' + self.label.css_font_size = gui.to_pix(self.label_font_size) + self.append(self.label) + + self.process_inputs_fb = FunctionBlockView("Process INPUTS", self) + self.process_inputs_fb.add_io_widget(OutputView("in1")) + self.process_inputs_fb.outputs['in1'].set_value(True) + self.process_inputs_fb.add_io_widget(OutputView("in2")) + self.process_inputs_fb.outputs['in2'].set_value(False) + self.process_inputs_fb.outline.set_fill("transparent") + self.add_function_block(self.process_inputs_fb) + + self.process_outputs_fb = FunctionBlockView("Process OUTPUTS", self) + self.process_outputs_fb.add_io_widget(InputView("out1")) + self.process_outputs_fb.add_io_widget(InputView("out2")) + self.process_outputs_fb.outline.set_fill("transparent") + self.add_function_block(self.process_outputs_fb) + def onselection_start(self, emitter, x, y): self.selected_input = self.selected_output = None print("selection start: ", type(emitter)) @@ -698,7 +741,7 @@ def main(self): """, 0, 0 ) - self.process = ProcessView(width=600, height=600) + self.process = ProcessView("Process", width=600, height=600) self.process.onfunction_block_clicked.do(self.onprocessview_function_block_clicked) self.attributes_editor = EditorAttributes(self) self.toolbox = FBToolbox(self) From 95fbacefe79ee44a0ec9cf07c56220fd1e0c94e0 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 4 Dec 2022 23:06:55 +0100 Subject: [PATCH 10/19] Created Linkable object to better abstract the problem. --- editor/FBD_model.py | 68 +++++++++++++++++--------------- editor/FBD_view.py | 9 +++-- editor/widgets/toolbox_opencv.py | 2 +- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/editor/FBD_model.py b/editor/FBD_model.py index 8736c6e5..40bcd4e0 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -1,44 +1,64 @@ import inspect -class Input(): + +class Linkable(): name = None + linked_nodes = None + linked_nodes_max_count = 0 + + def __init__(self, name, linked_nodes_max_count = 1, *args, **kwargs): + self.name = name + self.linked_nodes_max_count = linked_nodes_max_count + self.linked_nodes = [] + + def link(self, node): + if not self.is_linked_to(node): + if self.linked_nodes_max_count > len(self.linked_nodes): + self.linked_nodes.append(node) + return True + + return False + + def is_linked(self): + return len(self.linked_nodes) > 0 + + def is_linked_to(self, node): + return node in self.linked_nodes + + def unlink(self, node = None): + if node is None: + self.linked_nodes = [] + return + self.linked_nodes.remove(node) + + +class Input(Linkable): default = None typ = None - source = None #has to be an Output - + def __init__(self, name, default = inspect.Parameter.empty, typ = None): - self.name = name + Linkable.__init__(self, name, linked_nodes_max_count=1) self.default = default self.typ = typ def get_value(self): if not self.is_linked(): return self.default - return self.source.get_value() + return self.linked_nodes[0].get_value() def has_default(self): return not (self.default == inspect.Parameter.empty) - def link(self, output): - self.source = output - - def is_linked(self): - return self.source != None - - def unlink(self): - Input.link(self, None) - -class Output(): +class Output(Linkable): name = None typ = None - destinations = None #has to be an Input value = None def __init__(self, name, typ = None): + Linkable.__init__(self, name, linked_nodes_max_count=0xff) self.name = name self.typ = typ - self.destinations = [] def get_value(self): return self.value @@ -46,20 +66,6 @@ def get_value(self): def set_value(self, value): self.value = value - def link(self, destination): - if not issubclass(type(destination), Input): - return - self.destinations.append(destination) - - def is_linked(self): - return len(self.destinations) > 0 - - def unlink(self, destination = None): - if not destination is None: - self.destinations.remove(destination) - return - self.destinations = [] - class FunctionBlock(): name = None diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 320f700a..02b59d8e 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -22,6 +22,8 @@ import FBD_model import types +import widgets.toolbox_opencv + class MixinPositionSize(): def get_position(self): return float(self.attr_x), float(self.attr_y) @@ -189,8 +191,8 @@ def set_value(self, value): @gui.decorate_event def onpositionchanged(self): - for destination in self.destinations: - destination.link_view.update_path() + for node in self.linked_nodes: + node.link_view.update_path() return () @@ -424,7 +426,7 @@ def remove_io_widget(self, name): if issubclass(type(io), FBD_model.Input): io.link_view.unlink() else: - for dest in io.destinations: + for dest in io.linked_nodes: if dest.name == name: dest.link_view.unlink() break @@ -750,7 +752,6 @@ def main(self): self.container = gui.VBox() self.main_container.append(self.container, "container") - import widgets.toolbox_opencv import FBD_library imread = widgets.toolbox_opencv.OpencvImRead(r"C:\Users\davide\Documents\GIT\remi\editor\widgets\camera.png") fb = FBD_library.FBWrapObjectMethod("imread", imread.get_image_data, self.process) diff --git a/editor/widgets/toolbox_opencv.py b/editor/widgets/toolbox_opencv.py index f6d5955a..033fb103 100644 --- a/editor/widgets/toolbox_opencv.py +++ b/editor/widgets/toolbox_opencv.py @@ -63,7 +63,7 @@ def __init__(self, filename='', *args, **kwargs): kwargs['style'] = self.default_style kwargs['width'] = kwargs['style'].get('width', kwargs.get('width','200px')) kwargs['height'] = kwargs['style'].get('height', kwargs.get('height','180px')) - super(OpencvImage, self).__init__(filename, *args, **kwargs) + gui.Image.__init__(self, filename, *args, **kwargs) OpencvWidget._setup(self) def on_new_image_listener(self, emitter): From 60bd6b40e7bdd88ea0935529b8113623d4bd34c8 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Mon, 5 Dec 2022 10:05:17 +0100 Subject: [PATCH 11/19] Function blocks can now be removed. --- editor/FBD_model.py | 3 +++ editor/FBD_view.py | 29 ++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/editor/FBD_model.py b/editor/FBD_model.py index 40bcd4e0..293751c5 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -134,6 +134,9 @@ def __init__(self): def add_function_block(self, function_block): self.function_blocks[function_block.name] = function_block + + def remove_function_block(self, function_block): + del self.function_blocks[function_block.name] def do(self): execution_priority = 0 diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 02b59d8e..6d3ad3ba 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -157,7 +157,7 @@ def __init__(self, name, *args, **kwargs): def link(self, destination, container): link_view = LinkView(self, destination, container) container.append(link_view) - bt_unlink = UnlinkButton() + bt_unlink = DeleteButton() container.append(bt_unlink) link_view.set_unlink_button(bt_unlink) FBD_model.Output.link(self, destination) @@ -196,7 +196,7 @@ def onpositionchanged(self): return () -class UnlinkButton(gui.SvgSubcontainer): +class DeleteButton(gui.SvgSubcontainer): def __init__(self, x=0, y=0, w=12, h=12, *args, **kwargs): gui.SvgSubcontainer.__init__(self, x, y, w, h, *args, **kwargs) self.outline = gui.SvgRectangle(0, 0, "100%", "100%") @@ -371,10 +371,18 @@ def __init__(self, name, container, x = 10, y = 10, execution_priority = 0, *arg self.append(self.priority_container) self.priority_container.append(self.label_priority) + self.delete_button = DeleteButton() + self.delete_button.onclick.do(self.on_delete_button_pressed, js_stop_propagation=True, js_prevent_default=True) + self.append(self.delete_button) + self.populate_io() self.stop_drag.do(lambda emitter, x, y:self.adjust_geometry()) + @gui.decorate_event + def on_delete_button_pressed(self, emitter): + return () + def set_execution_priority(self, execution_priority): if not self.label_priority is None: self.label_priority.set_text(str(self.execution_priority)) @@ -486,7 +494,8 @@ def set_name(self, name): parametri in input e output vedere i processi a più alto livello come function blocks entrare in un function block e comporlo come un processo - + eliminazione fb, con relativi link + ordinamento esecuzione """ class ProcessView(gui.Svg, FBD_model.Process): @@ -560,9 +569,23 @@ def onselection_end(self, emitter, x, y): def add_function_block(self, function_block): function_block.onclick.do(self.onfunction_block_clicked) + function_block.on_delete_button_pressed.do(self.remove_function_block) self.append(function_block, function_block.name) FBD_model.Process.add_function_block(self, function_block) + def remove_function_block(self, emitter): + if issubclass(type(emitter), FBD_model.FunctionBlock): + self.remove_child(emitter) + for IN in emitter.inputs.values(): + if IN.is_linked(): + IN.link_view.unlink() + + for OUT in emitter.outputs.values(): + if OUT.is_linked(): + for IN in OUT.linked_nodes: + IN.link_view.unlink() + FBD_model.Process.remove_function_block(self, emitter) + @gui.decorate_event def onfunction_block_clicked(self, function_block): if not self.selected_function_block is None: From 00f64bbc8c9497c6c1ec934e72538c8b070966fb Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Mon, 5 Dec 2022 12:00:37 +0100 Subject: [PATCH 12/19] Execution priority panel. --- editor/FBD_view.py | 104 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 6d3ad3ba..146b28fa 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -335,6 +335,8 @@ def has_enabling_input(self, value): outline = None + delete_button = None + io_font_size = 12 io_left_right_offset = 10 @@ -494,7 +496,6 @@ def set_name(self, name): parametri in input e output vedere i processi a più alto livello come function blocks entrare in un function block e comporlo come un processo - eliminazione fb, con relativi link ordinamento esecuzione """ @@ -512,6 +513,8 @@ class ProcessView(gui.Svg, FBD_model.Process): process_outputs_fb = None #this is required to route result values outside of Process process_inputs_fb = None #this is required to route parameters inside the Process + fb_exectution_priority_panel = None + def __init__(self, name, *args, **kwargs): self.name = name gui.Svg.__init__(self, *args, **kwargs) @@ -533,6 +536,9 @@ def __init__(self, name, *args, **kwargs): self.label.css_font_size = gui.to_pix(self.label_font_size) self.append(self.label) + self.fb_exectution_priority_panel = FBExecutionPriorityPanel() + self.fb_exectution_priority_panel.on_execution_priority_changed.do(self.on_execution_priority_changed) + self.process_inputs_fb = FunctionBlockView("Process INPUTS", self) self.process_inputs_fb.add_io_widget(OutputView("in1")) self.process_inputs_fb.outputs['in1'].set_value(True) @@ -547,6 +553,21 @@ def __init__(self, name, *args, **kwargs): self.process_outputs_fb.outline.set_fill("transparent") self.add_function_block(self.process_outputs_fb) + def on_execution_priority_changed(self, emitter, function_block, value): + del self.function_blocks[function_block.name] + d = dict(self.function_blocks) + self.function_blocks = {} + for k,v in d.items(): + if len(self.function_blocks.keys()) == value: + self.function_blocks[function_block.name] = function_block + self.function_blocks[k] = v + if not function_block.name in self.function_blocks.keys(): + self.function_blocks[function_block.name] = function_block + + self.fb_exectution_priority_panel.empty() + for fb in self.function_blocks.values(): + self.fb_exectution_priority_panel.add_function_block(fb) + def onselection_start(self, emitter, x, y): self.selected_input = self.selected_output = None print("selection start: ", type(emitter)) @@ -568,6 +589,7 @@ def onselection_end(self, emitter, x, y): self.selected_output.link(self.selected_input, self) def add_function_block(self, function_block): + self.fb_exectution_priority_panel.add_function_block(function_block) function_block.onclick.do(self.onfunction_block_clicked) function_block.on_delete_button_pressed.do(self.remove_function_block) self.append(function_block, function_block.name) @@ -575,6 +597,7 @@ def add_function_block(self, function_block): def remove_function_block(self, emitter): if issubclass(type(emitter), FBD_model.FunctionBlock): + self.fb_exectution_priority_panel.remove_function_block(emitter) self.remove_child(emitter) for IN in emitter.inputs.values(): if IN.is_linked(): @@ -742,6 +765,82 @@ def create_instance(self, widget): self.appInstance.add_function_block_to_editor(function_block) +class FBExecutionPriorityPanelItem(gui.ListItem): + function_block = None + + label = None + + bt_increase = None + bt_decrease = None + + def __init__(self, function_block, *args, **kwargs): + self.function_block = function_block + gui.ListItem.__init__(self, "", *args, **kwargs) + + self.label = gui.Label(self.function_block.name) + self.bt_increase = gui.Button(u"▲", width=15, height=15, style={'margin-right':'1px'}) + self.bt_increase.onclick.do(self.on_priority_increase) + self.bt_decrease = gui.Button(u"▼", width=15, height=15, style={'margin-right':'1px'}) + self.bt_decrease.onclick.do(self.on_priority_decrease) + item_content = gui.HBox(children=[self.label, gui.HBox(children=[self.bt_increase, self.bt_decrease])]) + item_content.css_justify_content = "space-between" + self.add_child(self.function_block.name, item_content) + + @gui.decorate_event + def on_priority_increase(self, emitter): + return (self.function_block,) + + @gui.decorate_event + def on_priority_decrease(self, emitter): + return (self.function_block,) + + +class FBExecutionPriorityPanel(gui.Container): + fb_list = None + + def __init__(self, *args, **kwargs): + gui.Container.__init__(self, *args, **kwargs) + self.lblTitle = gui.Label("Execution Priority", height=20) + self.lblTitle.add_class("DialogTitle") + self.fb_list = gui.ListView(width='100%', height='calc(100% - 20px)') + self.fb_list.style.update({'overflow-y': 'scroll', + 'overflow-x': 'hidden', + 'background-color': 'white'}) + self.append(self.lblTitle) + self.append(self.fb_list) + + def empty(self): + self.fb_list.empty() + + def add_function_block(self, function_block): + item = FBExecutionPriorityPanelItem(function_block) + item.on_priority_increase.do(self.increase_priority) + item.on_priority_decrease.do(self.decrease_priority) + self.fb_list.append(item) + + def remove_function_block(self, function_block): + for item in self.fb_list.children.values(): + if item.function_block == function_block: + self.fb_list.remove_child(item) + return + + def increase_priority(self, emitter, function_block): + i = list(self.fb_list.children.values()).index(emitter) + i -= 1 + i = max(0, i) + self.on_execution_priority_changed(function_block, i) + + def decrease_priority(self, emitter, function_block): + i = list(self.fb_list.children.values()).index(emitter) + i += 1 + i = min(i, len(self.fb_list.children.values())) + self.on_execution_priority_changed(function_block, i) + + @gui.decorate_event + def on_execution_priority_changed(self, function_block, value): + return (function_block, value) + + class MyApp(App): process = None toolbox = None @@ -762,7 +861,7 @@ def main(self): self.main_container.set_from_asciiart( """ |toolbox |process_view |attributes| - |container|process_view |attributes| + |container|process_view |exec_prio | """, 0, 0 ) @@ -789,6 +888,7 @@ def main(self): self.main_container.append(self.toolbox, 'toolbox') self.main_container.append(self.process, 'process_view') + self.main_container.append(self.process.fb_exectution_priority_panel, 'exec_prio') self.main_container.append(self.attributes_editor, 'attributes') # returning the root widget From 19e389b656a3fb45997d10334d25266d07db0bf8 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Mon, 5 Dec 2022 12:14:03 +0100 Subject: [PATCH 13/19] Execution priority label moved. --- editor/FBD_view.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 146b28fa..ae4441e9 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -359,19 +359,13 @@ def __init__(self, name, container, x = 10, y = 10, execution_priority = 0, *arg self.label.css_font_size = gui.to_pix(self.label_font_size) self.append(self.label) - self.label_priority = gui.SvgText("100%", 0, str(self.execution_priority)) - self.label_priority.style['pointer-events'] = 'none' - self.label_priority.attr_text_anchor = "end" + self.label_priority = gui.SvgText("50%", self.label_font_size, str(self.execution_priority)) + self.label_priority.attr_text_anchor = "middle" self.label_priority.attr_dominant_baseline = 'text-before-edge' - self.label_priority.set_fill("red") + self.label_priority.style['pointer-events'] = 'none' + self.label_priority.set_fill("gray") self.label_priority.css_font_size = gui.to_pix(self.label_font_size) - #self.append(self.label_priority) - - self.priority_container = gui.SvgSubcontainer(self.calc_width()-2, 2, 0, 0) - self.priority_container.css_overflow = "visible" - self.priority_container.style['pointer-events'] = 'none' - self.append(self.priority_container) - self.priority_container.append(self.label_priority) + self.append(self.label_priority) self.delete_button = DeleteButton() self.delete_button.onclick.do(self.on_delete_button_pressed, js_stop_propagation=True, js_prevent_default=True) @@ -459,7 +453,6 @@ def onposition_changed(self): def adjust_geometry(self): gui._MixinSvgSize.set_size(self, self.calc_width(), self.calc_height()) w, h = self.get_size() - self.priority_container.set_position(w-2, 2) i = 1 for inp in self.inputs.values(): @@ -491,12 +484,10 @@ def set_name(self, name): """ Bisogna gestire - nome processo tipo di esecuzione parametri in input e output vedere i processi a più alto livello come function blocks entrare in un function block e comporlo come un processo - ordinamento esecuzione """ class ProcessView(gui.Svg, FBD_model.Process): @@ -605,7 +596,7 @@ def remove_function_block(self, emitter): for OUT in emitter.outputs.values(): if OUT.is_linked(): - for IN in OUT.linked_nodes: + for IN in list(OUT.linked_nodes): IN.link_view.unlink() FBD_model.Process.remove_function_block(self, emitter) From 163f4b7b46b43f23b0f3eb005f17ef7669f84ee2 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Mon, 5 Dec 2022 15:38:21 +0100 Subject: [PATCH 14/19] Some doc strings. --- editor/FBD_library.py | 49 ++++++++++++++++ editor/FBD_model.py | 129 ++++++++++++++++++++++++++++++++++++++++++ editor/FBD_view.py | 28 +++++---- 3 files changed, 195 insertions(+), 11 deletions(-) diff --git a/editor/FBD_library.py b/editor/FBD_library.py index 07b4486b..c8f76640 100644 --- a/editor/FBD_library.py +++ b/editor/FBD_library.py @@ -39,6 +39,46 @@ def pre_do(*args, **kwargs): fb.do = pre_do return fb + +class SUM(FBD_view.FunctionBlockView): + @FBD_model.FunctionBlock.decorate_process(['RESULT',]) + def do(self, IN1, IN2): + return IN1 + IN2 + +class MUL(FBD_view.FunctionBlockView): + @FBD_model.FunctionBlock.decorate_process(['RESULT',]) + def do(self, IN1, IN2): + return IN1 * IN2 + +class DIV(FBD_view.FunctionBlockView): + @FBD_model.FunctionBlock.decorate_process(['RESULT',]) + def do(self, IN1, IN2): + return IN1 / IN2 + +class DIFFERENCE(FBD_view.FunctionBlockView): + @FBD_model.FunctionBlock.decorate_process(['RESULT',]) + def do(self, IN1, IN2): + return IN1 - IN2 + +class COUNTER(FBD_view.FunctionBlockView): + @property + @gui.editor_attribute_decorator("WidgetSpecific",'''Defines the actual value''', int, {'possible_values': '', 'min': 0, 'max': 0xffffffff, 'default': 1, 'step': 1}) + def value(self): + if len(self.outputs) < 1: + return 0 + return self.outputs['OUT'].get_value() + @value.setter + def value(self, value): self.outputs['OUT'].set_value(value) + + def __init__(self, name, *args, **kwargs): + FBD_view.FunctionBlockView.__init__(self, name, *args, **kwargs) + self.outputs['OUT'].set_value(0) + + @FBD_model.FunctionBlock.decorate_process(['OUT']) + def do(self): + OUT = self.outputs['OUT'].get_value() + 1 + return OUT + class PRINT(FBD_view.FunctionBlockView): @FBD_model.FunctionBlock.decorate_process([]) def do(self, IN): @@ -107,6 +147,15 @@ def do(self, IN): self.previous_value = IN return OUT +class FALLING_EDGE(FBD_view.FunctionBlockView): + previous_value = None + + @FBD_model.FunctionBlock.decorate_process(['OUT']) + def do(self, IN): + OUT = (self.previous_value != IN) and not IN + self.previous_value = IN + return OUT + class NOT(FBD_view.FunctionBlockView): @FBD_model.FunctionBlock.decorate_process(['OUT']) def do(self, IN): diff --git a/editor/FBD_model.py b/editor/FBD_model.py index 293751c5..37c6feda 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -2,16 +2,29 @@ class Linkable(): + """ This class represents a Linkable object + an object that can be connected to 'linked_nodes_max_count' other elements + """ name = None linked_nodes = None linked_nodes_max_count = 0 def __init__(self, name, linked_nodes_max_count = 1, *args, **kwargs): + """Args: + name (str): the object string identifier. + linked_nodes_max_count: the maximum number of nodes that can link to. + """ + self.name = name self.linked_nodes_max_count = linked_nodes_max_count self.linked_nodes = [] def link(self, node): + """Creates a new link. + + Args: + node (Linkable): The node to link to. + """ if not self.is_linked_to(node): if self.linked_nodes_max_count > len(self.linked_nodes): self.linked_nodes.append(node) @@ -20,12 +33,31 @@ def link(self, node): return False def is_linked(self): + """Check whether this object is linked to something. + + Returns: + result (bool): If the node is linked to something. + """ return len(self.linked_nodes) > 0 def is_linked_to(self, node): + """Check whether this object is linked to the given node. + + Args: + node (Linkable): The node to be checked if linked to. + + Returns: + result (bool): If this node is linked to the given node. + """ return node in self.linked_nodes def unlink(self, node = None): + """Removes a link to a specific node. + If the given node is null, removes all the links. + + Args: + node (Linkable): The node to be unlinked. None to remove all links. + """ if node is None: self.linked_nodes = [] return @@ -33,41 +65,84 @@ def unlink(self, node = None): class Input(Linkable): + """An input element of a FunctionBlock. + """ default = None typ = None def __init__(self, name, default = inspect.Parameter.empty, typ = None): + """Args: + name (str): The name of the input. + default (any): The default value of the input. + type (type): The input type. + """ Linkable.__init__(self, name, linked_nodes_max_count=1) self.default = default self.typ = typ def get_value(self): + """Returns the input value. + The value is the default one if the Input is not linked, + or the value given from the link. + + Returns: + result (any): The input value. + """ if not self.is_linked(): return self.default return self.linked_nodes[0].get_value() def has_default(self): + """Returns whether there is a default value. + + Returns: + result (bool): True if this input has a default value. + """ return not (self.default == inspect.Parameter.empty) class Output(Linkable): + """An output value of a FunctionBlock + """ name = None typ = None value = None def __init__(self, name, typ = None): + """Args: + name (str): The name of the output. + type (type): The output type. + """ Linkable.__init__(self, name, linked_nodes_max_count=0xff) self.name = name self.typ = typ def get_value(self): + """Returns the output value. + The value given from the FunctionBlock processing. + + Returns: + result (any): The output value. + """ return self.value def set_value(self, value): + """Sets the output value. + + Args: + value (any): the value to be assigned. + """ self.value = value class FunctionBlock(): + """A FunctionBlock represents a processing unit with its own logic. + The function block must implement the 'do' method that executes the algorithm. + A function block has N inputs, that are the parameters of the 'do' method, + and M outputs that are the result values of the 'do' method. + A function block can have an enabling input to enable or disable the logic exection. + A function block gets executed among the others, in the order given by the execution_priority member. + """ name = None inputs = None outputs = None @@ -88,57 +163,111 @@ def add_annotation(method): return add_annotation def __init__(self, name, execution_priority = 0): + """Args: + name (str): The function block name. + execution_priority (int): the order number. + """ self.name = name self.set_execution_priority(execution_priority) self.inputs = {} self.outputs = {} def set_execution_priority(self, execution_priority): + """Sets the execution order. + + Args: + execution_priority (int): The priority value. + """ self.execution_priority = execution_priority def add_io(self, io): + """Adds an Input or Output to the function block. + + Args: + io (Input/Output): The io instance. + """ if issubclass(type(io), Input): self.inputs[io.name] = io else: self.outputs[io.name] = io def add_enabling_input(self): + """Adds an enabling input. + """ self.add_io(Input('EN', False)) def remove_enabling_input(self): + """Removes the enabling input and the eventual link. + """ self.inputs['EN'].unlink() del self.inputs['EN'] @decorate_process([]) def do(self): + """Implements the FunctionBlock logic. + Inputs are passed to this function as parameters by the Process. + Results are assigned to the Outputs by the Process. + + Returns: + result (any): The result value. + """ return None class Link(): + """Represents the link between two Linkable nodes. + """ source = None destination = None def __init__(self, source_widget, destination_widget): + """Args: + source_widget (Linkable): a node participating to the link. + destination_widget (Linkable): a node participating to the link. + """ self.source = source_widget self.destination = destination_widget def unlink(self): + """Deletes the link. + """ self.source.unlink(self.destination) self.destination.unlink() class Process(): + """A Process is a collection of FunctionBlocks in which I/O are linked. + FunctionBlocks in a Process gets executed sequentially in the order given by the FunctionBlock priority. + The priority takes places by the order of function_blocks in the dictionary. + Such order can be adjusted by reordering elements in the dictionary. + """ function_blocks = None def __init__(self): self.function_blocks = {} def add_function_block(self, function_block): + """Adds a function block to the process. + + Args: + function_block (FunctionBlock): the function block to be added. + """ self.function_blocks[function_block.name] = function_block def remove_function_block(self, function_block): + """Removes a function block from the process. + + Args: + function_block (FunctionBlock): the function block to be removed. + """ del self.function_blocks[function_block.name] def do(self): + """Executed the FunctionBlocks. + Before to call a function block, all its Inputs are gethered by the linked outputs. + If the input is not linked, the corresponding parameter takes the default value, whether available. + The function block gets executed if all the parameters are available. + Once the function block is executed, the results are assigned to its Outputs. + """ execution_priority = 0 for function_block in self.function_blocks.values(): parameters = {} diff --git a/editor/FBD_view.py b/editor/FBD_view.py index ae4441e9..6561dd78 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -626,17 +626,23 @@ def __init__(self, appInstance, **kwargs): import FBD_library # load all widgets - self.add_widget_to_collection(FBD_library.NONE) - self.add_widget_to_collection(FBD_library.BOOL) - self.add_widget_to_collection(FBD_library.NOT) - self.add_widget_to_collection(FBD_library.AND) - self.add_widget_to_collection(FBD_library.OR) - self.add_widget_to_collection(FBD_library.XOR) - self.add_widget_to_collection(FBD_library.PULSAR) - self.add_widget_to_collection(FBD_library.STRING) - self.add_widget_to_collection(FBD_library.STRING_SWAP_CASE) - self.add_widget_to_collection(FBD_library.RISING_EDGE) - self.add_widget_to_collection(FBD_library.PRINT) + self.add_widget_to_collection(FBD_library.NONE, "Logic") + self.add_widget_to_collection(FBD_library.BOOL, "Logic") + self.add_widget_to_collection(FBD_library.NOT, "Logic") + self.add_widget_to_collection(FBD_library.AND, "Logic") + self.add_widget_to_collection(FBD_library.OR, "Logic") + self.add_widget_to_collection(FBD_library.XOR, "Logic") + self.add_widget_to_collection(FBD_library.PULSAR, "Event") + self.add_widget_to_collection(FBD_library.RISING_EDGE, "Event") + self.add_widget_to_collection(FBD_library.FALLING_EDGE, "Event") + self.add_widget_to_collection(FBD_library.STRING, "String") + self.add_widget_to_collection(FBD_library.STRING_SWAP_CASE, "String") + self.add_widget_to_collection(FBD_library.PRINT, "String") + self.add_widget_to_collection(FBD_library.SUM, "Math") + self.add_widget_to_collection(FBD_library.MUL, "Math") + self.add_widget_to_collection(FBD_library.DIV, "Math") + self.add_widget_to_collection(FBD_library.DIFFERENCE, "Math") + self.add_widget_to_collection(FBD_library.COUNTER, "Math") def add_widget_to_collection(self, functionBlockClass, group='standard_tools', **kwargs_to_widget): # create an helper that will be created on click From 5167d9e364a802f4d596e8e8c6564d29f1580226 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Mon, 5 Dec 2022 17:51:59 +0100 Subject: [PATCH 15/19] Execution priority panel removed. Execution priority change buttons are now inside the function blocks. --- editor/FBD_view.py | 127 ++++++++++++--------------------------------- 1 file changed, 34 insertions(+), 93 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 6561dd78..9b8320e5 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -341,6 +341,8 @@ def has_enabling_input(self, value): io_left_right_offset = 10 execution_priority = 0 + bt_increase_priority = None + bt_decrease_priority = None def __init__(self, name, container, x = 10, y = 10, execution_priority = 0, *args, **kwargs): FBD_model.FunctionBlock.__init__(self, name, execution_priority) @@ -371,10 +373,35 @@ def __init__(self, name, container, x = 10, y = 10, execution_priority = 0, *arg self.delete_button.onclick.do(self.on_delete_button_pressed, js_stop_propagation=True, js_prevent_default=True) self.append(self.delete_button) + self.bt_increase_priority = gui.SvgText("0%", self.label_font_size, u"▲") + self.bt_increase_priority.attr_text_anchor = "start" + self.bt_increase_priority.attr_dominant_baseline = 'text-before-edge' + self.bt_increase_priority.set_fill("gray") + self.bt_increase_priority.css_font_size = gui.to_pix(self.label_font_size) + self.bt_increase_priority.style["cursor"] = "pointer" + self.bt_increase_priority.onclick.do(self.on_execution_priority_changed, -1, js_stop_propagation=True, js_prevent_default=True) + self.bt_increase_priority.attr_title = "Increase priority" + self.append(self.bt_increase_priority) + + self.bt_decrease_priority = gui.SvgText("100%", self.label_font_size, u"▼") + self.bt_decrease_priority.attr_text_anchor = "end" + self.bt_decrease_priority.attr_dominant_baseline = 'text-before-edge' + self.bt_decrease_priority.set_fill("gray") + self.bt_decrease_priority.css_font_size = gui.to_pix(self.label_font_size) + self.bt_decrease_priority.style["cursor"] = "pointer" + self.bt_decrease_priority.onclick.do(self.on_execution_priority_changed, +1, js_stop_propagation=True, js_prevent_default=True) + self.bt_decrease_priority.attr_title = "Decrease priority" + self.append(self.bt_decrease_priority) + self.populate_io() self.stop_drag.do(lambda emitter, x, y:self.adjust_geometry()) + @gui.decorate_event + def on_execution_priority_changed(self, emitter, direction): + actual_priority = int(self.label_priority.get_text()) + return (self, actual_priority + direction) + @gui.decorate_event def on_delete_button_pressed(self, emitter): return () @@ -454,16 +481,17 @@ def adjust_geometry(self): gui._MixinSvgSize.set_size(self, self.calc_width(), self.calc_height()) w, h = self.get_size() - i = 1 + margin = 1 + i = 2 for inp in self.inputs.values(): - inp.set_position(0, self.label_font_size + self.io_font_size*i) + inp.set_position(margin, self.label_font_size + self.io_font_size*i - margin) inp.onpositionchanged() i += 1 - i = 1 + i = 2 for o in self.outputs.values(): ow, oh = o.get_size() - o.set_position(w - ow, self.label_font_size + self.io_font_size*i) + o.set_position(w - ow - margin, self.label_font_size + self.io_font_size*i - margin) o.onpositionchanged() i += 1 @@ -504,8 +532,6 @@ class ProcessView(gui.Svg, FBD_model.Process): process_outputs_fb = None #this is required to route result values outside of Process process_inputs_fb = None #this is required to route parameters inside the Process - fb_exectution_priority_panel = None - def __init__(self, name, *args, **kwargs): self.name = name gui.Svg.__init__(self, *args, **kwargs) @@ -527,9 +553,6 @@ def __init__(self, name, *args, **kwargs): self.label.css_font_size = gui.to_pix(self.label_font_size) self.append(self.label) - self.fb_exectution_priority_panel = FBExecutionPriorityPanel() - self.fb_exectution_priority_panel.on_execution_priority_changed.do(self.on_execution_priority_changed) - self.process_inputs_fb = FunctionBlockView("Process INPUTS", self) self.process_inputs_fb.add_io_widget(OutputView("in1")) self.process_inputs_fb.outputs['in1'].set_value(True) @@ -555,10 +578,6 @@ def on_execution_priority_changed(self, emitter, function_block, value): if not function_block.name in self.function_blocks.keys(): self.function_blocks[function_block.name] = function_block - self.fb_exectution_priority_panel.empty() - for fb in self.function_blocks.values(): - self.fb_exectution_priority_panel.add_function_block(fb) - def onselection_start(self, emitter, x, y): self.selected_input = self.selected_output = None print("selection start: ", type(emitter)) @@ -580,15 +599,14 @@ def onselection_end(self, emitter, x, y): self.selected_output.link(self.selected_input, self) def add_function_block(self, function_block): - self.fb_exectution_priority_panel.add_function_block(function_block) function_block.onclick.do(self.onfunction_block_clicked) + function_block.on_execution_priority_changed.do(self.on_execution_priority_changed) function_block.on_delete_button_pressed.do(self.remove_function_block) self.append(function_block, function_block.name) FBD_model.Process.add_function_block(self, function_block) def remove_function_block(self, emitter): if issubclass(type(emitter), FBD_model.FunctionBlock): - self.fb_exectution_priority_panel.remove_function_block(emitter) self.remove_child(emitter) for IN in emitter.inputs.values(): if IN.is_linked(): @@ -762,82 +780,6 @@ def create_instance(self, widget): self.appInstance.add_function_block_to_editor(function_block) -class FBExecutionPriorityPanelItem(gui.ListItem): - function_block = None - - label = None - - bt_increase = None - bt_decrease = None - - def __init__(self, function_block, *args, **kwargs): - self.function_block = function_block - gui.ListItem.__init__(self, "", *args, **kwargs) - - self.label = gui.Label(self.function_block.name) - self.bt_increase = gui.Button(u"▲", width=15, height=15, style={'margin-right':'1px'}) - self.bt_increase.onclick.do(self.on_priority_increase) - self.bt_decrease = gui.Button(u"▼", width=15, height=15, style={'margin-right':'1px'}) - self.bt_decrease.onclick.do(self.on_priority_decrease) - item_content = gui.HBox(children=[self.label, gui.HBox(children=[self.bt_increase, self.bt_decrease])]) - item_content.css_justify_content = "space-between" - self.add_child(self.function_block.name, item_content) - - @gui.decorate_event - def on_priority_increase(self, emitter): - return (self.function_block,) - - @gui.decorate_event - def on_priority_decrease(self, emitter): - return (self.function_block,) - - -class FBExecutionPriorityPanel(gui.Container): - fb_list = None - - def __init__(self, *args, **kwargs): - gui.Container.__init__(self, *args, **kwargs) - self.lblTitle = gui.Label("Execution Priority", height=20) - self.lblTitle.add_class("DialogTitle") - self.fb_list = gui.ListView(width='100%', height='calc(100% - 20px)') - self.fb_list.style.update({'overflow-y': 'scroll', - 'overflow-x': 'hidden', - 'background-color': 'white'}) - self.append(self.lblTitle) - self.append(self.fb_list) - - def empty(self): - self.fb_list.empty() - - def add_function_block(self, function_block): - item = FBExecutionPriorityPanelItem(function_block) - item.on_priority_increase.do(self.increase_priority) - item.on_priority_decrease.do(self.decrease_priority) - self.fb_list.append(item) - - def remove_function_block(self, function_block): - for item in self.fb_list.children.values(): - if item.function_block == function_block: - self.fb_list.remove_child(item) - return - - def increase_priority(self, emitter, function_block): - i = list(self.fb_list.children.values()).index(emitter) - i -= 1 - i = max(0, i) - self.on_execution_priority_changed(function_block, i) - - def decrease_priority(self, emitter, function_block): - i = list(self.fb_list.children.values()).index(emitter) - i += 1 - i = min(i, len(self.fb_list.children.values())) - self.on_execution_priority_changed(function_block, i) - - @gui.decorate_event - def on_execution_priority_changed(self, function_block, value): - return (function_block, value) - - class MyApp(App): process = None toolbox = None @@ -858,7 +800,7 @@ def main(self): self.main_container.set_from_asciiart( """ |toolbox |process_view |attributes| - |container|process_view |exec_prio | + |container|process_view |attributes | """, 0, 0 ) @@ -885,7 +827,6 @@ def main(self): self.main_container.append(self.toolbox, 'toolbox') self.main_container.append(self.process, 'process_view') - self.main_container.append(self.process.fb_exectution_priority_panel, 'exec_prio') self.main_container.append(self.attributes_editor, 'attributes') # returning the root widget From 38b1fa3b43cfae7daea0b38f67a7da29cb70c1a1 Mon Sep 17 00:00:00 2001 From: Davide Date: Mon, 5 Dec 2022 23:18:06 +0100 Subject: [PATCH 16/19] Some more FBD in the library, link highlight. --- editor/FBD_library.py | 39 ++++++++++++++++++++++++++++++++++++++- editor/FBD_view.py | 26 +++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/editor/FBD_library.py b/editor/FBD_library.py index c8f76640..8336fa08 100644 --- a/editor/FBD_library.py +++ b/editor/FBD_library.py @@ -74,11 +74,48 @@ def __init__(self, name, *args, **kwargs): FBD_view.FunctionBlockView.__init__(self, name, *args, **kwargs) self.outputs['OUT'].set_value(0) + @FBD_model.FunctionBlock.decorate_process(['OUT']) + def do(self, reset=False): + if reset: + self.value = 0 + return 0 + self.value += 1 + return self.value + +class INT(FBD_view.FunctionBlockView): + @property + @gui.editor_attribute_decorator("WidgetSpecific",'''Defines the actual value''', int, {'possible_values': '', 'min': 0, 'max': 0xffffffff, 'default': 1, 'step': 1}) + def value(self): + if len(self.outputs) < 1: + return False + return self.outputs['OUT'].get_value() + @value.setter + def value(self, value): self.outputs['OUT'].set_value(value) + + def __init__(self, name, *args, **kwargs): + FBD_view.FunctionBlockView.__init__(self, name, *args, **kwargs) + self.outputs['OUT'].set_value(False) + @FBD_model.FunctionBlock.decorate_process(['OUT']) def do(self): - OUT = self.outputs['OUT'].get_value() + 1 + OUT = self.outputs['OUT'].get_value() return OUT +class GREATER_THAN(FBD_view.FunctionBlockView): + @FBD_model.FunctionBlock.decorate_process(['RESULT',]) + def do(self, IN1, IN2): + return IN1 > IN2 + +class LESS_THAN(FBD_view.FunctionBlockView): + @FBD_model.FunctionBlock.decorate_process(['RESULT',]) + def do(self, IN1, IN2): + return IN1 < IN2 + +class EQUAL_TO(FBD_view.FunctionBlockView): + @FBD_model.FunctionBlock.decorate_process(['RESULT',]) + def do(self, IN1, IN2): + return IN1 == IN2 + class PRINT(FBD_view.FunctionBlockView): @FBD_model.FunctionBlock.decorate_process([]) def do(self, IN): diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 9b8320e5..1207d831 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -224,7 +224,7 @@ def __init__(self, source_widget, destination_widget, container, *args, **kwargs self.container = container gui.SvgPolyline.__init__(self, 2, *args, **kwargs) FBD_model.Link.__init__(self, source_widget, destination_widget) - self.set_stroke(1, 'black') + self.set_stroke(1, 'rgba(0,0,0,0.5)') self.set_fill('transparent') self.attributes['stroke-dasharray'] = "4 2" @@ -254,6 +254,9 @@ def get_absolute_node_position(self, node): xs, ys = self.get_absolute_node_position(np) return x+xs, y+ys + def highlight(self, enb): + self.set_stroke(1, 'rgba(0,0,0,1.0)' if enb else 'rgba(0,0,0,0.5)') + def update_path(self, emitter=None): self.attributes['points'] = '' @@ -622,8 +625,15 @@ def remove_function_block(self, emitter): def onfunction_block_clicked(self, function_block): if not self.selected_function_block is None: self.selected_function_block.label.css_font_weight = "normal" + for IN in self.selected_function_block.inputs.values(): + if IN.is_linked(): + IN.link_view.highlight(False) + self.selected_function_block = function_block self.selected_function_block.label.css_font_weight = "bolder" + for IN in self.selected_function_block.inputs.values(): + if IN.is_linked(): + IN.link_view.highlight(True) return (function_block,) @@ -661,6 +671,10 @@ def __init__(self, appInstance, **kwargs): self.add_widget_to_collection(FBD_library.DIV, "Math") self.add_widget_to_collection(FBD_library.DIFFERENCE, "Math") self.add_widget_to_collection(FBD_library.COUNTER, "Math") + self.add_widget_to_collection(FBD_library.GREATER_THAN, "Math") + self.add_widget_to_collection(FBD_library.LESS_THAN, "Math") + self.add_widget_to_collection(FBD_library.EQUAL_TO, "Math") + self.add_widget_to_collection(FBD_library.INT, "Math") def add_widget_to_collection(self, functionBlockClass, group='standard_tools', **kwargs_to_widget): # create an helper that will be created on click @@ -800,7 +814,7 @@ def main(self): self.main_container.set_from_asciiart( """ |toolbox |process_view |attributes| - |container|process_view |attributes | + |container|process_view |attributes| """, 0, 0 ) @@ -814,7 +828,7 @@ def main(self): self.main_container.append(self.container, "container") import FBD_library - imread = widgets.toolbox_opencv.OpencvImRead(r"C:\Users\davide\Documents\GIT\remi\editor\widgets\camera.png") + imread = widgets.toolbox_opencv.OpencvImRead(r"C:\Users\progr\OneDrive\Software e progetti\remi\editor\res\widget_Image.png") fb = FBD_library.FBWrapObjectMethod("imread", imread.get_image_data, self.process) #fb.add_io_widget(OutputView("IMAGE")) self.process.add_function_block(fb) @@ -825,6 +839,12 @@ def main(self): self.process.add_function_block(fb) self.container.append(imthreshold) + def set_threshold(value): + imthreshold.threshold = value + fb = FBD_library.FBWrapObjectMethod("imthreshold", set_threshold, self.process) + self.process.add_function_block(fb) + self.container.append(imthreshold) + self.main_container.append(self.toolbox, 'toolbox') self.main_container.append(self.process, 'process_view') self.main_container.append(self.attributes_editor, 'attributes') From 36af0e9291146d4edeafdc7752924d80bfaaf941 Mon Sep 17 00:00:00 2001 From: Davide Date: Mon, 5 Dec 2022 23:39:31 +0100 Subject: [PATCH 17/19] Implementing zoom functionality. --- editor/FBD_view.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 1207d831..5023adbf 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -535,6 +535,8 @@ class ProcessView(gui.Svg, FBD_model.Process): process_outputs_fb = None #this is required to route result values outside of Process process_inputs_fb = None #this is required to route parameters inside the Process + zoom = 1.0 + def __init__(self, name, *args, **kwargs): self.name = name gui.Svg.__init__(self, *args, **kwargs) @@ -569,6 +571,22 @@ def __init__(self, name, *args, **kwargs): self.process_outputs_fb.add_io_widget(InputView("out2")) self.process_outputs_fb.outline.set_fill("transparent") self.add_function_block(self.process_outputs_fb) + self.onwheel.do(None) + + @gui.decorate_event_js("var params={};" \ + "params['value']=event.deltaY;" \ + "remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);") + def onwheel(self, value): + """Called when the user rotate the mouse wheel. + + Args: + value (float): the scroll amount + """ + self.zoom += float(value)*0.0005 + self.zoom = max(0.25, self.zoom) + self.zoom = min(4.0, self.zoom) + self.set_viewbox(0,0,500*self.zoom,500*self.zoom) + return (value,) def on_execution_priority_changed(self, emitter, function_block, value): del self.function_blocks[function_block.name] From 9a2220053f839378e7cfbca6d7bc2f82822886da Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Tue, 6 Dec 2022 10:25:50 +0100 Subject: [PATCH 18/19] Objects movement is now consistent with zoom. --- editor/FBD_view.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 5023adbf..7ec83ae5 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -36,32 +36,41 @@ class MoveableWidget(gui.EventSource, MixinPositionSize): container = None x_start = 0 y_start = 0 + actual_zoom = 1.0 + start_pos_acquired = False def __init__(self, container, *args, **kwargs): gui.EventSource.__init__(self) self.container = container self.active = False self.onmousedown.do(self.start_drag, js_stop_propagation=True, js_prevent_default=True) - + def start_drag(self, emitter, x, y): - self.x_start = float(x) - self.y_start = float(y) + if 'zoom' in self.container.style.keys(): + self.actual_zoom = float(self.container.style['zoom']) self.active = True self.container.onmousemove.do(self.on_drag, js_stop_propagation=True, js_prevent_default=True) self.onmousemove.do(None, js_stop_propagation=False, js_prevent_default=True) self.container.onmouseup.do(self.stop_drag) self.container.onmouseleave.do(self.stop_drag, 0, 0) + self.start_pos_acquired = False @gui.decorate_event def stop_drag(self, emitter, x, y): self.active = False - return (x, y) + return (float(x)/self.actual_zoom, float(y)/self.actual_zoom) @gui.decorate_event def on_drag(self, emitter, x, y): + if not self.start_pos_acquired: + self_x, self_y = self.get_position() + self.x_start = (float(x)/self.actual_zoom - self_x) + self.y_start = (float(y)/self.actual_zoom - self_y) + self.start_pos_acquired = True + if self.active: #self.set_position(float(x) - float(self.attr_width)/2.0, float(y) - float(self.attr_height)/2.0) - self.set_position(float(x) - self.x_start, float(y) - self.y_start) - return (x, y) + self.set_position((float(x)/self.actual_zoom - self.x_start), (float(y)/self.actual_zoom - self.y_start)) + return (float(x)/self.actual_zoom, float(y)/self.actual_zoom) class SvgTitle(gui.Widget, gui._MixinTextualWidget): @@ -585,7 +594,8 @@ def onwheel(self, value): self.zoom += float(value)*0.0005 self.zoom = max(0.25, self.zoom) self.zoom = min(4.0, self.zoom) - self.set_viewbox(0,0,500*self.zoom,500*self.zoom) + #self.set_viewbox(0,0,500*self.zoom,500*self.zoom) + self.style['zoom'] = str(self.zoom) return (value,) def on_execution_priority_changed(self, emitter, function_block, value): @@ -835,6 +845,7 @@ def main(self): |container|process_view |attributes| """, 0, 0 ) + self.main_container.css_overflow = 'hidden' self.process = ProcessView("Process", width=600, height=600) self.process.onfunction_block_clicked.do(self.onprocessview_function_block_clicked) From e4829bcee782f2e6a536e2f49fd338d1604c32a9 Mon Sep 17 00:00:00 2001 From: Davide Rosa Date: Tue, 6 Dec 2022 16:29:13 +0100 Subject: [PATCH 19/19] View zoom and pan. --- editor/FBD_view.py | 159 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 31 deletions(-) diff --git a/editor/FBD_view.py b/editor/FBD_view.py index 7ec83ae5..5cb3265a 100644 --- a/editor/FBD_view.py +++ b/editor/FBD_view.py @@ -32,11 +32,128 @@ def get_size(self): return float(self.attr_width), float(self.attr_height) +class NavigableArea(gui.Svg): + zoom = 1.0 + dragging_active = False + dragging_start_x = 0 + dragging_start_y = 0 + + view_width = 0 + view_height = 0 + def __init__(self, view_width, view_height, *args, **kwargs): + gui.Svg.__init__(self, *args, **kwargs) + self.view_width = view_width + self.view_height = view_height + self.set_viewbox(0, 0, self.view_width, self.view_height) + self.attr_preserveAspectRatio = "XMidYMid meet" + self.onmousedown.do(None) + self.onmouseup.do(None) + self.onmousemove.do(None) + + @gui.decorate_event_js("var params={};" \ + "var boundingBox = this.getBoundingClientRect();" \ + "params['x']=event.clientX-boundingBox.left;" \ + "params['y']=event.clientY-boundingBox.top;" \ + "params['buttons']=event.buttons;" \ + "remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);") + def onmousedown(self, x, y, buttons): + """Called when the user presses left or right mouse button over a Widget. + + Args: + x (float): position of the mouse inside the widget + y (float): position of the mouse inside the widget + buttons (int): the mouse button code + + 0: No button or un-initialized + 1: Primary button (usually the left button) + 2: Secondary button (usually the right button) + 4: Auxiliary button (usually the mouse wheel button or middle button) + 8: 4th button (typically the "Browser Back" button) + 16 : 5th button (typically the "Browser Forward" button) + """ + buttons = int(buttons) + if buttons & 4: + self.dragging_active = True + self.dragging_start_x = float(x) + self.dragging_start_y = float(y) + return (x, y) + + @gui.decorate_event_js("var params={};" \ + "var boundingBox = this.getBoundingClientRect();" \ + "params['x']=event.clientX-boundingBox.left;" \ + "params['y']=event.clientY-boundingBox.top;" \ + "remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);") + def onmouseup(self, x, y): + """Called when the user releases left or right mouse button over a Widget. + + Args: + x (float): position of the mouse inside the widget + y (float): position of the mouse inside the widget + """ + self.dragging_active = False + return (x, y) + + @gui.decorate_event_js("var params={};" \ + "var boundingBox = this.getBoundingClientRect();" \ + "params['x']=event.clientX-boundingBox.left;" \ + "params['y']=event.clientY-boundingBox.top;" \ + "params['buttons']=event.buttons;" \ + "remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);") + def onmousemove(self, x, y, buttons): + """Called when the mouse cursor moves inside the Widget. + + Args: + x (float): position of the mouse inside the widget + y (float): position of the mouse inside the widget + buttons (int): the mouse button code + + 0: No button or un-initialized + 1: Primary button (usually the left button) + 2: Secondary button (usually the right button) + 4: Auxiliary button (usually the mouse wheel button or middle button) + 8: 4th button (typically the "Browser Back" button) + 16 : 5th button (typically the "Browser Forward" button) + """ + if self.dragging_active: + vx, vy, vw, vh = self.get_actual_viewbox_values() + self.set_viewbox(vx + self.dragging_start_x - float(x), vy + self.dragging_start_y - float(y),vw, vh) + self.dragging_start_x = float(x) + self.dragging_start_y = float(y) + self.attr_preserveAspectRatio = "XMidYMid meet" + return (x, y) + + @gui.decorate_event_js("var params={};" \ + "params['value']=event.deltaY;" \ + "remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);") + def onwheel(self, value): + """Called when the user rotate the mouse wheel. + + Args: + value (float): the scroll amount + """ + self.zoom += float(value)*0.0005 + self.zoom = max(0.25, self.zoom) + self.zoom = min(4.0, self.zoom) + vx, vy, vw, vh = self.get_actual_viewbox_values() + self.set_viewbox(vx, vy, self.view_width*self.zoom, self.view_height*self.zoom) + self.attr_preserveAspectRatio = "XMidYMid meet" + #self.style['zoom'] = str(self.zoom) + return (value,) + + def get_actual_viewbox_values(self): + """Returns floating point values of the actual viewbox. + + Returns: + x, y, w, h (float): values + """ + viewbox = [float(v) for v in self.attr_viewBox.split(' ')] + return (*viewbox,) + + class MoveableWidget(gui.EventSource, MixinPositionSize): container = None x_start = 0 y_start = 0 - actual_zoom = 1.0 start_pos_acquired = False def __init__(self, container, *args, **kwargs): gui.EventSource.__init__(self) @@ -45,8 +162,6 @@ def __init__(self, container, *args, **kwargs): self.onmousedown.do(self.start_drag, js_stop_propagation=True, js_prevent_default=True) def start_drag(self, emitter, x, y): - if 'zoom' in self.container.style.keys(): - self.actual_zoom = float(self.container.style['zoom']) self.active = True self.container.onmousemove.do(self.on_drag, js_stop_propagation=True, js_prevent_default=True) self.onmousemove.do(None, js_stop_propagation=False, js_prevent_default=True) @@ -57,20 +172,20 @@ def start_drag(self, emitter, x, y): @gui.decorate_event def stop_drag(self, emitter, x, y): self.active = False - return (float(x)/self.actual_zoom, float(y)/self.actual_zoom) + return (float(x), float(y)) @gui.decorate_event def on_drag(self, emitter, x, y): if not self.start_pos_acquired: self_x, self_y = self.get_position() - self.x_start = (float(x)/self.actual_zoom - self_x) - self.y_start = (float(y)/self.actual_zoom - self_y) + self.x_start = (float(x) - self_x) + self.y_start = (float(y) - self_y) self.start_pos_acquired = True - + if self.active: #self.set_position(float(x) - float(self.attr_width)/2.0, float(y) - float(self.attr_height)/2.0) - self.set_position((float(x)/self.actual_zoom - self.x_start), (float(y)/self.actual_zoom - self.y_start)) - return (float(x)/self.actual_zoom, float(y)/self.actual_zoom) + self.set_position((float(x) - self.x_start), (float(y) - self.y_start)) + return (float(x), float(y)) class SvgTitle(gui.Widget, gui._MixinTextualWidget): @@ -391,7 +506,7 @@ def __init__(self, name, container, x = 10, y = 10, execution_priority = 0, *arg self.bt_increase_priority.set_fill("gray") self.bt_increase_priority.css_font_size = gui.to_pix(self.label_font_size) self.bt_increase_priority.style["cursor"] = "pointer" - self.bt_increase_priority.onclick.do(self.on_execution_priority_changed, -1, js_stop_propagation=True, js_prevent_default=True) + self.bt_increase_priority.onclick.do(self.on_execution_priority_changed, -1, js_stop_propagation=False, js_prevent_default=True) self.bt_increase_priority.attr_title = "Increase priority" self.append(self.bt_increase_priority) @@ -401,7 +516,7 @@ def __init__(self, name, container, x = 10, y = 10, execution_priority = 0, *arg self.bt_decrease_priority.set_fill("gray") self.bt_decrease_priority.css_font_size = gui.to_pix(self.label_font_size) self.bt_decrease_priority.style["cursor"] = "pointer" - self.bt_decrease_priority.onclick.do(self.on_execution_priority_changed, +1, js_stop_propagation=True, js_prevent_default=True) + self.bt_decrease_priority.onclick.do(self.on_execution_priority_changed, +1, js_stop_propagation=False, js_prevent_default=True) self.bt_decrease_priority.attr_title = "Decrease priority" self.append(self.bt_decrease_priority) @@ -530,7 +645,7 @@ def set_name(self, name): entrare in un function block e comporlo come un processo """ -class ProcessView(gui.Svg, FBD_model.Process): +class ProcessView(NavigableArea, FBD_model.Process): name = None selected_input = None @@ -544,11 +659,9 @@ class ProcessView(gui.Svg, FBD_model.Process): process_outputs_fb = None #this is required to route result values outside of Process process_inputs_fb = None #this is required to route parameters inside the Process - zoom = 1.0 - def __init__(self, name, *args, **kwargs): self.name = name - gui.Svg.__init__(self, *args, **kwargs) + NavigableArea.__init__(self, 500, 500, *args, **kwargs) FBD_model.Process.__init__(self) self.css_border_color = 'black' @@ -582,22 +695,6 @@ def __init__(self, name, *args, **kwargs): self.add_function_block(self.process_outputs_fb) self.onwheel.do(None) - @gui.decorate_event_js("var params={};" \ - "params['value']=event.deltaY;" \ - "remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);") - def onwheel(self, value): - """Called when the user rotate the mouse wheel. - - Args: - value (float): the scroll amount - """ - self.zoom += float(value)*0.0005 - self.zoom = max(0.25, self.zoom) - self.zoom = min(4.0, self.zoom) - #self.set_viewbox(0,0,500*self.zoom,500*self.zoom) - self.style['zoom'] = str(self.zoom) - return (value,) - def on_execution_priority_changed(self, emitter, function_block, value): del self.function_blocks[function_block.name] d = dict(self.function_blocks)