dotcode_pack.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2008, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 from __future__ import with_statement, print_function
00034 
00035 import re
00036 
00037 from rospkg.common import ResourceNotFound
00038 from qt_dotgraph.colors import get_color_for_string
00039 
00040 MAX_EDGES = 1500
00041 
00042 
00043 def matches_any(name, patternlist):
00044     for pattern in patternlist:
00045         if name == pattern:
00046             return True
00047         if re.match("^[a-zA-Z0-9_]+$", pattern) is None:
00048             if re.match(pattern, name) is not None:
00049                 return True
00050     return False
00051 
00052 
00053 class RosPackageGraphDotcodeGenerator:
00054 
00055     def __init__(self, rospack, rosstack):
00056         """
00057         :param rospack: use rospkg.RosPack()
00058         :param rosstack: use rospkg.RosStack()
00059         """
00060         self.rospack = rospack
00061         self.rosstack = rosstack
00062         self.stacks = {}
00063         self.packages = {}
00064         self.edges = []
00065         self.last_drawargs = None
00066         self.last_selection = None
00067 
00068     def generate_dotcode(self,
00069                          dotcode_factory,
00070                          selected_names=[],
00071                          excludes=[],
00072                          depth=3,
00073                          with_stacks=True,
00074                          descendants=True,
00075                          ancestors=True,
00076                          hide_transitives=True,
00077                          mark_selected=True,
00078                          colortheme=None,
00079                          rank='same',  # None, same, min, max, source, sink
00080                          ranksep=0.2,  # vertical distance between layers
00081                          rankdir='TB',  # direction of layout (TB top > bottom, LR left > right)
00082                          simplify=True,  # remove double edges
00083                          force_refresh=False):
00084         """
00085 
00086         :param hide_transitives: if true, then dependency of children to grandchildren will be hidden if parent has same dependency
00087         """
00088 
00089         # defaults
00090         selected_names = filter(lambda x: x is not None and x != '', selected_names)
00091         excludes = filter(lambda x: x is not None and x != '', excludes)
00092         if selected_names is None or selected_names == []:
00093             selected_names = ['.*']
00094             self.depth = 1
00095         if depth is None:
00096             depth = -1
00097 
00098         # update arguments
00099 
00100         selection_args = {
00101             "dotcode_factory": dotcode_factory,
00102             "with_stacks": with_stacks,
00103             "depth": depth,
00104             "hide_transitives": hide_transitives,
00105             "selected_names": selected_names,
00106             "excludes": excludes,
00107             "ancestors": ancestors,
00108             "descendants": descendants
00109             }
00110 
00111         # if selection did not change, we need not build up the graph again
00112         selection_changed = False
00113         if self.last_selection != selection_args:
00114             selection_changed = True
00115             self.last_selection = selection_args
00116 
00117             self.dotcode_factory = dotcode_factory
00118             self.with_stacks = with_stacks
00119             self.depth = depth
00120             self.hide_transitives = hide_transitives
00121             self.selected_names = selected_names
00122             self.excludes = excludes
00123             self.ancestors = ancestors
00124             self.descendants = descendants
00125 
00126         if force_refresh or selection_changed:
00127             self.stacks = {}
00128             self.packages = {}
00129             self.edges = []
00130             # update internal graph structure
00131             for name in self.rospack.list():
00132                 if matches_any(name, self.selected_names):
00133                     if descendants:
00134                         self.add_package_descendants_recursively(name)
00135                     if ancestors:
00136                         self.add_package_ancestors_recursively(name)
00137             for stackname in self.rosstack.list():
00138                 if matches_any(stackname, self.selected_names):
00139                     for package_name in self.rosstack.packages_of(stackname):
00140                         if descendants:
00141                             self.add_package_descendants_recursively(package_name)
00142                         if ancestors:
00143                             self.add_package_ancestors_recursively(package_name)
00144 
00145         drawing_args = {
00146             'dotcode_factory': dotcode_factory,
00147             "rank": rank,
00148             "rankdir": rankdir,
00149             "ranksep": ranksep,
00150             "simplify": simplify,
00151             "colortheme": colortheme,
00152             "mark_selected": mark_selected
00153             }
00154 
00155         # if selection and display args did not change, no need to generate dotcode
00156         display_changed = False
00157         if self.last_drawargs != drawing_args:
00158             display_changed = True
00159             self.last_drawargs = drawing_args
00160 
00161             self.dotcode_factory = dotcode_factory
00162             self.rank = rank
00163             self.rankdir = rankdir
00164             self.ranksep = ranksep
00165             self.simplify = simplify
00166             self.colortheme = colortheme
00167             self.dotcode_factory = dotcode_factory
00168             self.mark_selected = mark_selected
00169 
00170         #generate new dotcode
00171         if force_refresh or selection_changed or display_changed:
00172             self.graph = self.generate(self.dotcode_factory)
00173             self.dotcode = dotcode_factory.create_dot(self.graph)
00174 
00175         return self.dotcode
00176 
00177     def generate(self, dotcode_factory):
00178         graph = dotcode_factory.get_graph(rank=self.rank,
00179                                           rankdir=self.rankdir,
00180                                           ranksep=self.ranksep,
00181                                           simplify=self.simplify)
00182         # print("In generate", self.with_stacks, len(self.stacks), len(self.packages), len(self.edges))
00183         packages_in_stacks = []
00184         if self.with_stacks:
00185             for stackname in self.stacks:
00186                 color = None
00187                 if self.mark_selected and not '.*' in self.selected_names and matches_any(stackname, self.selected_names):
00188                     color = 'red'
00189                 else:
00190                     if self.colortheme is not None:
00191                         color = get_color_for_string(stackname)
00192                 g = dotcode_factory.add_subgraph_to_graph(graph,
00193                                                           stackname,
00194                                                           color=color,
00195                                                           rank=self.rank,
00196                                                           rankdir=self.rankdir,
00197                                                           ranksep=self.ranksep,
00198                                                           simplify=self.simplify)
00199 
00200                 for package_name in self.stacks[stackname]['packages']:
00201                     packages_in_stacks.append(package_name)
00202                     self._generate_package(dotcode_factory, g, package_name)
00203 
00204         for package_name in self.packages:
00205             if package_name not in packages_in_stacks:
00206                 self._generate_package(dotcode_factory, graph, package_name)
00207         if (len(self.edges) < MAX_EDGES):
00208             for edge_tupel in self.edges:
00209                 dotcode_factory.add_edge_to_graph(graph, edge_tupel[0], edge_tupel[1])
00210         else:
00211             print("Too many edges %s > %s, abandoning generation of edge display" % (len(self.edges), MAX_EDGES))
00212         return graph
00213 
00214     def _generate_package(self, dotcode_factory, graph, package_name):
00215         color = None
00216         if self.mark_selected and not '.*' in self.selected_names and matches_any(package_name, self.selected_names):
00217             color = 'red'
00218         dotcode_factory.add_node_to_graph(graph, package_name, color=color)
00219 
00220     def _add_stack(self, stackname):
00221         if stackname is None or stackname in self.stacks:
00222             return
00223         self.stacks[stackname] = {'packages': []}
00224 
00225     def _add_package(self, package_name, parent=None):
00226         """
00227         adds object based on package_name to self.packages
00228         :param parent: packagename which referenced package_name (for debugging only)
00229         """
00230         if package_name in self.packages:
00231             return False
00232         self.packages[package_name] = {}
00233 
00234         if self.with_stacks:
00235             try:
00236                 stackname = self.rospack.stack_of(package_name)
00237             except ResourceNotFound as e:
00238                 print('RosPackageGraphDotcodeGenerator._add_package(%s), parent %s: ResourceNotFound:' % (package_name, parent), e)
00239                 stackname = None
00240             if not stackname is None and stackname != '':
00241                 if not stackname in self.stacks:
00242                     self._add_stack(stackname)
00243                 self.stacks[stackname]['packages'].append(package_name)
00244         return True
00245 
00246     def _add_edge(self, name1, name2, attributes=None):
00247         self.edges.append((name1, name2, attributes))
00248 
00249     def add_package_ancestors_recursively(self, package_name, expanded_up=None, depth=None, implicit=False, parent=None):
00250         """
00251         :param package_name: the name of package for which to add ancestors
00252         :param expanded_up: names that have already been expanded (to avoid cycles)
00253         :param depth: how many layers to follow
00254         :param implicit: arg to rospack
00255         :param parent: package that referenced package_name for error message only
00256         """
00257         if matches_any(package_name, self.excludes):
00258             return False
00259         if (depth == 0):
00260             return False
00261         if (depth == None):
00262             depth = self.depth
00263         self._add_package(package_name, parent=parent)
00264         if expanded_up is None:
00265             expanded_up = []
00266         expanded_up.append(package_name)
00267         if (depth != 1):
00268             try:
00269                 depends_on = self.rospack.get_depends_on(package_name, implicit=implicit)
00270             except ResourceNotFound as e:
00271                 print('RosPackageGraphDotcodeGenerator.add_package_ancestors_recursively(%s), parent %s: ResourceNotFound:' % (package_name, parent), e)
00272                 depends_on = []
00273             new_nodes = []
00274             for dep_on_name in [x for x in depends_on if not matches_any(x, self.excludes)]:
00275                 if not self.hide_transitives or not dep_on_name in expanded_up:
00276                     new_nodes.append(dep_on_name)
00277                     self._add_edge(dep_on_name, package_name)
00278                     self._add_package(dep_on_name, parent=package_name)
00279                     expanded_up.append(dep_on_name)
00280             for dep_on_name in new_nodes:
00281                 self.add_package_ancestors_recursively(package_name=dep_on_name,
00282                                                        expanded_up=expanded_up,
00283                                                        depth=depth - 1,
00284                                                        implicit=implicit,
00285                                                        parent=package_name)
00286 
00287     def add_package_descendants_recursively(self, package_name, expanded=None, depth=None, implicit=False, parent=None):
00288         if matches_any(package_name, self.excludes):
00289             return False
00290         if (depth == 0):
00291             return False
00292         if (depth == None):
00293             depth = self.depth
00294         self._add_package(package_name, parent=parent)
00295         if expanded is None:
00296             expanded = []
00297         expanded.append(package_name)
00298         if (depth != 1):
00299             try:
00300                 depends = self.rospack.get_depends(package_name, implicit=implicit)
00301             except ResourceNotFound as e:
00302                 print('RosPackageGraphDotcodeGenerator.add_package_descendants_recursively(%s), parent: %s: ResourceNotFound:' % (package_name, parent), e)
00303                 depends = []
00304             new_nodes = []
00305             for dep_name in [x for x in depends if not matches_any(x, self.excludes)]:
00306                 if not self.hide_transitives or not dep_name in expanded:
00307                     new_nodes.append(dep_name)
00308                     self._add_edge(package_name, dep_name)
00309                     self._add_package(dep_name, parent=package_name)
00310                     expanded.append(dep_name)
00311             for dep_name in new_nodes:
00312                 self.add_package_descendants_recursively(package_name=dep_name,
00313                                                          expanded=expanded,
00314                                                          depth=depth - 1,
00315                                                          implicit=implicit,
00316                                                          parent=package_name)


rqt_dep
Author(s): Thibault Kruse
autogenerated on Fri Jan 3 2014 11:56:46