render.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import argparse
  2. import sys
  3. import subprocess
  4. import multiprocessing
  5. from lib.os_tools import *
  6. from lib.builder import *
  7. from sccd.compiler.utils import FormattedWriter
  8. from sccd.runtime.statecharts_core import *
  9. if __name__ == '__main__':
  10. parser = argparse.ArgumentParser(
  11. description="Render statecharts as SVG images.")
  12. parser.add_argument('path', metavar='PATH', type=str, nargs='*', help="Models to render. Can be a XML file or a directory. If a directory, it will be recursively scanned for XML files.")
  13. parser.add_argument('--build-dir', metavar='DIR', type=str, default='build', help="As a first step, input XML files first must be compiled to python files. Directory to store these files. Defaults to 'build'")
  14. parser.add_argument('--output-dir', metavar='DIR', type=str, default='', help="Directory for SVG rendered output. Defaults to '.' (putting the SVG files with the XML source files)")
  15. parser.add_argument('--keep-smcat', action='store_true', help="Whether to NOT delete intermediary SMCAT files after producing SVG output. Default = off (delete files)")
  16. parser.add_argument('--no-svg', action='store_true', help="Don't produce SVG output. This option only makes sense in combination with the --keep-smcat option. Default = off")
  17. parser.add_argument('--pool-size', metavar='INT', type=int, default=multiprocessing.cpu_count()+1, help="Number of worker processes. Default = CPU count + 1.")
  18. args = parser.parse_args()
  19. builder = Builder(args.build_dir)
  20. srcs = get_files(args.path, filter=filter_xml)
  21. if len(srcs):
  22. if not args.no_svg:
  23. try:
  24. subprocess.run(["state-machine-cat", "-h"], capture_output=True)
  25. except:
  26. print("Failed to run 'state-machine-cat'. Make sure this application is installed on your system.")
  27. exit()
  28. else:
  29. print("No input files specified.")
  30. print()
  31. parser.print_usage()
  32. def process(src):
  33. module = builder.build_and_load(src)
  34. model = module.Model()
  35. # Produce an output file for each class in the src file
  36. for class_name, _class in model.classes.items():
  37. target_path = lambda ext: os.path.join(args.output_dir, dropext(src)+'_'+class_name+ext)
  38. smcat_target = target_path('.smcat')
  39. svg_target = target_path('.svg')
  40. make_dirs(smcat_target)
  41. f = open(smcat_target, 'w')
  42. w = FormattedWriter(f)
  43. sc = _class().statechart
  44. def name_to_label(name):
  45. label = name.split('/')[-1]
  46. return label if len(label) else "root"
  47. def name_to_name(name):
  48. return name.replace('/','_')
  49. class PseudoState:
  50. def __init__(self, name):
  51. self.name = name
  52. class PseudoTransition:
  53. def __init__(self, source, targets):
  54. self.source = source
  55. self.targets = targets
  56. self.trigger = None
  57. transitions = []
  58. def write_state(s, hide=False):
  59. if not hide:
  60. w.write(name_to_name(s.name))
  61. w.extendWrite(' [label="')
  62. w.extendWrite(name_to_label(s.name))
  63. w.extendWrite('"')
  64. if isinstance(s, ParallelState):
  65. w.extendWrite(' type=parallel')
  66. elif isinstance(s, HistoryState):
  67. w.extendWrite(' type=history')
  68. elif isinstance(s, DeepHistoryState):
  69. w.extendWrite(' type=deephistory')
  70. else:
  71. w.extendWrite(' type=regular')
  72. w.extendWrite(']')
  73. if s.children:
  74. if not hide:
  75. w.extendWrite(' {')
  76. w.indent()
  77. if s.default_state:
  78. w.write(name_to_name(s.name)+'_initial [type=initial],')
  79. transitions.append(PseudoTransition(source=PseudoState(s.name+'/initial'), targets=[s.default_state]))
  80. for i, c in enumerate(s.children):
  81. write_state(c)
  82. w.extendWrite(',' if i < len(s.children)-1 else ';')
  83. if not hide:
  84. w.dedent()
  85. w.write('}')
  86. transitions.extend(s.transitions)
  87. write_state(sc.root, hide=True)
  88. ctr = 0
  89. for t in transitions:
  90. label = ""
  91. if t.trigger and t.trigger.name:
  92. label = (t.trigger.port + '.' if t.trigger.port else '') + t.trigger.name
  93. if len(t.targets) == 1:
  94. w.write(name_to_name(t.source.name) + ' -> ' + name_to_name(t.targets[0].name))
  95. if label:
  96. w.extendWrite(': '+label)
  97. w.extendWrite(';')
  98. else:
  99. w.write(name_to_name(t.source.name) + ' -> ' + ']split'+str(ctr))
  100. if label:
  101. w.extendWrite(': '+label)
  102. w.extendWrite(';')
  103. for tt in t.targets:
  104. w.write(']split'+str(ctr) + ' -> ' + name_to_name(tt.name))
  105. w.extendWrite(';')
  106. ctr += 1
  107. f.close()
  108. if args.keep_smcat:
  109. print("Wrote "+smcat_target)
  110. if not args.no_svg:
  111. subprocess.run(["state-machine-cat", smcat_target, "-o", svg_target])
  112. print("Wrote "+svg_target)
  113. if not args.keep_smcat:
  114. os.remove(smcat_target)
  115. with multiprocessing.Pool(processes=args.pool_size) as pool:
  116. print("Created a pool of %d processes."%args.pool_size)
  117. pool.map(process, srcs)