Skip to content

Commit f5a2821

Browse files
authored
Display Workflows and save as file (#1300)
* Save workflow as symbolic workflow * Save workflow to JSON format * Add a Workflow.view method * Refactor to define name variable * Add optional title argument * Fix docstring * Fix input management * Use graphviz.view instead of PIL * Try catch pyvista import * Update Workflow.view and add test Signed-off-by: paul.profizi <[email protected]> * Update logic and test Signed-off-by: paul.profizi <[email protected]> * Mention required GraphViz install in docstring Signed-off-by: paul.profizi <[email protected]> * Mention required GraphViz install in docstring Signed-off-by: paul.profizi <[email protected]> * Mention required GraphViz install in docstring Signed-off-by: paul.profizi <[email protected]> * Fix QA Signed-off-by: paul.profizi <[email protected]> * Add HAS_GRAPHVIZ conditional skip Signed-off-by: paul.profizi <[email protected]> --------- Signed-off-by: paul.profizi <[email protected]>
1 parent 3b844b7 commit f5a2821

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

src/ansys/dpf/core/workflow.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
========
66
"""
77
import logging
8+
import os
89
import traceback
910
import warnings
1011

1112
from enum import Enum
13+
from typing import Union
14+
1215
from ansys import dpf
1316
from ansys.dpf.core import dpf_operator, inputs, outputs
1417
from ansys.dpf.core.check_version import server_meet_version, version_requires, server_meet_version_and_raise
@@ -844,6 +847,68 @@ def create_on_other_server(self, *args, **kwargs):
844847
"or both ip and port inputs) or a server is required"
845848
)
846849

850+
def view(
851+
self,
852+
title: Union[None, str] = None,
853+
save_as: Union[None, str, os.PathLike] = None,
854+
off_screen: bool = False,
855+
keep_dot_file: bool = False,
856+
) -> Union[str, None]:
857+
"""Run a viewer to show a rendering of the workflow.
858+
859+
.. warning::
860+
The workflow is rendered using GraphViz and requires:
861+
- installation of GraphViz on your computer (see `<https://graphviz.org/download/>`_)
862+
- installation of the ``graphviz`` library in your Python environment.
863+
864+
865+
Parameters
866+
----------
867+
title:
868+
Name to use in intermediate files and in the viewer.
869+
save_as:
870+
Path to a file to save the workflow view as.
871+
off_screen:
872+
Render the image off_screen.
873+
keep_dot_file:
874+
Whether to keep the intermediate DOT file generated.
875+
876+
Returns
877+
-------
878+
Returns the path to the image file rendered is ``save_as``, else None.
879+
"""
880+
try:
881+
import graphviz
882+
except ImportError:
883+
raise ValueError("To render workflows using graphviz, run 'pip install graphviz'.")
884+
885+
if title is None:
886+
name = f"workflow_{repr(self).split()[-1][:-1]}"
887+
else:
888+
name = title
889+
890+
if save_as:
891+
dot_path = os.path.splitext(str(save_as))[0]+".dot"
892+
image_path = save_as
893+
else:
894+
dot_path = os.path.join(os.getcwd(), f"{name}.dot")
895+
image_path = os.path.join(os.getcwd(), f"{name}.png")
896+
897+
# Create graphviz file of workflow
898+
self.to_graphviz(dot_path)
899+
# Render workflow
900+
graphviz.render(engine='dot', filepath=dot_path, outfile=image_path)
901+
if not off_screen:
902+
# View workflow
903+
graphviz.view(filepath=image_path)
904+
if not keep_dot_file:
905+
os.remove(dot_path)
906+
return image_path
907+
908+
def to_graphviz(self, path: Union[os.PathLike, str]):
909+
"""Saves the workflow to a GraphViz file."""
910+
return self._api.work_flow_export_graphviz(self, str(path))
911+
847912
def __del__(self):
848913
try:
849914
if hasattr(self, "_internal_obj"):

tests/test_workflow.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,67 @@
1+
import os
2+
13
import numpy as np
24
import pytest
35
import platform
46

57
import ansys.dpf.core.operators as op
68
import conftest
79
from ansys import dpf
10+
from ansys.dpf.core import misc
11+
12+
if misc.module_exists("graphviz"):
13+
HAS_GRAPHVIZ = True
14+
else:
15+
HAS_GRAPHVIZ = False
816

917

1018
def test_create_workflow(server_type):
1119
wf = dpf.core.Workflow(server=server_type)
1220
assert wf._internal_obj
1321

1422

23+
@pytest.fixture()
24+
def remove_dot_file(request):
25+
"""Cleanup a testing directory once we are finished."""
26+
27+
dot_path = os.path.join(os.getcwd(), "test.dot")
28+
png_path = os.path.join(os.getcwd(), "test.png")
29+
png_path1 = os.path.join(os.getcwd(), "test1.png")
30+
31+
def remove_files():
32+
if os.path.exists(dot_path):
33+
os.remove(os.path.join(os.getcwd(), dot_path))
34+
if os.path.exists(png_path):
35+
os.remove(os.path.join(os.getcwd(), png_path))
36+
if os.path.exists(png_path1):
37+
os.remove(os.path.join(os.getcwd(), png_path1))
38+
39+
request.addfinalizer(remove_files)
40+
41+
42+
@pytest.mark.skipif(not HAS_GRAPHVIZ, reason="Please install pyvista")
43+
def test_workflow_view(server_in_process, remove_dot_file):
44+
pre_wf = dpf.core.Workflow(server=server_in_process)
45+
pre_op = dpf.core.operators.utility.forward(server=server_in_process)
46+
pre_wf.add_operator(pre_op)
47+
pre_wf.set_input_name("prewf_input", pre_op.inputs.any)
48+
pre_wf.set_output_name("prewf_output", pre_op.outputs.any)
49+
50+
wf = dpf.core.Workflow(server=server_in_process)
51+
forward_op = dpf.core.operators.utility.forward(server=server_in_process)
52+
wf.add_operator(forward_op)
53+
wf.set_input_name("wf_input", forward_op.inputs.any)
54+
wf.set_output_name("wf_output", forward_op.outputs.any)
55+
56+
wf.connect_with(pre_wf, {"prewf_output": "wf_input"})
57+
wf.view(off_screen=True, title="test1")
58+
assert not os.path.exists("test1.dot")
59+
assert os.path.exists("test1.png")
60+
wf.view(off_screen=True, save_as="test.png", keep_dot_file=True)
61+
assert os.path.exists("test.dot")
62+
assert os.path.exists("test.png")
63+
64+
1565
def test_connect_field_workflow(server_type):
1666
wf = dpf.core.Workflow(server=server_type)
1767
wf.progress_bar = False

0 commit comments

Comments
 (0)