Example #1
0
    def __init__(self, vertex):
        dict.__init__(self)
        HasAdHoc.__init__(self)
        Observed.__init__(self)

        self.vertex = ref(vertex)
        self.__id = None
Example #2
0
    def __init__(self, inputs=(), outputs=()):
        """

        :param inputs: list of dict(name='X', interface=IFloat, value=0)
        :param outputs: list of dict(name='X', interface=IFloat)

        .. note::
            if IO names are not a string, they will be converted with str()
        """

        AbstractNode.__init__(self)
        self.clear_inputs()
        self.clear_outputs()
        self.set_io(inputs, outputs)

        # Node State
        self.modified = True

        # Internal Data
        self.internal_data["caption"] = '' # str(self.__class__.__name__)
        self.internal_data["lazy"] = True
        self.internal_data["block"] = False # Do not evaluate the node
        self.internal_data["priority"] = 0
        self.internal_data["hide"] = True # hide in composite node widget
        self.internal_data["port_hide_changed"] = set()
        # Add delay
        self.internal_data["delay"] = 0

        # Observed object to notify final nodes wich are continuously evaluated
        self.continuous_eval = Observed()
Example #3
0
    def __init__(self, vertex):
        dict.__init__(self)
        HasAdHoc.__init__(self)
        Observed.__init__(self)

        self.vertex = ref(vertex)
        self.__id = None
Example #4
0
    def __init__(self,
                 name,
                 description='',
                 category='',
                 inputs=(),
                 outputs=(),
                 lazy=True,
                 delay=0,
                 view=None,
                 alias=None,
                 authors=None,
                 **kargs):
        """
        Create a factory.

        :param name: user name for the node (must be unique) (String)
        :param description: description of the node (String)
        :param category: category of the node (String)
        :param inputs: inputs description
        :param outputs: outputs description, value=0
        :param lazy: enable lazy evaluation (default = False)
        :param view: custom view (default = None)
        :param alias: list of alias name
        :param authors: authors of the node. If Node, it should be replaced by the package authors.

        .. note:: inputs and outputs parameters are list of dictionnary such

        inputs = (dict(name='x', interface=IInt, value=0,)
        outputs = (dict(name='y', interface=IInt)
        """
        Observed.__init__(self)

        # Factory info
        self.name = name
        self.description = description
        self.category = category

        self.__pkg__ = None
        self.__pkg_id__ = None

        self.inputs = inputs
        self.outputs = outputs

        self.lazy = lazy
        self.view = view
        self.delay = delay
        self.alias = alias
        self.authors = authors
Example #5
0
    def __init__(self,
                 name,
                 description='',
                 category='',
                 inputs=(),
                 outputs=(),
                 lazy=True,
                 delay=0,
                 view=None,
                 alias=None,
                 authors=None,
                 **kargs):
        """
        Create a factory.

        :param name: user name for the node (must be unique) (String)
        :param description: description of the node (String)
        :param category: category of the node (String)
        :param inputs: inputs description
        :param outputs: outputs description, value=0
        :param lazy: enable lazy evaluation (default = False)
        :param view: custom view (default = None)
        :param alias: list of alias name
        :param authors: authors of the node. If Node, it should be replaced by the package authors.

        .. note:: inputs and outputs parameters are list of dictionnary such

        inputs = (dict(name='x', interface=IInt, value=0,)
        outputs = (dict(name='y', interface=IInt)
        """
        Observed.__init__(self)

        # Factory info
        self.name = name
        self.description = description
        self.category = category

        self.__pkg__ = None
        self.__pkg_id__ = None

        self.inputs = inputs
        self.outputs = outputs

        self.lazy = lazy
        self.view = view
        self.delay = delay
        self.alias = alias
        self.authors = authors
Example #6
0
    def __init__(self, inputs=(), outputs=()):
        """

        :param inputs: list of dict(name='X', interface=IFloat, value=0)
        :param outputs: list of dict(name='X', interface=IFloat)

        .. note::
            if IO names are not a string, they will be converted with str()
        """

        AbstractNode.__init__(self)
        self.clear_inputs()
        self.clear_outputs()
        self.set_io(inputs, outputs)

        # Node State
        self.modified = True

        # Internal Data
        self.internal_data["caption"] = '' # str(self.__class__.__name__)
        self.internal_data["lazy"] = True
        self.internal_data["block"] = False # Do not evaluate the node
        self.internal_data["priority"] = 0
        self.internal_data["hide"] = True # hide in composite node widget
        self.internal_data["port_hide_changed"] = set()
        # Add delay
        self.internal_data["delay"] = 0

        # Observed object to notify final nodes wich are continuously evaluated
        self.continuous_eval = Observed()
