00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
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',
00080 ranksep=0.2,
00081 rankdir='TB',
00082 simplify=True,
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
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
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
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
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
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
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
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)