123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- from pypdevs.DEVS import CoupledDEVS, Port
- class FlowchartGenerator:
- def __init__(self, model: CoupledDEVS) -> None:
- self.model = model
- self._tab_level: int = 0
- self._result: str = ""
- def _add(self, text: str) -> None:
- """ Add text to the result, with the appropriate tab level"""
- self._result += ('\t' * self._tab_level) + text + '\n'
- def _tab(self):
- """ Increase tab level """
- self._tab_level += 1
- def _untab(self):
- """ Decrease tab level """
- self._tab_level = max(0, self._tab_level - 1)
- @staticmethod
- def _get_port_name(port: Port) -> (str, str):
- """ Output port names, first element is id, second is shorthand name"""
- names = [port.name]
- parent = port.host_DEVS
- while parent is not None:
- names.append(parent.name)
- try:
- parent = parent.host_DEVS
- except AttributeError:
- parent = None
- identifier = '_'.join(reversed(names))
- shorthand = names[0]
- return identifier, shorthand
- def _add_component(self, component):
- self._add(f"subgraph {component.name}")
- self._tab()
- for port in set(component.IPorts) | set(component.OPorts):
- port_name = self._get_port_name(port)
- self._add(f"{port_name[0]}(\"{port_name[1]}\")")
- # Create invisible links between the ports
- for i, iport in enumerate(component.IPorts):
- iport_name = self._get_port_name(iport)
- if len(component.OPorts) <= i:
- break
- oport_name = self._get_port_name(component.OPorts[i])
- self._add(f"{iport_name[0]} ~~~ {oport_name[0]}")
- if hasattr(component, "component_set"):
- for sub_component in component.component_set:
- self._add_component(sub_component)
- self._untab()
- self._add(f"end")
- self._add_component_connections(component)
- def _add_component_connections(self, component):
- # Go over all connections and connect them to the appropriate ends.
- for port_out in component.OPorts + component.IPorts:
- port_out_name = self._get_port_name(port_out)
- for port_in in port_out.outline:
- port_in_name = self._get_port_name(port_in)
- # Add this connection
- self._add(f"{port_out_name[0]} --> {port_in_name[0]}")
- def generate(self, orientation: str = "LR") -> str:
- """
- Generates a mermaid flowchart for your coupled-DEVS model.
- :return: a string containing the mermaid flowchart.
- """
- self._result = "" # clear previous result
- self._add("---")
- self._add(f"title: {self.model.name}")
- self._add("---")
- self._add(f"flowchart {orientation}")
- self._tab()
- # Declare all submodels with their ports
- for component in self.model.component_set:
- self._add_component(component)
- self._untab()
- return self._result
- def generate_file(self, filepath: str = "flowchart.md", orientation: str = "LR") -> None:
- """ Generates a flowchart for a DEVS model and writes it to a file."""
- try:
- with open(filepath, 'w', encoding='utf-8') as file:
- file.write("```mermaid\n")
- file.write(self.generate(orientation))
- file.write("```")
- except OSError as e:
- print(f"Error writing to file: {e}")
- raise
- if __name__ == '__main__':
- from devs_models.fischertechnik_factory import FischertechnikFactory
- mdl = FischertechnikFactory("FischertechnikFactory")
- fg = FlowchartGenerator(mdl)
- chart = fg.generate(orientation="LR")
- print(chart)
|