Example #7
0
 def notify_listeners(self, event):
     txt, trevent = Node.is_deprecated_event(event)
     if txt:
         Observed.notify_listeners(self, trevent)
         Observed.notify_listeners(self, event)
     else:
         Observed.notify_listeners(self, event)
Example #8
0
 def notify_listeners(self, event):
     txt, trevent = Node.is_deprecated_event(event)
     if txt:
         Observed.notify_listeners(self, trevent)
         Observed.notify_listeners(self, event)
     else:
         Observed.notify_listeners(self, event)
Example #9
0
    def __init__(self):
        """
        Default Constructor
        Create Internal Data dictionnary
        """
        HasAdHoc.__init__(self)
        Observed.__init__(self)

        self.__id = None
        # Internal Data (caption...)
        self.internal_data = {}
        self.factory = None

        # -- the ugly back reference --
        # !! Christophe, don't look at this one.
        # proxy to higher level structure, aka CompositeNode.
        # This is currently only used by the "wrap_method" node
        # that needs to modify the topology of the graph.
        self._composite_node = None

        # The default layout
        self.view = None
        self.user_application = None
        self.__inError = False
Example #10
0
    def __init__(self):
        """
        Default Constructor
        Create Internal Data dictionnary
        """
        HasAdHoc.__init__(self)
        Observed.__init__(self)

        self.__id = None
        # Internal Data (caption...)
        self.internal_data = {}
        self.factory = None

        # -- the ugly back reference --
        # !! Christophe, don't look at this one.
        # proxy to higher level structure, aka CompositeNode.
        # This is currently only used by the "wrap_method" node
        # that needs to modify the topology of the graph.
        self._composite_node = None

        # The default layout
        self.view = None
        self.user_application = None
        self.__inError = False
