예제 #1
0
class Graph(object):
    '''The graph. Contains all our nodes.'''
    def __init__(self):
        # ID -> node
        self._nodes = {}
        self._root_node_id = None

        self._selected_nodes = set()

        # Nodes
        self.node_added = Signal()
        self.node_removed = Signal()
        self.node_parent_changed = Signal()

        # Parameters
        self.parameter_added = Signal()
        self.parameter_removed = Signal()
        self.parameter_sink_changed = Signal()
        self.parameter_source_changed = Signal()

        # Connections
        self.connection_added = Signal()
        self.connection_removed = Signal()

        # node attributes
        self.node_id_changed = Signal()
        self.node_label_changed = Signal()
        self.node_attributes_changed = Signal()
        self.node_pos_changed = Signal()
        self.node_selected_changed = Signal()
        self.node_name_changed = Signal()

        self.node_added.connect(self._node_added_callback)
        self.node_removed.connect(self._node_removed_callback)
        self.node_selected_changed.connect(
            self._node_selected_changed_callback)

    @property
    def nodes(self):
        return self._nodes.values()

    @nodes.setter
    def nodes(self, new_nodes):
        cur_nodes = set(self._nodes.values())
        new_nodes = set(new_nodes)
        # Don't delete the root node!
        new_nodes.add(self.root_node)
        nodes_to_add = new_nodes.difference(cur_nodes)
        nodes_to_remove = cur_nodes.difference(new_nodes)
        for node in nodes_to_add:
            node.graph = self
        for node in nodes_to_remove:
            node.graph = None

    @nodes.deleter
    def nodes(self):
        self.clear()

    def create_node(self, *args, **kwargs):
        # convenience
        kwargs['graph'] = self
        return node.Node(*args, **kwargs)

    def clear(self):
        self.nodes = []

    @property
    def selected_nodes(self):
        return set(self._selected_nodes)

    def __getitem__(self, node_id):
        return self._nodes.get(node_id, None)

    def __contains__(self, node_id):
        return node_id in self._nodes

    @property
    def selected_nodes(self):
        return set(self._selected_nodes)

    @selected_nodes.setter
    def selected_nodes(self, selected_nodes):
        selected_nodes = set(selected_nodes)
        nodes_to_select = selected_nodes.difference(self._selected_nodes)
        nodes_to_unselect = self._selected_nodes.difference(selected_nodes)
        for node in nodes_to_select:
            node.selected = True
        for node in nodes_to_unselect:
            node.selected = False

    def clear_selection(self):
        for node in self.get_selected_nodes():
            node.set_selected(False)

    def _uniquefy_node_id(self, node_id):
        if node_id not in self:
            return node_id
        # The node_id can be either a string or int (or anything else you want,
        # but we need to be able to make a unique type)
        if type(node_id) == str:
            attempt = 0
            unique_node_id = node_id
            while unique_node_id in self:
                attempt += 1
                unique_node_id = node_id + '_' + str(attempt)
            return unique_node_id

        elif type(node_id) == int or type(node_id) == float:
            while node_id in self:
                node_id += 1
            return node_id

        print('Warning! Unable to unquefy node_id type %s' %
              str(type(node_id)))
        return node_id

    @property
    def root_node(self):
        if self._root_node_id is None:
            self._root_node_id = self._uniquefy_node_id('root')
            self._root_node = node.SubgraphNode(node_id=self._root_node_id,
                                                graph=self)
            return self._root_node
        return self[self._root_node_id]

    def _node_added_callback(self, node):
        node_id = node.node_id
        if node_id in self:
            node_id = self._uniquefy_node_id(node_id)
            node.node_id = node_id
        if node.parent is None and node_id != self._root_node_id:
            node.parent = self.root_node
        self._nodes[node_id] = node

    def _node_removed_callback(self, node):
        if node.node_id not in self:
            return
        if node.node_id == self._root_node_id:
            print('Warning: deleting root node')
            self._root_node_id = None
        del self._nodes[node.node_id]

    def _node_selected_changed_callback(self, node):
        if not node.selected and node in self.selected_nodes:
            self._selected_nodes.remove(node)
        elif node.selected and node not in self.selected_nodes:
            self._selected_nodes.add(node)
