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

1"""Generate mermaid code via templetes respectively template-functions.""" 

2 

3import textwrap 

4from pathlib import Path 

5import bim2sim 

6from bim2sim.plugins import load_plugin 

7 

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 

13 

14 

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. 

22 

23 WIP: so the some structure stuff like tpye of diagram is added here, later 

24 it should move to def generate_diagram". 

25 

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 

33 

34 Returns: 

35 Mermaid code of an task. 

36 WIP: check how to pipe. now it will printed 

37 

38 Attention: 

39 - Tasknames should be unique, when merge different mermaid 

40 templates instances. 

41 

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 = "" 

75 

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 

90 

91 

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. 

95 

96 The plugin structure is fix: next plugin is connected to the plugin before. 

97 

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) 

113 

114 # task elements of the mermaid diagram 

115 if central_state: 

116 

117 state_code = """ 

118state[("state: 

119project 

120data storage")] 

121""" 

122 mermaid_code = mermaid_code + state_code 

123 

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 

142 

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 

150 

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 

163 

164 # connections of the task elements of the mermaid diagram 

165 code_connection_templ = """task{taskname_from} --> task{taskname_to} \n""" 

166 

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 

173 

174 mermaid_code = mermaid_code + code_connections 

175 

176 return mermaid_code 

177 

178 

179def write_file(mermaid_code: str, filename: str): 

180 """Create a file including mermaid code. 

181 

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) 

188 

189 

190def get_plugin_infos(plugin) -> dict: 

191 """Get plugin infos, like name. 

192 

193 Args: 

194 project: Project object 

195 

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__ 

203 

204 plugin_info = {'name': plugin_name, 'module': plugin_module} 

205 return plugin_info 

206 

207 

208def get_task_infos(plugin) -> list: 

209 """Get information of the task of the plugin. 

210 

211 Args: 

212 project: Project object 

213 

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__ 

221 

222 if len(task.reads) == 0: 

223 reads = ' - ' 

224 else: 

225 reads = ', '.join(task.reads) 

226 

227 if len(task.touches) == 0: 

228 touches = ' - ' 

229 else: 

230 touches = ', '.join(task.touches) 

231 

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) 

237 

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) 

247 

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 

253 

254 

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. 

259 

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 

276 

277 Return: 

278 nothing, code is witten into the defined file 

279 

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)) 

291 

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") 

301 

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.") 

306 

307 

308if __name__ == '__main__': 

309 

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) 

349 

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) 

368 

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) 

378 

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) 

387 

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)