Example #11
0
class Node(AbstractNode):
    """
    It is a callable object with typed inputs and outputs.
    Inputs and Outpus are indexed by their position or by a name (str)
    """

    @staticmethod
    def is_deprecated_event(event):
        evLen = len(event)
        conversionTxt = None
        retEvent = event
        if evLen == 2:
            if event[0] == "caption_modified":
                retEvent = "data_modified", "caption", event[1]
                conversionTxt = "('data_modified', 'caption', caption)"
        elif evLen == 3:
            if event[0] == "data_modified":
                if event[1] == "delay":
                    retEvent = "internal_state_changed", "delay", event[2]
                    conversionTxt = "('internal_state_changed', 'delay', delay)"
            elif event[0] == "internal_data_changed":
                retEvent = ('internal_state_changed', event[1], event[2])
                conversionTxt = "('internal_state_changed', 'key', value)"
        return conversionTxt, retEvent

    def __init__(self, inputs=(), outputs=()):
        """

        :param inputs: list of dict(name='X', interface=IFloat, value=0)
        :param outputs: list of dict(name='X', interface=IFloat)

        .. note::
            if IO names are not a string, they will be converted with str()
        """

        AbstractNode.__init__(self)
        self.clear_inputs()
        self.clear_outputs()
        self.set_io(inputs, outputs)

        # Node State
        self.modified = True

        # Internal Data
        self.internal_data["caption"] = '' # str(self.__class__.__name__)
        self.internal_data["lazy"] = True
        self.internal_data["block"] = False # Do not evaluate the node
        self.internal_data["priority"] = 0
        self.internal_data["hide"] = True # hide in composite node widget
        self.internal_data["port_hide_changed"] = set()
        # Add delay
        self.internal_data["delay"] = 0

        # Observed object to notify final nodes wich are continuously evaluated
        self.continuous_eval = Observed()

    def notify_listeners(self, event):
        txt, trevent = Node.is_deprecated_event(event)
        if txt:
            Observed.notify_listeners(self, trevent)
            Observed.notify_listeners(self, event)
        else:
            Observed.notify_listeners(self, event)

    def __call__(self, inputs=()):
        """ Call function. Must be overriden """
        raise NotImplementedError()

    def get_tip(self):
        return self.__doc__

    def copy_to(self, other):
        # we copy some attributes.
        self.transfer_listeners(other)
        # other.internal_data.update(self.internal_data)
        other.get_ad_hoc_dict().update(self.get_ad_hoc_dict())
        for portOld, portNew in zip(self.input_desc + self.output_desc,
                                    other.input_desc + other.output_desc):
            portOld.copy_to(portNew)

    def get_process_obj(self):
        """ Return the process obj """
        return self

    # property
    process_obj = property(get_process_obj)

    def set_factory(self, factory):
        """ Set the factory of the node (if any)
        and uptdate caption """
        self.factory = factory
        if factory:
            self.set_caption(factory.name)

    def get_input_port(self, name=None):
        """Gets port by name.

        Long description of the function functionality.

        :param name: the name of the port
        :type name: string
        :returns: Input port characterized by name
        :rtype: InputPort

        """
        index = self.map_index_in[name]
        return self.input_desc[index]

    ##############
    # Properties #
    ##############
    def get_lazy(self):
        """todo"""
        return self.internal_data.get("lazy", True)

    def set_lazy(self, data):
        """todo"""
        self.internal_data["lazy"] = data
        self.notify_listeners(("internal_data_changed", "lazy", data))

    lazy = property(get_lazy, set_lazy)

    def get_delay(self):
        """todo"""
        return self.internal_data.get("delay", 0)

    def set_delay(self, delay):
        """todo"""
        self.internal_data["delay"] = delay
        self.notify_listeners(("internal_data_changed", "delay", delay))

    delay = property(get_delay, set_delay)

    def get_block(self):
        """todo"""
        return self.internal_data.get("block", False)

    def set_block(self, data):
        """todo"""
        self.internal_data["block"] = data
        self.notify_listeners(("internal_data_changed", "blocked", data))

    block = property(get_block, set_block)

    def get_user_application(self):
        """todo"""
        return self.internal_data.get("user_application", False)

    def set_user_application(self, data):
        """todo"""
        self.internal_data["user_application"] = data
        self.notify_listeners(("internal_data_changed", "user_application", data))

    user_application = property(get_user_application, set_user_application)

    def set_caption(self, newcaption):
        """ Define the node caption """
        self.internal_data['caption'] = newcaption
        self.notify_listeners(("caption_modified", newcaption))

    def get_caption(self):
        """ Return the node caption """
        return self.internal_data.get('caption', "")

    caption = property(get_caption, set_caption)

    def is_port_hidden(self, index_key):
        """ Return the hidden state of a port """
        index = self.map_index_in[index_key]
        s = self.input_desc[index].is_hidden() # get('hide', False)
        changed = self.internal_data["port_hide_changed"]

        c = index in changed

        if(index in changed):
            return not s
        else:
            return s

    def set_port_hidden(self, index_key, state):
        """
        Set the hidden state of a port.

        :param index_key: the input port index.
        :param state: a boolean value.
        """
        index = self.map_index_in[index_key]
        s = self.input_desc[index].is_hidden() # get('hide', False)

        changed = self.internal_data["port_hide_changed"]

        if (s != state):
            changed.add(index)
            self.input_desc[index].get_ad_hoc_dict().set_metadata("hide", state)
            self.notify_listeners(("hiddenPortChange",))
        elif(index in changed):
            changed.remove(index)
            self.input_desc[index].get_ad_hoc_dict().set_metadata("hide", state)
            self.notify_listeners(("hiddenPortChange",))


    # Status
    def unvalidate_input(self, index_key, notify=True):
        """
        Unvalidate node and notify listeners.

        This method is called when the input value has changed.
        """
        self.modified = True
        index = self.map_index_in[index_key]
        if(notify):
            self.notify_listeners(("input_modified", index))
            self.continuous_eval.notify_listeners(("node_modified",))

    # Declarations
    def set_io(self, inputs, outputs):
        """
        Define the number of inputs and outputs

        :param inputs: list of dict(name='X', interface=IFloat, value=0)
        :param outputs: list of dict(name='X', interface=IFloat)
        """

        # # Values
        if(inputs is None or len(inputs) != len(self.inputs)):
            self.clear_inputs()
            if inputs:
                for d in inputs:
                    self.add_input(**d)

        if(outputs is None or len(outputs) != len(self.outputs)):
            self.clear_outputs()
            if outputs:
                for d in outputs:
                    self.add_output(**d)

        # to_script
        self._to_script_func = None

    def clear_inputs(self):
        # Values
        self.inputs = []
        # Description (list of dict (name=, interface=, ...))
        self.input_desc = []
        # translation of name to id or id to id (identity)...
        self.map_index_in = {}
        # Input states : "connected", "hidden"
        self.input_states = []
        self.notify_listeners(("cleared_input_ports",))

    def clear_outputs(self):
        # Values
        self.outputs = []
        # Description (list of dict (name=, interface=, ...))
        self.output_desc = []
        # translation of name to id or id to id (identity)...
        self.map_index_out = {}
        self.notify_listeners(("cleared_output_ports",))


    def add_input(self, **kargs):
        """ Create an input port """

        # Get parameters
        name = str(kargs['name'])
        interface = kargs.get('interface', None)

        # default value
        if(interface and not kargs.has_key('value')):
            if isinstance(interface, str):
                # Create mapping between interface name and interface class
                from openalea.core.interface import TypeNameInterfaceMap
                interface = TypeNameInterfaceMap()[interface]
                kargs['interface'] = interface

            value = interface.default()
        else:
            value = kargs.get('value', None)

        value = copy(value)

        name = str(name) # force to have a string
        self.inputs.append(None)

        port = InputPort(self)
        port.update(kargs)
        self.input_desc.append(port)

        self.input_states.append(None)
        index = len(self.inputs) - 1
        self.map_index_in[name] = index
        self.map_index_in[index] = index
        port.set_id(index)

        self.set_input(name, value, False)
        port.get_ad_hoc_dict().set_metadata("hide",
                                            kargs.get("hide", False))
        self.notify_listeners(("input_port_added", port))
        return port

    def add_output(self, **kargs):
        """ Create an output port """

        # Get parameters
        name = str(kargs['name'])
        self.outputs.append(None)

        port = OutputPort(self)
        port.update(kargs)
        self.output_desc.append(port)
        index = len(self.outputs) - 1
        self.map_index_out[name] = index
        self.map_index_out[index] = index
        port.set_id(index)
        self.notify_listeners(("output_port_added", port))
        return port

    # I/O Functions

    def set_input(self, key, val=None, notify=True):
        """
        Define the input value for the specified index/key
        """
        index = self.map_index_in[key]

        changed = True
        if(self.lazy):
            # Test if the inputs has changed
            try:
                changed = (cmp(self.inputs[index], val) != 0)
            except:
                pass

        if(changed):
            self.inputs[index] = val
            self.unvalidate_input(index, notify)

    def set_output(self, key, val):
        """
        Define the input value for the specified index/key
        """

        index = self.map_index_out[key]
        self.outputs[index] = val
        self.notify_listeners(("output_modified", key, val))

    def output(self, key):
        return self.get_output(key)

    def get_input(self, index_key):
        """ Return the input value for the specified index/key """
        index = self.map_index_in[index_key]
        return self.inputs[index]

    def get_output(self, index_key):
        """ Return the output for the specified index/key """
        index = self.map_index_out[index_key]
        return self.outputs[index]

    def get_input_state(self, index_key):
        index = self.map_index_in[index_key]
        return self.input_states[index]

    def set_input_state(self, index_key, state):
        """ Set the state of the input index/key (state is a string) """

        index = self.map_index_in[index_key]
        self.input_states[index] = state
        self.unvalidate_input(index)

    def get_nb_input(self):
        """ Return the nb of input ports """
        return len(self.inputs)

    def get_nb_output(self):
        """ Return the nb of output ports """
        return len(self.outputs)

    # Functions used by the node evaluator

    def eval(self):
        """
        Evaluate the node by calling __call__
        Return True if the node needs a reevaluation
        and a timed delay if the node needs a reevaluation at a later time.
        """
        # lazy evaluation
        if self.block and self.get_nb_output() != 0 and self.output(0) is not None:
            return False
        if (self.delay == 0 and self.lazy) and not self.modified:
            return False

        self.notify_listeners(("start_eval",))

        # Run the node
        outlist = self.__call__(self.inputs)

        # Copy outputs
        # only one output
        if len(self.outputs) == 1:
            try:
                if hasattr(outlist, "__getitem__") and len(outlist) == 1:
                    self.outputs[0] = outlist[0]
                else:
                    self.outputs[0] = outlist
            except TypeError:
                self.outputs[0] = outlist

            self.output_desc[0].notify_listeners(("tooltip_modified",))

        else: # multi output
            if(not isinstance(outlist, tuple) and
               not isinstance(outlist, list)):
                outlist = (outlist,)

            for i in range(min(len(outlist), len(self.outputs))):
                self.output_desc[i].notify_listeners(("tooltip_modified",))
                self.outputs[i] = outlist[i]

        # Set State
        self.modified = False
        self.notify_listeners(("stop_eval",))

        if self.delay == 0:
            return False
        return self.delay

    def __getstate__(self):
        """ Pickle function : remove not saved data"""

        odict = self.__dict__.copy()
        odict.update(AbstractNode.__getstate__(self))

        odict['modified'] = True


        outputs = range(len(self.outputs))
        for i in range(self.get_nb_output()):
            try:
                outputs[i] = copy(self.outputs[i])
            except:
                outputs[i] = None
        odict['outputs'] = outputs

        inputs = range(self.get_nb_input())
        for i in range(self.get_nb_input()):
            try:
                inputs[i] = copy(self.inputs[i])
            except:
                inputs[i] = None
        odict['inputs'] = inputs

        in_ports = []
        out_ports = []

        for i, port in enumerate(self.input_desc):
            port.vertex = None
            in_ports.append(copy(port))
            port.vertex = ref(self)

        for i, port in enumerate(self.output_desc):
            port.vertex = None
            out_ports.append(copy(port))
            port.vertex = ref(self)

        odict['input_desc'] = in_ports
        odict['output_desc'] = out_ports

        return odict

    def __setstate__(self, dict):
        self.__dict__.update(dict)

        for port in self.input_desc:
            port.vertex = ref(self)
        for port in self.output_desc:
            port.vertex = ref(self)

    def reload(self):
        """ Reset ports """
        for i in range(self.get_nb_output()):
            self.outputs[i] = None

        i = self.get_nb_input()
        for i in xrange(i):
            # if(not connected or self.input_states[i] is "connected"):
            self.set_input(i, self.input_desc[i].get('value', None))

        if(i > 0):
            self.invalidate()

    def reset(self):
        """ Reset ports """
        for i in range(self.get_nb_output()):
            self.outputs[i] = None

        i = self.get_nb_input()

        if(i > 0):
            self.invalidate()

    def invalidate(self):
        """ Invalidate node """

        self.modified = True
        self.notify_listeners(("input_modified", -1))

        self.continuous_eval.notify_listeners(("node_modified", self))

