def on_save_case(self, save_as=None): """ Defines what happens when save case button is clicked. Saves a freecad scene definition, and a dump of dsph data for the case.""" if Case.the().was_not_saved() or save_as: save_name, _ = QtGui.QFileDialog.getSaveFileName( self, __("Save Case"), Case.the().info.last_used_directory) Case.the().info.update_last_used_directory(save_name) else: save_name = Case.the().path if not save_name: return Case.the().info.needs_to_run_gencase = True save_case(save_name, Case.the()) save_current_freecad_document(Case.the().path) if Case.the().has_materials() and Case.the( ).execution_parameters.rigidalgorithm not in (2, 3): warning_dialog( __("Properties and Material information wasn't written. See details for more info." ), __("The case being saved has some properties/materials defined in one of its MKs.\n" "However, the solid-solid interaction on the execution parameters must be set to DEM or CHRONO for this feature to work." )) self.need_refresh.emit()
def on_execute_gencase(self): """ Saves data into disk and uses GenCase to generate the case files.""" self.on_save_case() if not Case.the().executable_paths.gencase: warning_dialog(__("GenCase executable is not set.")) return gencase_full_path = path.abspath(Case.the().executable_paths.gencase) arguments = [ "{path}/{name}_Def".format(path=Case.the().path, name=Case.the().name), "{path}/{name}_out/{name}".format(path=Case.the().path, name=Case.the().name), "-save:+all" ] cmd_string = "{} {}".format(gencase_full_path, " ".join(arguments)) refocus_cwd() process = QtCore.QProcess(get_fc_main_window()) process.setWorkingDirectory(Case.the().path) ensure_process_is_executable_or_fail(gencase_full_path) process.start(gencase_full_path, arguments) debug("Executing -> {}".format(cmd_string)) process.waitForFinished() try: output = str(process.readAllStandardOutput().data(), encoding='utf-8') except UnicodeDecodeError: output = str(process.readAllStandardOutput().data(), encoding='latin1') if process.exitCode(): Case.the().info.is_gencase_done = False error_dialog( __("Error executing GenCase. Did you add objects to the case?. Another reason could be memory issues. View details for more info." ), output) else: try: total_particles_text = output[ output.index("Total particles: "):output.index(" (bound=")] total_particles = int( total_particles_text[total_particles_text.index(": ") + 2:]) Case.the().info.particle_number = total_particles GencaseCompletedDialog(particle_count=total_particles, detail_text=output, cmd_string=cmd_string, parent=get_fc_main_window()).show() Case.the().info.is_gencase_done = True self.on_save_case() Case.the().info.needs_to_run_gencase = False except ValueError: print_exc() Case.the().info.is_gencase_done = False Case.the().info.needs_to_run_gencase = True # Refresh widget enable/disable status as GenCase finishes self.gencase_completed.emit(Case.the().info.is_gencase_done)
def on_nnparameters_button(self): """ Defines nnparameters button behaviour""" if Case.the().executable_paths.dsphysics.find('NNewtonian') == -1: warning_dialog( __("DualSPHysics executable is not set as Non-Newtonian!")) else: NonNewtonianDialog(parent=get_fc_main_window()) self.accept()
def on_lsparameters_button(self): """ Defines lsparameters button behaviour""" if Case.the().executable_paths.dsphysics.find('LiquidSediment') == -1: warning_dialog( __("DualSPHysics executable is not set as Liquid-Sediment!")) else: LiquidSedimentDialog(parent=get_fc_main_window()) self.accept()
def __init__(self, run_out, parent = None): super().__init__(parent = parent) self.setWindowTitle("Run summary") if os.path.isfile(run_out): self.setMinimumHeight(600) self.setMinimumWidth(600) self.layout = QtGui.QVBoxLayout() run_textbox = QtGui.QTextEdit() run_textbox.setReadOnly(True) self.layout.addWidget(run_textbox) file = open(run_out, 'r') text = '' for line in file: text += line run_textbox.setText(text) self.setLayout(self.layout) self.show() else: warning_dialog("First run simulation!")
def load_case(load_path: str) -> "Case": """ Loads a case from the given folder and returns its Case data. """ refocus_cwd() project_folder_path = path.dirname(load_path) freecad_document_file_path = path.abspath("{}/DSPH_Case.FCStd".format(project_folder_path)) if not path.isfile(freecad_document_file_path): error_dialog(__("DSPH_Case.FCStd file could not be found. Please check if the project was moved or the file was renamed.")) return None if document_count() and not prompt_close_all_documents(): return None FreeCAD.open(project_folder_path + "/DSPH_Case.FCStd") with open(load_path, "rb") as load_picklefile: try: loaded_data = pickle.load(load_picklefile) if not loaded_data.version: warning_dialog(__("The case data you're trying to load is older than version 0.6 and cannot be loaded.")) prompt_close_all_documents(prompt=False) return None if loaded_data.version < VERSION: warning_dialog(__("The case data you are loading is from a previous version ({}) of this software. They may be missing features or errors.").format(loaded_data.version)) elif loaded_data.version > VERSION: warning_dialog(__("You're loading a case data from a future version ({}) of this software. You should upgrade DesignSPHysics as they may be errors using this file.").format(loaded_data.version)) return loaded_data except AttributeError: error_dialog(__("There was an error opening the case. Case Data file seems to be corrupted.")) return None
def compute_warnings(self) -> None: """ Checks the resulting output for a [Warnings] tab and adds a button to see them. """ details_text: str = self.run_details_text.toPlainText() if "[WARNINGS]" not in details_text: return warning_list: list = details_text.split("[WARNINGS]\n")[1].split( "\n\n")[0].split("\n") self.run_group_label_completed.setText( "<b style='color: #ABA400'>{}</b>".format( __("Simulation completed with warnings."))) try: self.run_button_warnings.clicked.disconnect() except RuntimeError: # If nothing is yet connected it will throw an exception. pass self.run_button_warnings.clicked.connect(lambda _=False, text=( LINE_END * 2).join(warning_list): warning_dialog(text)) self.run_button_warnings.show()
def on_ex_simulate(self): """ Defines what happens on simulation button press. It shows the run window and starts a background process with dualsphysics running. Updates the window with useful info.""" refocus_cwd() if Case.the().info.needs_to_run_gencase: # Warning window about save_case warning_dialog("You should run GenCase again. Otherwise, the obtained results may not be as expected") static_params_exe = [Case.the().get_out_xml_file_path(), Case.the().get_out_folder_path(), "-{device}".format(device=self.device_selector.currentText().lower()), "-svres"] additional_parameters = list() if Case.the().info.run_additional_parameters: additional_parameters = Case.the().info.run_additional_parameters.split(" ") final_params_ex = static_params_exe + additional_parameters cmd_string = "{} {}".format(Case.the().executable_paths.dsphysics, " ".join(final_params_ex)) run_dialog = RunDialog(case_name=Case.the().name, processor=self.device_selector.currentText(), number_of_particles=Case.the().info.particle_number, cmd_string=cmd_string, parent=get_fc_main_window()) run_dialog.set_value(0) run_dialog.run_update(0, 0, None) Case.the().info.is_simulation_done = False run_fs_watcher = QtCore.QFileSystemWatcher() self.simulation_started.emit() # Cancel button handler def on_cancel(): log(__("Stopping simulation")) if process: process.kill() run_dialog.hide_all() Case.the().info.is_simulation_done = True self.simulation_cancelled.emit() run_dialog.cancelled.connect(on_cancel) # Launch simulation and watch filesystem to monitor simulation filelist = [f for f in os.listdir(Case.the().path + "/" + Case.the().name + "_out/") if f.startswith("Part")] for f in filelist: os.remove(Case.the().path + "/" + Case.the().name + "_out/" + f) def on_dsph_sim_finished(exit_code): """ Simulation finish handler. Defines what happens when the process finishes.""" # Reads output and completes the progress bar output = str(process.readAllStandardOutput().data(), encoding='utf-8') run_dialog.set_detail_text(str(output)) run_dialog.run_complete() run_fs_watcher.removePath(Case.the().path + "/" + Case.the().name + "_out/") if exit_code == 0: # Simulation went correctly Case.the().info.is_simulation_done = True Case.the().info.needs_to_run_gencase = False self.simulation_complete.emit(True) else: # In case of an error Case.the().info.needs_to_run_gencase = True if "exception" in str(output).lower(): log("There was an error on the execution. Opening an error dialog for that.") run_dialog.hide() self.simulation_complete.emit(False) error_dialog(__("An error occurred during execution. Make sure that parameters exist and are properly defined. " "You can also check your execution device (update the driver of your GPU). Read the details for more information."), str(output)) save_case(Case.the().path, Case.the()) # Launches a QProcess in background process = QtCore.QProcess(get_fc_main_window()) process.finished.connect(on_dsph_sim_finished) process.start(Case.the().executable_paths.dsphysics, final_params_ex) def on_fs_change(): """ Executed each time the filesystem changes. This updates the percentage of the simulation and its details.""" run_file_data = "" with open(Case.the().path + "/" + Case.the().name + "_out/Run.out", "r") as run_file: run_file_data = run_file.readlines() # Fill details window run_dialog.set_detail_text("".join(run_file_data)) # Set percentage scale based on timemax for l in run_file_data: if Case.the().execution_parameters.timemax == -1: if "TimeMax=" in l: Case.the().execution_parameters.timemax = float(l.split("=")[1]) current_value: float = 0.0 totalpartsout: int = 0 last_estimated_time = None # Update execution metrics last_part_lines = list(filter(lambda x: "Part_" in x and "stored" not in x and " " in x, run_file_data)) if last_part_lines: current_value = (float(last_part_lines[-1].split(None)[1]) * float(100)) / float(Case.the().execution_parameters.timemax) else: current_value = None # Update particles out last_particles_out_lines = list(filter(lambda x: "(total: " in x and "Particles out:" in x, run_file_data)) if last_particles_out_lines: totalpartsout = int(last_particles_out_lines[-1].split("(total: ")[1].split(")")[0]) try: last_estimated_time = str(" ".join(last_part_lines[-1].split(None)[-2:])) except IndexError: last_estimated_time = None # Update run dialog run_dialog.run_update(current_value, totalpartsout, last_estimated_time) # Set filesystem watcher to the out directory. run_fs_watcher.addPath(Case.the().path + "/" + Case.the().name + "_out/") run_fs_watcher.directoryChanged.connect(on_fs_change) # Handle error on simulation start if process.state() == QtCore.QProcess.NotRunning: # Probably error happened. run_fs_watcher.removePath(Case.the().path + "/" + Case.the().name + "_out/") process = None error_dialog("Error on simulation start. Check that the DualSPHysics executable is correctly set.") else: run_dialog.show()
def __init__(self, parent=None): super().__init__(parent=parent) self.title = 'Non-Newtonian parameters' self.left = 0 self.top = 0 self.width = 300 self.height = 200 self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.fluids = self.get_used_mkfluids() fluid_labels = self.get_used_mkfluid_labels() if Case.the().info.is_gencase_done: self.fluid_phases_num = len(self.fluids) if self.fluid_phases_num == 2: self.execution_options_tab = ExecutionOptionsTab() self.execution_options_tab.rheology_treatment.setCurrentIndex( int(Case.the().custom.nnparam.rheology_treatment)) self.execution_options_tab.velocity_gradient_type.setCurrentIndex( int(Case.the().custom.nnparam.velocity_gradient_type)) self.execution_options_tab.relaxation_dt.setText( Case.the().custom.nnparam.relaxation_dt) self.execution_options_tab.visco_treatment.setChecked( Case.the().custom.nnparam.visco_treatment) self.nonnewtonian_parameters_tabwidget = QtGui.QTabWidget() self.nonnewtonian_parameters_tabwidget.addTab( self.execution_options_tab, "Execution") self.phases_parameters_tab_list = [] for current_phase in range(self.fluid_phases_num): self.phases_parameters_tab_list.append( PhaseParametersTab()) self.set_phase_parameters_tab_entries(current_phase) self.nonnewtonian_parameters_tabwidget.addTab( self.phases_parameters_tab_list[current_phase], fluid_labels[current_phase]) apply_button = QtGui.QPushButton('Apply') apply_button.clicked.connect(self.on_apply_button) apply_button.setToolTip('Save current settings') reset_button = QtGui.QPushButton('Reset') reset_button.clicked.connect(self.set_default_parameters) reset_button.setToolTip('Reset default settings') cancel_button = QtGui.QPushButton('Cancel') cancel_button.clicked.connect(self.close) cancel_button.setToolTip('Discard any unapplied change') self.layout = QtGui.QGridLayout(self) self.layout.addWidget(self.nonnewtonian_parameters_tabwidget, 0, 0, 1, 3) self.layout.addWidget(apply_button, 1, 0, 1, 1) self.layout.addWidget(reset_button, 1, 1, 1, 1) self.layout.addWidget(cancel_button, 1, 2, 1, 1) self.setLayout(self.layout) self.setMinimumWidth(400) self.center() self.exec_() else: warning_dialog( 'Liquid-Sediment simulation requires exactly 2 fluid phases' ) else: warning_dialog( 'Need to run GenCase before setting liquid-sediment parameters' )
def on_multilayeredmb_menu(self, action): """ Defines MLPiston menu behaviour""" # Get currently selected object try: selection = FreeCADGui.Selection.getSelection()[0] except IndexError: error_dialog(__("You must select an object")) return # Check if object is in the simulation if not Case.the().is_object_in_simulation(selection.Name): error_dialog( __("The selected object must be added to the simulation")) return # Check if it is fluid and warn the user. if Case.the().get_simulation_object( selection.Name).type == ObjectType.FLUID: error_dialog( __("You can't apply a piston movement to a fluid.\nPlease select a boundary and try again" )) return # Get selection mk selection_obj = Case.the().get_simulation_object(selection.Name) selection_mk: int = selection_obj.obj_mk mk_properties: MKBasedProperties = Case.the().get_mk_based_properties( selection_obj.type, selection_mk) # Check that this mk has no other motions applied if mk_properties.has_movements(): # MK has motions applied. Warn the user and delete them motion_delete_warning = ok_cancel_dialog( APP_NAME, __("This mk already has motions applied. Setting a Multi-layered piston will delete all of its movement. Are you sure?" )) if motion_delete_warning == QtGui.QMessageBox.Cancel: return mk_properties.remove_all_movements() # 1D or 2D piston if __("1 Dimension") in action.text(): if mk_properties.mlayerpiston and not isinstance( mk_properties.mlayerpiston, MLPiston1D): overwrite_warn = ok_cancel_dialog( APP_NAME, __("You're about to overwrite a previous coupling movement for this mk. Are you sure?" )) if overwrite_warn == QtGui.QMessageBox.Cancel: return config_dialog = MLPiston1DConfigDialog(selection_mk, mk_properties.mlayerpiston, parent=get_fc_main_window()) if config_dialog.result() == QtGui.QDialog.Accepted: warning_dialog( __("All changes have been applied for mk = {}").format( selection_mk)) mk_properties.mlayerpiston = config_dialog.mlpiston1d if __("2 Dimensions") in action.text(): # Check that there's no other multilayered piston for this mk if mk_properties.mlayerpiston and not isinstance( mk_properties.mlayerpiston, MLPiston2D): overwrite_warn = ok_cancel_dialog( APP_NAME, __("You're about to overwrite a previous coupling movement for this mk. Are you sure?" )) if overwrite_warn == QtGui.QMessageBox.Cancel: return config_dialog = MLPiston2DConfigDialog(selection_mk, mk_properties.mlayerpiston, parent=get_fc_main_window()) if config_dialog.result() == QtGui.QDialog.Accepted: warning_dialog( __("All changes have been applied for mk = {}").format( selection_mk)) mk_properties.mlayerpiston = config_dialog.mlpiston2d self.accept()