Coverage for docs / utilities / plugindiagram / template_mermaid.py: 0%
127 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-20 13:33 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-20 13:33 +0000
1"""Generate mermaid code via templetes respectively template-functions."""
3import textwrap
4from pathlib import Path
5import bim2sim
6from bim2sim.plugins import load_plugin
8def split_string_50(text, max_width=50):
9 """Split the text after max. 50 characters."""
10 wraped_text = textwrap.wrap(text, max_width)
11 wraped_text = '\n'.join(wraped_text)
12 return wraped_text
15def generate_task_code(taskname: str = "bim2simtask", # pylint: disable=too-many-arguments
16 module_path: str = "module_path",
17 reads: str = "reads",
18 touches: str = "touches",
19 doc: str = "docstring",
20 reads_touches_vis: bool = True) -> str:
21 """Generate mermaid code representing a bim2sim task.
23 WIP: so the some structure stuff like tpye of diagram is added here, later
24 it should move to def generate_diagram".
26 Args:
27 taskname: name of the bim2sim taks of the plugin, no space allowed
28 module_path: path to the module/task definition
29 reads: input the task uses (from the central state)
30 touches: output the task reply (to the central state)
31 reads_touches_vis: enable/disable the subgraph showing
32 the reads and touches
34 Returns:
35 Mermaid code of an task.
36 WIP: check how to pipe. now it will printed
38 Attention:
39 - Tasknames should be unique, when merge different mermaid
40 templates instances.
42 """
43 # optional reads and touches subgraph, must defined before the templete
44 if reads_touches_vis:
45 code_reads_touches = """
46state{taskname}[("state
47 (reads/touches)")]
48 """
49 code_rt_state = code_reads_touches.format(
50 taskname=taskname)
51 code_rt = code_rt_state
52 # check for reads
53 if reads != ' - ':
54 code_rt_reads = """
55state{taskname} -- {reads} --> t{taskname}
56"""
57 code_rt_reads = code_rt_reads.format(
58 reads=reads,
59 taskname=taskname)
60 code_rt = code_rt + code_rt_reads
61 # check for touches
62 if touches != ' - ':
63 code_rt_touches = """
64t{taskname} -- {touches} --> state{taskname}
65"""
66 code_rt_touches = code_rt_touches.format(
67 touches=touches,
68 taskname=taskname)
69 code_rt = code_rt + code_rt_touches
70 else:
71 code_rt_touches = """direction RL"""
72 code_rt = code_rt + code_rt_touches
73 else:
74 code_rt = ""
76 code_template = """
77subgraph task{taskname}["task {taskname}"]
78 subgraph "" \n
79 t{taskname}["{module_path} \n {taskname}"]
80 ext{taskname}(" {doc} " )
81 end
82{code_rt}
83end
84 """
85 code = code_template.format(taskname=taskname,
86 module_path=module_path,
87 doc=doc,
88 code_rt=code_rt)
89 return code
92def generate_diagram(plugin_infos: list, tasks_infos: list, # pylint: disable=too-many-locals
93 central_state: bool = False) -> str:
94 """Print mermaid code of the whole task structure of one bim2sim plugin.
96 The plugin structure is fix: next plugin is connected to the plugin before.
98 Args:
99 plugin_infos: information about the whole plugin [name, module, .. ]
100 tasks: list of infos of tasks [{name, reads, touches ...}, {...}]
101 central_state: bool
102 True: reads and touches connected the the central state
103 False: reads and touches includes in task element
104 """
105 # header of the mermaid diagram
106 plugin_name = plugin_infos['name']
107 digram_header = """---
108title: plugin {plugin_name}
109---
110flowchart TB
111 """
112 mermaid_code = digram_header.format(plugin_name=plugin_name)
114 # task elements of the mermaid diagram
115 if central_state:
117 state_code = """
118state[("state:
119project
120data storage")]
121"""
122 mermaid_code = mermaid_code + state_code
124 for task_infos in tasks_infos:
125 taskname = task_infos['name']
126 reads = task_infos['reads']
127 touches = task_infos['touches']
128 task_code = generate_task_code(taskname=taskname,
129 module_path=task_infos['module_path'],
130 doc=task_infos['doc_first_sentence'],
131 reads_touches_vis=False)
132 mermaid_code = mermaid_code + task_code
133 # connetion reads and touches of the task to the state
134 code_connection_state = ''
135 if touches != ' - ':
136 code_connection_state_touches = """
137t{taskname} -- {touches} --> state\n"""
138 code_connection_state_touches = code_connection_state_touches.format(
139 taskname=taskname,
140 touches=touches)
141 code_connection_state += code_connection_state_touches
143 if reads != ' - ':
144 code_connection_state_reads = """
145state -- {reads} --> t{taskname} \n"""
146 code_connection_state_reads = code_connection_state_reads.format(
147 taskname=taskname,
148 reads=reads)
149 code_connection_state += code_connection_state_reads
151 mermaid_code = mermaid_code + code_connection_state
152 # if every task has their own state visualisation
153 else:
154 for task_infos in tasks_infos:
155 task_code = generate_task_code(taskname=task_infos['name'],
156 module_path=task_infos['module_path'],
157 reads=task_infos['reads'],
158 touches=task_infos['touches'],
159 doc=task_infos['doc_first_sentence'],
160 reads_touches_vis=True)
161 mermaid_code = mermaid_code + task_code
162 # state element
164 # connections of the task elements of the mermaid diagram
165 code_connection_templ = """task{taskname_from} --> task{taskname_to} \n"""
167 code_connections = ''
168 for i in range(len(tasks_infos) - 1):
169 code_connection = code_connection_templ.format(
170 taskname_from=tasks_infos[i]['name'],
171 taskname_to=tasks_infos[i+1]['name'])
172 code_connections = code_connections + code_connection
174 mermaid_code = mermaid_code + code_connections
176 return mermaid_code
179def write_file(mermaid_code: str, filename: str):
180 """Create a file including mermaid code.
182 Args:
183 mermaid_code: complete mermaid code which represents the diagram
184 filename: name of the source code file of the figure
185 """
186 with open(filename, "w", encoding="utf-8") as f:
187 f.write(mermaid_code)
190def get_plugin_infos(plugin) -> dict:
191 """Get plugin infos, like name.
193 Args:
194 project: Project object
196 Return:
197 dict (name, module)
198 - name of the plugin
199 - module the plugin is integrated
200 """
201 plugin_name = plugin.name
202 plugin_module = plugin.__module__
204 plugin_info = {'name': plugin_name, 'module': plugin_module}
205 return plugin_info
208def get_task_infos(plugin) -> list:
209 """Get information of the task of the plugin.
211 Args:
212 project: Project object
214 Return:
215 list of task names
216 """
217 tasks = plugin.default_tasks
218 task_infos = []
219 for task in tasks:
220 name = task.__name__
222 if len(task.reads) == 0:
223 reads = ' - '
224 else:
225 reads = ', '.join(task.reads)
227 if len(task.touches) == 0:
228 touches = ' - '
229 else:
230 touches = ', '.join(task.touches)
232 doc = task.__doc__
233 doc_first_sentence = str(doc).replace("\n", "").replace(" ", " ")
234 doc_first_sentence = doc_first_sentence.split(".", maxsplit=1)[0]
235 doc_first_sentence = doc_first_sentence + '.'
236 doc_first_sentence = split_string_50(doc_first_sentence)
238 module = task.__module__
239 module_list = str(module).split('.')
240 path_list = module_list[:-1]
241 path_list_arrow = [str(item) + ' > ' for item in path_list]
242 # add line break after third item
243 if len(path_list_arrow) > 3:
244 path_list_arrow[2] = str(path_list_arrow[2]) + '\n'
245 # join list items into a string
246 path_list_str = ''.join(path_list_arrow)
248 info = {'name': name, 'reads': reads, 'touches': touches,
249 'doc': doc, 'doc_first_sentence': doc_first_sentence,
250 'module_path': path_list_str}
251 task_infos.append(info)
252 return task_infos
255def generate_plugin_structure_fig(path_file: str,
256 plugin_name: str,
257 central_state: bool = False):
258 """Generate mermaid code visulzing the task structure of a plugin.
260 This generated mermaid code will written into a file (path_name).
261 To generate a figure, pls use:
262 - https://mermaid.live
263 - mmdc (must installed on your system)
264 - https://github.com/mermaid-js/mermaid-cli
265 - eg. mmdc -i input.mmd -o output.png -t dark -b transparent
266 - code can past into github issues (add ```mermaid code ```)
267 - code can used in Sphinx docuemtation (check for howtos)
268 - code can used in org-mode of emacs (check for howtos)
269 Args:
270 path_file: absolute path to the file (saves mermaid code)
271 plugin_name: name of the choosen plugin - string
272 default value: 'teaser'
273 central_state: bool
274 True: reads and touches connected the the central state
275 False: reads and touches includes in task element
277 Return:
278 nothing, code is witten into the defined file
280 """
281 try:
282 plugin = load_plugin(plugin_name)
283 plugin_infos = get_plugin_infos(plugin)
284 task_infos = get_task_infos(plugin)
285 path_name = Path(path_file) # should import the path for all os
286 write_file(generate_diagram(plugin_infos, task_infos,
287 central_state=central_state),
288 path_name)
289 print('run successful: \nmermaid code was save to:\n'
290 + str(path_name))
292 except ModuleNotFoundError as e:
293 print(e)
294 print("Pls, choose a plugin_name like: \n"
295 + " - 'teaser'\n"
296 + " - 'energyplus'\n"
297 + " - 'aixlib'\n"
298 # + " - 'cfd'\n"
299 + " - 'HKESim'\n"
300 + " - 'lca'\n")
302 except FileNotFoundError as e:
303 print(e)
304 print("Pls choose an existing (absolut) path.")
305 print("At the end of the path add the filename.")
308if __name__ == '__main__':
310 img_folder = (
311 Path(bim2sim.__file__).parent.parent /
312 'docs/source/img')
313 # Examples 9
314 # setup simple plugin, here template decentral state
315 # visualisation
316 PATH_NAME = (
317 img_folder /
318 'dynamic/plugindiagram/ifccheck_structure_decentral_state.mmd')
319 generate_plugin_structure_fig(PATH_NAME,
320 plugin_name='IFCCheck',
321 central_state=False)
322 # Examples 8
323 # setup simple plugin, here template decentral state
324 # visualisation
325 PATH_NAME = (
326 img_folder /
327 "dynamic/plugindiagram/template_structure_decentral_state.mmd")
328 generate_plugin_structure_fig(PATH_NAME,
329 plugin_name='Template',
330 central_state=False)
331 # Examples 7
332 # setup simple plugin, here lca decentral state
333 # visualisation
334 PATH_NAME = (
335 img_folder /
336 "dynamic/plugindiagram/lca_structure_decentral_state.mmd")
337 generate_plugin_structure_fig(PATH_NAME,
338 plugin_name='lca',
339 central_state=False)
340 # Examples 6
341 # setup simple plugin, here HKESim decentral state
342 # visualisation
343 PATH_NAME = (
344 img_folder /
345 "dynamic/plugindiagram/HKESim_structure_decentral_state.mmd")
346 generate_plugin_structure_fig(PATH_NAME,
347 plugin_name='HKESim',
348 central_state=False)
350 # Examples 5
351 # setup simple plugin, here aixlib decentral state
352 # visualisation
353 PATH_NAME = (
354 img_folder /
355 "dynamic/plugindiagram/aixlib_structure_decentral_state.mmd")
356 generate_plugin_structure_fig(PATH_NAME,
357 plugin_name='aixlib',
358 central_state=False)
359 # Examples 3
360 # setup simple plugin, here EnergyPlus decentral state
361 # visualisation
362 PATH_NAME = (
363 img_folder /
364 "dynamic/plugindiagram/energyplus_structure_decentral_state.mmd")
365 generate_plugin_structure_fig(PATH_NAME,
366 plugin_name='energyplus',
367 central_state=False)
369 # Examples 2
370 # setup simple plugin, here TEASER decentral state
371 # visualisation
372 PATH_NAME = (
373 img_folder /
374 "dynamic/plugindiagram/teaser_structure_decentral_state.mmd")
375 generate_plugin_structure_fig(PATH_NAME,
376 plugin_name='teaser',
377 central_state=False)
379 # Examples 1
380 # setup simple plugin, here TEASER, central state
381 PATH_NAME = (
382 img_folder /
383 "dynamic/plugindiagram/teaser_structure_central_state.mmd")
384 generate_plugin_structure_fig(PATH_NAME,
385 plugin_name='teaser',
386 central_state=True)
388 # # Examples 4 - not working, because cfd plugin not included since 0.2.1
389 # # setup simple plugin, here cfd decentral state
390 # # visualisation
391 # PATH_NAME = (
392 # img_folder /
393 # "dynamic/plugindiagram/cfd_structure_decentral_state.mmd")
394 # generate_plugin_structure_fig(PATH_NAME,
395 # plugin_name='cfd',
396 # central_state=False)