|
| 1 | +''' |
| 2 | +Santiago Montagud |
| 3 | +ESI Group |
| 4 | +''' |
| 5 | + |
| 6 | +import tkinter as tk |
| 7 | +from tkinter import messagebox |
| 8 | +from tkinter import filedialog |
| 9 | +from pyautomationml import PyAutomationML |
| 10 | +from pyautomationml import AmlElement |
| 11 | +from pyautomationml import Element |
| 12 | +from lxml import etree |
| 13 | +import csv |
| 14 | +import os |
| 15 | +import json |
| 16 | +import datetime |
| 17 | + |
| 18 | +class AML_GUI_App: |
| 19 | + def __init__(self, root): |
| 20 | + self.root = root |
| 21 | + self.root.title("Checkbox Selection") |
| 22 | + # Graphical configuration |
| 23 | + #self.root.geometry("600x400") |
| 24 | + |
| 25 | + # parse configuration file |
| 26 | + self.mainfolder = os.getcwd() |
| 27 | + Configfolder = os.path.join(self.mainfolder, "src") |
| 28 | + Config_txt_file_name = 'Config.json' |
| 29 | + config_file_path = os.path.join(Configfolder, Config_txt_file_name) |
| 30 | + self.parse_config_file(config_file_path) |
| 31 | + |
| 32 | + self.AML_master_path = os.path.join(self.mainfolder, self.automationml_files_folder, self.master_file_name) |
| 33 | + self.AutomationMLtemplate_path = os.path.join(self.mainfolder, self.automationml_files_folder, self.template_file_name) |
| 34 | + self.W_IDs_txt_path = os.path.join(self.mainfolder, self.sources_file, self.W_IDs_txt_file_name) |
| 35 | + self.PathOfDoE = 'Not DoE selected' # Default value |
| 36 | + self.workflow_column_name = "Workflow Options" # Name to be shown on the App |
| 37 | + self.outputs_name = "Output Options" # Name to be shown on the App |
| 38 | + self.selections = {self.workflow_column_name: None, self.outputs_name: []} |
| 39 | + |
| 40 | + #Names of Internal Elements |
| 41 | + self.Master_WorkflowsIE_name = 'Workflows' # Interal element where the IDs of the workflows are stored to avoid repetition of IDs |
| 42 | + self.Workflow_InformationIE_name = 'Information' |
| 43 | + self.Workflow_DefinitionIE_name = 'Workflow_definition' |
| 44 | + self.Information_Sequence_type_name = 'Sequence_type' |
| 45 | + self.InputsIE_name = 'Inputs' |
| 46 | + self.DoEIE_name = 'DoE' |
| 47 | + self.OutputsIE_name = 'Outputs' |
| 48 | + self.PhasesIE_name = 'Phases' |
| 49 | + self.Software_softwareID_name = 'software_ID' |
| 50 | + self.Software_Parameters_values_name = 'values' |
| 51 | + self.Software_Parameters_OutputFile_name = 'output_file' |
| 52 | + |
| 53 | + # Create the titles, bottons, etc. |
| 54 | + self.create_widgets() |
| 55 | + |
| 56 | + # Read configuration file and store information in internal variables |
| 57 | + # strings are to be modified in the script and in the config file with the same string characters |
| 58 | + def parse_config_file(self, config_file_path): |
| 59 | + with open(config_file_path, "r") as json_file: |
| 60 | + data = json.load(json_file) |
| 61 | + self.master_file_name = data['master_file_name'] |
| 62 | + self.template_file_name = data['template_file_name'] |
| 63 | + self.Workflow_options = data['Workflow_options'] |
| 64 | + self.output_options = data['output_options'] |
| 65 | + self.W_IDs_txt_file_name = data['W_IDs_txt_file_name'] |
| 66 | + self.automationml_files_folder = data['automationml_files_folder'] |
| 67 | + self.sources_file = data['sources_file'] |
| 68 | + self.Predefined_workflows = data['Predefined_workflows'] |
| 69 | + self.Models = data['Models'] |
| 70 | + |
| 71 | + def create_AML_element(self, parent, Name, text=None): |
| 72 | + # default: |
| 73 | + context_global = {} |
| 74 | + context_local = {} |
| 75 | + # Define the tag, attributes, and text for the new element |
| 76 | + tag = "{http://www.dke.de/CAEX}InternalElement" |
| 77 | + attribute = {"Name": Name} |
| 78 | + new_aml_element = Element(tag, attrib=attribute, text=text, context_global=context_global, context_local=context_local) |
| 79 | + parent.append(new_aml_element.__element__) |
| 80 | + |
| 81 | + def add_attribute_on_AML_element(self, Internal_Element_obj, attribute_name): |
| 82 | + new_attribute = etree.Element("Attribute", Name=attribute_name, AttributeDataType="xs:string") |
| 83 | + Internal_Element_obj.append(new_attribute) |
| 84 | + |
| 85 | + |
| 86 | + def get_value_AML_element(self, AML, InternalElement, attribute_name): |
| 87 | + IE = AML.root.find(".//*[@Name={}]".format(InternalElement)) |
| 88 | + Atrbt = IE.find(".//*[@Name={}]".format(attribute_name)) |
| 89 | + value = Atrbt.__element__.Value |
| 90 | + return value |
| 91 | + |
| 92 | + def get_max_value_AML_workflow(self,AML, InternalElement): |
| 93 | + IE = AML.find(f"./*[@Name='{InternalElement}']") |
| 94 | + max_value = 0 |
| 95 | + for atribute in IE.iterchildren(): |
| 96 | + value = int(atribute.__element__.attrib['Name'].replace("w", "")) |
| 97 | + if value > max_value: |
| 98 | + max_value = value |
| 99 | + return max_value |
| 100 | + |
| 101 | + def find_all_children_by_name(self, element, name, matching_elements=None): |
| 102 | + if matching_elements is None: |
| 103 | + matching_elements = [] |
| 104 | + |
| 105 | + if element.get('Name') == name: |
| 106 | + matching_elements.append(element) |
| 107 | + |
| 108 | + for child in element.iterchildren(): |
| 109 | + self.find_all_children_by_name(child, name, matching_elements) |
| 110 | + |
| 111 | + return matching_elements |
| 112 | + |
| 113 | + def write_value_on_AML_attribute(self, InternalElementObj, attribute_name, value): |
| 114 | + Atrbt = InternalElementObj.find(f"./*[@Name='{attribute_name}']") |
| 115 | + Atrbt.__element__.Value = value |
| 116 | + |
| 117 | + def write_ID_w_on_txt(self, W_IDs_txt_path, value): |
| 118 | + with open(W_IDs_txt_path, 'a') as f: |
| 119 | + f.write(str(value) + '\n') |
| 120 | + |
| 121 | + def create_widgets(self): |
| 122 | + #Select DoE |
| 123 | + self.frame_Select_DoE = tk.Frame(self.root) |
| 124 | + self.frame_Select_DoE.grid(row=0, column=0, padx=10, pady=5, sticky="n") |
| 125 | + self.select_DoE_button = tk.Button(self.frame_Select_DoE, text="Select DoE", command=self.select_DoE) |
| 126 | + self.select_DoE_button.pack() |
| 127 | + |
| 128 | + #Select Workflow |
| 129 | + self.workflow_frame = tk.Frame(self.root) |
| 130 | + self.workflow_frame.grid(row=0, column=1, padx=10, pady=5, sticky="n") |
| 131 | + self.workflow_label = tk.Label(self.workflow_frame, text=self.workflow_column_name) |
| 132 | + self.workflow_label.pack() |
| 133 | + self.show_radio_buttons(self.Workflow_options, self.workflow_frame, self.workflow_column_name) |
| 134 | + |
| 135 | + #Select Outputs |
| 136 | + self.outputs_frame = tk.Frame(self.root) |
| 137 | + self.outputs_frame.grid(row=0, column=2, padx=10, pady=5, sticky="n") |
| 138 | + self.outputs_label = tk.Label(self.outputs_frame, text=self.outputs_name) |
| 139 | + self.outputs_label.pack() |
| 140 | + self.show_checkboxes(self.output_options, self.outputs_frame, self.outputs_name) |
| 141 | + |
| 142 | + #Save |
| 143 | + self.save_AML_button = tk.Button(self.root, text="Save to AutomationML", command=self.save_AML) |
| 144 | + self.save_AML_button.grid(row=1, columnspan=2, padx=10, pady=10, sticky="e") |
| 145 | + |
| 146 | + def select_DoE(self): |
| 147 | + file_path = filedialog.askopenfilename(filetypes=[("CSV Files", "*.csv")]) |
| 148 | + if file_path: |
| 149 | + try: |
| 150 | + with open(file_path, 'r') as file: |
| 151 | + csv_reader = csv.reader(file) |
| 152 | + data = [next(csv_reader) for _ in range(3)] |
| 153 | + print(data) # Update WW with the data or do other processing |
| 154 | + |
| 155 | + # Save the path of the DoE file |
| 156 | + self.PathOfDoE = file_path |
| 157 | + print("DoE Path:", self.PathOfDoE) |
| 158 | + |
| 159 | + except Exception as e: |
| 160 | + print("Error reading CSV file:", e) |
| 161 | + |
| 162 | + def show_radio_buttons(self, options, frame, set_name): |
| 163 | + var = tk.StringVar() |
| 164 | + for option in options: |
| 165 | + radio_button = tk.Radiobutton( |
| 166 | + frame, text=option, variable=var, value=option |
| 167 | + ) |
| 168 | + radio_button.pack(anchor="w", padx=5, pady=2) |
| 169 | + self.selections[set_name] = var |
| 170 | + |
| 171 | + def show_checkboxes(self, options, frame, set_name): |
| 172 | + for option in options: |
| 173 | + var = tk.StringVar(value="") |
| 174 | + checkbox = tk.Checkbutton( |
| 175 | + frame, text=option, variable=var, onvalue=option, offvalue="" |
| 176 | + ) |
| 177 | + checkbox.pack(anchor="w", padx=5, pady=2) |
| 178 | + self.selections[set_name].append(var) |
| 179 | + |
| 180 | + def write_on_AML(self): |
| 181 | + rootmaster = PyAutomationML(self.AML_master_path) |
| 182 | + |
| 183 | + if os.path.exists(self.AutomationMLtemplate_path): |
| 184 | + roottemplate = PyAutomationML(self.AutomationMLtemplate_path) |
| 185 | + else: |
| 186 | + print(f"The path '{self.AutomationMLtemplate_path}' does not exist.") |
| 187 | + |
| 188 | + # Get the root of the master file |
| 189 | + self.master = rootmaster.root.find("./*[@Name='Workflow']") |
| 190 | + |
| 191 | + # Get the root of the AutomationML template |
| 192 | + self.workflow_instance = roottemplate.root.find("./*[@Name='Workflow']") |
| 193 | + |
| 194 | + # get workflow last value |
| 195 | + value = self.get_max_value_AML_workflow(self.master, self.Master_WorkflowsIE_name) |
| 196 | + print(f'Old workflow ID is {value}') |
| 197 | + new_workflow_number = value + 1 |
| 198 | + print(f'New workflow ID is {new_workflow_number}') |
| 199 | + |
| 200 | + # write ID number on txt file (keep an extra copy of the information to avoid misuse) |
| 201 | + self.write_ID_w_on_txt(self.W_IDs_txt_path, new_workflow_number) |
| 202 | + |
| 203 | + # Set AML ID name like 'w + ID number + .aml' |
| 204 | + self.workflow_instance_name = 'w' + str(new_workflow_number) |
| 205 | + print(f'New workflow name is {self.workflow_instance_name}') |
| 206 | + self.template_instance_name = 'w' + str(new_workflow_number) + '.aml' |
| 207 | + print(f'New workflow file name is {self.template_instance_name}') |
| 208 | + self.template_instance_path = os.path.join(self.automationml_files_folder, self.template_instance_name) |
| 209 | + |
| 210 | + # write ID on master file as an attribute in Workflows IE and as an element |
| 211 | + WorkflowsObj = self.master.find(f"./*[@Name='{self.Master_WorkflowsIE_name}']") |
| 212 | + self.add_attribute_on_AML_element(WorkflowsObj, self.workflow_instance_name) |
| 213 | + self.create_AML_element(WorkflowsObj, self.workflow_instance_name) |
| 214 | + Workflow_instance_Obj = WorkflowsObj.find(f".//{{http://www.dke.de/CAEX}}InternalElement[@Name='{self.workflow_instance_name}']") |
| 215 | + self.add_attribute_on_AML_element(Workflow_instance_Obj, 'path') |
| 216 | + |
| 217 | + # write selected workflow type in AutomationML template on information IE on the text of the existing attribute 'Sequence_type' |
| 218 | + InformationIE = self.workflow_instance.find(f"./*[@Name='{self.Workflow_InformationIE_name}']") |
| 219 | + self.write_value_on_AML_attribute(InformationIE, 'ID', self.workflow_instance_name) |
| 220 | + self.write_value_on_AML_attribute(InformationIE, self.Information_Sequence_type_name, self.selections[self.workflow_column_name].get()) |
| 221 | + current_date = datetime.date.today() |
| 222 | + formatted_date = current_date.strftime("%Y/%m/%d") |
| 223 | + self.write_value_on_AML_attribute(InformationIE,'Date', str(formatted_date)) |
| 224 | + |
| 225 | + Info_OutputsIE = InformationIE.find("./*[@Name='Outputs']") |
| 226 | + selected_output_options = [var.get() for var in self.selections[self.outputs_name]] |
| 227 | + self.filtered_selected_output_options = [item for item in selected_output_options if item.strip() != ""] |
| 228 | + for output_element in self.filtered_selected_output_options: |
| 229 | + self.create_AML_element(Info_OutputsIE, output_element) |
| 230 | + |
| 231 | + # write selected workflow information in AutomationML template on Workflow Definition for execution |
| 232 | + |
| 233 | + # write phases when they exist |
| 234 | + Workflow_DefinitionIE = self.workflow_instance.find(f"./*[@Name='{self.Workflow_DefinitionIE_name}']") |
| 235 | + PhasesIE = Workflow_DefinitionIE.find(f"./*[@Name='{self.PhasesIE_name}']") |
| 236 | + # extract the models: |
| 237 | + Selected_Workflow_Simulations = self.Predefined_workflows[self.selections[self.workflow_column_name].get()] |
| 238 | + Preprocess_models = [element for element in self.Models["Preprocess"] if element in Selected_Workflow_Simulations] |
| 239 | + Simulation_models = [element for element in self.Models["Simulation"] if element in Selected_Workflow_Simulations] |
| 240 | + Postprocess_models = [element for element in self.Models["Postprocess"] if element in Selected_Workflow_Simulations] |
| 241 | + # 1a. Preprocess |
| 242 | + Bool_Preproc_models = any(element in self.Models["Preprocess"] for element in self.Predefined_workflows[self.selections[self.workflow_column_name].get()]) |
| 243 | + if Bool_Preproc_models: |
| 244 | + self.create_AML_element(PhasesIE, 'Preprocess') |
| 245 | + PreprocessIE = PhasesIE.find("./*[@Name='Preprocess']") |
| 246 | + #create chain of charaters for HPC: |
| 247 | + chain_string = "" |
| 248 | + for element in Preprocess_models: |
| 249 | + chain_string += f'{element}.run>>' |
| 250 | + chain_string = chain_string.rstrip('>>') |
| 251 | + #add attribute 'Sequence': |
| 252 | + self.add_attribute_on_AML_element(PreprocessIE, 'Sequence') |
| 253 | + #add chain to attribute 'Sequence' |
| 254 | + self.write_value_on_AML_attribute(PreprocessIE, 'Sequence', chain_string) |
| 255 | + #create the phase internal elements |
| 256 | + for element in Preprocess_models: |
| 257 | + self.create_AML_element(PreprocessIE, element) |
| 258 | + #get the element just created |
| 259 | + ElementIE = PreprocessIE.find(f"./*[@Name='{element}']") |
| 260 | + #add ID attribute |
| 261 | + self.add_attribute_on_AML_element(ElementIE, self.Software_softwareID_name) |
| 262 | + #fill in ID attribute |
| 263 | + self.write_value_on_AML_attribute(ElementIE, self.Software_softwareID_name, element) |
| 264 | + #add Parameters IE |
| 265 | + self.create_AML_element(ElementIE, 'Parameters') |
| 266 | + #get Parameters IE |
| 267 | + ParametersIE = ElementIE.find("./*[@Name='Parameters']") |
| 268 | + #add output_file atribute to Parameters IE |
| 269 | + self.add_attribute_on_AML_element(ParametersIE, self.Software_Parameters_OutputFile_name) |
| 270 | + else: |
| 271 | + print("No preprocess") |
| 272 | + |
| 273 | + # 1b. Preprocess |
| 274 | + Bool_Sim_models = any(element in self.Models["Simulation"] for element in self.Predefined_workflows[self.selections[self.workflow_column_name].get()]) |
| 275 | + if Bool_Sim_models: |
| 276 | + self.create_AML_element(PhasesIE, 'Simulations') |
| 277 | + SimulationsIE = PhasesIE.find("./*[@Name='Simulations']") |
| 278 | + #create chain of charaters for HPC: |
| 279 | + chain_string = "" |
| 280 | + for element in Simulation_models: |
| 281 | + chain_string += f'{element}.run>>' |
| 282 | + chain_string = chain_string.rstrip('>>') |
| 283 | + #add it to attribute 'Sequence': |
| 284 | + self.add_attribute_on_AML_element(SimulationsIE, 'Sequence') |
| 285 | + #add chain to attribute 'Sequence' |
| 286 | + self.write_value_on_AML_attribute(SimulationsIE, 'Sequence', chain_string) |
| 287 | + #create the phase internal elements |
| 288 | + idx = 0 |
| 289 | + for idx,element in enumerate(Simulation_models): |
| 290 | + self.create_AML_element(SimulationsIE, element) |
| 291 | + #get the element just created |
| 292 | + ElementIE = SimulationsIE.find(f"./*[@Name='{element}']") |
| 293 | + #add ID attribute |
| 294 | + self.add_attribute_on_AML_element(ElementIE, self.Software_softwareID_name) |
| 295 | + #fill in ID attribute |
| 296 | + self.write_value_on_AML_attribute(ElementIE, self.Software_softwareID_name, element) |
| 297 | + #add Parameters IE |
| 298 | + self.create_AML_element(ElementIE, 'Parameters') |
| 299 | + #get Parameters IE |
| 300 | + ParametersIE = ElementIE.find("./*[@Name='Parameters']") |
| 301 | + #add values atribute to Parameters IE |
| 302 | + self.add_attribute_on_AML_element(ParametersIE, self.Software_Parameters_values_name) |
| 303 | + self.write_value_on_AML_attribute(ParametersIE, 'values', '{doe_row}') |
| 304 | + #add output_file atribute to Parameters IE |
| 305 | + self.add_attribute_on_AML_element(ParametersIE, self.Software_Parameters_OutputFile_name) |
| 306 | + value_field = '{results_folder}/' + element + '_{line_number}.out' |
| 307 | + self.write_value_on_AML_attribute(ParametersIE, self.Software_Parameters_OutputFile_name, value_field) |
| 308 | + if idx > 0: |
| 309 | + #add inputs_file atribute to Parameters IE |
| 310 | + self.add_attribute_on_AML_element(ParametersIE, 'input_file') |
| 311 | + value_field = '{results_folder}/' + Simulation_models[idx-1] + '_{line_number}.out' |
| 312 | + self.write_value_on_AML_attribute(ParametersIE, 'input_file', value_field) |
| 313 | + |
| 314 | + else: |
| 315 | + print("No simulations") |
| 316 | + |
| 317 | + # 1c. Postprocess |
| 318 | + Bool_Postproc_models = any(element in self.Models["Postprocess"] for element in self.Predefined_workflows[self.selections[self.workflow_column_name].get()]) |
| 319 | + if Bool_Postproc_models: |
| 320 | + self.create_AML_element(PhasesIE, 'Postprocess') |
| 321 | + PostprocessIE = PhasesIE.find("./*[@Name='Postprocess']") |
| 322 | + #create chain of charaters for HPC: |
| 323 | + chain_string = "" |
| 324 | + for element in Postprocess_models: |
| 325 | + chain_string += f'{element}.run>>' |
| 326 | + chain_string = chain_string.rstrip('>>') |
| 327 | + #add it to attribute 'Sequence': |
| 328 | + self.add_attribute_on_AML_element(PostprocessIE, 'Sequence') |
| 329 | + #add chain to attribute 'Sequence' |
| 330 | + self.write_value_on_AML_attribute(PostprocessIE, 'Sequence', chain_string) |
| 331 | + #create the phase internal elements |
| 332 | + for element in Postprocess_models: |
| 333 | + self.create_AML_element(PostprocessIE, element) |
| 334 | + #get the element just created |
| 335 | + ElementIE = PostprocessIE.find(f"./*[@Name='{element}']") |
| 336 | + #add ID attribute |
| 337 | + self.add_attribute_on_AML_element(ElementIE, self.Software_softwareID_name) |
| 338 | + #fill in ID attribute |
| 339 | + self.write_value_on_AML_attribute(ElementIE, self.Software_softwareID_name, element) |
| 340 | + #add Parameters IE |
| 341 | + self.create_AML_element(ElementIE, 'Parameters') |
| 342 | + #get Parameters IE |
| 343 | + ParametersIE = ElementIE.find("./*[@Name='Parameters']") |
| 344 | + #add output_file atribute to Parameters IE |
| 345 | + self.add_attribute_on_AML_element(ParametersIE, self.Software_Parameters_OutputFile_name) |
| 346 | + else: |
| 347 | + print("No postprocess") |
| 348 | + |
| 349 | + # write inputs in Workflow_definition |
| 350 | + #find inputs IE |
| 351 | + InputsIE = Workflow_DefinitionIE.find(f"./*[@Name='{self.InputsIE_name}']") |
| 352 | + #find DoE |
| 353 | + DoEIE = InputsIE.find(f"./*[@Name='{self.DoEIE_name}']") |
| 354 | + #write DoE source in source attribute from the user's input |
| 355 | + self.write_value_on_AML_attribute(DoEIE, 'source', self.PathOfDoE) |
| 356 | + #write destination? |
| 357 | + |
| 358 | + # write outputs in Workflow_definition |
| 359 | + OutputsIE = Workflow_DefinitionIE.find(f"./*[@Name='{self.OutputsIE_name}']") |
| 360 | + for output_element in self.filtered_selected_output_options: |
| 361 | + self.add_attribute_on_AML_element(OutputsIE, output_element) |
| 362 | + |
| 363 | + # save all modifications |
| 364 | + roottemplate.save(self.template_instance_path) |
| 365 | + rootmaster.save(self.AML_master_path) |
| 366 | + |
| 367 | + def save_AML(self): |
| 368 | + selected_output_options = [var.get() for var in self.selections[self.outputs_name]] |
| 369 | + self.filtered_selected_output_options = [item for item in selected_output_options if item.strip() != ""] |
| 370 | + |
| 371 | + self.write_on_AML() |
| 372 | + |
| 373 | + print('Data has been saved to the instance AutomationML file: {}'.format(self.template_instance_path)) |
| 374 | + print('Data has been saved to the master AutomationML file: {}'.format(self.AML_master_path)) |
| 375 | + |
| 376 | +if __name__ == "__main__": |
| 377 | + root = tk.Tk() |
| 378 | + app = AML_GUI_App(root) |
| 379 | + root.mainloop() |
| 380 | + |
| 381 | + |
0 commit comments