Loading src/ips_fastran_gui/app/models/ips_fastran.py +5 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,10 @@ class IPSFastranTool(BasicTool): self.store = connection.get_data_store("ips_fastran") def prepare_tool(self) -> Tuple[Tool, Parameters]: # TODO: This method needs to be rewritten to support the SF API for use when not connected to Galaxy. # Need to talk with others about how they want to authenticate (access token provided or generated) before # doing this. # Prepare file ingestion into Galaxy self.inputs_dataset = Dataset(name="inputs.zip") with zipfile.ZipFile("input.zip", "w") as zip_obj: Loading @@ -32,7 +36,7 @@ class IPSFastranTool(BasicTool): zip_obj.writestr(file["name"], file["content"].encode("latin1")) self.inputs_dataset.path = "input.zip" self.config_dataset = Dataset(name="config.txt") self.config_dataset.set_content(self.model.config.file_contents) # self.config_dataset.set_content(self.model.config.file_contents) # Create the tool instance tool_params = Parameters() Loading src/ips_fastran_gui/app/models/main_model.py +22 −7 Original line number Diff line number Diff line """Module for the main model.""" import json import zipfile from io import BytesIO from pathlib import Path from typing import Any, Dict, List Loading @@ -23,7 +25,6 @@ class Config(BaseModel): input_files: List[Dict[str, Any]] = Field(default=[], title="Input Files") shot_number: str = Field(default="000001", title="Shot Number") time_id: str = Field(default="00001", title="Time ID") file_contents: str = Field(default="") result_file: str = Field(default="", title="File to Plot") result_files: List[str] = Field(default=[]) Loading @@ -31,6 +32,7 @@ class Config(BaseModel): class ResourceParameters(BaseModel): """Contains resource parameters for running the job.""" access_token: str = Field(default="", title="Superfacility API Access Token") partition: str = Field(default="debug", title="Partition") number_of_nodes: int = Field(default=1, ge=1, le=2, title="Number of Nodes") time_limit: int = Field(default=10, ge=1, le=10080, title="Time Limit (minutes)") Loading @@ -51,19 +53,32 @@ class MainModel: self.plot_json = PlotJSON() self.resource_params = ResourceParameters() def get_config_file(self) -> str: return self.config.file_contents def download_files(self) -> bytes: archive = BytesIO() with zipfile.ZipFile(archive, "w") as zip_obj: for file in self.config.input_files: zip_obj.writestr(file["relative_path"], file["content"]) archive.seek(0) def set_config(self, json_data: str) -> None: self.config.file_contents = json_data return archive.read() def set_files(self, files: List[Dict[str, Any]]) -> None: def set_file_contents(self, index: int, json_data: str) -> None: self.config.input_files[index]["content"] = json_data def set_file_path(self, index: int, path: str) -> None: self.config.input_files[index]["name"] = path.split("/")[-1] self.config.input_files[index]["relative_path"] = path def set_files(self, files: List[Dict[str, Any]], relative_paths: List[str]) -> None: if not files: self.config.input_files = [] for file in files: for index, file in enumerate(files): relative_path = relative_paths[index] decoded_file = file.copy() decoded_file["content"] = decoded_file["content"].decode("latin1") decoded_file["relative_path"] = "/".join(relative_path.split("/")[1:]) self.config.input_files.append(decoded_file) def set_plot(self, json_data: str) -> None: Loading src/ips_fastran_gui/app/view_models/execution.py +3 −0 Original line number Diff line number Diff line Loading @@ -43,3 +43,6 @@ class ExecutionViewModel: def store_factory(self) -> str: return "ips_fastran" def download_files(self) -> bytes: return self.model.download_files() src/ips_fastran_gui/app/view_models/main_view_model.py +28 −8 Original line number Diff line number Diff line Loading @@ -19,6 +19,9 @@ class ViewState(BaseModel): active_tab: str = Field(default="0") errors: List[str] = Field(default=[]) results_disabled: bool = Field(default=False) editor_index: int = Field(default=0) editor_path: str = Field(default="", title="File Path") editor_content: str = Field(default="") class MainViewModel: Loading @@ -42,7 +45,7 @@ class MainViewModel: self.model.resource_params, callback_after_update=self.on_change_resource_params ) self.plot_json_bind = binding.new_bind() self.view_state_bind = binding.new_bind(self.view_state) self.view_state_bind = binding.new_bind(self.view_state, callback_after_update=self.on_change_view_state) # Signals to process events from other view models self.execution_signal = signal(get_signal_id("ips_fastran", Signal.TOOL_COMMAND)) Loading @@ -50,9 +53,6 @@ class MainViewModel: self.completion_signal = signal("ips_fastran_complete") self.completion_signal.connect(self.on_completion, weak=False) def get_config_file(self) -> str: return self.model.get_config_file() def get_figure(self) -> Figure: return self.figure Loading @@ -60,14 +60,15 @@ class MainViewModel: self.config_bind.update_in_view(self.model.config) self.plot_json_bind.update_in_view(self.model.plot_json.model_dump_json(indent=2)) def on_change_config_file(self, json_data: str) -> None: def on_change_file(self, json_data: str) -> None: # Monaco fires input events with internal data that need to be ignored. if "_vts" in json_data: return self.view_state.errors = [] try: self.model.set_config(json_data) self.view_state.editor_content = json_data self.model.set_file_contents(self.view_state.editor_index, json_data) except ValidationError as e: for error in e.errors(): msg = "" Loading Loading @@ -98,6 +99,14 @@ class MainViewModel: # You can take actions in Python when specific fields are changed here. pass def on_change_view_state(self, results: Dict[str, Any]) -> None: updated = results.get("updated", []) for update in updated: match update: case "editor_path": self.model.set_file_path(self.view_state.editor_index, self.view_state.editor_path) self.config_bind.update_in_view(self.model.config) def on_completion(self, _sender: Any) -> None: self.config_bind.update_in_view(self.model.config) Loading @@ -112,10 +121,21 @@ class MainViewModel: self.view_state.active_tab = "2" self.view_state_bind.update_in_view(self.view_state) def set_files(self, files: List[Dict[str, Any]]) -> None: self.model.set_files(files) def set_files(self, files: List[Dict[str, Any]], relative_paths: List[str]) -> None: self.model.set_files(files, relative_paths) self.config_bind.update_in_view(self.model.config) def edit_file(self, index: int) -> None: if index >= len(self.model.config.input_files): return file = self.model.config.input_files[index] self.view_state.editor_index = index self.view_state.editor_path = file["relative_path"] self.view_state.editor_content = file["content"] self.view_state_bind.update_in_view(self.view_state) def update_figure(self) -> None: plotter = PlotFastran(self.figure, self.model.plot_json.input_params) plotter.load_fastran(Path(self.model.config.result_file)) Loading src/ips_fastran_gui/app/views/execution_panel.py +22 −2 Original line number Diff line number Diff line """Module for the Execution panel.""" from nova.trame.view.components import ExecutionButtons from nova.trame.view.layouts import HBoxLayout from trame.widgets import vuetify3 as vuetify from trame_server.core import Server from ips_fastran_gui.app.view_models.execution import ExecutionViewModel class ExecutionPanel: """Execution panel view class. Responsible for displaying execution buttons.""" def __init__(self) -> None: def __init__(self, server: Server, view_model: ExecutionViewModel) -> None: self.server = server self.view_model = view_model self.create_ui() def create_ui(self) -> None: @self.server.controller.trigger("download_files") def download_files() -> bytes: return self.view_model.download_files() with HBoxLayout(gap="2em", halign="center", valign="center"): vuetify.VBtn( "Download Files", prepend_icon="mdi-file-download", click="utils.download('ips_fastran_files.zip', trigger('download_files'), 'application/zip')", ) vuetify.VDivider(classes="my-2", vertical=True) with HBoxLayout(): ExecutionButtons("ips_fastran", download_btn=True) Loading
src/ips_fastran_gui/app/models/ips_fastran.py +5 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,10 @@ class IPSFastranTool(BasicTool): self.store = connection.get_data_store("ips_fastran") def prepare_tool(self) -> Tuple[Tool, Parameters]: # TODO: This method needs to be rewritten to support the SF API for use when not connected to Galaxy. # Need to talk with others about how they want to authenticate (access token provided or generated) before # doing this. # Prepare file ingestion into Galaxy self.inputs_dataset = Dataset(name="inputs.zip") with zipfile.ZipFile("input.zip", "w") as zip_obj: Loading @@ -32,7 +36,7 @@ class IPSFastranTool(BasicTool): zip_obj.writestr(file["name"], file["content"].encode("latin1")) self.inputs_dataset.path = "input.zip" self.config_dataset = Dataset(name="config.txt") self.config_dataset.set_content(self.model.config.file_contents) # self.config_dataset.set_content(self.model.config.file_contents) # Create the tool instance tool_params = Parameters() Loading
src/ips_fastran_gui/app/models/main_model.py +22 −7 Original line number Diff line number Diff line """Module for the main model.""" import json import zipfile from io import BytesIO from pathlib import Path from typing import Any, Dict, List Loading @@ -23,7 +25,6 @@ class Config(BaseModel): input_files: List[Dict[str, Any]] = Field(default=[], title="Input Files") shot_number: str = Field(default="000001", title="Shot Number") time_id: str = Field(default="00001", title="Time ID") file_contents: str = Field(default="") result_file: str = Field(default="", title="File to Plot") result_files: List[str] = Field(default=[]) Loading @@ -31,6 +32,7 @@ class Config(BaseModel): class ResourceParameters(BaseModel): """Contains resource parameters for running the job.""" access_token: str = Field(default="", title="Superfacility API Access Token") partition: str = Field(default="debug", title="Partition") number_of_nodes: int = Field(default=1, ge=1, le=2, title="Number of Nodes") time_limit: int = Field(default=10, ge=1, le=10080, title="Time Limit (minutes)") Loading @@ -51,19 +53,32 @@ class MainModel: self.plot_json = PlotJSON() self.resource_params = ResourceParameters() def get_config_file(self) -> str: return self.config.file_contents def download_files(self) -> bytes: archive = BytesIO() with zipfile.ZipFile(archive, "w") as zip_obj: for file in self.config.input_files: zip_obj.writestr(file["relative_path"], file["content"]) archive.seek(0) def set_config(self, json_data: str) -> None: self.config.file_contents = json_data return archive.read() def set_files(self, files: List[Dict[str, Any]]) -> None: def set_file_contents(self, index: int, json_data: str) -> None: self.config.input_files[index]["content"] = json_data def set_file_path(self, index: int, path: str) -> None: self.config.input_files[index]["name"] = path.split("/")[-1] self.config.input_files[index]["relative_path"] = path def set_files(self, files: List[Dict[str, Any]], relative_paths: List[str]) -> None: if not files: self.config.input_files = [] for file in files: for index, file in enumerate(files): relative_path = relative_paths[index] decoded_file = file.copy() decoded_file["content"] = decoded_file["content"].decode("latin1") decoded_file["relative_path"] = "/".join(relative_path.split("/")[1:]) self.config.input_files.append(decoded_file) def set_plot(self, json_data: str) -> None: Loading
src/ips_fastran_gui/app/view_models/execution.py +3 −0 Original line number Diff line number Diff line Loading @@ -43,3 +43,6 @@ class ExecutionViewModel: def store_factory(self) -> str: return "ips_fastran" def download_files(self) -> bytes: return self.model.download_files()
src/ips_fastran_gui/app/view_models/main_view_model.py +28 −8 Original line number Diff line number Diff line Loading @@ -19,6 +19,9 @@ class ViewState(BaseModel): active_tab: str = Field(default="0") errors: List[str] = Field(default=[]) results_disabled: bool = Field(default=False) editor_index: int = Field(default=0) editor_path: str = Field(default="", title="File Path") editor_content: str = Field(default="") class MainViewModel: Loading @@ -42,7 +45,7 @@ class MainViewModel: self.model.resource_params, callback_after_update=self.on_change_resource_params ) self.plot_json_bind = binding.new_bind() self.view_state_bind = binding.new_bind(self.view_state) self.view_state_bind = binding.new_bind(self.view_state, callback_after_update=self.on_change_view_state) # Signals to process events from other view models self.execution_signal = signal(get_signal_id("ips_fastran", Signal.TOOL_COMMAND)) Loading @@ -50,9 +53,6 @@ class MainViewModel: self.completion_signal = signal("ips_fastran_complete") self.completion_signal.connect(self.on_completion, weak=False) def get_config_file(self) -> str: return self.model.get_config_file() def get_figure(self) -> Figure: return self.figure Loading @@ -60,14 +60,15 @@ class MainViewModel: self.config_bind.update_in_view(self.model.config) self.plot_json_bind.update_in_view(self.model.plot_json.model_dump_json(indent=2)) def on_change_config_file(self, json_data: str) -> None: def on_change_file(self, json_data: str) -> None: # Monaco fires input events with internal data that need to be ignored. if "_vts" in json_data: return self.view_state.errors = [] try: self.model.set_config(json_data) self.view_state.editor_content = json_data self.model.set_file_contents(self.view_state.editor_index, json_data) except ValidationError as e: for error in e.errors(): msg = "" Loading Loading @@ -98,6 +99,14 @@ class MainViewModel: # You can take actions in Python when specific fields are changed here. pass def on_change_view_state(self, results: Dict[str, Any]) -> None: updated = results.get("updated", []) for update in updated: match update: case "editor_path": self.model.set_file_path(self.view_state.editor_index, self.view_state.editor_path) self.config_bind.update_in_view(self.model.config) def on_completion(self, _sender: Any) -> None: self.config_bind.update_in_view(self.model.config) Loading @@ -112,10 +121,21 @@ class MainViewModel: self.view_state.active_tab = "2" self.view_state_bind.update_in_view(self.view_state) def set_files(self, files: List[Dict[str, Any]]) -> None: self.model.set_files(files) def set_files(self, files: List[Dict[str, Any]], relative_paths: List[str]) -> None: self.model.set_files(files, relative_paths) self.config_bind.update_in_view(self.model.config) def edit_file(self, index: int) -> None: if index >= len(self.model.config.input_files): return file = self.model.config.input_files[index] self.view_state.editor_index = index self.view_state.editor_path = file["relative_path"] self.view_state.editor_content = file["content"] self.view_state_bind.update_in_view(self.view_state) def update_figure(self) -> None: plotter = PlotFastran(self.figure, self.model.plot_json.input_params) plotter.load_fastran(Path(self.model.config.result_file)) Loading
src/ips_fastran_gui/app/views/execution_panel.py +22 −2 Original line number Diff line number Diff line """Module for the Execution panel.""" from nova.trame.view.components import ExecutionButtons from nova.trame.view.layouts import HBoxLayout from trame.widgets import vuetify3 as vuetify from trame_server.core import Server from ips_fastran_gui.app.view_models.execution import ExecutionViewModel class ExecutionPanel: """Execution panel view class. Responsible for displaying execution buttons.""" def __init__(self) -> None: def __init__(self, server: Server, view_model: ExecutionViewModel) -> None: self.server = server self.view_model = view_model self.create_ui() def create_ui(self) -> None: @self.server.controller.trigger("download_files") def download_files() -> bytes: return self.view_model.download_files() with HBoxLayout(gap="2em", halign="center", valign="center"): vuetify.VBtn( "Download Files", prepend_icon="mdi-file-download", click="utils.download('ips_fastran_files.zip', trigger('download_files'), 'application/zip')", ) vuetify.VDivider(classes="my-2", vertical=True) with HBoxLayout(): ExecutionButtons("ips_fastran", download_btn=True)