# X     @property
# X     def outputs(self):
# X         return [self.output(i) for i in range(self.get_nb_output())]

    def to_script (self):
        """Script translation of this node.
        """
        if self._to_script_func is None :
            return "#node %s do not define any scripting\n" % self.factory.name
        else :
            return self._to_script_func(self.inputs, self.outputs)
Example #12
0
class Node(AbstractNode):
    """
    It is a callable object with typed inputs and outputs.
    Inputs and Outpus are indexed by their position or by a name (str)
    """

    @staticmethod
    def is_deprecated_event(event):
        evLen = len(event)
        conversionTxt = None
        retEvent = event
        if evLen == 2:
            if event[0] == "caption_modified":
                retEvent = "data_modified", "caption", event[1]
                conversionTxt = "('data_modified', 'caption', caption)"
        elif evLen == 3:
            if event[0] == "data_modified":
                if event[1] == "delay":
                    retEvent = "internal_state_changed", "delay", event[2]
                    conversionTxt = "('internal_state_changed', 'delay', delay)"
            elif event[0] == "internal_data_changed":
                retEvent = ('internal_state_changed', event[1], event[2])
                conversionTxt = "('internal_state_changed', 'key', value)"
        return conversionTxt, retEvent

    def __init__(self, inputs=(), outputs=()):
        """

        :param inputs: list of dict(name='X', interface=IFloat, value=0)
        :param outputs: list of dict(name='X', interface=IFloat)

        .. note::
            if IO names are not a string, they will be converted with str()
        """

        AbstractNode.__init__(self)
        self.clear_inputs()
        self.clear_outputs()
        self.set_io(inputs, outputs)

        # Node State
        self.modified = True

        # Internal Data
        self.internal_data["caption"] = '' # str(self.__class__.__name__)
        self.internal_data["lazy"] = True
        self.internal_data["block"] = False # Do not evaluate the node
        self.internal_data["priority"] = 0
        self.internal_data["hide"] = True # hide in composite node widget
        self.internal_data["port_hide_changed"] = set()
        # Add delay
        self.internal_data["delay"] = 0

        # Observed object to notify final nodes wich are continuously evaluated
        self.continuous_eval = Observed()

    def notify_listeners(self, event):
        txt, trevent = Node.is_deprecated_event(event)
        if txt:
            Observed.notify_listeners(self, trevent)
            Observed.notify_listeners(self, event)
        else:
            Observed.notify_listeners(self, event)

    def __call__(self, inputs=()):
        """ Call function. Must be overriden """
        raise NotImplementedError()

    def get_tip(self):
        return self.__doc__

    def copy_to(self, other):
        # we copy some attributes.
        self.transfer_listeners(other)
        # other.internal_data.update(self.internal_data)
        other.get_ad_hoc_dict().update(self.get_ad_hoc_dict())
        for portOld, portNew in zip(self.input_desc + self.output_desc,
                                    other.input_desc + other.output_desc):
            portOld.copy_to(portNew)

    def get_process_obj(self):
        """ Return the process obj """
        return self

    # property
    process_obj = property(get_process_obj)

    def set_factory(self, factory):
        """ Set the factory of the node (if any)
        and uptdate caption """
        self.factory = factory
        if factory:
            self.set_caption(factory.name)

    def get_input_port(self, name=None):
        """Gets port by name.

        Long description of the function functionality.

        :param name: the name of the port
        :type name: string
        :returns: Input port characterized by name
        :rtype: InputPort

        """
        index = self.map_index_in[name]
        return self.input_desc[index]

    ##############
    # Properties #
    ##############
    def get_lazy(self):
        """todo"""
        return self.internal_data.get("lazy", True)

    def set_lazy(self, data):
        """todo"""
        self.internal_data["lazy"] = data
        self.notify_listeners(("internal_data_changed", "lazy", data))

    lazy = property(get_lazy, set_lazy)

    def get_delay(self):
        """todo"""
        return self.internal_data.get("delay", 0)

    def set_delay(self, delay):
        """todo"""
        self.internal_data["delay"] = delay
        self.notify_listeners(("internal_data_changed", "delay", delay))

    delay = property(get_delay, set_delay)

    def get_block(self):
        """todo"""
        return self.internal_data.get("block", False)

    def set_block(self, data):
        """todo"""
        self.internal_data["block"] = data
        self.notify_listeners(("internal_data_changed", "blocked", data))

    block = property(get_block, set_block)

    def get_user_application(self):
        """todo"""
        return self.internal_data.get("user_application", False)

    def set_user_application(self, data):
        """todo"""
        self.internal_data["user_application"] = data
        self.notify_listeners(("internal_data_changed", "user_application", data))

    user_application = property(get_user_application, set_user_application)

    def set_caption(self, newcaption):
        """ Define the node caption """
        self.internal_data['caption'] = newcaption
        self.notify_listeners(("caption_modified", newcaption))

    def get_caption(self):
        """ Return the node caption """
        return self.internal_data.get('caption', "")

    caption = property(get_caption, set_caption)

    def is_port_hidden(self, index_key):
        """ Return the hidden state of a port """
        index = self.map_index_in[index_key]
        s = self.input_desc[index].is_hidden() # get('hide', False)
        changed = self.internal_data["port_hide_changed"]

        c = index in changed

        if(index in changed):
            return not s
        else:
            return s

    def set_port_hidden(self, index_key, state):
        """
        Set the hidden state of a port.

        :param index_key: the input port index.
        :param state: a boolean value.
        """
        index = self.map_index_in[index_key]
        s = self.input_desc[index].is_hidden() # get('hide', False)

        changed = self.internal_data["port_hide_changed"]

        if (s != state):
            changed.add(index)
            self.input_desc[index].get_ad_hoc_dict().set_metadata("hide", state)
            self.notify_listeners(("hiddenPortChange",))
        elif(index in changed):
            changed.remove(index)
            self.input_desc[index].get_ad_hoc_dict().set_metadata("hide", state)
            self.notify_listeners(("hiddenPortChange",))


    # Status
    def unvalidate_input(self, index_key, notify=True):
        """
        Unvalidate node and notify listeners.

        This method is called when the input value has changed.
        """
        self.modified = True
        index = self.map_index_in[index_key]
        if(notify):
            self.notify_listeners(("input_modified", index))
            self.continuous_eval.notify_listeners(("node_modified",))

    # Declarations
    def set_io(self, inputs, outputs):
        """
        Define the number of inputs and outputs

        :param inputs: list of dict(name='X', interface=IFloat, value=0)
        :param outputs: list of dict(name='X', interface=IFloat)
        """

        # # Values
        if(inputs is None or len(inputs) != len(self.inputs)):
            self.clear_inputs()
            if inputs:
                for d in inputs:
                    self.add_input(**d)

        if(outputs is None or len(outputs) != len(self.outputs)):
            self.clear_outputs()
            if outputs:
                for d in outputs:
                    self.add_output(**d)

        # to_script
        self._to_script_func = None

    def clear_inputs(self):
        # Values
        self.inputs = []
        # Description (list of dict (name=, interface=, ...))
        self.input_desc = []
        # translation of name to id or id to id (identity)...
        self.map_index_in = {}
        # Input states : "connected", "hidden"
        self.input_states = []
        self.notify_listeners(("cleared_input_ports",))

    def clear_outputs(self):
        # Values
        self.outputs = []
        # Description (list of dict (name=, interface=, ...))
        self.output_desc = []
        # translation of name to id or id to id (identity)...
        self.map_index_out = {}
        self.notify_listeners(("cleared_output_ports",))


    def add_input(self, **kargs):
        """ Create an input port """

        # Get parameters
        name = str(kargs['name'])
        interface = kargs.get('interface', None)

        # default value
        if(interface and not kargs.has_key('value')):
            if isinstance(interface, str):
                # Create mapping between interface name and interface class
                from openalea.core.interface import TypeNameInterfaceMap
                interface = TypeNameInterfaceMap()[interface]
                kargs['interface'] = interface

            value = interface.default()
        else:
            value = kargs.get('value', None)

        value = copy(value)

        name = str(name) # force to have a string
        self.inputs.append(None)

        port = InputPort(self)
        port.update(kargs)
        self.input_desc.append(port)

        self.input_states.append(None)
        index = len(self.inputs) - 1
        self.map_index_in[name] = index
        self.map_index_in[index] = index
        port.set_id(index)

        self.set_input(name, value, False)
        port.get_ad_hoc_dict().set_metadata("hide",
                                            kargs.get("hide", False))
        self.notify_listeners(("input_port_added", port))
        return port

    def add_output(self, **kargs):
        """ Create an output port """

        # Get parameters
        name = str(kargs['name'])
        self.outputs.append(None)

        port = OutputPort(self)
        port.update(kargs)
        self.output_desc.append(port)
        index = len(self.outputs) - 1
        self.map_index_out[name] = index
        self.map_index_out[index] = index
        port.set_id(index)
        self.notify_listeners(("output_port_added", port))
        return port

    # I/O Functions

    def set_input(self, key, val=None, notify=True):
        """
        Define the input value for the specified index/key
        """
        index = self.map_index_in[key]

        changed = True
        if(self.lazy):
            # Test if the inputs has changed
            try:
                changed = (cmp(self.inputs[index], val) != 0)
            except:
                pass

        if(changed):
            self.inputs[index] = val
            self.unvalidate_input(index, notify)

    def set_output(self, key, val):
        """
        Define the input value for the specified index/key
        """

        index = self.map_index_out[key]
        self.outputs[index] = val
        self.notify_listeners(("output_modified", key, val))

    def output(self, key):
        return self.get_output(key)

    def get_input(self, index_key):
        """ Return the input value for the specified index/key """
        index = self.map_index_in[index_key]
        return self.inputs[index]

    def get_output(self, index_key):
        """ Return the output for the specified index/key """
        index = self.map_index_out[index_key]
        return self.outputs[index]

    def get_input_state(self, index_key):
        index = self.map_index_in[index_key]
        return self.input_states[index]

    def set_input_state(self, index_key, state):
        """ Set the state of the input index/key (state is a string) """

        index = self.map_index_in[index_key]
        self.input_states[index] = state
        self.unvalidate_input(index)

    def get_nb_input(self):
        """ Return the nb of input ports """
        return len(self.inputs)

    def get_nb_output(self):
        """ Return the nb of output ports """
        return len(self.outputs)

    # Functions used by the node evaluator

    def eval(self):
        """
        Evaluate the node by calling __call__
        Return True if the node needs a reevaluation
        and a timed delay if the node needs a reevaluation at a later time.
        """
        # lazy evaluation
        if self.block and self.get_nb_output() != 0 and self.output(0) is not None:
            return False
        if (self.delay == 0 and self.lazy) and not self.modified:
            return False

        self.notify_listeners(("start_eval",))

        # Run the node
        outlist = self.__call__(self.inputs)

        # Copy outputs
        # only one output
        if len(self.outputs) == 1:
            try:
                if hasattr(outlist, "__getitem__") and len(outlist) == 1:
                    self.outputs[0] = outlist[0]
                else:
                    self.outputs[0] = outlist
            except TypeError:
                self.outputs[0] = outlist

            self.output_desc[0].notify_listeners(("tooltip_modified",))

        else: # multi output
            if(not isinstance(outlist, tuple) and
               not isinstance(outlist, list)):
                outlist = (outlist,)

            for i in range(min(len(outlist), len(self.outputs))):
                self.output_desc[i].notify_listeners(("tooltip_modified",))
                self.outputs[i] = outlist[i]

        # Set State
        self.modified = False
        self.notify_listeners(("stop_eval",))

        if self.delay == 0:
            return False
        return self.delay

    def __getstate__(self):
        """ Pickle function : remove not saved data"""

        odict = self.__dict__.copy()
        odict.update(AbstractNode.__getstate__(self))

        odict['modified'] = True


        outputs = range(len(self.outputs))
        for i in range(self.get_nb_output()):
            try:
                outputs[i] = copy(self.outputs[i])
            except:
                outputs[i] = None
        odict['outputs'] = outputs

        inputs = range(self.get_nb_input())
        for i in range(self.get_nb_input()):
            try:
                inputs[i] = copy(self.inputs[i])
            except:
                inputs[i] = None
        odict['inputs'] = inputs

        in_ports = []
        out_ports = []

        for i, port in enumerate(self.input_desc):
            port.vertex = None
            in_ports.append(copy(port))
            port.vertex = ref(self)

        for i, port in enumerate(self.output_desc):
            port.vertex = None
            out_ports.append(copy(port))
            port.vertex = ref(self)

        odict['input_desc'] = in_ports
        odict['output_desc'] = out_ports

        return odict

    def __setstate__(self, dict):
        self.__dict__.update(dict)

        for port in self.input_desc:
            port.vertex = ref(self)
        for port in self.output_desc:
            port.vertex = ref(self)

    def reload(self):
        """ Reset ports """
        for i in range(self.get_nb_output()):
            self.outputs[i] = None

        i = self.get_nb_input()
        for i in xrange(i):
            # if(not connected or self.input_states[i] is "connected"):
            self.set_input(i, self.input_desc[i].get('value', None))

        if(i > 0):
            self.invalidate()

    def reset(self):
        """ Reset ports """
        for i in range(self.get_nb_output()):
            self.outputs[i] = None

        i = self.get_nb_input()

        if(i > 0):
            self.invalidate()

    def invalidate(self):
        """ Invalidate node """

        self.modified = True
        self.notify_listeners(("input_modified", -1))

        self.continuous_eval.notify_listeners(("node_modified", self))

# X     @property
# X     def outputs(self):
# X         return [self.output(i) for i in range(self.get_nb_output())]

    def to_script (self):
        """Script translation of this node.
        """
        if self._to_script_func is None :
            return "#node %s do not define any scripting\n" % self.factory.name
        else :
            return self._to_script_func(self.inputs, self.outputs)