diff --git a/pymead/gui/gui.py b/pymead/gui/gui.py index c24c251c..fca7f0a8 100644 --- a/pymead/gui/gui.py +++ b/pymead/gui/gui.py @@ -1290,10 +1290,111 @@ def multi_airfoil_analysis_accepted(self): def multi_airfoil_analysis_rejected(self): self.multi_airfoil_analysis_settings = self.dialog.valuesFromWidgets() + def display_mses_result(self, aero_data: dict, mset_settings: dict, mses_settings: dict): + + def display_fail(): + # Throw a GUI error + self.disp_message_box("MSES Analysis Failed", message_mode='error') + + # Output failed MSES analysis info to console + self.output_area_text( + f"[{str(self.n_analyses).zfill(2)}] " + f"MSES Converged = {aero_data['converged']} | " + f"Errored out = {aero_data['errored_out']} | " + f"Timed out = {aero_data['timed_out']}", + line_break=True + ) + + def display_success(): + # Calculate L/D if necessary + if "L/D" not in aero_data.keys(): + aero_data["L/D"] = np.true_divide(aero_data["Cl"], aero_data["Cd"]) + + # Compute the output analysis directory + analysis_dir_full_path = os.path.abspath( + os.path.join(mset_settings['airfoil_analysis_dir'], mset_settings['airfoil_coord_file_name'], '') + ) + + # Output successful MSES analysis data to console + self.output_area_text(f"[{str(self.n_analyses).zfill(2)}] ") # Number of analyses + self.output_area_text(f"MSES", mode="html") # Folder link + self.output_area_text( + f" ({mset_settings['mea']}, " + f"\u03b1 = {aero_data['alf']:.3f}\u00b0, " + f"Re = {mses_settings['REYNIN']:.3E}, " # Reynolds number + f"Ma = {mses_settings['MACHIN']:.3f}): " # Mach number + f"Cl = {aero_data['Cl']:+7.4f} | " # Lift coefficient + f"Cd = {aero_data['Cd']:+.5f} | " # Drag coefficient + f"Cm = {aero_data['Cm']:+7.4f} | " # Pitching moment coefficient + f"L/D = {aero_data['L/D']:+8.4f}".replace("-", "\u2212"), # Lift-to-drag ratio + # Note that the hyphens are replaced with the unicode subtraction char because the hyphen does not + # obey the fixed-width rule + line_break=True + ) + + if not aero_data['converged'] or aero_data['errored_out'] or aero_data['timed_out']: + display_fail() + else: + display_success() + + def display_svgs(self, mset_settings: dict, mplot_settings: dict): + + def display_svg(): + f_name = os.path.join(mset_settings['airfoil_analysis_dir'], + mset_settings['airfoil_coord_file_name'], + f"{svg_plot}.svg") + if not os.path.exists(f_name): + self.disp_message_box(f"SVG file {f_name} was not found") + return + + image = QSvgWidget(f_name) + graphics_scene = QGraphicsScene() + graphics_scene.addWidget(image) + view = CustomGraphicsView(graphics_scene, parent=self) + view.setRenderHint(QPainter.Antialiasing) + Mach_contour_widget = QWidget(self) + widget_layout = QGridLayout() + Mach_contour_widget.setLayout(widget_layout) + widget_layout.addWidget(view, 0, 0, 4, 4) + start_counter = 1 + max_tab_name_search = 1000 + for idx in range(max_tab_name_search): + name = f"{svg_plot}_{start_counter}" + if name in self.dock_widget_names: + start_counter += 1 + else: + self.add_new_tab_widget(Mach_contour_widget, name) + break + + for svg_plot in SVG_PLOTS: + if mplot_settings[SVG_SETTINGS_TR[svg_plot]]: + display_svg() + + def plot_mses_pressure_coefficient_distribution(self, aero_data: dict, mea: MEA): + if self.analysis_graph is None: + # Need to set analysis_graph to None if analysis window is closed + self.analysis_graph = AnalysisGraph( + background_color=self.themes[self.current_theme]["graph-background-color"]) + self.add_new_tab_widget(self.analysis_graph.w, "Analysis") + + # Get the index of the pen to determine which style to use + pen_idx = self.n_converged_analyses % len(self.pens) + + # Get the maximum physical extent of the airfoil system in the x-direction (used to prevent showing + # off-body pressure recovery) + x_max = mea.get_max_x_extent() + + # Plot the Cp distribution for each airfoil side + for side in aero_data["BL"]: + pg_plot_handle = self.analysis_graph.v.plot( + pen=pg.mkPen(color=self.pens[pen_idx][0], style=self.pens[pen_idx][1]), name=str(self.n_analyses) + ) + x = side["x"] if isinstance(side["x"], np.ndarray) else np.array(side["x"]) + Cp = side["Cp"] if isinstance(side["Cp"], np.ndarray) else np.array(side["Cp"]) + pg_plot_handle.setData(x[np.where(x <= x_max)[0]], Cp[np.where(x <= x_max)[0]]) + def multi_airfoil_analysis(self, mset_settings: dict, mses_settings: dict, mplot_settings: dict): - # print(f"{mplot_settings = }") - # mea = self.mea.deepcopy() if mset_settings["use_downsampling"] else self.mea mea = self.geo_col.container()["mea"][mset_settings["mea"]] @@ -1310,83 +1411,19 @@ def multi_airfoil_analysis(self, mset_settings: dict, mses_settings: dict, self.disp_message_box(str(os_error), message_mode="error") return - if not aero_data['converged'] or aero_data['errored_out'] or aero_data['timed_out']: - # Throw a GUI error - self.disp_message_box("MSES Analysis Failed", message_mode='error') - - # Output failed MSES analysis info to console - self.output_area_text( - f"[{str(self.n_analyses).zfill(2)}] MSES Converged = {aero_data['converged']} | Errored out = " - f"{aero_data['errored_out']} | Timed out = {aero_data['timed_out']}", line_break=True) - else: - # Calculate L/D if necessary - if "L/D" not in aero_data.keys(): - aero_data["L/D"] = np.true_divide(aero_data["Cl"], aero_data["Cd"]) - - # Output successful MSES analysis data to console - self.output_area_text( - f"[{str(self.n_analyses).zfill(2)}] ") - self.output_area_text(f"MSES", mode="html") - self.output_area_text(f" ({mset_settings['mea']}, \u03b1 = {aero_data['alf']:.3f}\u00b0, Re = {mses_settings['REYNIN']:.3E}, " - f"Ma = {mses_settings['MACHIN']:.3f}): " - f"Cl = {aero_data['Cl']:+7.4f} | Cd = {aero_data['Cd']:+.5f} | " - f"Cm = {aero_data['Cm']:+7.4f} | L/D = {aero_data['L/D']:+8.4f}".replace("-", "\u2212"), line_break=True) + self.display_mses_result(aero_data, mset_settings, mses_settings) if aero_data['converged'] and not aero_data['errored_out'] and not aero_data['timed_out']: + self.plot_mses_pressure_coefficient_distribution(aero_data, mea) + self.display_svgs(mset_settings, mplot_settings) + + # Update the last successful analysis directory (for easy access in field plotting) self.last_analysis_dir = os.path.join(mset_settings["airfoil_analysis_dir"], mset_settings["airfoil_coord_file_name"]) - if self.analysis_graph is None: - # Need to set analysis_graph to None if analysis window is closed! Might also not want to allow - # geometry docking window to be closed - self.analysis_graph = AnalysisGraph(background_color=self.themes[self.current_theme]["graph-background-color"]) - self.add_new_tab_widget(self.analysis_graph.w, "Analysis") - pen_idx = self.n_converged_analyses % len(self.pens) - x_max = mea.get_max_x_extent() - for side in aero_data['BL']: - pg_plot_handle = self.analysis_graph.v.plot(pen=pg.mkPen(color=self.pens[pen_idx][0], - style=self.pens[pen_idx][1]), - name=str(self.n_analyses)) - x = side["x"] - Cp = side["Cp"] - if not isinstance(x, np.ndarray): - x = np.array(x) - if not isinstance(Cp, np.ndarray): - Cp = np.array(Cp) - pg_plot_handle.setData(x[np.where(x <= x_max)[0]], Cp[np.where(x <= x_max)[0]]) - # pg_plot_handle.setData(x, Cp) - # pg_plot_handle = self.analysis_graph.v.plot(pen=pg.mkPen(color=self.pens[pen_idx][0], - # style=self.pens[pen_idx][1]), - # name=str(self.n_analyses)) - # pg_plot_handle.setData(aero_data['BL'][0]['x'], aero_data['BL'][0]['Cp']) - # pen = pg.mkPen(color='green') + + # Increment the number of converged analyses and the total number of analyses self.n_converged_analyses += 1 self.n_analyses += 1 - for svg_plot in SVG_PLOTS: - if mplot_settings[SVG_SETTINGS_TR[svg_plot]]: - f_name = os.path.join(mset_settings['airfoil_analysis_dir'], - mset_settings['airfoil_coord_file_name'], - f"{svg_plot}.svg") - if os.path.exists(f_name): - image = QSvgWidget(f_name) - graphics_scene = QGraphicsScene() - graphics_scene.addWidget(image) - view = CustomGraphicsView(graphics_scene, parent=self) - view.setRenderHint(QPainter.Antialiasing) - Mach_contour_widget = QWidget(self) - widget_layout = QGridLayout() - Mach_contour_widget.setLayout(widget_layout) - widget_layout.addWidget(view, 0, 0, 4, 4) - # new_image = QSvgWidget(os.path.join(RESOURCE_DIR, 'sec_34.svg')) - # temp_widget.setWidget(new_image) - start_counter = 1 - max_tab_name_search = 1000 - for idx in range(max_tab_name_search): - name = f"{svg_plot}_{start_counter}" - if name in self.dock_widget_names: - start_counter += 1 - else: - self.add_new_tab_widget(Mach_contour_widget, name) - break else: self.n_analyses += 1