예제 #2
0
class Parameter(parentable.Parentable, connectable.Connectable):
    def __init__(self,
                 label=None,
                 parameter_id=0,
                 parent=None,
                 index=0,
                 node=None,
                 source=False,
                 sink=False):
        super(Parameter, self).__init__()
        if parent is not None:
            if parent.node is not node and node is not None:
                print("Warning: Specified node differs from parent's node. "
                      "Using parent's node")
            node = parent.node
        # Properties
        self._parameter_id = parameter_id
        self._label = label
        self._inbex = index

        # Attached node
        self._node = node
        # Outward and inward connections
        self._connections_out = set()
        self._connections_in = set()

        # property signals
        self.parameter_id_changed = Signal()
        self.label_changed = Signal()
        self.index_changed = Signal()

        self.connection_added = Signal()
        self.connection_removed = Signal()
        self.sink_changed = Signal()
        self.source_changed = Signal()

        self.parent = parent
        # TODO: this needs to fill in the other sink/source value
        # self.connectionModeChanged = Signal()
        # self.sink_changed.connect(self.connectionModeChanged.emit)
        # self.source_changed.connect(self.connectionModeChanged.emit)
        self.source = source
        self.sink = sink
        self.register_callbacks()
        if self._node is not None:
            self._node.parameter_added.emit(parameter=self, param=self)

    def delete(self):
        self.parent = None
        self.node = None

    def register_callbacks(self):
        if self._node is None:
            return
        self.connection_added.connect(self.node.connection_added.emit,
                                      parameter=self,
                                      param=self)
        self.connection_removed.connect(self.node.connection_removed.emit,
                                        parameter=self,
                                        param=self)
        self.sink_changed.connect(self.node.parameter_sink_changed.emit,
                                  parameter=self,
                                  param=self)
        self.source_changed.connect(self.node.parameter_source_changed.emit,
                                    parameter=self,
                                    param=self)

    def deregister_callbacks(self):
        if self._node is None:
            return
        self.connection_added.disconnect(self.node.connection_added.emit)
        self.connection_removed.disconnect(self.node.connection_removed.emit)
        self.sink_changed.disconnect(self.node.parameter_sink_changed.emit)
        self.source_changed.disconnect(self.node.parameter_source_changed.emit)

    def __repr__(self):
        '''fake convenience repr'''
        node_str = 'No node'
        if self.node is not None:
            node_str = 'Node: %s' % repr(self.node)

        return '<%s: %s/%s (%s)>' % (self.__class__.__name__,
                                     repr(self.parameter_id), self.label,
                                     node_str)

    @property
    def parameter_id(self):
        return self._parameter_id

    @parameter_id.setter
    def parameter_id(self, new_val):
        self._parameter_id = new_val
        self.parameter_id_changed.emit(parameter_id=self._parameter_id)

    @parameter_id.deleter
    def parameter_id(self):
        del self._parameter_id

    @property
    def label(self):
        return self._label

    @label.setter
    def label(self, new_val):
        self._label = new_val
        self.label_changed.emit(label=self._label)

    @label.deleter
    def label(self):
        del self._label

    @property
    def index(self):
        return self._index

    @index.setter
    def index(self, new_val):
        self._index = new_val
        self.index_changed.emit(index=self._index)

    @index.deleter
    def index(self):
        del self._index

    @property
    def node(self):
        return self._node

    @node.setter
    def node(self, new_node):
        if self._parent is not None:
            self.parent = None
        if self._node is not None:
            self.deregister_callbacks()
            self.parameter_removed.emit(parameter=self, param=self)
        self._node = new_node
        self.register_callbacks()
        self.parameter_added.emit(parameter=self, param=self)

    def is_transitively_connected(self, param):
        # TODO: two-sided BFS a la path tracing
        return param in self.connections

    @property
    def connection_subgraph(self):
        return self.node.parent

    def loft(self):
        # In ambiguous cases, loft the sink
        if self.sink:
            return self.loft_sink()
        if self.source:
            return self.loft_source()

    def loft_sink(self):
        if not self.sink:
            return None
        if self.node.parent is None:
            return None
        for tunnel_param in self.node.parent.tunnel_parameters:
            if self in tunnel_param.connections:
                return self.node.parent.get_parameter_for_tunnel(tunnel_param)
        lofted_param = Parameter(node=self.node.parent)
        lofted_param.sink = True
        self.node.parent.get_tunnel_parameter(lofted_param).connect(self)
        return lofted_param

    def loft_source(self):
        if not self.source:
            return None
        if self.node.parent is None:
            return None
        for tunnel_param in self.node.parent.tunnel_parameters:
            if self in tunnel_param.connections:
                return self.node.parent.get_parameter_for_tunnel(tunnel_param)
        lofted_param = Parameter(node=self.node.parent)
        lofted_param.source = True
        self.node.parent.get_tunnel_parameter(lofted_param).connect(self)
        return lofted_param

    def connect(self, param):
        # Easy case, connection in the same subgraph
        if self.connection_subgraph == param.connection_subgraph:
            return super(Parameter, self).connect(param)

        # Hard case, different parents
        # Loft up the one with more ancestors.
        # (in the case of equal length but different ancestors just choose
        # one and the other will be lofted in recursion)
        # Determine directionality
        # In ambiguous cases, prefer that we are the source
        self_is_source = True
        if self.source and param.sink:
            self_is_source = True
        elif self.sink and param.source:
            self_is_source = False
        else:
            print('unable to connect')

        deep_param = self
        surface_param = param
        deep_is_source = self_is_source
        if len(self.node.ancestors) < len(param.node.ancestors):
            deep_param = param
            surface_param = self
            deep_is_source = not self_is_source
        if deep_is_source:
            lofted_param = deep_param.loft_source()
        else:
            lofted_param = deep_param.loft_sink()
        if lofted_param is not None:
            return lofted_param.connect(surface_param)