diff --git a/grill/views/_graph.py b/grill/views/_graph.py index b40a6529..4311f913 100644 --- a/grill/views/_graph.py +++ b/grill/views/_graph.py @@ -44,12 +44,14 @@ _NO_PEN = QtGui.QPen(QtCore.Qt.NoPen) -_DOT_ENVIRONMENT_ERROR = """In order to display composition arcs in a graph, +_DOT_ENVIRONMENT_ERROR = """In order to display content in this graph view, the 'dot' command must be available on the current environment. Please make sure graphviz is installed and 'dot' available on the system's PATH environment variable. -For more details on installing graphviz, visit https://graphviz.org/download/ or https://grill.readthedocs.io/en/latest/install.html#conda-environment-example +For more details on installing graphviz, visit: + - https://graphviz.org/download/ or + - https://grill.readthedocs.io/en/latest/install.html#conda-environment-example """ @@ -591,21 +593,24 @@ def _load_graph(self, graph): self.scene().clear() self.viewport().update() + _default_text_interaction = QtCore.Qt.LinksAccessibleByMouse if _IS_QT5 else QtCore.Qt.TextBrowserInteraction + if not _core._which("dot"): # dot has not been installed print(_DOT_ENVIRONMENT_ERROR) text_item = QtWidgets.QGraphicsTextItem() text_item.setPlainText(_DOT_ENVIRONMENT_ERROR) - text_item.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse if _IS_QT5 else QtCore.Qt.TextBrowserInteraction) + text_item.setTextInteractionFlags(_default_text_interaction) self.scene().addItem(text_item) return try: # exit early if pydot is not installed, needed for positions positions = drawing.nx_pydot.graphviz_layout(graph, prog='dot') except ImportError as exc: - message = str(exc) + message = f"{exc}\n\n{_DOT_ENVIRONMENT_ERROR}" print(message) text_item = QtWidgets.QGraphicsTextItem() text_item.setPlainText(message) + text_item.setTextInteractionFlags(_default_text_interaction) self.scene().addItem(text_item) return @@ -752,6 +757,7 @@ def __init__(self, *args, **kwargs): layout.addWidget(self._error_view) layout.setContentsMargins(0, 0, 0, 0) self._error_view.setVisible(False) + self._error_view.setLineWrapMode(QtWidgets.QTextBrowser.NoWrap) self.setLayout(layout) self._dot2svg = None self._threadpool = QtCore.QThreadPool() diff --git a/tests/test_data/_mini_graph.dot b/tests/test_data/_mini_graph.dot index 4ee61255..eee049be 100644 --- a/tests/test_data/_mini_graph.dot +++ b/tests/test_data/_mini_graph.dot @@ -1,17 +1,21 @@ digraph { rankdir=LR; edge [color=crimson]; -1 [label="{<0>x:y:z|<1>z}", style="rounded,filled", shape=record]; -2 [label="{<0>a|<1>b}", style="rounded,filled", shape=record]; -3 [label="{<0>c|<1>d}", style="rounded,filled", shape=record]; -4 [label="{<0>k}", style=invis]; -ancestor [active_plugs="{'surface', 'cycle_out', 'cycle_in', 'roughness'}", shape=none, connections="{'surface': [('successor', 'surface')], 'cycle_out': [('ancestor', 'cycle_in')]}", label=<
ancestor
cycle_in
roughness
cycle_out
surface
>]; -successor [active_plugs="{'surface'}", shape=none, connections="{}", label=<
successor
surface
>]; +1 [label="{x:y:z|z}", style=rounded, shape=record]; +2 [label="{a|b}", style=rounded, shape=record]; +3 [label="{c|d}", style=rounded, shape=record]; +parent [shape=box, fillcolor="#afd7ff", color="#1E90FF", style="filled,rounded"]; +child1 [shape=box, fillcolor="#afd7ff", color="#1E90FF", style="filled,rounded"]; +child2 [shape=box, fillcolor="#afd7ff", color="#1E90FF", style=invis]; +ancestor [shape=none, label=<
ancestor
cycle_in
roughness
cycle_out
surface
>]; +successor [shape=none, label=<
successor
surface
>]; 1 -> 1 [key=0, color="sienna:crimson:orange"]; 1 -> 2 [key=0, color=crimson]; -2 -> 1 [key=0, color=green]; -2 -> 4 [key=0, color=yellow, label="edge_label"]; -3 -> 2 [key=0, color=blue, tailport=0]; -ancestor -> ancestor [key=0, tailport="cycle_in", headport="cycle_out", tooltip="ancestor.cycle_in -> ancestor.cycle_out"]; -successor -> ancestor [key=0, tailport=surface, headport=surface, tooltip="successor.surface -> ancestor.surface"]; +2 -> 1 [key=0, color=seagreen]; +3 -> 2 [key=0, color=steelblue, tailport=five]; +3 -> 1 [key=0, color=hotpink, tailport=five]; +parent -> child1 [key=0]; +parent -> child2 [key=0, label=invis]; +ancestor -> ancestor [key=0, tailport="cycle_out", headport="cycle_in", tooltip="ancestor.cycle_out -> ancestor.cycle_in"]; +ancestor -> successor [key=0, tailport=surface, headport=surface, tooltip="ancestor.surface -> successor.surface"]; } diff --git a/tests/test_data/_mini_graph.svg b/tests/test_data/_mini_graph.svg index 266b65be..c94b0774 100644 --- a/tests/test_data/_mini_graph.svg +++ b/tests/test_data/_mini_graph.svg @@ -4,109 +4,133 @@ - - - + + + 1 - -x:y:z - -z + +x:y:z + +z 1->1 - - - - + + + + 2 - -a - -b + +a + +b 1->2 - - + + 2->1 - - - - - - -2->4 - - -edge_label + + 3 - -c - -d + +c + +d - + -3:0->2 - - +3:five->1 + + - + + +3:five->2 + + + + + +parent + +parent + + +child1 + +child1 + + + +parent->child1 + + + + + + +parent->child2 + + +invis + + + ancestor - - -ancestor - -cycle_in - -roughness - -cycle_out - -surface - + + +ancestor + +cycle_in + +roughness + +cycle_out + +surface + - -ancestor:cycle_in->ancestor:cycle_out - - - + +ancestor:cycle_out->ancestor:cycle_in + + + - + successor - - -successor - -surface - - - - -successor:surface->ancestor:surface - - - + + +successor + +surface + + + + +ancestor:surface->successor:surface + + + diff --git a/tests/test_views.py b/tests/test_views.py index 4f780f45..b53e7fe2 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -558,31 +558,38 @@ def test_graph_views(self): nodes_info = { 1: dict( - label="{<0>x:y:z|<1>z}", - style="rounded,filled", + label="{x:y:z|z}", + style="rounded", # these can be set at the graph level shape="record", ), 2: dict( - label="{<0>a|<1>b}", - style="rounded,filled", + label="{a|b}", + style='rounded', shape="record", ), 3: dict( - label="{<0>c|<1>d}", - style="rounded,filled", + label="{c|d}", + style='rounded', shape="record", ), - 4: dict( - label="{<0>k}", - style="invis", + "parent": dict( + shape="box", fillcolor="#afd7ff", color="#1E90FF", style="filled,rounded" + ), + "child1": dict( + shape="box", fillcolor="#afd7ff", color="#1E90FF", style="filled,rounded" + ), + "child2": dict( + shape="box", fillcolor="#afd7ff", color="#1E90FF", style="invis" ), } edges_info = ( (1, 1, dict(color='sienna:crimson:orange')), (1, 2, dict(color='crimson')), - (2, 1, dict(color='green')), - (3, 2, dict(color='blue', tailport='0')), - (2, 4, dict(color='yellow', label='edge_label')), + (2, 1, dict(color='seagreen')), + (3, 2, dict(color='steelblue', tailport='five')), + (3, 1, dict(color='hotpink', tailport='five')), + ("parent", "child1"), + ("parent", "child2", dict(label='invis')), ) graph = _graph.nx.MultiDiGraph() @@ -603,16 +610,14 @@ def test_graph_views(self): 'cycle_out': 3, 'surface': 4 }, - active_plugs={'cycle_in', 'cycle_out', 'roughness', 'surface'}, shape='none', connections=dict( surface=[('successor', 'surface')], cycle_out=[('ancestor', 'cycle_in')], - ) + ), ), successor=dict( plugs={'': 0, 'surface': 1}, - active_plugs={'surface'}, shape='none', connections=dict(), ) @@ -637,10 +642,13 @@ def _add_edges(src_node, src_name, tgt_node, tgt_name): # color = plug_colors[type(plug)] if isinstance(plug, UsdShade.Output) or sources else background_color label += table_row.format(port=plug_name, color=color, text=f'{plug_name}') for source_node, source_plug in sources: - _add_edges(source_node, source_plug, node, plug_name) + # node_id='ancestor', plug_name='cycle_out', ancestor, source.sourceName='cycle_in' + # tooltip='/TexModel/boardMat/PBRShader.cycle_in -> /TexModel/boardMat/PBRShader.cycle_out' + _add_edges(node, plug_name, source_node, source_plug) label += '>' data['label'] = label + data.pop('connections', None) graph.add_nodes_from(connection_nodes.items()) graph.add_edges_from(connection_edges) @@ -656,12 +664,14 @@ def _use_test_svg(self, filepath): def _test_positions(graph, prog): return { - 1: (40.0, 91.692), - 2: (157.37, 91.692), - 3: (40.0, 36.692), - 4: (332.85, 91.692), - 'ancestor': (157.37, 208.69), - 'successor': (40.0, 174.69), + 1: (218.75, 90.1), + 2: (322.75, 90.1), + 3: (76.125, 61.1), + 'parent': (76.125, 190.1), + 'child1': (218.75, 217.1), + 'child2': (218.75, 163.1), + 'ancestor': (76.125, 316.1), + 'successor': (218.75, 282.1), } with (