diff --git a/python_scripts/run_simstadt_from_python/run_simstadt_workflow.py b/python_scripts/run_simstadt_from_python/run_simstadt_workflow.py new file mode 100644 index 0000000000000000000000000000000000000000..cc3251ade617c4983eb06f5faa14202ec3ad46bb --- /dev/null +++ b/python_scripts/run_simstadt_from_python/run_simstadt_workflow.py @@ -0,0 +1,198 @@ +r"""Tries to find simstadt, find a workflowstep, and run it with the specified +CityGMLs. + +It could be useful in order to run the same workflows with many different CityGMLs, or with slightly different +parameters. + +User config part will probably need to be updated. +Corresponding workflows can be defined and saved in the SimStadt GUI. + + +Example output with the current configuration: + + # SimStadt found in C:\Users\eric.duminil\Desktop\SimStadt_0.10.0-SNAPSHOT_master_20230601_607b426 + + # Preparing 'Gruenbuehl_PhotovoltaicPotential&Financial' workflow (a.step): + * Adding Gruenbuehl_LOD2_ALKIS_1010.gml + * Adding Gruenbuehl_LOD2_ALKIS_1010_2buildings.gml + + # Launching Gruenbuehl_PhotovoltaicPotential&Financial: + Workflow finished succesfuly! + + # Listing written files : + * 'Gruenbuehl.proj\a.step\a.step\a.step\a.step\a.step\hourly_GHI_DHI_Ta.prn' + * 'Gruenbuehl.proj\a.step\a.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_2buildings_pvgis_SARAH_2005_2016_Hay_pv_potential.csv' + * 'Gruenbuehl.proj\a.step\a.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_pvgis_SARAH_2005_2016_Hay_pv_potential.csv' + + + # Preparing 'Gruenbuehl_HeatDemand' workflow (b.step): + * Adding Gruenbuehl_LOD2_ALKIS_1010.gml + * Adding Gruenbuehl_LOD2_ALKIS_1010_2buildings.gml + + # Launching Gruenbuehl_HeatDemand: + Workflow finished succesfuly! + + # Listing written files : + * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\hourly_GHI_DHI_Ta.prn' + * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_2buildings_DIN18599_HEATING.csv' + * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_2buildings_DIN18599_HEATING.log' + * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_DIN18599_HEATING.csv' + * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_DIN18599_HEATING.log' +""" + +from pathlib import Path +import logging +import platform +import subprocess +import os +import sys +from xml.etree import ElementTree as et + +logging.basicConfig(format='%(message)s') +# NOTE: Can set to logging.DEBUG for more info +logging.getLogger().setLevel(logging.INFO) + +SCRIPT_DIR = Path(__file__).parent + +# TODO: Possibility to change param in params.xml +# TODO: Possibility to change attribute in physics or usage library +# TODO: Possibility to change attribute in CityGML +# TODO: Parse CSV output + +############################################## +# User config + +SIMSTADT_GLOB = 'Desktop/SimStadt_0.*/' + +REPO_PATH = SCRIPT_DIR.parent.parent / 'simstadt' / 'TestRepository' +PROJECT_NAME = 'Gruenbuehl' +WORKFLOW_FOLDERS = ['a.step', 'b.step'] +CITYGMLS = ['Gruenbuehl_LOD2_ALKIS_1010.gml', 'Gruenbuehl_LOD2_ALKIS_1010_2buildings.gml'] + + +############################################## + +PROJECT_PATH = REPO_PATH / f'{PROJECT_NAME}.proj' +PARAMS = 'params.xml' + + +# TODO: write tests +def find_simstadt(simstadt_glob): + try: + found = next(Path.home().glob(simstadt_glob)) + logging.info("# SimStadt found in %s\n", found) + return found + except StopIteration: + sys.exit(f"Sorry, no SimStadt installation could be found in {simstadt_glob}") + + +def check_paths(repo_path, workflow_path): + if not repo_path.exists(): + sys.exit(f"Sorry, no Repository could be found in {repo_path}") + + if not workflow_path.exists(): + sys.exit(f"Sorry, no workflow could be found in {workflow_path}") + + if not (workflow_path / PARAMS).exists(): + sys.exit(f"Sorry, no workflow is defined in {workflow_path / PARAMS}") + + proj_path = workflow_path.parent + + for gml in proj_path.glob('*.gml'): + logging.debug(" * Available citygml : %s", gml.name) + + +def prepare_workflow(workflow_path, citygmls): + tree = et.parse(workflow_path / PARAMS) + root = tree.getroot() + + name = root.find(".//void[@property='name']/string").text + logging.info("# Preparing '%s' workflow (%s):", name, workflow_path.name) + + citygml_node = root.find(".//void[@property='cityGmlFileNames']") + + if citygml_node: + logging.debug(' Removing old citygmls') + children = list(citygml_node) + + for add_node in children: + citygml_node.remove(add_node) + else: + logging.debug(' Add new XML node') + workflow_node = root.find(".//object[@class='eu.simstadt.workflows.CityGmlWorkflow']") + citygml_node = et.SubElement(workflow_node, 'void') + citygml_node.set('property', 'cityGmlFileNames') + + for citygml in citygmls: + logging.info(" * Adding %s", citygml) + add_node = et.SubElement(citygml_node, 'void') + add_node.set('method', 'add') + gml = et.SubElement(add_node, 'string') + gml.text = citygml + + tree.write(workflow_path / PARAMS) + + return name + + +def get_all_files(folder): + files = [f for f in Path(folder).glob('**/*') if f.is_file() and f.name != PARAMS] + return {f: f.stat().st_mtime for f in files} + + +def simstadt_script(): + if platform.system().lower() == 'windows': + return 'SimStadt.bat' + else: + return './SimStadt.sh' + + +def run_simstadt(simstadt_path, workflow_path, name): + os.chdir(simstadt_path) + try: + logging.info("\n# Launching %s:", name) + result = subprocess.run(' '.join([simstadt_script(), str(workflow_path)]), + text=True, + capture_output=True, + check=True + ) + logging.debug(result.stderr) + logging.info(" Workflow finished succesfuly!\n") + except subprocess.CalledProcessError: + # NOTE: Current SimStadt actually never raises an error. + logging.warning(" Workflow failed!\n") + logging.info(result.stderr) + + +def compare_written_files(repo_path, before, after): + logging.info("# Listing written files :") + for result_file, modification_time in after.items(): + before_time = before.get(result_file, 0) + if modification_time > before_time: + logging.info(" * '%s'", result_file.relative_to(repo_path)) + logging.info("\n") + + +def run_workflow(simstadt_path, workflow_folder, citygmls): + workflow_path = PROJECT_PATH / workflow_folder + + check_paths(REPO_PATH, workflow_path) + + name = prepare_workflow(workflow_path, citygmls) + + before = get_all_files(workflow_path) + run_simstadt(simstadt_path, workflow_path, name) + after = get_all_files(workflow_path) + + compare_written_files(REPO_PATH, before, after) + + +def main(glob, workflows, citygmls): + simstadt_path = find_simstadt(glob) + + for workflow_folder in workflows: + run_workflow(simstadt_path, workflow_folder, citygmls) + + +if __name__ == '__main__': + main(SIMSTADT_GLOB, WORKFLOW_FOLDERS, CITYGMLS)