diff --git a/editor/FBD_library.py b/editor/FBD_library.py index d3db464b..8336fa08 100644 --- a/editor/FBD_library.py +++ b/editor/FBD_library.py @@ -8,13 +8,127 @@ 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 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, 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() + 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, EN = True): - if not EN: - return + 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, {}) @@ -70,6 +184,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 3d62eb76..37c6feda 100644 --- a/editor/FBD_model.py +++ b/editor/FBD_model.py @@ -1,92 +1,155 @@ import inspect -class Input(): + +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) + return True + + 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 + self.linked_nodes.remove(node) + + +class Input(Linkable): + """An input element of a FunctionBlock. + """ default = None typ = None - source = None #has to be an Output - + def __init__(self, name, default = inspect.Parameter.empty, typ = None): - self.name = name + """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.source.get_value() + 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) - def link(self, output): - if not issubclass(type(output), Output): - return - self.source = output - - def is_linked(self): - return self.source != None - - def unlink(self): - Input.link(self, None) - -class Output(): +class Output(Linkable): + """An output value of a FunctionBlock + """ name = None typ = None - destinations = None #has to be an Input 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 - self.destinations = [] 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 - 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 ObjectBlock(): +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 - 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 - + execution_priority = 0 -class FunctionBlock(): - name = None - inputs = None - outputs = None + has_enabling_input = False #This property gets overloaded in FBD_view.FunctionBlockView def decorate_process(output_list): """ setup a method as a process FunctionBlock """ @@ -99,58 +162,120 @@ def add_annotation(method): return method return add_annotation - def __init__(self, name): + 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 - 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 + """Adds a function block to the process. - def add_object_block(self, object_block): - self.object_blocks[object_block.name] = object_block + 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): - 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) - - for function_block in (*self.function_blocks.values(), *sub_function_blocks): + """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 = {} 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 @@ -159,6 +284,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 34202307..5cb3265a 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) @@ -30,31 +32,160 @@ 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 + 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.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), 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_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) - return (x, y) + #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 (float(x), float(y)) class SvgTitle(gui.Widget, gui._MixinTextualWidget): @@ -85,7 +216,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') @@ -102,12 +233,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 @@ -135,7 +271,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 +281,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 = DeleteButton() container.append(bt_unlink) link_view.set_unlink_button(bt_unlink) FBD_model.Output.link(self, destination) @@ -162,8 +298,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') @@ -176,95 +315,24 @@ 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 () -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): - def __init__(self, x=0, y=0, w=15, h=15, *args, **kwargs): +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%") - 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): @@ -280,7 +348,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" @@ -296,7 +364,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) @@ -310,6 +378,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'] = '' @@ -373,200 +444,99 @@ 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): +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 outline = None - reference_object = None + delete_button = 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) + 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) gui.SvgSubcontainer.__init__(self, x, y, self.calc_width(), self.calc_height(), *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.outline.set_stroke(2, 'black') 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.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.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: - #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_fb_view(c(method_name, container)) - self.add_fb_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_fb_view(ObjectFunctionBlockView(evt, evt, "do", evt.event_method_bound.__name__ + ".do", self)) - """ - self.stop_drag.do(lambda emitter, x, y:self.adjust_geometry()) - - def calc_height(self): - xmax = ymax = 0 - if not self.FBs is None: - for fb in self.FBs.values(): - 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 - if not self.FBs is None: - for fb in self.FBs.values(): - 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), 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) - - self.adjust_geometry() - - def onfunction_block_position_changed(self, emitter, x, y): - emitter.adjust_geometry() - 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 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): - 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) - ofbv.add_io_widget(OutputView("Value")) - self.add_fb_view(ofbv) - - 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') - - -class FunctionBlockView(FBD_model.FunctionBlock, gui.SvgSubcontainer, MoveableWidget): - - label = None - label_font_size = 12 - - outline = None - - io_font_size = 12 - io_left_right_offset = 10 - - input_event = None + 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.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.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.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=False, 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=False, js_prevent_default=True) + self.bt_decrease_priority.attr_title = "Decrease priority" + self.append(self.bt_decrease_priority) - 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) - MoveableWidget.__init__(self, container, *args, **kwargs) + self.populate_io() - self.outline = gui.SvgRectangle(0, 0, "100%", "100%") - self.outline.set_fill('white') - self.outline.set_stroke(2, 'black') - self.append(self.outline) + self.stop_drag.do(lambda emitter, x, y:self.adjust_geometry()) - 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) + @gui.decorate_event + def on_execution_priority_changed(self, emitter, direction): + actual_priority = int(self.label_priority.get_text()) + return (self, actual_priority + direction) - self.populate_io() + @gui.decorate_event + def on_delete_button_pressed(self, emitter): + return () - 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 @@ -595,11 +565,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) @@ -608,6 +578,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.linked_nodes: + 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() @@ -619,20 +608,17 @@ 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 + 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 @@ -651,64 +637,74 @@ def set_name(self, name): self.adjust_geometry() -class ObjectFunctionBlockView(FunctionBlockView): - reference_object = None - method = None - method_name = None +""" + Bisogna gestire + 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 +""" - 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)) +class ProcessView(NavigableArea, FBD_model.Process): + name = None - 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)) + selected_input = None + selected_output = None - 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 + selected_function_block = None + label = None + label_font_size = 18 -class ProcessView(gui.Svg, FBD_model.Process): - selected_input = None - selected_output = None + 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, *args, **kwargs): - gui.Svg.__init__(self, *args, **kwargs) + def __init__(self, name, *args, **kwargs): + self.name = name + NavigableArea.__init__(self, 500, 500, *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')" + + 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) + self.onwheel.do(None) + + 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 def onselection_start(self, emitter, x, y): self.selected_input = self.selected_output = None @@ -732,16 +728,37 @@ 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_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 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) + 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 list(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: + 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,) @@ -762,16 +779,27 @@ def __init__(self, appInstance, **kwargs): import FBD_library # load all widgets - 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") + 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 @@ -910,17 +938,39 @@ 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 ) + self.main_container.css_overflow = 'hidden' - 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) - - self.process.add_object_block(TextInputAdapter(gui.TextInput(), self.process)) + #a container to put some widgets for debugging inside + self.container = gui.VBox() + self.main_container.append(self.container, "container") + + import FBD_library + 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) + 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) + + 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') diff --git a/editor/widgets/toolbox_opencv.py b/editor/widgets/toolbox_opencv.py index 79b67a14..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): @@ -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):