def __init__(self, parent=None): super().__init__(parent=parent) self.setWindowTitle("About Thermopack") self.setMinimumSize(800, 600) self.setOpenExternalLinks(True) try: file = open(os.path.join(os.getcwd(), "gui", "about.html"), "r") except FileNotFoundError: msg = MessageBox("Error", "Could not open file containing the About Info") msg.exec_() return html = file.read() file.close() self.setHtml(html)
def delete_model_setup(self, name): """ Deletes a model setup :param name: Name of model setup to be deleted """ # Deleting entry in the data dictionary try: del self.data["Model setups"][name] except KeyError: msg = MessageBox("Error", "Could not delete model setup " + name) msg.exec_() return # Closing the composition tab for i in range(self.tabs.count()): if self.tabs.tabText(i)[-len(name):] == name: self.tabs.removeTab(i) break # Removing name from the menu for i in range(self.models_list.count()): if self.models_list.item(i).text() == name: self.models_list.takeItem(i) break
def save_composition(self): """ Saves the selected composition to the session data, and emits a signal to indicate that a composition list is created or edited """ if self.selected_components: if self.selected_components not in self.final_compositions.values( ): if self.set_name_edit.text(): list_name = self.set_name_edit.text() else: list_name = self.set_name_edit.placeholderText() if list_name not in self.data["Component lists"]: component_list = self.selected_components.copy() self.final_compositions[list_name] = component_list id_list = [ self.fluids[name].json_data["ident"] for name in component_list ] self.data["Component lists"][list_name] = { "Names": component_list, "Identities": id_list } self.component_list_updated.emit(list_name, True, list_name) # Clear table and selection self.selected_components_table.setRowCount(0) self.selected_components = [] self.set_component_list_placeholder_name() else: error_msg = "A component list with this name (%s) already exists" % list_name msg = MessageBox("Error", error_msg) self.set_name_edit.undo() msg.exec_() else: error_msg = "This composition is already stored as " + str( list(self.final_compositions.keys())[list( self.final_compositions.values()).index( self.selected_components)]) msg = MessageBox("Error", error_msg) msg.exec_() else: msg = MessageBox("Error", "No components are chosen.") msg.exec_()
def open_file(self, file_path=None): """ Opens and loads an existing JSON-file and populates the main window. :param file_path: Path to JSON-file """ if not file_path: file_dialog = QFileDialog() file_dialog.setWindowTitle("Open File") file_dialog.setDirectory(os.getcwd()) file_dialog.setNameFilter('Text files (*.json)') if file_dialog.exec_() == QFileDialog.Accepted: file_path = file_dialog.selectedFiles()[0] else: return loaded_data = get_json_data(file_path) loaded_component_data = loaded_data["Component lists"] loaded_model_setup_data = loaded_data["Model setups"] loaded_units_data = loaded_data["Units"] loaded_plotting_preferences = loaded_data["Plotting preferences"] # If loaded data contains names which currently exist, an error message will be displayed loaded_data_is_compatible = True for i in range(self.compositions_list.count()): if self.compositions_list.item( i).text() in loaded_component_data.keys(): loaded_data_is_compatible = False name = self.compositions_list.item(i).text() for i in range(self.models_list.count()): if self.models_list.item( i).text() in loaded_model_setup_data.keys(): loaded_data_is_compatible = False name = self.models_list.item(i).text() if not loaded_data_is_compatible: error_msg = "Could not load data from %s. %s already exists. " \ "Either change its name or delete it before trying to load again." \ % (file_path, name) msg = MessageBox("Error", error_msg) msg.exec_() return self.data["Component lists"].update(loaded_component_data) self.data["Model setups"].update(loaded_model_setup_data) self.data["Units"].update(loaded_units_data) self.data["Plotting preferences"].update(loaded_plotting_preferences) self.json_file = file_path self.setWindowTitle(self.windowTitle() + " - " + self.json_file) # Populate menus to the left self.log("Opened and loaded " + self.json_file + " successfully!") # Clear menu for list_name in loaded_component_data.keys(): self.update_component_lists(list_name, is_new=True, old_name=None) for list_name in loaded_model_setup_data.keys(): model_select_widget = ModelSelectWidget(self.data, name=list_name, parent=self) model_select_widget.model_setup_deleted.connect( self.delete_model_setup) model_select_widget.model_name_changed.connect( self.update_model_lists) QListWidgetItem(list_name, parent=self.models_list) self.save()
def calculate(self): """ Calls thermopack's flash functions, and populates the table with the results """ fractions = np.array(self.component_data["Fractions"]) mole_fraction_sum = np.sum(fractions) if abs(mole_fraction_sum - 1.00) > 1e-8: msg_title = "Molar fractions error" msg_text = "Molar fractions have to add up to 1.00. Currently the sum is %s." % mole_fraction_sum msg = MessageBox(msg_title, msg_text) msg.exec_() return else: # Setting the last mol fraction to 1 - the rest of them, to ensure that the total sum is exactly 1 fractions[-1] = 1 - np.sum(fractions[:-1]) self.table.clearContents() self.set_table_units() init_thermopack(self.tp, self.component_data, self.comp_list_name, self.settings) flash_mode = self.flash_mode_selection.currentText() self.tp.set_ph_tolerance(float(self.ph_tol.text())) if flash_mode == "TP": T = float(self.tp_t_input.text()) P = float(self.tp_p_input.text()) # Conversion to standard SI to be used in functions T = self.ureg.Quantity( T, self.units["Temperature"]).to("degK").magnitude P = self.ureg.Quantity(P, self.units["Pressure"]).to("Pa").magnitude # TODO: Need an exception thrown in two_phase_tpflash() to be caught in case calculation fails x, y, beta_vap, beta_liq, phase = self.tp.two_phase_tpflash( T, P, fractions) elif flash_mode == "PS": P = float(self.ps_p_input.text()) S = float(self.ps_s_input.text()) # Unit conversion P = self.ureg.Quantity(P, self.units["Pressure"]).to("Pa").magnitude S = self.ureg.Quantity( S, self.units["Entropy"]).to("J / (degK * mol)").magnitude if self.ps_initial_guess.isChecked(): T = float(self.ps_t_guess.text()) T = self.ureg.Quantity( T, self.units["Temperature"]).to("degK").magnitude else: T = None try: T, x, y, beta_vap, beta_liq, phase = self.tp.two_phase_psflash( press=P, z=fractions, entropy=S, temp=T) except Exception as error: msg = MessageBox("Error", str(error)) msg.exec_() return elif flash_mode == "PH": P = float(self.ph_p_input.text()) H = float(self.ph_h_input.text()) # Convert units P = self.ureg.Quantity(P, self.units["Pressure"]).to("Pa").magnitude H = self.ureg.Quantity( H, self.units["Enthalpy"]).to("J / mol").magnitude if self.ph_initial_guess.isChecked(): T = float(self.ph_t_guess.text()) T = self.ureg.Quantity( T, self.units["Temperature"]).to("degK").magnitude else: T = None try: T, x, y, beta_vap, beta_liq, phase = self.tp.two_phase_phflash( press=P, z=fractions, enthalpy=H, temp=T) except Exception as error: msg = MessageBox("Error", str(error)) msg.exec_() return elif flash_mode == "UV": U = float(self.uv_u_input.text()) V = float(self.uv_v_input.text()) # Unit conversion U = self.ureg.Quantity( U, self.units["Internal energy"]).to("J / mol").magnitude V = self.ureg.Quantity( V, self.units["Specific volume"]).to("m ** 3 / mol").magnitude if self.uv_t_initial_guess.isChecked(): T = float(self.uv_t_guess.text()) T = self.ureg(T, self.units["Temperature"]).to("degK").magnitude else: T = None if self.uv_p_initial_guess.isChecked(): P = float(self.uv_p_guess.text()) P = self.ureg.Quantity( P, self.units["Pressure"]).to("Pa").magnitude else: P = None # TODO: Need an exception thrown in two_phase_uvflash() to be caught in case calculation fails T, P, x, y, beta_vap, beta_liq, phase = self.tp.two_phase_uvflash( z=fractions, specific_energy=U, specific_volume=V, temp=T, press=P) else: return phase_type = self.tp.get_phase_type(phase) LIQUID, VAPOR = 1, 2 is_liq, is_vap = True, True if phase_type == "TWO_PHASE": self.table.horizontalHeaderItem(1).setText("Vapor") self.table.horizontalHeaderItem(2).setText("Liquid") pass elif phase_type == "LIQUID": is_vap = False self.table.horizontalHeaderItem(1).setText("Vapor") self.table.horizontalHeaderItem(2).setText("Liquid") elif phase_type == "VAPOR": is_liq = False self.table.horizontalHeaderItem(1).setText("Vapor") self.table.horizontalHeaderItem(2).setText("Liquid") elif phase_type == "SINGLE": self.table.horizontalHeaderItem(1).setText("Single") self.table.horizontalHeaderItem(2).setText("Single") pass elif phase_type == "MINIMUM_GIBBS": self.table.horizontalHeaderItem(1).setText("Minimum Gibbs") self.table.horizontalHeaderItem(2).setText("Minimum Gibbs") pass elif phase_type == "FAKE": msg = MessageBox( "Error", "Currently no functionality for phase " + phase_type) msg.exec_() elif phase_type == "SOLID": msg = MessageBox( "Error", "Currently no functionality for phase " + phase_type) msg.exec_() else: return component_indices = [ self.tp.getcompindex(comp) for comp in self.component_data["Identities"] ] molecular_weights = [ self.tp.compmoleweight(index) for index in component_indices ] if is_liq: V_liq, dVdT_liq, dVdn_liq = self.tp.specific_volume(T, P, x, phase=LIQUID, dvdt=True, dvdn=True) H_liq, dHdT_liq, dHdP_liq, dHdn_liq = self.tp.enthalpy( T, P, x, phase=LIQUID, dhdt=True, dhdp=True, dhdn=True) S_liq, dSdT_liq, dSdP_liq, dSdn_liq = self.tp.entropy(T, P, x, phase=LIQUID, dsdt=True, dsdp=True, dsdn=True) U_liq, dUdT_liq, dUdV_liq = self.tp.internal_energy_tv(T, V_liq, x, dedt=True, dedv=True) sos_liq = self.tp.speed_of_sound(T, P, x, y, fractions, beta_vap, beta_liq, phase=LIQUID) U_liq = H_liq - P * V_liq G_liq = H_liq - T * S_liq Cp_liq = dHdT_liq Cv_liq = dUdT_liq mol_weight_liq = sum([ x[i] * molecular_weights[i] for i in range(len(molecular_weights)) ]) self.set_table_value("Temperature", "Liq", "degK", self.units["Temperature"], T) self.set_table_value("Pressure", "Liq", "Pa", self.units["Pressure"], P) self.set_table_value("Specific volume", "Liq", "m ** 3 / mol", self.units["Specific volume"], V_liq) self.set_table_value("Internal energy", "Liq", "J / mol", self.units["Internal energy"], U_liq) self.set_table_value("Enthalpy", "Liq", "J / mol", self.units["Enthalpy"], H_liq) self.set_table_value("Entropy", "Liq", "J / (K * mol)", self.units["Entropy"], S_liq) self.set_table_value("Gibbs energy", "Liq", "J / mol", self.units["Gibbs energy"], G_liq) self.set_table_value("Isobar heat capacity", "Liq", "J / (K * mol)", self.units["Isobar heat capacity"], Cp_liq) self.set_table_value("Isochor heat capacity", "Liq", "J / (K * mol)", self.units["Isochor heat capacity"], Cv_liq) self.set_table_value("Speed of sound", "Liq", "m / s", self.units["Speed of sound"], sos_liq) self.set_table_value("Phase fraction", "Liq", "mol / mol", self.units["Phase fraction"], beta_liq) self.set_table_value("Molecular weight", "Liq", "kg / mol", self.units["Molecular weight"], mol_weight_liq) if is_vap: V_vap, dVdT_vap, dVdP_vap, dVdn_vap = self.tp.specific_volume( T, P, x, phase=VAPOR, dvdt=True, dvdp=True, dvdn=True) H_vap, dHdT_vap, dHdP_vap, dHdn_vap = self.tp.enthalpy(T, P, x, phase=VAPOR, dhdt=True, dhdp=True, dhdn=True) S_vap, dSdT_vap, dSdP_vap, dSdn_vap = self.tp.entropy(T, P, x, phase=VAPOR, dsdt=True, dsdp=True, dsdn=True) U_vap, dUdT_vap, dUdV_vap = self.tp.internal_energy_tv(T, V_vap, x, dedt=True, dedv=True) sos_vap = self.tp.speed_of_sound(T, P, x, y, fractions, beta_vap, beta_liq, phase=VAPOR) U_vap = H_vap - P * V_vap G_vap = H_vap - T * S_vap Cp_vap = dHdT_vap Cv_vap = dUdT_vap mol_weight_vap = sum([ y[i] * molecular_weights[i] for i in range(len(molecular_weights)) ]) self.set_table_value("Temperature", "Vap", "degK", self.units["Temperature"], T) self.set_table_value("Pressure", "Vap", "Pa", self.units["Pressure"], P) self.set_table_value("Specific volume", "Vap", "m**3 / mol", self.units["Specific volume"], V_vap) self.set_table_value("Internal energy", "Vap", "J / mol", self.units["Internal energy"], U_vap) self.set_table_value("Enthalpy", "Vap", "J / mol", self.units["Enthalpy"], H_vap) self.set_table_value("Entropy", "Vap", "J / (K * mol)", self.units["Entropy"], S_vap) self.set_table_value("Gibbs energy", "Vap", "J / mol", self.units["Gibbs energy"], G_vap) self.set_table_value("Isobar heat capacity", "Vap", "J / (K * mol)", self.units["Isobar heat capacity"], Cp_vap) self.set_table_value("Isochor heat capacity", "Vap", "J / (K * mol)", self.units["Isochor heat capacity"], Cv_vap) self.set_table_value("Speed of sound", "Vap", "m / s", self.units["Speed of sound"], sos_vap) self.set_table_value("Phase fraction", "Vap", "mol / mol", self.units["Phase fraction"], beta_vap) self.set_table_value("Molecular weight", "Vap", "kg / mol", self.units["Molecular weight"], mol_weight_vap) if is_liq and is_vap: if beta_vap == -1 and beta_liq == -1: beta_vap, beta_liq = 0.5, 0.5 V_overall = V_vap * beta_vap + V_liq * beta_liq U_overall = U_vap * beta_vap + U_liq * beta_liq H_overall = H_vap * beta_vap + H_liq * beta_liq S_overall = S_vap * beta_vap + S_liq * beta_liq G_overall = G_vap * beta_vap + G_liq * beta_liq Cp_overall = Cp_vap * beta_vap + Cp_liq * beta_liq Cv_overall = Cv_vap * beta_vap + Cv_liq * beta_liq sos_overall = sos_vap * beta_vap + sos_liq * beta_liq frac_overall = beta_vap + beta_liq if mol_weight_liq == 0: mol_weight_overall = mol_weight_vap elif mol_weight_vap == 0: mol_weight_overall = mol_weight_liq else: mol_weight_overall = mol_weight_vap * beta_vap + mol_weight_liq * beta_liq elif is_liq: V_overall = V_liq U_overall = U_liq H_overall = H_liq S_overall = S_liq G_overall = G_liq Cp_overall = Cp_liq Cv_overall = Cv_liq sos_overall = sos_liq frac_overall = beta_liq mol_weight_overall = mol_weight_liq elif is_vap: V_overall = V_vap U_overall = U_vap H_overall = H_vap S_overall = S_vap G_overall = G_vap Cp_overall = Cp_vap Cv_overall = Cv_vap sos_overall = sos_vap frac_overall = beta_vap mol_weight_overall = mol_weight_vap if is_liq or is_vap: self.set_table_value("Temperature", "Overall", "degK", self.units["Temperature"], T) self.set_table_value("Pressure", "Overall", "Pa", self.units["Pressure"], P) self.set_table_value("Specific volume", "Overall", "m**3 / mol", self.units["Specific volume"], V_overall) self.set_table_value("Internal energy", "Overall", "J / mol", self.units["Internal energy"], U_overall) self.set_table_value("Enthalpy", "Overall", "J / mol", self.units["Enthalpy"], H_overall) self.set_table_value("Entropy", "Overall", "J / (K * mol)", self.units["Entropy"], S_overall) self.set_table_value("Gibbs energy", "Overall", "J / mol", self.units["Gibbs energy"], G_overall) self.set_table_value("Isobar heat capacity", "Overall", "J / (K * mol)", self.units["Isobar heat capacity"], Cp_overall) self.set_table_value("Isochor heat capacity", "Overall", "J / (K * mol)", self.units["Isochor heat capacity"], Cv_overall) self.set_table_value("Speed of sound", "Overall", "m / s", self.units["Speed of sound"], sos_overall) self.set_table_value("Phase fraction", "Overall", "mol / mol", self.units["Phase fraction"], frac_overall) self.set_table_value("Molecular weight", "Overall", "kg / mol", self.units["Molecular weight"], mol_weight_overall) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() self.download_csv_btn.setEnabled(True)
def plot_envelope(self, tp, prim_vars, fractions): """ Plots a phase envelope :param tp: Thermopack instance :param prim_vars: Primary variables for the plot (e.g. PT, PH, ..) :param fractions: List of molar fractions for the components """ tpv_settings = self.plotting_preferences["Phase envelope"]["TPV"] isopleth_settings = self.plotting_preferences["Phase envelope"][ "Isopleths"] critical_settings = self.plotting_preferences["Phase envelope"][ "Critical"] plot_settings = self.plotting_preferences["Phase envelope"]["Plotting"] p_initial = tpv_settings["Initial pressure"] t_min = tpv_settings["Minimum temperature"] p_max = tpv_settings["Maximum pressure"] step_size = tpv_settings["Step size"] # Calculate T, P, V T, P, V = tp.get_envelope_twophase(initial_pressure=p_initial, z=fractions, maximum_pressure=p_max, minimum_temperature=t_min, step_size=step_size, calc_v=True) H = np.array( [tp.enthalpy_tv(T[i], V[i], fractions) for i in range(len(T))]) S = np.array( [tp.entropy_tv(T[i], V[i], fractions) for i in range(len(T))]) global H_list global T_list global S_list global P_list n_isopleths = isopleth_settings["Number of isopleths"] H_list = np.linspace(np.min(H), np.max(H), n_isopleths) S_list = np.linspace(np.min(S), np.max(S), n_isopleths) T_list = np.linspace(np.min(T) * 0.60, np.max(T) * 1.40, n_isopleths) P_list = np.linspace(np.min(P) * 0.60, np.max(P) * 1.40, n_isopleths) temp = critical_settings["Temperature"] v = critical_settings["Volume"] tol = critical_settings["Error tolerance"] # Calculate critical variables try: T_c, V_c, P_c = tp.critical(n=fractions, temp=temp, v=v, tol=tol) H_c = tp.enthalpy_tv(T_c, V_c, fractions) S_c = tp.entropy_tv(T_c, V_c, fractions) except Exception as e: msg = MessageBox("Error", str(e)) msg.exec_() T_c, V_c, P_c, H_c, S_c = None, None, None, None, None # Set global variables, so that they are accessible in all phase envelope plot functions global isopleth_1_color global isopleth_2_color global P_min global P_max global T_min global T_max global nmax isopleth_1_color = plot_settings["Colors"][2] isopleth_2_color = plot_settings["Colors"][3] P_min = isopleth_settings["Minimum pressure"] P_max = isopleth_settings["Maximum pressure"] T_min = isopleth_settings["Minimum temperature"] T_max = isopleth_settings["Maximum temperature"] nmax = isopleth_settings["N max"] # Plot depending on which primary variables are chosen if prim_vars == "PT": x, y, crit_x, crit_y = self.plot_envelope_PT( tp, T, P, T_c, P_c, fractions) elif prim_vars == "PH": x, y, crit_x, crit_y = self.plot_envelope_PH( tp, P, H, P_c, H_c, fractions) elif prim_vars == "PS": x, y, crit_x, crit_y = self.plot_envelope_PS( tp, P, S, P_c, S_c, fractions) elif prim_vars == "TH": x, y, crit_x, crit_y = self.plot_envelope_TH( tp, T, H, T_c, H_c, fractions) elif prim_vars == "TS": x, y, crit_x, crit_y = self.plot_envelope_TS( tp, T, S, T_c, S_c, fractions) else: return # Plotting line_color = plot_settings["Colors"][0] point_color = plot_settings["Colors"][1] grid_on = plot_settings["Grid on"] xlabel = plot_settings["x label"] ylabel = plot_settings["y label"] title = plot_settings["Title"] self.axes.plot(x, y, color=line_color, label="Phase envelope") self.axes.scatter([crit_x], [crit_y], color=point_color, label="Critical point") self.axes.set_title(title) self.axes.grid(grid_on) self.axes.set_xlabel(xlabel) self.axes.set_ylabel(ylabel) # Sort entries in the legend legend = True if legend: if n_isopleths > 0: handles, labels = self.axes.get_legend_handles_labels() self.axes.legend( [handles[3], handles[2], handles[0], handles[1]], [labels[3], labels[2], labels[0], labels[1]], loc="best") else: self.axes.legend() self.draw()
def plot(self): """ Checks type of plot selected, gets the correct parameters, inits thermopack, and calls the correct plot function in MplCanvas """ category = self.settings["Model category"] plot_type = self.plot_type_btn_group.checkedButton().text() prim_vars = self.prim_vars_dropdown.currentText() if category in ["Cubic", "CPA"]: eos = self.model_btn_group.checkedButton().text() if self.settings["EOS"] != eos: self.settings["EOS"] = eos elif category == "SAFT-VR Mie": self.settings["Model options"]["A1"] = self.a1_checkbox.isChecked() self.settings["Model options"]["A2"] = self.a2_checkbox.isChecked() self.settings["Model options"]["A3"] = self.a3_checkbox.isChecked() self.settings["Model options"][ "Hard sphere"] = self.hard_sphere_checkbox.isChecked() self.settings["Model options"][ "Chain"] = self.chain_checkbox.isChecked() init_thermopack(self.tp, self.component_data, self.comp_list_name, self.settings) fractions = np.array(self.component_data["Fractions"]) if self.canvas.empty: self.canvas.axes = self.canvas.fig.add_subplot(111) self.canvas.empty = False if self.redraw: self.canvas.axes.cla() self.isopleth_btn_stack.hide() self.download_csv_btn.setEnabled(True) if plot_type in ["Phase envelope", "Pressure density"]: mole_fraction_sum = np.sum(fractions) if abs(mole_fraction_sum - 1.00) > 1e-8: msg_title = "Molar fractions error" msg_text = "Molar fractions have to add up to 1.00. Currently the sum is %s." % mole_fraction_sum msg = MessageBox(msg_title, msg_text) msg.exec_() return else: # Setting the last mol fraction to 1 - the rest of them, to ensure that the total sum is exactly 1 fractions[-1] = 1 - np.sum(fractions[:-1]) if plot_type == "Phase envelope": self.canvas.plot_envelope(self.tp, prim_vars, fractions) self.canvas.show() self.mpl_toolbar.show() if self.plotting_preferences["Phase envelope"]["Isopleths"][ "Number of isopleths"] > 0: self.isopleth_btn_stack.show() elif plot_type == "Binary pxy": self.canvas.plot_binary_pxy(self.tp) self.canvas.show() self.mpl_toolbar.show() elif plot_type == "Pressure density": self.canvas.plot_pressure_density(self.tp, fractions) self.canvas.show() self.mpl_toolbar.show() elif plot_type == "Global binary": self.canvas.plot_global_binary(self.tp) self.canvas.show() self.mpl_toolbar.show() else: pass
def save_composition(self): """ Saves (Updates) the composition """ if self.selected_components: if self.set_name_edit.text(): list_name = self.set_name_edit.text() elif self.set_name_edit.placeHolderText(): list_name = self.set_name_edit.placeholderText() else: list_name = get_unique_name("Composition", self.final_compositions.keys()) self.set_name_edit.setPlaceholderText(list_name) if list_name == self.name: component_list = self.selected_components.copy() self.final_compositions[list_name] = component_list id_list = [ self.fluids[comp].json_data["ident"] for comp in component_list ] self.data["Component lists"][list_name] = { "Names": component_list, "Identities": id_list } self.component_list_updated.emit(list_name, False, self.name) self.name = list_name elif list_name not in self.data["Component lists"]: component_list = self.selected_components.copy() self.final_compositions[list_name] = component_list id_list = [ self.fluids[comp].json_data["ident"] for comp in component_list ] self.data["Component lists"][self.name] = { "Names": component_list, "Identities": id_list } self.data["Component lists"][list_name] = self.data[ "Component lists"].pop(self.name) self.component_list_updated.emit(list_name, False, self.name) self.name = list_name else: error_msg = "A component list with this name (%s) already exists" % list_name msg = MessageBox("Error", error_msg) self.set_name_edit.undo() msg.exec_() else: msg = MessageBox("Error", "No components are chosen.") msg.exec_()