diff --git a/doc/changelog.d/71.documentation.md b/doc/changelog.d/71.documentation.md new file mode 100644 index 00000000..b8b3eac8 --- /dev/null +++ b/doc/changelog.d/71.documentation.md @@ -0,0 +1 @@ +Clean up internal refs diff --git a/doc/developer_docs/adrs/01-visor-tenets.md b/doc/developer_docs/adrs/01-visor-tenets.md deleted file mode 100644 index f103b258..00000000 --- a/doc/developer_docs/adrs/01-visor-tenets.md +++ /dev/null @@ -1,44 +0,0 @@ -# VISOR - (Visual Interactive Simulation Object Renderer) Solutions Applications 3D Visualization Components Tenets - -## Decision - -VISOR (Visual Interactive Simulation Object Renderer, from here on "VISOR") is a framework providing Solutions Applications the necessary visualization components in order to be able to support the diversity of requirements for 3D visualization needs of Solution Applications while being able to follow the following tenets in terms terms non-functional requirements: - * Seamless integration and architecture compatibility with SAF - * Seamless integration with Ansys Dynamic Reporting - * Ease of use and integratability delivered through PyAnsys initiatives (pyansys-visualization-tools) - * Scalability in terms of supporting more complicated 3D models, multiple users - * Deploymentability without sacrificing functionality, performance and maintainability requirements for desktop, on-prem and cloud architectures. The deployment processes should adhere to KPIs such as Deployment Frequency, Lead Time for Changes, Change Failure Rate, Deployment Duration, Automated Test Coverage, Resource Utilization, User Impact, Deployment Success Rate. In order to achieve the previous, containerizing and deploying the solution is part of our process and the KPIs towards that include Image Build Time, Image Size, Image PUll Time, Image Vulnerabilities, Image Layers, Image Age, Image Reuse, Image Compatibility, Resource Utilization, Compliance, Automated Test Coverage. - * Performance in terms of web component KPIs as as well as service side KPIs. - * Maintainability and alignment with best practises and development processes of CASEBU STCs. One of the processes followed is to have our documentation based on the relevant CASEBU ADRs in terms of architecture and developer documentation and examples of using this component. - -## Context - -VISOR is a new STC which is aiming to provide Solution Applications a set of 3D visualization components which can support all the different aspects of functional and non-functional requirements. As this is targeting solutions applications instead of a specific product and is created as part of the Enablement Platform AIEP development framework there are specific tenets that make sense to be agreed upon now that its being started so that they can remain and evolve along with the project. - - -## Options - -1. ✔️ Framework that follows an architecture design which can scale in terms of adding different frameworks and providing customized components for the different integrations and functional and non-functional requirements as they are being created -1. ❌ New 3D Viewer Rendering Components -1. ❌ Specific 3D Viewer / 3D Viewer Framework (Trame, Ceetron Hoops, AVZ) - -## Consequences - -1. ✔️ It depends on technologies provided by other internal or external teams and provides all the integration interfaces, APIs and tools targeting CASEBU STCs and products which are included in Solution Applications. The rationale for choosing this option is that: - - the functional requirements can be provided by existing and under development 3D visualization technologies - - the non-functional requirements are not provided by any existing or under development 3D visualization technology - - solutions applications have extremely diverse needs and a matrix of deployment options and there is a lot of effort that needs engineering expertise to make the integration robust, secure, scalable and easy to integrate by ACE groups. - - even though it leads to adding complexity to the components as they need to provide flexibility for rendering choices and deployment options, its the unique value that this project can provide which external technologies cannot do, as they cannot work this closely with products and ACE groups. - - it does lead to depending on different rendering technologies not necessarily owned by the same team or group and can be external companies, but its chosen due to time frame and resourcing constraints -1. ❌ New 3D Viewer Rendering Components - - It's not recommended even though there is expertise in that area which we can utilize due to time-frame and resourcing constraints - - It is also an issue of separating concerns and being able to put resources on providing components which ACE groups developing Solutions can integrate and be productive so that a continuous pain point is addressed - - Even if the same group develops new rendering technologies to address problems that cannot be solved from other groups that doesn't mean they cannot be integrated with this framework when they are read and provide an easier path for migration to new graphics technologies for Solutions Applications rather than a straight on integration. -1. ❌ Specific 3D Viewer / 3D Viewer Framework (Trame, Ceetron Hoops, AVZ) - - This is the fastest and less complicated route for first delivery but its not future proof - - It leads to vendor-locking - - None of the existing technologies can currently deliver the full list of functional and non-functional requirements so its inevitable that we are going to keep going forward and keep evolving our visualization tools to meet the growing needs of the Solutions group. - -## Advice - -Complete advice process recorded [here](https://github.com/ansys-internal/aap/discussions/43). \ No newline at end of file diff --git a/doc/developer_docs/adrs/02-visor-technology-components.md b/doc/developer_docs/adrs/02-visor-technology-components.md deleted file mode 100644 index 76ffd55f..00000000 --- a/doc/developer_docs/adrs/02-visor-technology-components.md +++ /dev/null @@ -1,172 +0,0 @@ -## VISOR technology components - -### Status -Approved - -### Decision -Based on the project tenets, the wide variety of projects to integrate with in our roadmap and our prioritized use cases and time frame, we are aiming at being technology agnostic on the API level for VISOR and provide ways to support different rendering and framework technologies on the backend. The technology we are aiming for our first release is going to be Trame framework targeting VTK WASM. We will continue researching other options and technologies as the project evolves. - -### Context - -VISOR is targeting to provide a 3D viewer web component and the services, tools and utilities to support the growing needs of Solutions Applications. The first release is aiming for Q4 2024/Q1 2025 based on continuous integration releases (QP2). The assessment in terms of options is done based on this time frame and the options that are not viable due to the timeframe we have can be evaluated for a different milestone in our roadmap. The Minimum Viable Product (MVP) shortlist is tracked [here](https://ansys-my.sharepoint.com.mcas.ms/:x:/r/personal/marina_galvagni_ansys_com/_layouts/15/Doc.aspx?sourcedoc=%7B1D341238-D500-4E4B-A2A6-11DC492C2CFB%7D&file=MVP_shortlist.xlsx&action=default&mobileredirect=true) and covers all functional requirements. Key requirements non-functional are the following: - -|No | Requirement | Priority | -----|-------------|-----------| -| 1 | JS/React Client | MVP | -| 2 | Python API (service side) | MVPt | -| 3 | Multiple user support | Long-term roadmap (to be decided) | -| 4 | Data streaming of updates | Long-term roadmap (to be decided)| -| 5 | Integratable with Dynamic Reporting | MVP | -| 6 | Integratable and interoperable with SAF components | MVP | -| 7 | Deployment target: Desktop | MVP | -| 8 | Deployment target: On Premise | MVP | -| 9 | Deployment target : public/private cloud | 3- Low | -| 10 | Ease of use through Python scripts | MVP | -| 11 | Contributors documentation | MVP | -| 12 | Internal User documentation | MVP | -| 13 | Interoperable with pyansys-visualization-tools | Long-term roadmap (to be decided) | -| 14 | Multiple viewers (single) per session | MVP| -| 15 | Single user per session | MVP | -| 16 | Multiple users per session | Long-term roadmap (to be decided) | -| 17 | Multiple sessions | Long-term roadmap (to be decided) | -| 18 | Performance targets for pipeline for SAF, DPF, HPS for small/medium non-complex meshes| MVP | -| 19 | Performance targets for pipeline for SAF, DPF, HPS, ADR for small/medium non-complex meshes| MVP (To be decided)| -| 20 | Performance targets for pipeline for SAF, DPF, HPS, ADR for larger/ complex meshes| Long-term roadmap (to be decided) | - -### Options - -There are several options for this project where each of them has different advantages and challenges. The research done for each option and the rationale behind each of them is explained as: - -1. [Trame](https://kitware.github.io/trame/) (VTK.js) :heavy_plus_sign: : This is a visualization framework that is already in production based on VTK.js and supported by Kitware. This is the mainline Trame framework already included in the official VTK releases. - - _Advantages_: - - * :heavy_plus_sign: _Released framework, so it is already available for integration and it is stable._ - * :heavy_plus_sign: _It has all the components we will require in order to get an estimation of delivering by the end of the year, given there is still risk in this estimation. We can get support from Kitware in terms of bugfixing for these_ - * :heavy_plus_sign: It provides server-side rendering capabilities for larger/more complicated models. - * It is an open source project so we don't transfer cost to ACE or our customers for using this technology. - * The GLTF 2.0 import/export. - * VTK based visualization frameworks including Trame are being used or their visualization of choice by ACE groups, PyAnsys and some Ansys products, which can aid integratability and ease of use. - - _Disadvantages_: - - * :heavy_minus_sign: Performance is poor on the client side for medium models (~3m elements) - * :heavy_minus_sign: It is going to be replaced by the new generation of WASM based rendering which has a different JS API and bindings on the client side - * There are other frameworks that can potentially provide better performance long term due to using more optimal graphics formats -and better architecture in terms of streaming APIs and services for on-prem and cloud deployment targets. - - _Mitigation_: - * We can have Kitware support on bugfixes and issues we have - * We can use server-side rendering for more complex or larger models - - -2. [Trame targeting VTK.WASM](https://github.com/Kitware/trame-vtklocal) :heavy_check_mark: : This is the new generation for Trame which uses VTK.WASM for rendering and is also going to target WebGPU from the VTK side. This has been in development phase and only now is transitioning to productization with expected release in November. - - _Advantages_: - - * :heavy_plus_sign: It has all the components we will require in order to get an estimation of delivering at the end of the year or beginning of Q1 2025, given there is still risk in this estimation. We can get support from Kitware in terms of bugfixing for these and the engineering effort for creating and maintaining the customized viewer is the smallest of the alternatives. - * It targets VTK.WASM on the client side which is able to provide the extra performance required for small and medium sized meshes. - * :heavy_plus_sign: It has an object manager API which is able to serialize/deserialize objects and be used for easier integration of client side and server side rendering as well as being able to provide easier interoperability on the client side with different client side frameworks and components. Its much easier to utilize this API from a JS API and React component to integrate with SAF rather than the alternatives which would be to define the API and the multiple layers of bindings. - * :heavy_plus_sign: It is going to be the main version of Trame on the next release of VTK.WASM and Trame frameworks. - * :heavy_plus_sign: It needs less work on the client side to create our own client side viewer. - * The new WebGPU releases on the VTK side will bring more performance on the client side so it will reduce the need for server side for medium or more complex models. - * It is an open source project so we don't transfer cost to ACE or our customers for using this technology - * VTK based visualization frameworks including Trame are being used or their visualization of choice by ACE groups, PyAnsys and some Ansys products, which can aid integratability and ease of use. - * - - _Disadvantages_: - - * :heavy_minus_sign: It is expected to be released in November - * It hasn't been stable so we will need more support from Kitware in order to be able to release in our timeframes and it's still going to be an issue if they don't make the stable release in November. - * There are other frameworks that can potentially provide better performance long term due to using more optimal graphics formats and better architecture in terms of streaming APIs and services for on-prem and cloud deployment targets. - - _Mitigation_: - * We can have Kitware support on bugfixes and issues we have - -3. [Omniverse](https://www.nvidia.com/en-us/omniverse/) (OpenUSD) :heavy_multiplication_x: : NVIDIA Omniverse™ is a platform of APIs, SDKs, and services that enable developers to easily integrate Universal Scene Description (OpenUSD) and RTX rendering technologies into existing software tools and simulation workflows. There are multiple initiatives within Ansys that are integrating omniverse in their visualization workflows, which can be seen in this [slide deck](https://ansys-my.sharepoint.com/:p:/p/nicolas_dalmasso/EfBCg7P1hWRFmRQaFg1X8TABFuFUpk_imKN3IZsjayzD-g?e=966IyE) by Nicolas Dalmasso. This option hasn't been explored fully in terms of integrating the current version and then moving on to the next version of APIs and SDKs in terms of effort. There is a related task for more in-depth research though a [spike](https://github.com/ansys-internal/theia/issues/7). - - _Advantages_: - - * This platform has the latest rendering capabilities provided by NVIDIA - * It is in partnership with Ansys and there are a lot of initiatives which are integrating Omniverse with existing tools, e.g. EnSight - * It supports [Universal Scene Description (OpenUSD)](https://www.nvidia.com/en-us/omniverse/usd/) which is an open standard that Ansys is a collaborator - * It has APIs and SDK Kits for developing viewers which can utilize the rendering capabilities of RTX technologies. - * It will provide Streaming APIs covering the matrix of deployment targets Desktop, On-Prem and Cloud technologies - - _Disadvantages_: - - * It is now going through a large refactoring of their APIs which will lead to a much better performance and usability support and currently its in beta but not ready for release. - * It requires engineering effort greater than other alternatives for supporting computational meshes and operations to provide a first version for a viewer. - * It has a cost associated for using this platform as well as having the corresponding hardware in a desktop or on-prem configuration, which has to be agreed on for the Solution Applications and ACE if its going to be the default or a required option. - -4. AVZ :heavy_multiplication_x: : This is the current viewer for Solutions Applications targeting desktop integration. The option would be to re-engineer AVZ in order to be able to support all the current functional requirements, target the new Khronos Standard GLTF 2.0 and also create a service that is able to support the On-Prem and cloud deployment targets. - - _Advantages_: - * This is the current viewer which has experts within Ansys and could potentially deliver a next generation AVZ if the priorities were provided as such - * It is already integrated with ADR and in the desktop version it is integrated with SAF. - * It has proven that has the performance capabilities to be integraed in Ansys products (FLUENT). - * It is integrated with a lot of Ansys products already so there is less effort required in building interfaces with Ansys flagship products. - - _Disadvantages_: - * It needs engineering effort in order to be able to target the next generation of rendering technologies and standards required. - * It doesn't have an efficient service for on premise and cloud deployment targets. - * It is currently in flux in terms of roadmap, code ownership and codebase refactoring which needs to be resolved in order to be able to deliver our integration targets and ease of use. - -5. WebGX :heavy_multiplication_x: : This is the rendering framework developed by DBU which is targeting WebGPU. It is not researched in depth as it is currently in flux, but it could be a viable solution that we can re-evaluate. - - _Advantages_: - * This is a framework developed for web component visualization capabilities within Ansys and has expertise in Ansys. - * It is targeting WebGPU on the client side which has the potential to provide the performance requirements of VISOR. - - _Disadvantages_: - * There is no maintained viewer component for VISOR - * It is currently in flux in terms of roadmap - -6. [Hoops](https://docs.techsoft3d.com/hps/latest/index.html) :heavy_multiplication_x: : It is an engineering 3D visualization SDK and it is going to provide the services and integration for our deployment targets at their next generation of software. It has not been fully evaluated as it is a vendor specific offering which has its own internal graphics format and it was not prioritized as high as other ones. We can organise time for looking into it more thoroughly but it hasn't currently been done in depth. - - _Advantages:_ - * It provides scientific SDK for JS client which aids the ease of development of the viewer and covering functional requirements (haven't researched whether all are covered with the associated performance requirements). - * It is an established framework that we are using in Ansys products. - - _Disadvantages:_ - * It doesn't target an standard graphics format that we are contributing into - * It has vendor requirements for using it - * It is the next version which covers our non-functional requirements. - - -7. New viewer based on [Three.JS](https://threejs.org/) :heavy_multiplication_x:: This is the option of using a new custom Three.JS viewer with the use of visualization libraries to be researched and creating the service infrastructure and tools to support our functional and on-functional requirements. This option has not been researched yet, this is the corresponding [spike task](https://github.com/ansys-internal/theia/issues/18). - - _Advantages:_ - * Fully customized viewer for our needs - * Expertise within the company - - _Disadvantages:_ - * Needs the more engineering effort in order to be able to deliver the functional requirements and the non-functional ones in terms of time and resourcing - -### Consequences - -These [tenets](https://github.com/ansys/visor/blob/main/doc/developer_docs/adrs/01-visor-tenets.md) allow the project to change direction without transferring effort to the groups integrated with VISOR or at least with minimal effort. This means that the option decided now it doesn't have to be the only option going forward but it is going to necessitate engineering effort to add more targets or transition the project to a new visualization SDK or platform. The view of the project is to be itself a platform for visualization components for the Solutions Group and handle the engineering complexity of those components and integrating them in VISOR rather than pushing it to the Solutions Applications. This is also a matter of choosing vendor or making it possible to have different initial, running cost and maintenance cost for the different approaches as the deployment targets and the cost evaluation differs. - -The current target is to release in Q4 2024 a VISOR MVP version. The consequences per option are outlined in the following table: - -| Option | Technology | Decision | Reason | Potential future target | -|---------|-----------|-----------|--------|-------------------------| -| 1 | Trame VTK.JS | No | Performance is not future looking, the architecture of our viewer is simpler and more effective with the next generation, it is a risk in terms of making the Q4 release based on the Trame VTK.WASM delivering in November officially.| No | -| 2 | Trame VTK.WASM | Yes | Performance is more future looking, architecture and engineering effort on the viewer is the shortest of all the options and we have funding for Kitware resources to help us with delivering| Yes | -|3 | Omniverse | No | It requires further investigation and the streaming APIs that we would want to target are not released yet, it also has a cost, deployment requirements and we need to make sure that Solutions Applications are all agreeing on that| Yes | -|4| AVZ (next gen) | No| As we need to define the plan and roadmap for this next generation as it doesn't currently fulfill our requirements | Yes | -|5| WebGX | No | It needs further research | Yes| -|6| Hoops | No | It needs further research | Yes | -|7| New Three.js Viewer | No| It needs further research | No | - - - - - - - - - - - - diff --git a/doc/developer_docs/adrs/03-server-python-api.md b/doc/developer_docs/adrs/03-server-python-api.md deleted file mode 100644 index 22f5e550..00000000 --- a/doc/developer_docs/adrs/03-server-python-api.md +++ /dev/null @@ -1,285 +0,0 @@ -# ADR 03: VISOR Python API - -## Status -Decided - -## Context -The VISOR visualization component can be integrated into a Python application in the context of the pyAnsys initiative. A pythonic interface helps with interoperability with initiatives and interfaces like the pyansys-visualization-tools interface. - -## Decision -Provide a Python API which aids integration with Python projects from the PyAnsys initiative and leads to easier Python programming in Jupyter notebooks and Ansys labs as well as eases integration with ansys-visualization-interface from the PyAnsys initiative. This Python API is only meant to be used in a Python application environment and not as a way to integrate with SAF. - -### VISOR instance - -The VISOR instance is able to select the rendering engine, the url where the visualization will be starting for the native browser to be able to handle the visualization as well as the ability to have a standalone visualization or use this Python API for integrating with a local desktop Dash application. Each instance of VISOR is only going to work on a single url and session. - -VISOR is a server-client architecture and currently it only support a single backend which is using VTK.WASM utilizing the Trame framework. VISOR service is starting a [Trame](https://trame.readthedocs.io/en/latest/) [server](https://trame.readthedocs.io/en/latest/viewer.server.html) on the backend. The Trame server is calling [wslink](https://github.com/Kitware/wslink) to setup a websocket connection to from the host to the client. The only way to provide input is through the service side API and not through the client API. - -The VISOR instance marks the lifecycle of VISOR within this Python execution environment. There is no way to connect to this instance from a different Python environment, apart from the url which is executing the client. The client side is updating the input of VISOR as VISOR is a service that is always managed by the server side. When the VISOR instance is destroyed due to the Python process ending or getting out of scope all of the services and temporary files and folders need to be cleaned up. - -```text -visor_default_instance = Visor( - url: str | None = None, - input: str| vtkDataSet | None = None, - metadata: Metadata | None = None, - standalone: bool = True, -) - -``` - -The VISOR instance makes it possible to change the default configuration for url, logs, input_file_paths and all relevant settings through a Settings object constructed based on the -ansys.visor.viewer config.py file. -The Settings object is defined as follows: - -```text -class Settings(BaseSettings): - app_name: str = "VISOR Viewer" - default_host: str = "localhost" - default_port: int = 8081 - default_standalone: bool = True - default_client_bundle: str = Path("client_bundle") - default_log_dir: str = str(Path.cwd().joinpath("logs")) - trame_log_dir: str | None = None - -``` -The app_name is able to rename the application name of the component for the current execution. -The default_host and default_port make it possible to provide a different host and port of execution. -The default_standalone is whether the VISOR server is going to be hosting the webclient. -The default_client_bundle is where the client bundle has been deployed for the static client code -The default_log_dir provides the path to logs -The default_trame_log_dir provides the path to trame logging. - - - -#### Start - -```text -def start(self, input: vtkDataSet| str | None, timeout = 0) -> int -``` - -Starts the Trame server [start](https://trame.readthedocs.io/en/latest/viewer.server.html#trame_server.viewer.Server.start). All the relevant configurations have been provided at the time of instantiation from VISOR. There is the option to provide an input on the start function where it can be an in-memory vtk object in terms of a vktDataSet or a path to a file which at this point can only be a VTK formatted file. Due to VISOR accepting only its internal native format VISOR doesn't do any internal conversions. - -The visualization starts on a background thread instead of the current process to allow for the update() and stop() APIs to be used without any multi-threading management to happen on the user's side. - -```mermaid - sequenceDiagram - VISOR_instance->>Trame_server: start() - Trame_server-->>wslink: start -``` - -The relevant API is the following: -```text -// Start visualization application in the same thread. -// When the browser is closed. -def start( - self, - input: str | vtkDataSet | None, - metadata: Metadata | None = None, - timeout: int = 0, -) -> int -``` - - *Example usage* -```text - visualizer = Visor() - output = converter.to_data_set() - visualizer.start() - #...Thread continues execution while VISOR is visualizing the input -``` - -#### Update -The update function updates the input of the current visualization already running in the same Python process. -This clears any existing datasets from the scene, and adds the new dataset (and optionally metadata) to the scene. - -```text -def update(self, input: str|vtkDataSet, metadata: Metadata | None = None) -> int -``` - -Example: -```text -visualizer = Visor() - output = converter.to_vtk_file(a_processed_file,a_processed_file_vtk) - visualizer.start(input = a_processed_file_vtk) - another_output = converter.to_vtk_dataset(a_processed_result, vtk_dataset) - visualizer.update(vtk_dataset) - ### more code executed here -``` - -#### Add dataset -The `add_dataset` function adds a new dataset as input to current visualization already running in the same Python process. -This keeps any existing datasets in the scene, and adds the new dataset (and optionally metadata) to the scene. - -```text -def add_dataset(self, input: str|vtkDataSet, metadata: Metadata | None = None) -> int -``` - -Example: -```text -visualizer = Visor() - output = converter.to_vtk_file(a_processed_file,a_processed_file_vtk) - dataset_id1 = visualizer.start(input = a_processed_file_vtk) - another_output = converter.to_vtk_dataset(a_processed_result, vtk_dataset) - dataset_id2 = visualizer.add_dataset(vtk_dataset) - ### more code executed here -``` - -#### List datasetss -The `list_datasets` function lists all datasets currently in the scene of the current visualization already running in -the same Python process. The scene is not modified with this operation. - -```text -def list_datasets(self) -> list[int] -``` - -Example: -```text -visualizer = Visor() - output = converter.to_vtk_file(a_processed_file,a_processed_file_vtk) - visualizer.start(input = a_processed_file_vtk) - another_output = converter.to_vtk_dataset(a_processed_result, vtk_dataset) - visualizer.add_dataset(vtk_dataset) - visualizer.list_datasets() - ### more code executed here -``` - - -#### Remove dataset -The `remove_dataset` function removes a dataset from the current visualization already running in the same Python process. - - -```text -def remove_dataset(self, dataset_id: int) -``` - -Example: -```text -visualizer = Visor() - output = converter.to_vtk_file(a_processed_file,a_processed_file_vtk) - dataset_id1 = visualizer.start(input = a_processed_file_vtk) - another_output = converter.to_vtk_dataset(a_processed_result, vtk_dataset) - dataset_id2 = visualizer.add_dataset(vtk_dataset) - visualizer.remove_dataset(dataset_id2) - - ### more code executed here -``` - -#### List variables -The `list_variables` function lists all variables for a given dataset in the scene of the current visualization already running in -the same Python process. The scene is not modified with this operation. - -```text -def list_variables(self, dataset_id: int) -> list[VisorVariable] -``` - -Example: -```text -visualizer = Visor() - output = converter.to_vtk_file(a_processed_file,a_processed_file_vtk) - dataset_id1 = visualizer.start(input = a_processed_file_vtk) - dataset_id1_variables = visualizer.list_variables(dataset_id1) - ### more code executed here -``` - -#### Update variables -The `update_variables` function updates the variables for a given dataset in the scene of the current visualization already running in -the same Python process. - -**Limitation:** This feature is currently only supported for VTK datasets that are either vtkPolyData or vtkUnstructuredGrid. -VISOR also supports vtkMultiBlockDataSet and vtkMultiPieceDataset, and we plan to support for variable updates -on parts within these composite datasets in a future release, but -as of 2026/02/03, that is not yet supported. - - -```text -def update_variables( - self, - dataset_id: int, - variables: list[dict[str, Any]], -) -> None -``` - -Example: -```text -visualizer = Visor() - output = converter.to_vtk_file(a_processed_file,a_processed_file_vtk) - dataset_id1 = visualizer.start(input = a_processed_file_vtk) - dataset1_variables = visualizer.list_variables(dataset_id1) - # Example assumes updating the first variable in the list - variable_to_update = dataset1_variables[0] - # Get number of points and components if needed - num_points = variable_to_update.num_points - num_components = variable_to_update.num_components - name = variable_to_update.name - # Generate new variable data as a list or numpy array - new_vector_values = np.zeros((num_points, num_components)) - # Create dictionary for variable update - variable_update_info = { - "type": "point", - "name": name, - "num_components": num_components, - "data": new_vector_values - } - # Update the variable in VISOR - visualizer.update_variables(dataset_id1, [variable_update_info]) - ### more code executed here -``` - - -#### Stop -This API stops the rendering from the Trame service, it doesn't terminate the server or the connection. If the stop method isn't called, the visualization is stopped by an interrupt when the connections and servers are killed when the lifecycle of the VISOR instance ends. - -```text - def stop() -``` - - -Example: -```text -visualizer = Visor() - output = converter.to_vtk_file(a_processed_file,a_processed_file_vtk) - visualizer.start(input = a_processed_file_vtk) - another_output = converter.to_vtk_dataset(a_processed_result, vtk_dataset) - visualizer.update(vtk_dataset) - visualizer.stop() - ### more code executed here -``` - -#### Save state - -This is a function that saves the current state of the VISOR service to a file given the filepath. - -```text -async def save_state(self, state_directory_path) -``` - -#### Load state - -This is a function that loads the current state to the VISOR viewer. - -```text -def load_state(self, state_file) -``` - - - -## References - -* trame services used are documented [here](https://trame.readthedocs.io/en/latest/viewer.server.html#). -* [wslink](https://github.com/Kitware/wslink) -* The branch that contains the above code in a PoC form is [demo](https://github.com/ansys-internal/theia/tree/demo) - - -### Notes from discussion on 14th Nov. '24 - -* Rendering engine can be changed and it should be in the initialization of the service -* Add connect_to(server) api -* Initialize using an existing server -* Save state API - -### Notes from discussion on 21st Nov '24 -* The load/save state in terms of locking when multiple users/multiple sessions are involved -* Options for RenderingEngine versus configuration like VTK / WASM / Local Rendering / Remote Rendering - - -### Notes -The API for Python applications needs to be able to start VISOR on a background thread without the user needing to manage asynchronous code from their side. The destruction of reference of the VISOR instance can signal the destruction of the VISOR instance using the atexit python library. \ No newline at end of file diff --git a/doc/developer_docs/adrs/04-jsdoc-annotations.md b/doc/developer_docs/adrs/04-jsdoc-annotations.md deleted file mode 100644 index c664a690..00000000 --- a/doc/developer_docs/adrs/04-jsdoc-annotations.md +++ /dev/null @@ -1,82 +0,0 @@ -# ADR 04: JSDoc Annotations - -## Status -Proposed - -## Context -The VISOR web visualization project, in support of VTK.wasm, makes use of JavaScript to establish WebSocket connections and thereby communicate with its server-side component. By nature, JavaScript is dynamically typed, which means IDE code hinting features (e.g. Intellisense in VSCode) will often be unable to determine if written JavaScript is valid. This means that developers working in the VISOR project who rely on this feature for Python and TypeScript will be unable to do so when writing JavaScript. VISOR does have a React frontend which makes use of TypeScript (a statically typed language), although the decision to maintain "vanilla" JavaScript for the viewer WebSocket communications allows for quicker field testing and debugging, as it allows developers to skip a compilation step. - -## Decision -Ensure a reasonable amount of JSDoc annotations exist alongside JavaScript functions, classes, and other items to enable code hinting features in IDEs for developers adding to or modifying VISOR JavaScript. The JSDoc type definition names (i.e. "typedef" names) will be of the form "Visor_[type name]". For example: Visor_Vector3, Visor_WebSocketConnection, Visor_StateManager, etc. - -#### JSDoc on JavaScript Functions - -There are a multitude of ways JavaScript functions in VISOR may be annotated with JSDoc. Because functions in JavaScript can be standard declarations or expressions, corresponding JSDoc comments alongside functions may vary: - -```javascript -// example 1 - -/**@type{function(arr:any[]):number}*/ -const getArrayLength = arr => { - return arr.length; -}; -//////////////////////////////////////////////////////// -// example 2 - -/**@type{(arr:any[])=>number}*/ -const getArrayLength = arr => { - return arr.length; -}; -//////////////////////////////////////////////////////// -// example 3 - -/** - * @param {any[]} arr - * @return number - */ -function getArrayLength(arr) { - return arr.length; -} -``` - -#### JSDoc on JavaScript Objects - -Like JavaScript functions, there are several ways JavaScript objects in VISOR may be annotated with JSDoc. Unlike with functions however, JSDoc comments on objects may be slightly less straightforward: - -```javascript -// example 1 - -/** - * @typedef {Object} Visor_Vector3 - * @property {number} x - The x component. - * @property {number} y - The y component. - * @property {number} z - The z component. - */ - -/**@type{Visor_Vector3}*/ -let myVariable = null; -//////////////////////////////////////////////////////// -// example 2 - -/** - * @typedef {{x:number,y:number,z:number}} Visor_Vector3 - */ - -/**@type{Visor_Vector3}*/ -let myVariable = null; -//////////////////////////////////////////////////////// -// example 3 - note the use of the "@lends" tag - -/** - * @typedef {Object} Visor_Vector3 - */ - -let myVariable =/**@lends VISOR_Vector3#*/{ - /**@type{number}*/ - x: 0, - /**@type{number}*/ - y: 1, - /**@type{number}*/ - z: 0 -}; -``` \ No newline at end of file diff --git a/doc/developer_docs/adrs/05-states.md b/doc/developer_docs/adrs/05-states.md deleted file mode 100644 index c768b319..00000000 --- a/doc/developer_docs/adrs/05-states.md +++ /dev/null @@ -1,43 +0,0 @@ -# ADR 05: Definition of States and User Cases - -## Status -Verified and Accepted by PM and ACE stakeholders - -## Context -The VISOR project shall allow for saving and restoring of states, in order to provide a smooth experience to the end user. The scope of this ADR is to define what a state is, what information it should contain, and how the restore operation should work in different deployments. - -## User case scenarios -There are three scenarios in which save and restore states will be implemented. -1. VISOR is implemented as part of a larger application. The user exits the session. When they re-enter the application, VISOR will restore the state. This will work for all deployment types (desktop, on prem and cloud). -2. The application sends to VISOR a new model to dynamically upload (streaming scenario). -3. VISOR is deployed in an on-prem or cloud application, with multiple users having access to the same project. In this scenario, VISOR should always restore the latest state for each session, regardless of which user was the last one to run VISOR. - -The consequence of the three user case scenarios outlined above is that the VISOR state files will be saved per session, and no information about which user saved the state file needs to be stored / used in the restore operation. - -**Note:** In scenario 3., there will be users with different permissions on the session - edit / read-only. In the context of the VISOR project, this is irrelevant as VISOR does not allow for modification of the model / simulation workflow itself, but only visualization. Therefore, any user who is allows to launch VISOR and load the model should be allowed to save and restore states. - -## State definition -The following information needs to be stored in a state: -1. Camera settings (look at point, look from point, rotation, zooming factor) -2. Part visibility -3. Cross section settings (on/off. Orientation and position of the cross section plane) -4. Mesh visualization (on/off) -5. Parts color by variable (on a per-part basis) -6. Legend visualization settings (hide / show, position of the legend) -7. Legend min / max -8. Legend palette -No information about the current status of the UI should be stored - -## State restore -Here we describe the expectation when restoring a state. -All the settings defined in a state should restore automatically. The UI will reset to the default UI status when you load a model for the first time. -In user case scenario 2, there is the possibility that the new model contains a different topology (part list) or a different list of variables. Here the expected behavior: -1. Missing parts: the new model has missing parts compared to the one stored in the state. Drop the information on the extra parts. This is not needed and should not be used in any way. Do not store it moving forward. -2. Extra parts: the new model has new parts compared to the one stored in the state. Visualize them with the default settings: visible and colored by a constant. - -**Note:** part matching is done based on the part name - -3. Missing variables: the new model has missing variables compared to the ones stored in the state. Drop the information on the extra variables. This is not needed and should not be used in any way. Do not store it moving forward. -4. Extra variables: the new models has new variables compared to the ones stored in the state. They will appear in the list of available variables to color a part by (when appropriate), but their settings should be the default ones. - - diff --git a/doc/developer_docs/adrs/06-qp2_compliance.md b/doc/developer_docs/adrs/06-qp2_compliance.md deleted file mode 100644 index 26effb1f..00000000 --- a/doc/developer_docs/adrs/06-qp2_compliance.md +++ /dev/null @@ -1,96 +0,0 @@ -# ADR 06: Qp-2 compliance - -## Status -Approved - -## Context -The VISOR project follows the continuous development lifecycle of Ansys products. As such, it needs to adhere to the QP-2 guidelines. The full Quality Procedure can be found [here](https://ansys.policytech.com/dotNet/documents/?docid=1452&app=pt&source=search). - -## Responsible parties -QP-2 requires the following parties to be well defined, as person responsible for the different aspects of compliance. As pf December 2024, this is the list of people and roles. - -| Role | Person | -|--------------------|-----------------------| -| Release Manager | Palaniappan Nagappan | -| Team Lead | Marina Galvagni | -| Test Lead | Laurent Gerboud | -| Documentation Lead | Paul Coinaud | -| Product Manager | Anna Kvarnstrom | - -## QP-2 Main Requirements -In this section, we address the main requirements of QP-2 and how the project fulfills them - -### Tracebility -Both the code base and the project board of VISOR are hosted in the Ansys internal Github space. This ensures the following: -1. From a code point of view, being hosted on github allows for version control. Any audit would be able to quickly retrieve different versions of the code base and analyze the differences between versions. Single contributions are also tracked, making sure an audit could easily determine when a specific feature is entered into the code base. -2. From a project management point of view, the platform offers the Github project feature. Through this, the team can create issues, assign them, and track the code changes corresponding to each issue. Tests can also be associated with each Github issue, ensuring each feature is fully tested before release. -The Project associated with the VISOR code base can be found [here](https://github.com/orgs/ansys-internal/projects/420). - -### Correct Issue Definition -Each issue has the following mandatory fields: -1. Title: brief description of the issue -2. Description: lengthy description of the issue, with details to explain its context -3. Acceptance Criteria: a set of criteria that need to be met in order for the issue to be considered Done. - -Additional, optional fields are available to help with project management, such as iteration, links, third party software, and so on. - -Labels are used to keep track of additional information. More notabily, the following labels: -1. beta: feature not fully released to the end user. As such, it does not require testing and documentation. -2. class3: used to mark a "class 3 defect". These are hidden defects and require a special level of attention for QP-2 - see later in the Bug section. -3. maintenance / research / technical: these issues do not require testing nor documentation assodiated with them. They track, respectively: maintenance work, research spikes, and technical work (such as code refactoring) that do not have any impact on the users experience -4. documentation: issues to describe documentation work. Does not need any testing associated with it -5. test case: issue describing a test case. It will be associated with a specific functional issue. -6. test log: issue describing the results of running a test case. These are necessary only for tests that are not automated. -7. functional: issue that describles a new functionality that the user will have access to. These issues need to have documentation and testing associated with them - -There are multiple hierarchical levels of issues. While this structure isn't explicitly required by QP-2 and therefore not enforced, it helps to keep the work organized. This is done via issue types: -1. Epic: an epic is the highest level, describing a high-level functionality. A single epic can span multiple releases -2. Feature: a feature is a functionality that will be delivered in a single release cycle. It is testable and capable of adding value for the customer. -3. User Story: a single step in implementing a feature, that adds value that a user can verify independently from other user stories. It can be delived within a single iteration. -4. Task: a specific part of the work to implement a user story. Might not be testable on its own. - -### Independent review -QP-2 requires an independent review process. This is enforced in the VISOR project via the following mechanism. No user can directly push new code into the main branch (branch-protection is active). In order for the code to be merged, it needs to be reviewed and approved by at least one person who has not contribuited in the code changes. This is automatically enforced by Github. - -Moreover, issues can only be closed by someone who has not authored any of the PRs related to the issue itself. This ensures that each code change has been reviewed by an independent entity. This currently is not enforced by Github, but it is a practice inside the team. It can be verified by reviewing the history of each issue. - -### Testing -As described above, we use labels to mark test cases and test logs. Each new functionality will have tests associated with it to, and they will be executed before the release. Note that this might be done at the Feature or at the User Story level, not necessarily at both. - -A test case will contain all the instructions to reproduce the test. After being created, it needs to be reviewed by someone who is not its author. When executing it, at least one person between the author of the test case and the reviewer must not have been involved in developing the functionality that is being tested. - -The test cases can be ran manually or as part of the automated tests system. These automated tests are run nightly and before each PR is merged into the code base. Note that for automated tests, no test logs are necessary. - -### Bugs -Bugs are filed into the repository as issues with issue type: Bug. When filing a bug, the following fields are mandatory: -1. Title: short description of the issue. -2. Description: Lengthy description of the problem and how to reproduce it. -3. Severity: severity level of the bug. These are the levels: - 3a. Class 1: crash or major data loss - 3b. Class 2: Seriour problem - 3c. Class 2: Minor problem - 3d. Class 3: Hidden error. This is a separate type of bug that needs special attention (see [here](https://ansys.policytech.com/dotNet/documents/?docid=1338&app=pt&source=search)). It is reserved for bugs where the user gets a wrong output. -4. Fixed in: version that contains the bug fix - -Please note that once a bug is fixed by the developers, it goes into "Resolved" mode. It can be moved from Resolved to Done only after it has been verified by a person who was not involved in the code changes for the bug fix. This person needs to verify that the code change addresses the bug and then move the bug report from Resolved to Done. - -### OSS Usage and Security Scan -The VISOR project takes advantage of Third-Party Software components. As such, it needs to follow the procedures outlined in [QP-10](https://ansys.policytech.com/dotNet/documents/?docid=1228&app=pt&source=search) to ensure the integrated Third-Party Software satisfies functional and quality requirements. - -The Team Lead will originate the request for use of a Third-Party Software Component. Independent reviewers assigned by the Released Management Unit will review and approve the requests. This is all done via the OSS Sharepoint form. OSR reviews will therefore be recorded on this Shareport [site](https://ansys.sharepoint.com/sites/OpenSourceSoftwareTrackingIntake/Lists/OSR%20Reviewed%20Components/AllItems.aspx). - -Note that the VISOR project also takes advantage of an automated github workflow to scan third-party libraries to identify security concerns. These scans are executed nightly and at each PR push. Find more information [here](https://empowerment.dev.ansysapis.com/docs/devops/vulnerability-management/). Note that this mechanism also allows for an automatically created and retained list of Third-Party libraries used by VISOR. - -### Release -As VISOR is part of the continuous development cycle, there are no set dates for the releases. These are created on a per-need basis, balancing the needs of the team and the requests from users (internal and external to Ansys). - -In order for a release to be created, the following criteria needs to be met: -1. Stories and their tests are complete, reviewed and accepted -2. Resolved bugs are verified -3. Automated testing report has been created and published -4. Documentation is complete and reviewed -5. Regression tests have at least 90% passing rate -6. Total and priority bugs are within limits (20 bugs in total; 5 for Class 2 bugs; 0 for Class 3 bugs) -7. Third-Party Software components have been reviewed and accepted to be integrated -8. Known issues and limitations are approved -9. Legal Notices and Software Bill Of Material (SBOM) are up to date diff --git a/doc/developer_docs/adrs/07-scene-graph.md b/doc/developer_docs/adrs/07-scene-graph.md deleted file mode 100644 index 157732a9..00000000 --- a/doc/developer_docs/adrs/07-scene-graph.md +++ /dev/null @@ -1,528 +0,0 @@ -# ADR 07: Scene Graph - -## Status - -Proposed - -## Context - -The VISOR viewer must be aware of and preserve any object hierarchies that exist in files that are loaded. This is because the viewer must have the ability to perform actions on a single object, a custom-selected group of objects, or all descendants in a specific object's hierarchy. Examples of such actions include show, hide, select, and deselect. - -In VTK parlance, a file can represent a `vtkDataSet` or `vtkCompositeDataSet`. A `vtkDataSet` is a single polygon mesh or unstructured grid, whereas a `vtkCompositeDataSet` is a _hierarchy_ of polygon meshes or unstructured grids. - -Common subclasses of `vtkDataSet` are the `vtkPolyData` and `vtkUnstructuredGrid` types. The file extensions that typically contain these types are the following: - -- **.vtp** - `vtkPolyData` -- **.vtu** - `vtkUnstructuredGrid` - -Common subclasses of `vtkCompositeDataSet` are the `vtkMultiBlockDataSet` and `vtkMultiPieceDataSet` types. The file extensions that typically contain these types are the following (as you can see, the .vtm extension is used for both composite dataset types): - -- **.vtm** - `vtkMultiBlockDataSet` and `vtkMultiPieceDataSet` - -Because of these peculiarities among VTK datasets, VISOR requires a scene graph for management of the object hierarchies that may be present in the datasets. A scene graph is a hierarchical data structure commonly used in computer graphics and visualization to organize and manage the various objects that make up a graphical scene. It represents the spatial arrangement and relationships between objects, as well as their properties, transformations, and interactions. The scene graph allows for efficient rendering, interaction, and manipulation of complex scenes in 3D environments. - -As mentioned earlier, the VTK object types that contain hierarchies are the `vtkMultiBlockDataSet` and `vtkMultiPieceDataSet` types, which are subclasses of `vtkCompositeDataSet`. - -Although technically the `vtkPolyData` and `vtkUnstructuredGrid` types do not contain hierarchies, VISOR still treats them as being hierarchical objects, only with no children. This concept of "everything is a hierarchy" is beneficial to development, as it allows developers to normalize all scene graph methods and routines, without having to excessively make exceptions for "non-hierarchical" objects in code. - -At a high level, the following is an example of a scene graph in VISOR after loading a file named _many_blocks.vtm_: - -```text -● root (root) -└── ● many_blocks.vtm (vtkMultiBlockDataSet) - ├── ● Group A (vtkMultiBlockDataSet) - │ ├── ● untitled (vtkPolyData) - │ ├── ● untitled (vtkPolyData) - └── ● Group B (vtkMultiPieceDataSet) - ├── ● untitled (vtkPolyData) - ├── ● untitled (vtkPolyData) - ├── ● untitled (vtkUnstructuredGrid) - ├── ● untitled (vtkUnstructuredGrid) - └── ● untitled (vtkUnstructuredGrid) -``` - -As you can see, the _many_blocks.vtm_ node is a child of the _root_ node. The _root_ node is always the top-level node, and not the file node. This design decision gives us the opportunity to load multiple files into the scene, if future requirements were to demand so. - -Each node in the scene graph represents a single dataset. The dataset the node represents, however, can be "composite" or "non-composite". Composite datasets are `vtkMultiBlockDataSet` and `vtkMultiPieceDataSet`. Non-composite datasets are `vtkPolyData` and `vtkUnstructuredGrid`. - -A composite node is just a group of other nodes, and cannot be rendered in VTK _by itself_. A composite node must contain non-composite children in order to be "rendered". - -## Implementation - -The simplest way of building a scene graph from a VTK file is to design a `SceneGraphNode` class whereby its constructor takes a VTK dataset and loops through each one of its immediate child datasets. A new `SceneGraphNode` object is created for each of these child datasets by passing the child dataset to the child node. The child node's constructor then loops through each of its own child datasets and creates nodes for them as well, and for the grandchildren, and so on, until the hierarchy is completely "walked". - -As mentioned earlier, the dataset that is provided to a node's constructor will either be a composite `vtkCompositeDataSet` or non-composite `vtkDataSet`. Therefore, the node's constructor must have the ability to determine the type of dataset it was provided, so that it can set its respective class properties accordingly. These properties include `.NodeType` and `.Actor`, which are different depending on what kind of dataset the node represents. For example, a `vtkPolyData` node will have "vtkPolyData" as the `.NodeType`, and a non-null `.Actor`. Alternatively, a `vtkMultiBlockDataSet` will have "vtkMultiBlockDataSet" as the `.NodeType`, but have a null `.Actor`. - -The following is rudimentary example of a `SceneGraphNode` class, with eager loading of each `vtkActor`: - -```python -from vtkmodules.vtkCommonDataModel import ( - vtkMultiBlockDataSet, - vtkMultiPieceDataSet, - vtkUnstructuredGrid, - vtkPolyData, -) -from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper -from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter -from vtkmodules.vtkCommonExecutionModel import vtkPolyDataAlgorithm -import os -import re -from vtkmodules.vtkIOXML import ( - vtkXMLMultiBlockDataReader, - vtkXMLUnstructuredGridReader, - vtkXMLPolyDataReader, -) -from vtkmodules.vtkCommonDataModel import vtkCompositeDataSet, vtkDataSet - - -class SceneGraphNode: - def __init__(self, dataset: vtkDataSet | vtkCompositeDataSet): - node_type: str - actor: vtkActor | None = None - children: list[SceneGraphNode] = [] - if isinstance(dataset, vtkMultiBlockDataSet): - node_type = "vtkMultiBlockDataSet" - for i in range(dataset.GetNumberOfBlocks()): - child = dataset.GetBlock(i) - item = SceneGraphNode(child) - children.append(item) - elif isinstance(dataset, vtkMultiPieceDataSet): - node_type = "vtkMultiPieceDataSet" - for i in range(dataset.GetNumberOfPieces()): - child = dataset.GetBlock(i) - item = SceneGraphNode(child) - children.append(item) - elif isinstance(dataset, vtkUnstructuredGrid): - node_type = "vtkUnstructuredGrid" - algorithm: vtkPolyDataAlgorithm = vtkGeometryFilter() - algorithm.SetInputData(dataset) - algorithm.Update(None) - mapper = vtkPolyDataMapper() - mapper.SetInputConnection(algorithm.GetOutputPort()) - actor = vtkActor() - actor.SetMapper(mapper) - elif isinstance(dataset, vtkPolyData): - node_type = "vtkPolyData" - mapper = vtkPolyDataMapper() - mapper.SetInputData(dataset) - actor = vtkActor() - actor.SetMapper(mapper) - else: - raise RuntimeError(f"dataset type not yet supported: {type(dataset)}") - self.__NodeType: str = node_type - self.__Actor: vtkActor | None = actor - self.__Children: list[SceneGraphNode] = children - - @property - def NodeType(self): - return self.__NodeType - - @property - def Actor(self): - return self.__Actor - - @property - def Children(self): - return self.__Children - - -def file_to_dataset(file_path: str) -> vtkDataSet | vtkCompositeDataSet: - """""" - # make file_path lowercase so extension testing is case-insensitive - filename: str = os.path.basename(file_path).lower() - extension: str = os.path.splitext(filename)[1][1:] - dataset: vtkDataSet | vtkCompositeDataSet - if extension == "vtu": - """""" - reader: vtkXMLUnstructuredGridReader = vtkXMLUnstructuredGridReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - elif extension == "vtp": - """""" - reader: vtkXMLPolyDataReader = vtkXMLPolyDataReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - elif extension == "vtm": - """""" - reader: vtkXMLMultiBlockDataReader = vtkXMLMultiBlockDataReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - else: - """""" - raise RuntimeError(f"Unsupported file: {file_path}") - return dataset - - -def main(): - dataset = file_to_dataset("c:/path/to/file.vtm") - root_node = SceneGraphNode(dataset) -``` - -There are interesting things to note with this example. First of all, notice that if the node represents a `vtkUnstructuredGrid` or `vtkPolyData`, its `.Children` property will be empty. Secondly, notice that if the node represents a `vtkMultiBlockDataSet` or `vtkMultiPieceDataSet`, its `.Actor` property will be `None`. This follows the design principle mentioned earlier whereby composite datasets cannot be rendered on their own (because they have no actor), in addition to non-composite objects still being treated as hierarchical, just with 0 children. - -It is worth mentioning that there are limitations in this rudimentary `SceneGraphNode` example. For example, there is no way to update each node's pipeline at runtime from outside the class (i.e., you cannot add extra algorithms to the VTK pipeline before the initial dataset is handed over to a `vtkMapper`). Secondly, there is no convenient way to access all of a node's descendants (i.e. there is only a `.Children` array, which is just a node's immediate children, and does not include grandchildren, great-grandchildren, and so on). These limitations and solutions are discussed in the next two sections. - -## Runtime VTK Algorithm Pipeline Modding - -In order to improve the scalability of the `SceneGraphNode` class, each node's pipeline should be changeable from outside the class. In the rudimentary `SceneGraphNode` code example shown earlier, each node's base dataset is directly converted to a `vtkActor`. In other words, there is no ability to inject "middleware" to the pipeline before the dataset is sent to the `vtkActor`. - -We can change this by introducing the ability to provide a function parameter to each node, whereby the function is given a `vtkAlgorithm`, and returns a `vtkAlgorithm`. The updated code for this is as follows (see the method `.UpdateDescendantOrSelfActors()`): - -```python -from vtkmodules.vtkFiltersCore import vtkAppendPolyData -from typing import Callable -from vtkmodules.vtkCommonDataModel import ( - vtkMultiBlockDataSet, - vtkMultiPieceDataSet, - vtkUnstructuredGrid, - vtkPolyData, -) -from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper -from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter -from vtkmodules.vtkCommonExecutionModel import vtkPolyDataAlgorithm -import os -import re -from vtkmodules.vtkIOXML import ( - vtkXMLMultiBlockDataReader, - vtkXMLUnstructuredGridReader, - vtkXMLPolyDataReader, -) -from vtkmodules.vtkCommonDataModel import vtkCompositeDataSet, vtkDataSet -from vtkmodules.vtkFiltersModeling import vtkLoopSubdivisionFilter - - -class SceneGraphNode: - def __init__(self, dataset: vtkDataSet | vtkCompositeDataSet): - node_type: str - base_algorithm: vtkPolyDataAlgorithm | None = None - actor: vtkActor | None = None - children: list[SceneGraphNode] = [] - if isinstance(dataset, vtkMultiBlockDataSet): - node_type = "vtkMultiBlockDataSet" - for i in range(dataset.GetNumberOfBlocks()): - child = dataset.GetBlock(i) - item = SceneGraphNode(child) - children.append(item) - elif isinstance(dataset, vtkMultiPieceDataSet): - node_type = "vtkMultiPieceDataSet" - for i in range(dataset.GetNumberOfPieces()): - child = dataset.GetBlock(i) - item = SceneGraphNode(child) - children.append(item) - elif isinstance(dataset, vtkUnstructuredGrid): - node_type = "vtkUnstructuredGrid" - base_algorithm = vtkGeometryFilter() - base_algorithm.SetInputData(dataset) - base_algorithm.Update(None) - mapper = vtkPolyDataMapper() - mapper.SetInputConnection(base_algorithm.GetOutputPort()) - actor = vtkActor() - actor.SetMapper(mapper) - elif isinstance(dataset, vtkPolyData): - node_type = "vtkPolyData" - base_algorithm = vtkAppendPolyData() - base_algorithm.SetInputData(dataset) - mapper = vtkPolyDataMapper() - mapper.SetInputData(dataset) - actor = vtkActor() - actor.SetMapper(mapper) - else: - raise RuntimeError(f"dataset type not yet supported: {type(dataset)}") - self.__NodeType: str = node_type - self.__BaseAlgorithm: vtkPolyDataAlgorithm | None = base_algorithm - self.__Actor: vtkActor | None = actor - self.__Children: list[SceneGraphNode] = children - - @property - def NodeType(self): - return self.__NodeType - - @property - def Actor(self): - return self.__Actor - - @property - def Children(self): - return self.__Children - - def UpdateDescendantOrSelfActors( - self, algorithm_filter: Callable[[vtkPolyDataAlgorithm], vtkPolyDataAlgorithm] - ): - if self.__BaseAlgorithm is None: - # if base algorithm is not present, then this is a composite node - # therefore loop through all the children with the algorithm filter - for node in self.__Children: - node.UpdateDescendantOrSelfActors(algorithm_filter) - else: - # if base algorithm is present, then this is an actual mesh node - # therefore update the mapper with the new algorithm (and thus - # the actor) - mapper = self.__Actor.GetMapper() - if isinstance(mapper, vtkPolyDataMapper): - algorithm = algorithm_filter(self.__BaseAlgorithm) - mapper.SetInputConnection(algorithm.GetOutputPort()) - else: - raise RuntimeError( - f"mapper is not vtkPolyDataMapper. actual type: {type(mapper)}" - ) - - -def file_to_dataset(file_path: str) -> vtkDataSet | vtkCompositeDataSet: - """""" - # make file_path lowercase so extension testing is case-insensitive - filename: str = os.path.basename(file_path).lower() - extension: str = os.path.splitext(filename)[1][1:] - dataset: vtkDataSet | vtkCompositeDataSet - if extension == "vtu": - """""" - reader: vtkXMLUnstructuredGridReader = vtkXMLUnstructuredGridReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - elif extension == "vtp": - """""" - reader: vtkXMLPolyDataReader = vtkXMLPolyDataReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - elif extension == "vtm": - """""" - reader: vtkXMLMultiBlockDataReader = vtkXMLMultiBlockDataReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - else: - """""" - raise RuntimeError(f"Unsupported file: {file_path}") - return dataset - - -def main(): - dataset = file_to_dataset("c:/path/to/file.vtm") - root_node = SceneGraphNode(dataset) - - def algorithm_filter(algorithm: vtkPolyDataAlgorithm): - subdivide: vtkPolyDataAlgorithm = vtkLoopSubdivisionFilter() - subdivide.SetInputConnection(algorithm.GetOutputPort()) - return subdivide - - root_node.UpdateDescendantOrSelfActors(algorithm_filter) -``` - -Notice the addition of `base_algorithm = vtkAppendPolyData()` to the `vtkPolyData` match case in the node constructor. This algorithm serves as a "pass-through" filter to allow us to use our `vtkPolyData` object as an algorithm. - -Lastly, notice the `algorithm_filter` function passed to the `.UpdateDescendantOrSelfActors()` method. This function argument will be applied to every descendant node under the root node (since we called the method on the root node). - -## Iterate All Node Descendants (not just immediate children) - -At this point, iterating through the immediate children of a node is straightforward: - -```python -scene_root = SceneGraphNode(dataset) -for node in scene_root.Children: - print(f"node type: {node.NodeType}") -``` - -However, iterating through ALL descendants of a node requires a function definition to be called recursively: - -```python -def recursive_func(node: SceneGraphNode): - print(f"node type: {node.NodeType}") - for node in node.Children: - recursive_func(node) - - -scene_root = SceneGraphNode(dataset) -recursive_func(scene_root) -``` - -Alternatively, we could attach a special array and dictionary to each node to make each node's descendants much easier to iterate. See the following code for this functionality: - -```python -from vtkmodules.vtkFiltersCore import vtkAppendPolyData -from typing import Callable -from vtkmodules.vtkCommonDataModel import ( - vtkMultiBlockDataSet, - vtkMultiPieceDataSet, - vtkUnstructuredGrid, - vtkPolyData, -) -from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper -from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter -from vtkmodules.vtkCommonExecutionModel import vtkPolyDataAlgorithm -import os -import re -from vtkmodules.vtkIOXML import ( - vtkXMLMultiBlockDataReader, - vtkXMLUnstructuredGridReader, - vtkXMLPolyDataReader, -) -from vtkmodules.vtkCommonDataModel import vtkCompositeDataSet, vtkDataSet -from vtkmodules.vtkFiltersModeling import vtkLoopSubdivisionFilter -import random - - -class SceneGraphNode: - def __init__(self, dataset: vtkDataSet | vtkCompositeDataSet): - node_type: str - base_algorithm: vtkPolyDataAlgorithm | None = None - actor: vtkActor | None = None - children: list[SceneGraphNode] = [] - # in case this node gets serialized to JSON and used in JavaScript, - # limit the maximum value to 9007199254740991, since this is - # JavaScript's maximum safe integer - javascript_safe_id: int = random.randint(1000000000000000, 9007199254740991) - descendantNodesOrSelfDictionary: dict[int, SceneGraphNode] = { - javascript_safe_id: self - } - descendantNodesOrSelfArray: list[SceneGraphNode] = [self] - self.__DescendantNodesOrSelfDictionary: dict[ - int, SceneGraphNode - ] = descendantNodesOrSelfDictionary - self.__DescendantNodesOrSelfArray: list[ - SceneGraphNode - ] = descendantNodesOrSelfArray - if isinstance(dataset, vtkMultiBlockDataSet): - node_type = "vtkMultiBlockDataSet" - for i in range(dataset.GetNumberOfBlocks()): - child = dataset.GetBlock(i) - item = SceneGraphNode(child) - children.append(item) - descendantNodesOrSelfDictionary.update( - item.DescendantNodesOrSelfDictionary - ) - descendantNodesOrSelfArray.extend(item.DescendantNodesOrSelfArray) - elif isinstance(dataset, vtkMultiPieceDataSet): - node_type = "vtkMultiPieceDataSet" - for i in range(dataset.GetNumberOfPieces()): - child = dataset.GetBlock(i) - item = SceneGraphNode(child) - children.append(item) - descendantNodesOrSelfDictionary.update( - item.DescendantNodesOrSelfDictionary - ) - descendantNodesOrSelfArray.extend(item.DescendantNodesOrSelfArray) - elif isinstance(dataset, vtkUnstructuredGrid): - node_type = "vtkUnstructuredGrid" - base_algorithm = vtkGeometryFilter() - base_algorithm.SetInputData(dataset) - base_algorithm.Update(None) - mapper = vtkPolyDataMapper() - mapper.SetInputConnection(base_algorithm.GetOutputPort()) - actor = vtkActor() - actor.SetMapper(mapper) - elif isinstance(dataset, vtkPolyData): - node_type = "vtkPolyData" - base_algorithm = vtkAppendPolyData() - base_algorithm.SetInputData(dataset) - mapper = vtkPolyDataMapper() - mapper.SetInputData(dataset) - actor = vtkActor() - actor.SetMapper(mapper) - else: - raise RuntimeError(f"dataset type not yet supported: {type(dataset)}") - self.__NodeType: str = node_type - self.__BaseAlgorithm: vtkPolyDataAlgorithm | None = base_algorithm - self.__Actor: vtkActor | None = actor - self.__Children: list[SceneGraphNode] = children - - @property - def DescendantNodesOrSelfDictionary(self): - return self.__DescendantNodesOrSelfDictionary - - @property - def DescendantNodesOrSelfArray(self): - return self.__DescendantNodesOrSelfArray - - @property - def NodeType(self): - return self.__NodeType - - @property - def Actor(self): - return self.__Actor - - @property - def Children(self): - return self.__Children - - def UpdateDescendantOrSelfActors( - self, algorithm_filter: Callable[[vtkPolyDataAlgorithm], vtkPolyDataAlgorithm] - ): - if self.__BaseAlgorithm is None: - # if base algorithm is not present, then this is a composite node - # therefore loop through all the children with the algorithm filter - for node in self.__Children: - node.UpdateDescendantOrSelfActors(algorithm_filter) - else: - # if base algorithm is present, then this is an actual mesh node - # therefore update the mapper with the new algorithm (and thus - # the actor) - mapper = self.__Actor.GetMapper() - if isinstance(mapper, vtkPolyDataMapper): - algorithm = algorithm_filter(self.__BaseAlgorithm) - mapper.SetInputConnection(algorithm.GetOutputPort()) - else: - raise RuntimeError( - f"mapper is not vtkPolyDataMapper. actual type: {type(mapper)}" - ) - - -def file_to_dataset(file_path: str) -> vtkDataSet | vtkCompositeDataSet: - """""" - # make file_path lowercase so extension testing is case-insensitive - filename: str = os.path.basename(file_path).lower() - extension: str = os.path.splitext(filename)[1][1:] - dataset: vtkDataSet | vtkCompositeDataSet - if extension == "vtu": - """""" - reader: vtkXMLUnstructuredGridReader = vtkXMLUnstructuredGridReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - elif extension == "vtp": - """""" - reader: vtkXMLPolyDataReader = vtkXMLPolyDataReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - elif extension == "vtm": - """""" - reader: vtkXMLMultiBlockDataReader = vtkXMLMultiBlockDataReader() - reader.SetFileName(file_path) - reader.Update() - dataset = reader.GetOutput() - else: - """""" - raise RuntimeError(f"Unsupported file: {file_path}") - return dataset - - -def main(): - dataset = file_to_dataset("c:/path/to/file.vtm") - root_node = SceneGraphNode(dataset) - - def algorithm_filter(algorithm: vtkPolyDataAlgorithm): - subdivide: vtkPolyDataAlgorithm = vtkLoopSubdivisionFilter() - subdivide.SetInputConnection(algorithm.GetOutputPort()) - return subdivide - - root_node.UpdateDescendantOrSelfActors(algorithm_filter) -``` - -The property `DescendantNodesOrSelfDictionary` is a dictionary that contains all descendant nodes of a node PLUS the node it was called from. The key is a random integer, and the value is the descendant node. - -Notice that the name of the properties `DescendantNodesOrSelfDictionary` and `DescendantNodesOrSelfArray` include the phrase "OrSelf". This is because the content of these collections depends on whether the node they are accessed from is a composite or non-composite node. If it is a composite node, the dictionary and array contain all descendant nodes of the node the property was accessed from PLUS the node the properties were accessed from. If the node the property was accessed from is a non-composite node, then the collections contain ONLY the node that the property was accessed from. - -We can now shorten our iteration code to the following: - -```python -scene_root = SceneGraphNode(dataset) -for node in scene_root.DescendantNodesOrSelfArray: - print(f"node type: {node.NodeType}") -``` - -The code above will iterate through all descendants of the scene root, BUT the first element in the collection will be the scene root itself. The same goes for the dictionary as well. \ No newline at end of file diff --git a/doc/developer_docs/adrs/08-awc-decision.md b/doc/developer_docs/adrs/08-awc-decision.md deleted file mode 100644 index 02c547a2..00000000 --- a/doc/developer_docs/adrs/08-awc-decision.md +++ /dev/null @@ -1,38 +0,0 @@ -# ADR 08: AWC adoption - -## Status - -Team agreement - -## Context - -In the initial implementation of VISOR, the Ansys Web Components (AWCs) were used to create the front end features. This has been done with the following goals in mind: -- make the look and feel of VISOR common with the rest of Ansys products. This is especially important considering that VISOR is a Shared Technology Component, to be embedded inside other Ansys frameworks; -- reduce technical debt moving forward. Once the AWCs are used in VISOR, changes in the Ansys guidelines for the UI / UX would result in a simple component update, without having to have the team re-write all the UI elements; -- Outsource the UI work to another team. As the VISOR team is small, we'd like to outsource as many components as possible. - -The team therefore started the project using AWCs for the front end. - -## Issues with AWCs adoption - -We list a number of issues the team has encountered when using the AWCs. - -- Lack of support for new Dash versions
-AWCs currently fully support previous version of Dash (2.6 released in August 2022), and VISOR requires newer versions of Dash (>2.16, but preferably 3.0.1) due to the usage of JacaSacript Modules in the Trame Client, which is used in the VISOR client. This is a crucial technology choice which requires WebAssembly files and JS Modules (mjs) files. This means that the VISOR integration inside of SAF can not be done with UI elements if they are developed using AWCs. - -- Non React-native: size and performance
-AWCs are available both in Angular and React. Upon further investigation, though, it appears that the React components are not native, but are obtained via a translation of the native Angular components. This creates performance and memory issues. The React AWCs tend to be very big in size and when VISOR needs to bundle all of its components and wrap them for its own bundle but even further the custom Dash component, the end result is excessively big in size and creates complications for the bundling of the VISOR client React component. The VISOR client already has to bundle in Dash the WebAssembly and JS Module files and adding excessive additional load to our bundle is creating issues that are ending up in loading times. At this point in time we have not bundled the AWCs in our dash component due to the fact that we would need to invest time to optimize and drive down the size of our bundle for it to be in acceptable sizes. - -- Lack or personalization and optimization options
-AWCs appear to be designed to be used 'as is' in a Solution Application. But this is not VISOR's user case. VISOR currently is only focusing on MVP functionalities but going forward there are more UI elements that are going to be needing further customization, such as elements related to time variant domains and animation capabilities, which aren't commonly required in other tools. The customization levels are going to be even more prevalent as the project expands and the UI elements we are going to be loading on screen are going to be needing high optimization so that they don't take up the memory budget we require for loading meshes. Without the alignment of high performance React native elements it will not be possible for VISOR long term to be able to use them. In the memory budget we have on the browser we are required to optimize for providing as much as possible of that budget to the graphics engine and the browser to load complicated and the biggest meshes possible so we are bound to drive down the cost of UI elements as much as possible in the long term. The more elements we use this is going to become an even bigger issue and since this is a very specialized visualization application the consistency is actually compared to other Ansys applications which also have their own specialized elements. - -- Low prioritization of missing features and bugs
-Some pretty basic features for VISOR's use case seem to be missing from AWC components. As the AWC team focuses mainly on the Angular version, little prioritization is given to our requests. For example, see the issues raised:
https://github.com/ansys-internal/ansys-web-components/issues/2156
The bugs associated with the issue have been filed 3 weeks after the initial report and given low or medium priority. The lack of responsiveness creates a dependency that is hard to accept and justify. These discussionss with the AWC team show that the requirements that VISOR provides are not necessarily aligned with the scope of AWCs. We require AWC which are fully customizable by other teams and able to be integrated in React applications, not necessarily used 'as is' in a Solution Application. - -- Rejection of features
-Some features we've requested to the AWC team have been rejected, even through we feel they're basic requests that VISOR can not stay without. For example, see item 1. in this discussion:
https://github.com/ansys-internal/ansys-web-components/issues/2156
The request to have a tree (for the part list) that adapts in size with the length of the part names is rejected. We therefore are left with a very large UI component that occupies almost 1/2 of the rendering window even if the text of the part list is small. Similarly, a request to control the padding of the strings is rejected, leaving us with UI components way too large. - -## Decision - -Given the issues listed in this ADR, the team has decided to abandon the AWCs for VISOR's front end and will be looking at having its own specialized elements. We remain open to discussion in the future if the project's requirement were to re-align with AWCs target. We also remain open to the possibility of alternative ways to achieve consistency with other Ansys products such as Theme libraries and shared CSS resources, as well as guidelines which allow for common look and feel even through the elements are implemented using different technologies. - diff --git a/doc/developer_docs/adrs/09-visor-trame-logging.md b/doc/developer_docs/adrs/09-visor-trame-logging.md deleted file mode 100644 index df70e2bc..00000000 --- a/doc/developer_docs/adrs/09-visor-trame-logging.md +++ /dev/null @@ -1,350 +0,0 @@ - -# ADR 09: VISOR Logging - -## Table of Contents -- [Decision](#decision) -- [Context](#context) - - [Unifying Logging in VISOR](#unifying-logging-in-visor) - - [Adding Trame Logs in VISOR](#adding-trame-logging-in-visor) -- [Proposed Changes](#proposed-changes) - - [Unify Logging](#unify-logging) - - [Trame Logging](#trame-logging) -- [Example Log Output](#example-log-output) -- [Observability Compliance](#observability-compliance) -- [Related Issues](#related-issues) - - - -## Decision - -* Unify the VISOR Python logging by implementing a custom VisorLogger class and -using it throughout the project. -* Add a default log directory where all Python logs are stored, configured in the `config.Settings` class. -* Allow a user to enable additional trame logging to a customizable log location, by adding a keyword -argument to the VISOR class constructor. In standalone VISOR, this is configured in the `config.Settings` -class and passed to the VISOR class upon instantiation. - -## Context - - -### Unifying Logging in VISOR - -Logging in the VISOR Python code is currently configured separately in individual VISOR viewer modules. Most of the -modules set up logging something like the following example code from `application.py`: -```angular2html -import logging -logger = logging.getLogger(__name__) -log_path = path.join(curdir, "logs") -from pathlib import Path - -Path(log_path).mkdir(parents=True, exist_ok=True) -file = path.join(log_path, "visor.log") -logging.basicConfig(filename=file, encoding="utf-8", level=logging.DEBUG) -``` - -There are a few reasons why we would benefit from centralizing this code for consistency across the project. -1. **Adds consistency in logging across the project**: This would allow our logs to be consistent in the log naming, -output location, formatting, and log level. -2. **Simplifies logging setup**: Easier to set up logging by utilizing reusable components. -3. **Adds clarity in logging practices**: Having centralized logging in the project allows us to more -easily evaluate and make changes to to comply with Ansys standards. - - -### Adding Trame Logging in VISOR - -In addition to the existing logging in VISOR, a user may want extra log info coming from Trame. - -We would like to allow a user to optionally enable additional logging about the Trame server, and for -them to be able to select the output location where those logs are written. - -By default, this option would be disabled. - - - -## Proposed Changes - -### Unify Logging -We can create a VisorLogger subclass of the Python logging.Logger class, where the file handling is centralized, and -the default logging level and format are defined. - -We can define a default log directory within the application `config.Settings` class as follows: -```angular2html -from pathlib import Path -from pydantic_settings import BaseSettings - -class Settings(BaseSettings): - app_name: str = "VISOR Viewer" - default_host: str = "localhost" - default_port: int = 8081 - default_standalone: bool = True - default_log_dir: str = str(Path.cwd().joinpath("logs")) # New setting -``` - -The following VisorLogging class can use the `default_log_dir` as a default if no other directory is set. -```angular2html -"""Logging configuration""" -import logging -from logging import Logger -from pathlib import Path - -from ansys.visor.viewer.config import Settings - -class VisorLogger(Logger): - """ - Custom logger for the VISOR app. - This logger writes logs to a file, allows setting the log level, - and ensures the log directory exists. - Args: - name (str): The name of the logger, typically the module or class name. - filename (str): The name of the log file. - log_dir (Optional[str]): Directory to save logs. - Default is the default_log_dir from config settings. - level (int): Logging level. Default is logging.DEBUG. - """ - - # Logging format to comply with Ansys ADR: - # https://github.com/ansys-internal/architecture-decision-records/blob/main/content/docs/adrs/0016-observability-strategy.md - LOGGING_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d %(funcName)s()] - %(message)s" - ENCODING = "utf-8" - - def __init__(self, - name: str, - filename: str, - log_dir: str | None = None, - level: int =logging.DEBUG): - # Initialize the parent class - super().__init__(name, level) - - # Log level - self.level = level - - # Set up the log directory and file path - self.filename = filename - self.log_dir = log_dir - if log_dir is None: - settings = Settings() - self.log_dir = settings.default_log_dir - self.file_path = self.get_file_path() - - # Ensure the log directory exists - self.create_dir() - - # Configure the root logger via basicConfig - # (this will affect any logger that doesn't have a handler) - logging.basicConfig( - level=level, - format=self.LOGGING_FORMAT, - handlers=[ - logging.FileHandler( - self.file_path, - encoding=self.ENCODING - ) - ], - ) - - # Create file handler and set logging level - file_handler = logging.FileHandler( - self.file_path, - encoding=self.ENCODING - ) - file_handler.setLevel(level) - - # Add formatter for file handler - formatter = logging.Formatter(self.LOGGING_FORMAT) - file_handler.setFormatter(formatter) - - self.addHandler(file_handler) - - # Prevent propagation to the root logger - self.propagate = False - - def create_dir(self) -> None: - Path(self.log_dir).mkdir(parents=True, exist_ok=True) - - def get_file_path(self) -> Path: - return Path(self.log_dir).joinpath(self.filename) -``` - -For convenience, we can also create a subclass of the above that uses the default log directory from the project settings, -and writes to a file called `visor.log`. - -```angular2html -class VisorDefaultLogger(VisorLogger): - """ - Custom logger for VISOR app with a default log file. - This logger writes to a fixed log file named "visor.log", ensuring - consistency across multiple modules within the project. - It inherits from the VisorLogger class, which allows for centralized - configuration and logging. - This default logger is intended for logging all of the project-related - messages to the same log file across different modules while maintaining - a consistent logging format and level. - Args: - name (str): The name of the logger, typically the module or class name. - """ - def __init__(self, name): - # Initialize the parent class - super().__init__(name, "visor.log") -``` - - -To summarize: - -* Create a `default_log_dir` in the `Settings` class, which a user will configure for -the needs of their application. -* Create a `VisorLogger` subclass of the Python `logging.Logger` class, which sets up logging to a file, -allows setting the log level, and ensures the log directory exists. If a log directory is not specified, use the -default_log_dir from the Settings class. - * Note that within `VisorLogger` the `basicConfig` is configured, which enables any logger that doesn't - have a handler to continue to write to the specified log file even if it is not using the logger - explicitly (e.g. the trame logger currently, but any other framework that does logging under the - hood will be captured by this too). -* Created a `VisorDefaultLogger` subclass of `VisorLogger` which takes only the logger name -(usually the file name) as input, and writes out to a file called `visor.log` in the `default_log_dir` directory. This class is a convenience that was created to simplify and unify the logging across modules. -* Use `VisorDefaultLogger` in most of the viewer modules (anywhere that had `visor.log` specified as the -output log file previously). -* Use `VisorLogger` to log to `server.log` - - -**Substantive changes**: The changes above should be mostly invisible to the user. However, the following will be different: -* Logging format across all files -* Ability to set the log output directory in `config.Settings` -* Output directory for `server_instances.log` is now the `default_log_dir` -(this used to be under `src\ansys\visor\viewer\logs`) - - -### Trame Logging - -We propose the following implementation. -1. Expose a `trame_log_dir` setting in `config.Settings`, which is by default set to None, but when set, -turns on Trame logging which will write the output log files to this directory. -2. Application logs: `visor_trame_app.log` - * **Direct Trame's native Python logs to a custom file**: - Trame uses Python's `logging` library to log using logger names `trame`, `trame_server`, and `trame.app`. - By default, these are written out to `visor.log`, but we can also capture these and redirect them to - a separate Trame application log file using the Python `logging` library. - * **Lifecycle hooks**: Trame offers hooks that can be added about the Trame server lifecycle - (e.g. `on_server_start`, `on_client_exited`). We can log these under a `trame_lifecycle` logger name - and write to the same log file as above. -3. Network logs: `visor_trame_network.log` - * The Trame server has an optional keyword argument `log_network` - (see the [Trame docs](https://trame.readthedocs.io/en/latest/core.server.html)), - which is False by default, but when set to a path to a log file, will write out - additional logs to that file. This logs communication between Python and the frontend. - - -Note that the trame log dir is configurable, but the trame log names are fixed. - - - -## Example Log Output - ---- -1. application logs via `trame.logger` and lifecycle hooks: provides information about the application lifecycle (tracking when the server start, updates, ends). The output looks like e.g. -``` -2025-05-01 08:09:26,050 - trame.decorators.klass - DEBUG - Instance created -2025-05-01 08:09:26,050 - trame.decorators.klass - DEBUG - server= prefix='' -2025-05-01 08:09:26,050 - trame.decorators.klass - DEBUG - state.change(['plane_widget'])(_on_widget_update) -2025-05-01 08:09:26,050 - trame.decorators.klass - DEBUG - trigger(get_scene_graph_json)(get_scene_graph_json) -2025-05-01 08:09:26,051 - trame.decorators.klass - DEBUG - trigger(node_hide)(node_hide) -2025-05-01 08:09:26,051 - trame.decorators.klass - DEBUG - trigger(node_show)(node_show) -2025-05-01 08:09:26,051 - trame.decorators.klass - DEBUG - trigger(toggle_cross_section)(toggle_cross_section) -2025-05-01 08:09:26,051 - trame.decorators.klass - DEBUG - trigger(toggle_wireframe)(toggle_wireframe) -2025-05-01 08:09:26,051 - trame.decorators.klass - DEBUG - trigger(update_selection)(update_selection) -2025-05-06 15:14:02,280 - trame_server.controller - INFO - [controller.py:70 register_trigger()] - trigger(update_selection) -2025-05-06 15:39:42,278 - trame_lifecycle - DEBUG - [application.py:163 server_ready()] - Server is ready. -2025-05-06 15:39:43,113 - trame_lifecycle - DEBUG - [application.py:168 client_connected()] - Client connected. -2025-05-06 15:39:46,232 - trame_lifecycle - DEBUG - [application.py:173 client_exited()] - Client exited. -2025-05-06 15:39:49,595 - trame_lifecycle - DEBUG - [application.py:168 client_connected()] - Client connected. -2025-05-06 15:39:51,736 - trame_lifecycle - DEBUG - [application.py:173 client_exited()] - Client exited. -2025-05-06 15:39:54,949 - trame_lifecycle - DEBUG - [application.py:178 server_exited()] - Server is exiting. -2025-05-06 15:39:57,494 - trame_lifecycle - DEBUG - [application.py:163 server_ready()] - Server is ready. -2025-05-06 15:39:59,178 - trame_lifecycle - DEBUG - [application.py:168 client_connected()] - Client connected. -2025-05-06 15:40:06,227 - trame_lifecycle - DEBUG - [application.py:178 server_exited()] - Server is exiting. -``` - - -2. The log_network Server option provides logs of communication between Python and the frontend, e.g. -``` ------------ STATE: Client => Server ----------- -[ - { - "key": "trame__busy", - "value": 0 - } -] ------------------------------------------------------------- ------------ STATE: Server => Client ----------- -{ - "trame__busy": 0 -} ------------------------------------------------------------- ------------ EVENT: Client => Server ----------- -{ - "name": "get_scene_graph_json", - "args": [], - "kwargs": {} -} ------------------------------------------------------------- ------------ EVENT: Client => Server ----------- -{ - "name": "node_show", - "args": [ - 7601913218618099 - ], - "kwargs": {} -} ------------------------------------------------------------- ------------ EVENT: Client => Server ----------- -{ - "name": "node_show", - "args": [ - 3465350337367369 - ], - "kwargs": {} -} -``` -## Observability Compliance - -ADR [#16](https://github.com/ansys-internal/architecture-decision-records/blob/main/content/docs/adrs/0016-observability-strategy.md) -outlines observability requirements on an Ansys level. - -We have logs, but no traces or metrics implemented yet. Traces are required for all applications / services -that are part of a distributed / microservices architecture, so we will need OpenTelemetry integration in VISOR. -This is in our backlog ([#94](https://github.com/ansys-internal/theia/issues/94)), -and we would like to take steps to move closer to this. - -After unifying the main logging mechanism in VISOR, we will have made a couple of improvements bringing us -closer to the logging requirements. - -| Requirement | Current | Unified Logs | -|-------------------------------------------------------------------------|-------------------------------|-------| -| [Required] Logs | ✅ | ✅ | -| [Required] Logs are structured, well formatted | ✅ | ✅ | -| [Required] Logs generate high severity log events (ERROR, FATAL) | ✅ | ✅ | -| [Required] Logs in distributed envs are JSON formatted | ❌ | ❌ | -| [Required] Logs able to change min severity level through configuration | Yes, but not in one place | ✅ | -| [Required Field] Message | ✅ | ✅ | -| [Required Field] LoggerName | ✅ | ✅ | -| [Required Field] Level | ✅ | ✅ | -| [Required Field] Timestamp | ❌ | ✅ | -| [Recommended Field] LineNo | ❌ | ✅ | -| [Recommended Field] FileName/Class/Module | ❌ | ✅ | -| Traces* | ❌ | ❌ | -| Metrics | ❌ | ❌ | -| [Required Field] TraceId | N/A (until traces implemented) | N/A (until traces implemented) | -| [Required Field] SpanId | N/A (until traces implemented) | N/A (until traces implemented) | -| [Required Field] ServiceName | N/A (until traces implemented) | N/A (until traces implemented) | - -\* Because do not have traces implemented yet, we can't yet to add the TraceId, SpandId, or ServiceName in our logs. - - -## Related Issues - -Two issues related to this topic are here: -* [#251](https://github.com/ansys-internal/theia/issues/251) -Unify logging mechanisms -(PR [#252](https://github.com/ansys-internal/theia/pull/252)) -* [#236](https://github.com/ansys-internal/theia/issues/236) -Create logging mechanism for Trame server in VISOR -(PR [239](https://github.com/ansys-internal/theia/pull/239)) diff --git a/doc/developer_docs/adrs/10-visor-http-api.md b/doc/developer_docs/adrs/10-visor-http-api.md deleted file mode 100644 index e6709964..00000000 --- a/doc/developer_docs/adrs/10-visor-http-api.md +++ /dev/null @@ -1,475 +0,0 @@ -# ADR 10: VISOR RESTful Service - -## Status -Decided - -## Context -VISOR is the visualization component for Solutions Applications Framework. VISOR is used a service through the PIM configuration management from SAF. This service is managed by SAF Product Instance Manager (PIM) in terms of its lifecycle and in that way its configuration allows it to be used by SAF engineers through the REST interface it provides. - -## VISOR Service - -The VISOR service currently only supports an infrastructure based on Trame client-side rendering through VTK.WASM technology. This Trame framework efficiently only supports only one session and the authentication and authorization for the session is handled outside of VISOR. The client-server connection is based on ws-link which creates a WebSocket connection from the server to the client browser of the user. Management of files for VISOR currently only support loading in memory from a local storage unit in order to create an internal representation in memory based on a scene-graph. The VTK pipeline is setup on the server and the final stage is transferred to the client where it will locally manage user interaction on an optimistic mechanism that most of the processes can be serialized through its architecture and locally caching and computation will only be transferred to the server in terms of state management through the internal VISOR mechanism. - -VISOR service can save its state and load from its previous state. The way the current VISOR service is managed is shown from the following sequence diagram. - - -#### Sequence Diagram - - -```mermaid - sequenceDiagram - Visor_instance->>Trame_server: start service - Trame_server->>wslink: start - wslink-->>Trame_server: wslink started successfully - Trame_server-->>Visor_instance: Trame server started successfully - Visor_instance->>Trame_server: update state and rendering - Trame_server-->Server.State: update state 'input_file' - Visor_instance-->VTK_Local_Rendering: update VTK pipeline - VTK_Local_Rendering-->wslink: update rendering - Visor_instance->>Trame_server: stop server - Trame_server->>wslink: wslink.stop() - wslink-->Tram_server: wslink has stopped successfully - Trame_server-->Visor_instance: trame server has stopped successfully -``` - - -### REST API - - VISOR's REST API is following the OpenAPI specification and is versioned with the same version as VISOR (it doesn't have an independent versioning scheme) from the rest of VISOR and the VISOR Python API. - -### GET / -The get root endpoint returns the url where the visualization is going to be hosted. -```json -{"/":{ - "get":{ - "summary":"Get Url", - "description":"Get the URL of visualizer. It initializes visualizer if it is not initialized", - "operationId":"get_url__get", - "responses":{ - "200":{"description":"Successful Response", - "content":{ - "application/json":{"schema":{}} - } - } - } - } - }, -``` - - -#### GET /info -Get the information of the visualizer instance. It provides the information set by the config.py file which contains the basic settings for creating a VISOR instance. -The Settings object is defined as follows: - -```python -class Settings(BaseSettings): - app_name: str = "VISOR Viewer" - default_host: str = "localhost" - default_port: int = 8081 - default_standalone: bool = True - default_client_bundle: str = Path("client_bundle") - default_log_dir: str = str(Path.cwd().joinpath("logs")) - trame_log_dir: str | None = None -``` -The app_name is able to rename the application name of the component for the current execution. -The default_host and default_port make it possible to provide a different host and port of execution. -The default_standalone is whether the VISOR server is going to be hosting the webclient. -The default_client_bundle is where the client bundle has been deployed for the static client code -The default_log_dir provides the path to logs -The default_trame_log_dir provides the path to trame logging. - - -When the VISOR service is started by an external program like uvicorn the service is using these Settings in order to launch VISOR on a specific host, port and use those logs. - - - -```json -{ - "app_name": "VISOR Viewer", - "host": "localhost", - "port": 8081, - "standalone": true, - "file_input_path": null -} -``` - -### POST initialize/ - -This endpoint provides the chance to initialize the defaults for the Trame service this VISOR service will be controlling. - -```Python -class InitProps(BaseModel): - """Properties for initializing the server.""" - host: str = Field(..., description="Host address", example="localhost") - port: int = Field(..., description="Port number", example=8081) -``` - -```json -"/initialize":{"post":{"summary":"Initialize Server","description":"Initialize the server with the given port, host and client distribution path","operationId":"initialize_server_initialize_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitProps"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}} -``` - - -### POST start/ - -This endpoint starts the visualization for the service on the provided url for a single session. -It can provide an input file on start as an option. -Only files are supported on this API since this is a RESTful service based on HTTP API. - -```json -"/start":{ - "post":{ - "summary":"Start Instance", - "description":"Start visualizer instance", - "operationId":"start_instance_start_post", - "requestBody":{ - "content":{ - "application/json":{ - "schema":{ - "$ref":"#/components/schemas/StartProps"} - } - }, - "required":true}, - "responses":{ - "200":{ - "description":"Successful Response", - "content":{"application/json":{"schema":{}}}}, - "422":{ - "description":"Validation Error", - "content":{ - "application/json":{ - "schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}, - -``` - -The parameter model for the start endpoint are the following: - -```Python -class StartProps(BaseModel): - """Properties for starting the visualizer instance.""" - file_path: Optional[str] = Field(None, description="Path to the input file", example="path/to/file.vtk") - metadata: Optional[Metadata] = Field(None, description="Metadata for the visualizer", example={"name": "test_model", "unit": "m"}) - timeout: Optional[int] = Field(0, description="Timeout in seconds") -``` - -### POST /update - -This endpoint updates the input file to the service. Only files are supported to this endpoint as there is currently no efficient serialization mechanism through this RESTful service for any other data formats. - -```json -"/update":{ - "post":{ - "summary":"Update", - "description":"Update the input file of visualizer instance", - "operationId":"update_update_post", - "requestBody":{ - "content":{ - "application/json":{ - "schema":{ - "$ref":"#/components/schemas/UpdateProps"}}}, - "required":true}, - "responses":{ - "200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}, - "422":{ - "description":"Validation Error", - "content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}, -``` - -The parameter model for the update endpoint are the following: -```Python -class UpdateProps(BaseModel): - """Input for updating the visualizer instance.""" - file_path: str = Field(..., description="Path to the new input file", example="path/to/updated_file.vtk") - metadata: Optional[Metadata] = Field(None, description="Metadata for the visualizer", example={"name": "updated_model", "unit": "m"}) -``` - -### POST /add_dataset - -This endpoint adds a new input file to the service. Only files are supported to this endpoint as there is currently no -efficient serialization mechanism through this RESTful service for any other data formats. - -```json -"/add_dataset": { - "post": { - "summary": "Add Dataset", - "operationId": "add_dataset_add_dataset_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateProps" - }, - "example": { - "file_path": "path/to/updated_file.vtk", - "metadata": { - "name": "updated_model", - "unit": "m" - } - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } -}, -``` - - -The parameter model for the add_dataset endpoint are the following (same as the update endpoint): -```Python -class UpdateProps(BaseModel): - """Input for updating the visualizer instance.""" - file_path: str = Field(..., description="Path to the new input file", example="path/to/updated_file.vtk") - metadata: Optional[Metadata] = Field(None, description="Metadata for the visualizer", example={"name": "updated_model", "unit": "m"}) -``` - -### GET /list_datasets -This endpoint lists all datasets currently loaded in the visualizer instance. -```json -"/list_datasets": { - "get": { - "summary": "List Datasets", - "operationId": "list_datasets_list_datasets_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - } - } - } -}, -``` - -### POST /remove_dataset -This endpoint removes a dataset from the visualizer instance. A dataset ID is required to identify which dataset to remove. - -```json -"/remove_dataset": { - "post": { - "summary": "Remove Dataset", - "operationId": "remove_dataset_remove_dataset_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RemoveDatasetProps" - }, - "example": { - "dataset_id": 123456 - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } -}, -``` - -The parameter model for the remove_dataset endpoint are the following: -```Python -class RemoveDatasetProps(BaseModel): - """Input for removing a dataset from the visualizer instance.""" - dataset_id: int = Field(..., description="ID of the dataset to remove", example=12345) -``` - -### GET /{dataset_id}/list_variables -This endpoint lists variables for a specific dataset in the visualizer instance. - -```json - "/{dataset_id}/list_variables": { - "get": { - "summary": "List Variables", - "operationId": "list_variables__dataset_id__list_variables_get", - "parameters": [ - { - "name": "dataset_id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "title": "Dataset Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } -}, -``` - -### POST /{dataset_id}/update_variables -This endpoint updates variables for a specific dataset in the visualizer instance. - -**Limitation:** This feature is currently only supported for VTK datasets that are either vtkPolyData or vtkUnstructuredGrid. -VISOR also supports vtkMultiBlockDataSet and vtkMultiPieceDataset, and we plan to support for variable updates -on parts within these composite datasets in a future release, but -as of 2026/02/03, that is not yet supported. - -```json -"/{dataset_id}/update_variables": { - "post": { - "summary": "Update Variables", - "operationId": "update_variables__dataset_id__update_variables_post", - "parameters": [ - { - "name": "dataset_id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "title": "Dataset Id" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateVariableProps" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } -}, -``` - -The parameter model for the update_variables endpoint are the following: -```Python -class UpdateVariableInfo(BaseModel): - """Input for updating a variable in the visualizer instance.""" - name: str = Field(..., description="Name of the variable to update", example="temperature") - type: str = Field(..., description="Type of the variable (point/cell)", example="point") - num_components: int = Field(..., description="Number of components", example=1) - data: list[float] = Field(..., description="Data array for the variable", example=[0.0, 1.0, 2.0, 3.0]) - -class UpdateVariableProps(BaseModel): - """Input for updating a variable in the visualizer instance.""" - variables: list[UpdateVariableInfo] = Field(..., description="List of variables to update") -``` - -### POST /stop_visualization - -This endpoint stops the visualization but not the running service. The Trame server is stopped and the websocket connection is killed but the service is still running. - -```json -"/stop_visualization":{ - "post":{ - "summary":"Stop Instance Visualization", - "description":"Stop instance visualization", - "operationId":"stop_instance_stop_visualization_post", - "responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}} -``` - -### POST /stop - -This endpoint stops the visualization and deletes the visualizer instance. The Trame server is stopped, the websocket connection is killed, and the visualizer instance is cleaned up. - -```json -"/stop":{ - "post":{ - "summary":"Stop Instance", - "description":"Stop visualizer instance", - "operationId":"stop_instance_stop_post", - "responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}} -``` - -### GET /health - -This endpoint provides a basic health check for the RESTful service. - -```json -"/health":{ - "get":{ - "summary":"Health", - "description":"Get the health status of the RESTful service", - "operationId":"health_health_get", - "responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}} -``` - - -### Implementation - -The Python API used for implementing this service is private to the VISOR service and it can be used to create wrappers or other services but it is only useful for this use case. \ No newline at end of file diff --git a/doc/developer_docs/adrs/11-visor-client.md b/doc/developer_docs/adrs/11-visor-client.md deleted file mode 100644 index af6b64af..00000000 --- a/doc/developer_docs/adrs/11-visor-client.md +++ /dev/null @@ -1,197 +0,0 @@ -# ADR 11: Introduce Python Client and Service Management for VISOR API - -## Status -Proposed - -This ADR was discussed but not adopted as of Aug 28, 2025. - - -## Base Context -Previously, Python users interacted directly with the `Visor` class, using its start, update, and stop methods to -manage visualizations. However, this approach poses challenges in interactive environments like Jupyter notebooks. - -Additionally, we have a command-line interface (CLI) tool, `visor-cli`, which allows users to manage VISOR instances -and visualizations via terminal commands. This CLI interacts with the VISOR HTTP service endpoints, providing -a consistent experience across different interfaces. - -To improve compatibility and usability, we propose updating the Python entrypoint. We introduce a new -`Visor` class that interacts with the VISOR HTTP service instead of instantiating Python-native visualization -object. This new class relies on a lightweight client that wraps HTTP API calls to the -service endpoints and simple service layer that optionally manages the server process. - -The new entrypoint enables reliable and Python-native interaction in notebooks and other interactive environments. -However, it does not support in-memory data inputs, as all interactions occur through the HTTP API. -In this ADR, we outline the proposed changes, their rationale, pros and cons of this approach, and example usage. - - - -## Problem Statement -We expect SAF users to interact with the VISOR using SAF's -[Product Instance Manager](https://saf.glow.docs.solutions.ansys.com/version/stable/user_guide/using_ansys_products/product_instance_management/custom_instance_managers.html) -(PIM) framework. -However, we also want to enable PyAnsys users outside of SAF to interact with VISOR endpoints -in a pythonic way, without requiring the full PIM stack. - -Until now, the VISOR class has served as the main entrypoint for Python users, allowing control -of the Trame server visualization. However, there are some potential limitations of this approach. -1. **Instability in interactive environments**: -Running a Trame server in a background thread within a native Python instance can lead to instability due -to differences in how various environments like scripts, Jupyter notebooks, and interactive shells manage event loops, -I/O, and concurrency. For more reliable and consistent behaviour across contexts, it's often preferable to -isolate the server lifecycle in a dedicated service or process. -2. **Fragmented entrypoints:** -This model introduces two separate entrypoints for VISOR usage: the VISOR HTTP service (via PIM) for SAF users, and -native Python instances for PyAnsys users. While maintaining both paths may offer short-term flexibility for -beta testing and feedback, it may increase long-term development and maintenance overhead. - -## Proposed Solution -To address these limitations, and to allow non-SAF users the ability to interact with the VISOR endpoints -pythonically, we are introducing a new Python `Visor` class that interacts with the HTTP service through -a simple client and service management layer. - -By allowing users to start and stop the VISOR HTTP service from Python in a subprocess, this approach -avoids event loop conflicts in Jupyter notebooks. Running the service in a separate process from the main -notebook allows it to freely perform asynchronous -operations - such as launching, starting, stopping, or updating Trame servers - without interfering -with the notebook’s event loop. - -The `Visor` class manages the HTTP service lifecycle through `uvicorn` in a subprocess, -and it provides programmatic access to the VISOR API endpoints. The user can optionally disable the service -management if they want to run the service themselves or connect to an existing service. - -With these changes, both SAF and non-SAF Python users can interact with VISOR endpoints -without relying on the full PIM stack. The HTTP service can also be run independently, -supporting access from any HTTP client, including Python scripts and web browsers. - - -The proposed changes are as follows: - -1. **New class (User-Facing):** `Visor` - - New main entrypoint for Python users, importable from `ansys.visor.viewer` - - Wrapper around the FastAPI layer (VisorAPI) that would enable a user to interact with the VISOR API - - Automatically runs the VISOR HTTP service via `uvicorn` subprocess, for users who should not need to worry about -starting/stopping the service. - - Includes a `manage_server` parameter to optionally disable automatic server management. -2. Rename old `Visor` class → `VisorVisualizer` - - Was previously exposed under `ansys.visor.viewer` -> remove this exposure - - Rename old `VisorTrameInterface` class → `VisorTrameVisualizer` accordingly -(it implements the old `Visor` class). -4. Update `visor-cli` commands to align with the above changes -5. Add a Jupyter notebook to show a concrete example of usage in Python - - -### Summary Table - -See the following table to better summarize how the VISOR HTTP service functionality maps between the HTTP endpoints, Python API, and VISOR CLI. - -| Function | VISOR HTTP Service | Python API | VISOR CLI | -|---------------------------------------------|----------------------------|------------------------------------|---------------------------------| -| **Server Operations** | | | | -| Start server | `uvicorn ...` | `server.start()` | `visor-cli server start` | -| Stop server | `ctrl+c` | `server.stop()` | `ctrl+c` | -| Server health | `/health` | `client.health()` | `visor-cli server health` | -| **Instance Management & Visualization** | | | | -| Connect to (or initialize new) instance | `/initialize` | `client.connect(host='localhost', port=8082)` | `visor-cli instance connect --port 8082` | -| Info about active instance | `/info` | `client.info()` | `visor-cli instance info` | -| Start visualization | `/start` | `client.start_visualization(my_file1)` | `visor-cli instance start path/to/my/file1.vtm` | -| Update visualization | `/update` | `client.update_visualization(my_file2)` | `visor-cli instance update path/to/my/file2.vtm` | -| Stop visualization | `/stop_visualization` | `client.stop_visualization()` | `visor-cli instance stop` | -| Terminate instance | `/stop` | `client.terminate_instance()` | `visor-cli instance terminate` | - - - -## Pros and Cons: Old vs New Entrypoints - -### Old Entrypoint (old `Visor` class -> renamed to `VisorVisualizer`) -**Pros:** -1. Supports in-memory data inputs: Enables workflows that do not require writing files to disk. -2. Simplicity: No need to manage subprocesses or external services. -3. Direct, low-level control: Advanced users can customize and extend behavior more easily. - -**Cons:** -1. Not robust in interactive environments: Event loop conflicts in Jupyter notebooks and similar environments. -2. No parity with CLI or HTTP API: Functionality and experience differ from other interfaces. -3. Limited scalability: Tightly coupled to the Python process, making containerization and -orchestration harder. - -### New Entrypoint (new `Visor` class) -**Pros:** -1. Robust, environment-agnostic usage: Works reliably in Python shells, Jupyter notebooks, CLI, and PIM. -2. Decoupled, language-agnostic architecture: Enables integration with other tools and languages via HTTP API. -3. Improved reliability and maintainability: Isolating the service in a subprocess reduces risk of main process crashes or memory leaks. -4. Unified and simplified instance management: Single entrypoint streamlines support, scaling, and deployment. - -**Cons:** -1. Loss of in-memory input support: All data must be file-based. -2. Increased complexity and resource overhead: Requires managing a subprocess and additional system resources. -3. Error handling complexity: New failure modes (e.g., subprocess management, port conflicts, orphaned processes). -4. Reduced extensibility for advanced users: Some customizations possible with direct in-process access are not feasible -through the HTTP API layer. - - -### In-Memory Data Support -VISOR has a requirement to support in-memory VTK inputs in addition to files. -This is not required for our MVP, but future use cases will require the ability to pass data directly, -without relying on file I/O. - -The `VisorVisualizer` class (the old `Visor` class) still accepts in-memory data inputs, but will no longer -be exposed to Python users. The new `Visor` class does not support in-memory data inputs, as all interactions -occur through the HTTP API. -This is a trade-off to enable robust usage in interactive environments like Jupyter notebooks. - -In order for this new approach to satisfy the in-memory input requirement, we will need to consider how we can enable -this through the HTTP API in a future ADR. Details are outside the scope of the present ADR, but -possible approaches include: -1. Extending the HTTP API to accept JSON payloads representing VTK data. -2. GRPC endpoints for streaming data. - -This will need to be addressed in order to fully satisfy all user requirements. - -## Example Usage - -### VISOR - -The `Visor` class is usable as follows. - -```python -from ansys.visor.viewer import Visor - -# Instantiate the VISOR class. By default, this starts a uvicorn command to run the VISOR service in a subprocess. -visor = Visor() - -# As soon as the VISOR service is initialized, -# a VISOR instance is created and ready on the default host/port -# (by default this is localhost and 8081), so we do not need to -# run the `initialize` API to get a VISOR instance up and running. - -# Start the visualization -visor.start_visualization("examples/assets/tensors9.vtp") -# Update the visualization -visor.update_visualization( - "tests/files/many_blocks/many_blocks.vtm", - metadata={"name": "many_blocks_asset", "unit": "cm"}, -) - -# Stop the visualization but keep the instance -visor.stop_visualization() -# Stop the visualization and delete the VISOR instance -visor.terminate_instance() - -# Initialize a new VISOR instance on a custom host/port -visor.connect(host="localhost", port=8082) - -# Stop the VISOR service by stopping the process running the uvicorn command -visor.shutdown() -``` - - -## Jupyter notebook example -Included in PR [#450](https://github.com/ansys-internal/theia/pull/450) is a Jupyter notebook, which has code similar to the above. -It shows how a user in Python can instantiate/connect to one or more VISOR instances -on different ports and interact with them. - -![Jupyter Notebook Screenshot 1](../images/11_jupyter_notebook_1.png) -![Jupyter Notebook Screenshot 2](../images/11_jupyter_notebook_2.png) - -## Implementation -An implementation of these proposed changes are in PR [#450](https://github.com/ansys-internal/theia/pull/450). diff --git a/doc/developer_docs/adrs/12-metadata-per-part-support.md b/doc/developer_docs/adrs/12-metadata-per-part-support.md deleted file mode 100644 index 6a4bb0d2..00000000 --- a/doc/developer_docs/adrs/12-metadata-per-part-support.md +++ /dev/null @@ -1,42 +0,0 @@ -# ADR 12: VISOR Metadata per Part Support - -## Status -Proposed - -## Context - -VISOR currently uses a separate `Metadata` class to store visualization-related attributes (e.g., name, unit), -since not all Ansys flagships can yet embed such data directly in their VTK outputs. -A new requirement introduces per-part opacity control. - -Following discussions with Kitware and input from @ahernsean, it was confirmed that VTK `FieldData` can -reliably persist small per-part JSON -metadata on leaf datasets (e.g., .vtp pieces under a .vtm). -In parallel, work is underway across flagships to define a shared data standard, -including support for embedded visualization metadata. - -## Discussion Summary - -* **Recommendation from review**: -Store per-part opacity directly in VTK `FieldData` on each leaf dataset (as a JSON string), for example `{"opacity": 0.5}` -stored under a `vtkStringArray` named `visor_state` -Reserve the external sidecar/Metadata concept for future session-level state. -This ensures defaults travel with geometry, avoids multiblock writer issues, and aligns with emerging VTK conventions. -* **Current team direction**: -Continue using the existing external `Metadata` class in the near term to support early adoption across flagships -and maintain flexibility before the shared format is finalized and adopted. -The `Metadata` object will continue to store per-part visualization settings (e.g., opacity) keyed by part name. - -## Decision -For this ADR: -* Short term: -Implement per-part opacity via the external Metadata class, using part names for association -* Long term: -Migrate to embedded per-leaf `FieldData`, likely once the flagship-standard VTK schema for -visualization metadata is available and adopted. - -## Notes -* Do not use `vtkInformation` for persistence; it is unsuitable for serialized metadata. -* XML-based VTK formats are the recommended path for preserving any per-leaf state. -* The external `Metadata` mechanism remains supported for early adopters and backward compatibility -until a unified data model is in place. \ No newline at end of file diff --git a/doc/developer_docs/adrs/13-scene-details.md b/doc/developer_docs/adrs/13-scene-details.md deleted file mode 100644 index b13369ad..00000000 --- a/doc/developer_docs/adrs/13-scene-details.md +++ /dev/null @@ -1,121 +0,0 @@ -# ADR 13: Complete VisorSceneDetails Schema - -## Context - -In order for VISOR to properly load the visualizer in a browser, VISOR's Python backend needs to send some data to the VTK-WASM frontend. This is to ensure the object tree, drop-down options, and various labels and such are populated with the correct information. - -In this ADR, we decide on a schema that the frontend will use to send data from the Python backend to the frontend. Specifically, this is the JSON structure that the data will have. The top-level container is called the `VisorSceneDetails`. Children of the `VisorSceneDetails` are the `VisorAppState` and `VtkInfo`. - -## Structure - -```text -VisorSceneDetails -│ -├─ appState : VisorAppState -│ │ -│ ├─ ui : VisorUiState -│ │ │ -│ │ ├─ darkTheme : boolean | undefined -│ │ ├─ panelTopLeftPanelCollapsed : boolean | undefined -│ │ ├─ panelTopRightPanelCollapsed : boolean | undefined -│ │ ├─ panelTopRightLegendCollapsed : boolean | undefined -│ │ └─ panelTopRightTabIndex : number | undefined -│ │ -│ └─ scene : VisorSceneState -│ │ -│ ├─ unit : string | undefined -│ ├─ orthographicEnabled: boolean | undefined -│ ├─ crossSectionEnabled: boolean | undefined -│ ├─ edgesEnabled: boolean | undefined -│ ├─ boundingBoxEnabled: boolean | undefined -│ │ -│ ├─ camera: VisorCameraState -│ │ ├─ position: number[] | undefined -│ │ ├─ focalPoint: number[] | undefined -│ │ ├─ viewUp: number[] | undefined -│ │ ├─ clippingRange: number[] | undefined -│ │ ├─ parallelProjection: boolean | undefined -│ │ ├─ viewAngle: number | undefined -│ │ └─ parallelScale: number | undefined -│ │ -│ ├─ crossSection: VisorCrossSectionState -│ │ ├─ origin: number[] | undefined -│ │ └─ normal: number[] | undefined -│ │ -│ ├─ datasetStates : Record -│ │ │ -│ │ └─ [datasetId] : VisorDatasetState -│ │ ├─ id : string -│ │ └─ partStates : Record -│ │ │ -│ │ └─ [partId] : VisorPartState -│ │ ├─ id : string -│ │ ├─ opacity : number | undefined -│ │ ├─ visible : boolean | undefined -│ │ ├─ diffuseRgb : number[] | undefined -│ │ ├─ selected : boolean | undefined -│ │ ├─ spectrumId : number | null | undefined -│ │ └─ spectrumComponent : number | undefined -│ │ -│ └─ spectrumStates : Record -│ │ -│ └─ [spectrumId] : VisorSpectrumState -│ ├─ id : string -│ ├─ magnitudeRange : [number, number] | undefined -│ └─ ranges : Array<[number, number] | undefined> -│ -└─ vtkInfo : VtkInfo - │ - ├─ orientationWidgetWasmId : number - ├─ crossSectionPlaneWasmId : number - ├─ crossSectionPlaneWidgetWasmId : number - ├─ crossSectionPlaneRepresentationWasmId : number - ├─ boundingBoxBoxAlgorithmWasmId : number - ├─ boundingBoxOutlineWasmActorId : number - ├─ boundingBoxAxesWasmActorId : number - │ - └─ sceneGraph : RootNode - ├─ id : number - ├─ wasmActorId : number - ├─ wasmPropertyId : number - ├─ wasmMapperId : number - ├─ dataArrays : Array - ├─ name : string - ├─ isGroupNode : true - ├─ isActorNode : false - ├─ nodeType : "root" - ├─ diffuseColor : [number, number, number] - ├─ bounds : number[] - │ - └─ children : Array - │ - └─ DatasetNode (vtkMultiBlockDataSet) - ├─ id : number - ├─ wasmActorId : number - ├─ wasmPropertyId : number - ├─ wasmMapperId : number - ├─ dataArrays : Array - ├─ name : string - ├─ isGroupNode : true - ├─ isActorNode : false - ├─ nodeType : "vtkMultiBlockDataSet" - ├─ diffuseColor : [number, number, number] - ├─ bounds : number[] - │ - └─ children : Array - │ - └─ PartNode (vtkPolyData) - ├─ id : number - ├─ wasmActorId : number - ├─ wasmPropertyId : number - ├─ wasmMapperId : number - ├─ dataArrays : Array - ├─ name : string - ├─ isGroupNode : false - ├─ isActorNode : true - ├─ nodeType : "vtkPolyData" - ├─ diffuseColor : [number, number, number] - ├─ bounds : number[] - └─ children : [] - -``` \ No newline at end of file diff --git a/doc/developer_docs/adrs/14-save-load-state.md b/doc/developer_docs/adrs/14-save-load-state.md deleted file mode 100644 index 08a6a748..00000000 --- a/doc/developer_docs/adrs/14-save-load-state.md +++ /dev/null @@ -1,456 +0,0 @@ -# ADR 14: VISOR Save and Load State -# ================================= - -## Status -Accepted - -## Context -VISOR needs a way for a user to save the current viewer state to disk and restore it later, so they can -close the application and return to the same state when reopening it. While basic Python APIs and method stubs -for saving and loading state exist currently, this functionality is not yet implemented. - -The frontend owns the core visualization state, including UI, navigation controls, widgets, and per-dataset -visualization settings. The backend manages data access and supporting services, but does not maintain a -complete view of the active visualization state on the client. - -Backend-driven changes are sent to the frontend via an existing update mechanism. It provides initial values for -some parts of the state (e.g. dataset per-part opacity), but does not include a complete representation of all -state that would need to be persisted and restored. - -### Decision Summary (high level) -- Persisted format: JSON (versioned) -- Version field name (current code): `version` (e.g. `"1.0"`) -- Persisted keying: - - datasets: by `dataset_name` (string) - - parts: by `part_name` (string) -- Load behavior: best-effort apply; warn+skip mismatches; do not fail the load unless the file is invalid -- Dataset serialization: each dataset is serialized to the save directory in VTKHDF format as - `_snapshot.vtkhdf` when `save_state` is called - -### Persisted Artifact Contract (v1) -- `save_state(path)` writes a directory containing: - - `visor.json` — the persisted viewer state (JSON, versioned) - - `_snapshot.vtkhdf` — one VTKHDF file per registered dataset, serialized at save time -- `load_state(path)` reads `visor.json` from that directory and applies it. -- If the scene is empty at load time, `load_state` also loads each dataset from the corresponding - `_snapshot.vtkhdf` file in the save directory before applying state from `visor.json`. -- If the scene is not empty at load time (one or more datasets already registered), `load_state` skips dataset - loading and applies only the viewer state from `visor.json`. -- The backend owns the persistence contract and validates the JSON; the frontend remains the runtime source-of-truth - for visualization state. - -This ADR is organized as follows: -* **Section 1 (Requirements and Constraints)** defines the scope, requirements, and -assumptions for this feature. -* **Section 2 (VISOR State Models)** clarifies the distinct viewer state representations -involved in this feature, their purpose, ownership, and lifecycle, and how they relate to each other. -* **Section 3 (Rough Proposed State Model)** presents a high-level diagram illustrating the proposed rough state model -structure, and how shared schema blocks are used across different state representations. -* **Section 4 (Client/Server State Synchronization)** describes how save/load interacts with the existing frontend -source-of-truth model and the request/response mechanisms used. -* **Section 5 (Full State Representation)** defines what we intend to capture in the persisted format and what is -phased/deferred. -* **Section 6 (Phased Implementation Plan)** proposes a staged -approach to implementing the feature in a way that manages risk and keeps each step focused. - - - -*** - -## 1. Requirements and Constraints - -### Requirements (functional) -1. Provide service + Python APIs to save and restore viewer state - * save_state(path) - * load_state(path) -2. The saved state should include anything necessary to restore the viewer to the same state, including at least: - * UI state - * dark mode - * open / closed panels - * VTK scene state: - * unit - * camera settings (camera location, point at/from, zooming factor) - * widget states (e.g. enabled, clipping planes, box selection, etc) - * dataset references and per-part settings: - * opacity - * visibility - * selected - * colored by (i.e. active variable) - * variable states - * variable min/max -3. The saved state should include a schema_version field so the format can evolve over time. -4. The load_state API supports loading the dataset inputs with the following behavior: - * If the scene is empty, load_state loads each dataset from the corresponding `_snapshot.vtkhdf` - file in the save directory, then applies the viewer state from `visor.json`. - * If the scene is not empty (one or more datasets already registered), load_state skips dataset loading and - applies only the viewer state from `visor.json`. -5. The save_state API serializes all registered datasets and viewer state to a directory: - * Each dataset is written as `_snapshot.vtkhdf` in VTKHDF format. - * The viewer state is written as `visor.json`. - * This is true whether the datasets were originally loaded from disk or constructed from native Python objects. - - **Note:** An `is_dirty` flag is maintained per registered dataset as an implementation-level detail. - The flag is set when a dataset is first registered or subsequently modified, and cleared on a successful - `save_state` call. This allows the implementation to identify datasets that have unsaved changes (i.e. have - been modified or have never been saved). It is a best-effort indicator: it reflects backend-side registrations - and modifications only; changes made solely on the frontend do not affect it. - -#### Definition: "scene is empty" -For the purposes of implementing (4), "scene is empty" means there are no datasets registered on the backend -(i.e., dataset registry count is zero). - - - -### Non-functional requirements -1. Performance: load_state should apply state efficiently and avoid noticeable UI freezes during normal use. -2. Robustness: applying state should tolerate mismatches, applying matches and providing warnings for any mismatches. - -### Out of scope -1. Autosave, crash recovery, or periodic snapshots. -2. Undo/redo support -3. Continuous frontend/backend synchronization (this is meant to be snapshot-based) -4. Exposing the persisted state as a user-editable or programmatically modifiable object outside the save_state/load_state APIs. -5. State file management or registry system (i.e. VISOR does not manage previously saved sessions by ID or otherwise). - -### Assumptions -1. Stable identifiers across sessions are dataset names and part names - * Discussion result 2026/01/09: Yes, use dataset names and part names as stable identifiers. But note that we may have a future -use case for applying the same state to a different dataset with similar structure with similar part names, -but a different dataset name. We can handle this case in the future when it comes up -2. It is the user's responsibility to: - * Provide unique dataset names and unique part names (within a dataset). - * If relying on load_state to load datasets from disk: - * Call the load_state API from an empty scene. - * Ensure the `_snapshot.vtkhdf` files are present in the directory specified by `load_state` - (these are written automatically by `save_state`). - * If loading datasets manually: - * Load the required datasets before calling load_state. - * Ensure `visor.json` exists in the save directory and corresponds to the intended scene. -3. Mismatch handling: - * If a dataset or part referenced in the saved state does not exist at load time, it is ignored with a warning. - * Any state that can be applied is applied; entries that don't match are skipped. -4. Saves and loads are infrequent/ad hoc. - -### Questions to confirm -1. **State file naming:** should the API accept a file path or a directory path? - * Decision: both `save_state` and `load_state` accept a directory path. `save_state` writes `visor.json` - and one `_snapshot.vtkhdf` per dataset into that directory. `load_state` reads `visor.json` - from the same directory, and reads dataset snapshots from there if the scene is empty. -2. **Backend-only mechanism:** Is it OK for save/load to be backend-driven only (no frontend button or autosave)? - * Discussion result 2026/01/09: Yes. -3. **Load state error handling:** if load_state reads a valid file, but none of the entries apply (e.g. no matching datasets/parts), should that be treated as an error, or as a successful load with warnings? -4. Will we save and apply the full state, or just the parts that have been changed from defaults? - * Discussion result 2026/01/09: Apply the full state. -5. **Does load_state reset the state to defaults** and then apply the saved state, or just apply the saved state - on top of the current state? - * Discussion result 2026/01/09: Apply on top of current state (additive). - -*** -## 2. VISOR State Models -This feature involves several distinct representations of viewer state that exist for different purposes -and at different points in the application lifecycle. Some of these representations already exist in the codebase -in various forms, while others are clarified here. Although they may contain similar per-part visualization -values, they differ in ownership, structure, mutability, and intended use. - -This section focuses on per-part dataset state, as this is the area where similar data -appears in multiple places at the moment, and has been a source of confusion. - -### Table 1: Per-part dataset state: what exists and why -The table below summarizes the different places where per-part dataset state currently exists in the backend -codebase, along with where each representation lives, what it is used for, and how it is keyed and structured. - -| State Type | Where it lives | What it's for | Key | Shape | -|------------|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|-----|--------------------------| -| **Metadata defaults** | `Metadata.state: PersistedDatasetState` | User-defined initial per-part visualization values provided as input (defaults applied at load time) | Per-part name (str) | Flat, single dataset | -| **Runtime state** | `RuntimeDatasetState.partStates` | Live per-part visualization state during an active viewing session | Internal part_id (int) | Nested: dataset -> parts | -| **Persisted state** | `SavedViewerStateV1.scene.dataset_states[*].parts: PersistedDatasetState` | Snapshot of per-part state to save and restore a session later | Per-part name (str) | Nested: dataset → parts | - -Although each of these representations stores per-part visualization values, they intentionally differ in keying, shape, -and purpose: defining initial defaults, supporting live interaction, and capturing a snapshot for save/load. - -**Note:** For the initial implementation, the per-part schema used for metadata defaults (InitialPartsState) and -persisted state (PersistedViewerDatasetState) may be shared to reduce duplication, as both represent serialized per-part -visualization values keyed by part name. Despite this shared schema, the two representations remain distinct -in purpose and lifecycle (defaults at load time vs snapshot for save/load). - -### Table 2: Per-part dataset state: ownership and lifecycle -The table below summarizes the per-part dataset state representations, including where each representation lives, what -it is used for, and how it is keyed and structured. - -| State Type | Created by / when | Who is allowed to create/edit | Written to disk? | Lifetime | Changes during session? | -|--------------------------------------------------------------|----------------------------------------------------------------------------------------|-------------------------------|---------------------------------------------------|----------|-------------------------| -| **Metadata defaults** | User-authored ahead of time; loaded at dataset import | User (in advance): treated as read-only at runtime | Yes (as input metadata, before any VISOR session) | Exists independently of sessions; loaded as input | No | -| **Frontend runtime per-part state (UI-owned)** | Initialized from backend-provided defaults/load_state results | Frontend during live interaction | No | Lives while dataset is loaded in VISOR session | Yes | -| **Backend runtime per-part state (RuntimePartsState.parts)** | Initialized during dataset/scene setup; refreshed from frontend snapshot when needed (e.g. save) | Backend (via backend operations & applying frontend snapshot) | No | Lives while dataset is loaded in VISOR session | Yes | -| **Persisted state** | Captured at save time and applied at load time | Backend, only via the save/load mechanism | Yes (only via the save/load mechanism) | Exists across sessions; in memory only during save/load | No | - -### Why these representations remain distinct - -These representations look similar in shape, but serve different purposes and cannot be unified without compromising -their individual requirements: - -- **Metadata defaults** are user-authored, serialized, and treated as read-only at runtime. -They use part names for stability across imports. -- **Runtime state** is keyed by internal `part_id` for performance during live interaction and must support fast -lookups and updates. -- **Persisted state** is keyed by part name for stability across sessions and structured by dataset for readability -and partial loading. - -Combining these into a single model would force one representation to satisfy conflicting constraints -(e.g., using part names for runtime lookups would hurt performance; using `part_id` in persisted state would break -across sessions when part IDs change). While the schema for per-part values may be shared where appropriate -(see note in Table 1), the state containers, ownership, and lifecycle remain distinct. - -### Summary - -Together, these representations form a coherent model in which similar schema blocks may be reused, but -state containers, ownership, and lifecycle remain distinct. This separation allows runtime interaction, persistence, -and external-facing defaults to coexist without introducing unintended APIs or tightly coupling -frontend and backend implementations. - -The next section presents a rough proposed state model illustrating how these representations -relate to one another at a high level. - -*** -## 3. Rough Proposed State Model - -The diagram below makes the runtime and persistence structure explicit by showing where per-part dataset state -is expected to exist and how it functions across the system: as user-authored metadata (initial defaults), as -frontend-facing runtime state, and persisted save/load state. - -It also fills in the surrounding runtime state structure to show how these pieces relate to the overall viewer state, -and makes explicit which components may be shared between runtime and persistence, with the expectation that they may -diverge as requirements evolve. - -Note that as this will also require schema changes to the runtime data transfer object (DTO) that is sent from the -backend to the frontend to initialize or update the viewer state. There is a -[separate ADR](https://github.com/ansys/visor/blob/main/doc/developer_docs/adrs/13-scene-details.md) to align on this -schema. (This ADR will be updated to reflect the final results of that discussion -once it is finalized.) - -![VISOR State Model Diagram](../images/visor-state-model.png) - - -*** -## 4. Client/Server State Synchronization - -During normal client/server interaction in VISOR, the frontend is the source of truth for visualization state, -but the backend may trigger updates via `local_view.update()` in response to backend-driven operations -(e.g., dataset addition). For most such operations driven by server API calls, -the `local_view.update()` trigger causes a full UI rebuild on the client, including state application. - -For the save/load state feature, we expect the following behaviour: at save time, the backend explicitly requests a -snapshot from the frontend to capture the authoritative current state. At load time, the backend reads the saved state -from disk and triggers a frontend update, which will overwrite existing values in the frontend's cached state. - -As part of this feature, we implement the following mechanisms to support backend-driven state updates and requests: - - -* **Load State update mechanism (backend -> frontend state update)** - - * The backend triggers a `setState` call on the client, with the saved state as the payload. - This is fire-and-forget from the server's perspective. - * **Notes:** As the feature expands, we will need to expand the shared schema to fully represent the viewer state - as required for save/load. - -* **Save State request mechanism: backend request for frontend state snapshot** - - * The backend triggers a `getState` request to the client, which is a request for the - frontend to send the current state back to the backend asynchronously. - * The client, in turn, triggers a `save_state_response` function on the server which sends the response - payload to the backend. - - -## 5. Full State Representation - -At the time the save/load state feature was started, the state representation for save/load state was limited -to the per-part dataset state (opacity only). - -For the runtime state at the client/server boundary, we maintain the schema definition in a -[separate ADR](https://github.com/ansys/visor/blob/main/doc/developer_docs/adrs/13-scene-details.md). -This schema is intended to evolve as we expand the state representation to include all components required to -capture the viewer state for the save/load feature. - -### 5a. Variable state -Currently, the frontend handles aggregating the global variables in a VISOR scene, which makes it the owner of the -min/max values for each variable, across the scene. -The backend is not currently aware of these global variable states, but they are needed to capture in the save/load -state to ensure a consistent restored state. -As such, we will need to add these variable states to the backend state representation and on-disk format as part of -this feature, and ensure they are included in the frontend snapshot and applied at load time. - -**Note 1:** While the frontend currently manages the global variable aggregation across datasets for the scene, this is something -that would be more appropriate for the backend to manage and communicate to the frontend as part of the variable state. - -This is something we can consider evolving in the future, but for the initial implementation, -we will keep the frontend as the owner of the variable state, and simply ensure it is included in the snapshot and -load application logic for save/load state. - -**Note 2:** The active variable for each part is stored as a field of the per-part dataset state, which is discussed in -5b below. - -**Note 3:** The variable state is required for the first phase of our implementation (as described in section 6 below). - -### 5b. Full per-part dataset state -The per-part dataset state is currently limited to opacity only, but for a complete save/load state, we will need to -expand this to include all relevant per-part visualization values, including: -* visibility -* selected state -* variable colored by (i.e. active variable) -* variable component colored by (i.e. active variable component) - -These values are owned by the client and currently exist in the frontend runtime state, but will need to be included in -the backend state representation and on-disk format for save/load state, and included in the frontend snapshot and -load application logic to ensure a consistent restored state. - -### 5c. UI state -The details of the UI state representation are still to be defined, but will include window collapse/expand state, in -addition to any other state required to ensure a consistent restored state. - -This is currently managed entirely on the frontend, but will need to be included in the save/load state representation -and application logic. - -**Note:** Our first phase (see section 6 below) requires only the UI panel state to be defined and implemented. -The rest of the UI state will be deferred until the second phase, to keep the scope of the first phase -manageable and focused on establishing the core save/load mechanism end to end. - -### 5d. Camera and view state -The details of the camera and view state representation are still to be defined, but includes the camera position, -point at/from, zoom level, focal point, etc. - -This is currently managed entirely on the frontend, but will need to be included in the save/load state representation -and application logic to ensure a consistent restored state. - -### 5d. Widget state -The widget state includes the enabled/disabled state of each widget, as well as any relevant settings for each widget -(e.g. clipping plane positions, box selection bounds, etc). -This is currently managed entirely on the frontend, but will need to be included in the save/load state representation -and application logic to ensure a consistent restored state. - -All widget state is deferred until the second phase of implementation to keep the scope of the first phase manageable -and focused on establishing the core save/load mechanism end to end, with the more complex widget state deferred until -we have that core mechanism in place. - -*** -## 6. Proposed Implementation Plan - -We propose implementing save/load state in two main phases. The goal is to get a working end-to-end solution in place early, -with basic save/load capability, and then expand what is included in a second phase, to more completely capture -the viewer state. - -The first phase will include all save/load state requirements _except_ the widget states and any UI state beyond -dark mode and open/closed panels. The second phase will add widget states and any remaining UI state. - -The backend is the entry point for save and load operations, but the frontend is the source of truth for the -visualization state. The backend needs to explicitly request the current client state at save time. The frontend -will need to ensure the frontend-cached state is correctly overwritten at load time. - -**Summary:** -* Phase 1: End to end save/load state with limited scope, no dataset serialization/loading -* Phase 2: End to end save/load state with limited scope, with dataset serialization and loading -* Phase 3: Full save/load state with _complete viewer state_, with dataset serialization and loading - -### Phase 1: End to end save/load state with limited scope, no dataset loading -The goal of this phase is to implement the core save/load mechanism end to end, with basic state captured and restored. -This phase is broken into two sub-phases to manage risk and keep each step focused. - -#### Phase 1a: End-to-end opacity-only save/load with frontend hooks - -The goal of this sub-phase is to implement the core save/load mechanism end to end, using only the per-part opacity state -as the captured/restored state. This allows us to validate the overall mechanism, without needing to: -* Define the full state schema upfront. -* Implement complex state application logic in the frontend that does not yet exist there. - -The steps involved are: -* Add backend APIs and HTTP endpoints for `save_state` and `load_state`. -* Define the on-disk state format and include basic versioning. -* Implement saving and loading of the per-part opacity state only. - -This phase establishes the core contract between frontend and backend and ensures that load produces a -consistent restored state in the viewer. - - -#### Phase 1b: Expand scope of captured state to include camera settings and basic UI state - -The goal of this sub-phase is to expand the set of state captured and restored to include: -* camera/view state (position, focal point, view up, zoom) -* basic UI state (dark mode, open/closed panels) -* variable state (active variable, min/max) -* per-part visibility, selected state, and colored by - -This phase builds on the core mechanism established in Phase 1a, and expands the state schema -and the frontend application logic to handle the additional state. Depending on the complexity involved, this -may be done in a single user story, or broken into multiple smaller stories, each focused on a specific state area -(e.g. camera, UI, variable, per-part visibility/selection). - -Out of scope: -* widget states -* other UI state beyond dark mode and open/closed panels - -**Result of Phase 1:** -At the end of Phase 1, we will have a working save/load state mechanism that captures and restores the core viewer state, -including camera, UI, variable, and per-part visibility/selection/colored by state. - -Note that this only supports datasets that were loaded from disk originally. `load_state` will not yet provide support -for loading datasets that were created in-memory at this time. - - -### Phase 2: End to end save/load state with limited scope, _with_ dataset serialization and loading -The goal of this phase is to enhance the `load_state` functionality to optionally load datasets from disk -if the current scene is empty. This allows users to restore the core elements of a session, including datasets, -when no datasets are currently loaded in VISOR. - -**Result of Phase 2:** -The end result of Phase 2 is a save/load state mechanism that captures and restores the core relevant viewer state, -including loading datasets from disk if the scene is empty. This allows the user to save a session with datasets, -and restore it later without needing to manually load the datasets first. (This means for the on-prem use -case, save state will serialize the datasets, and later restore without manually needing to load the datasets first.) - -### Phase 3: Full save/load state with _complete viewer state_, with dataset serialization and loading - -Expand saved state included in save/load state to improve completeness across the application. -The goal of this phase is to build on the core save/load mechanism established in Phase 1, and incrementally -expand the set of state that is captured and restored, to eventually achieve a functionally complete -save/load experience. - -This includes: -* widget states (e.g. enabled, clipping planes, box selection, etc) -* any remaining UI state (e.g. panel visibility, collapsed/expanded state, other user-facing controls) - -All of this state is captured via the frontend snapshot and re-applied during load as an explicit overwrite of the -frontend-cached values. - -As additional state is brought into scope: -* the backend state representation and on-disk format are extended to include the new fields -* the state DTO exchanged between backend and frontend is extended accordingly - -Depending on the complexity involved, we may treat this as a single user story, or break it into multiple smaller stories, -each focused on a specific widget or UI component. - -**Result of Phase 3:** -The end result of Phase 3 is a complete save/load state mechanism that captures and restores all relevant viewer state, -making the saved and loaded sessions functionally and visually equivalent from the user's perspective. -Dataset serialization and loading (from Phase 2) remain in place; this phase completes the state coverage by -adding widget and any remaining UI state. - -*** - -### Example persisted JSON shape (illustrative) -This is a minimal example of the intended persisted shape (not a complete schema): - -```json -{ - "version": "1.0", - "ui": { "darkTheme": false }, - "scene": { - "unit": "m", - "dataset_states": { - "my_dataset": { - "parts": { - "part_a": { "opacity": 0.5 } - } - } - } - } -} -``` diff --git a/doc/developer_docs/adrs/15-ansys-product-support.md b/doc/developer_docs/adrs/15-ansys-product-support.md deleted file mode 100644 index f2f017c8..00000000 --- a/doc/developer_docs/adrs/15-ansys-product-support.md +++ /dev/null @@ -1,159 +0,0 @@ -# VISOR Format Support of Flagship Simulation Products - -## Status -Team and Stakeholder Αpproved - - -## Decision -VISOR depends on legacy Ansys flagships to provide VTK format support and on PyAnsys APIs to enable those flagships to be used within Solutions Applications. These workflows improve maintainability and adoption by leveraging PyAnsys, which offers a specialized 3D viewer for examples and lowers the effort required for community users to access needed functionality. Each flagship product is responsible for converting its internal data to the common format. That format is aligned with SimAI requirements and maintained by the flagship teams, ensuring it stays optimized and in sync with the 3D viewer as products evolve. - -## Context -Our requirements for legacy Ansys product support were based on the requirements for legacy Ansys products namely, Discovery, SpaceClaim, Fluent, Mechanical, AEDT suite and EnSight. Our prioritized requirements are for Fluent, GeometryService, but all legacy Ansys flagship products are included in the VISOR roadmap as well as Electronics Simulation Products. VISOR is a component which will be available within the Solution Architecture Framework and it is in VISOR's scope to be able to visualize the models and the data produced in those products. It is out of scope for VISOR to be doing transformations of the Ansys product formats to its internal representation. VISOR is using a scene graph described in the -[08-scene-graph ADR](https://github.com/ansys/visor/blob/3fdbf4413c63d1c7c2b2ef62792a638f8d6f2b85/doc/developer_docs/adrs/07-scene-graph.md). This scene description graph supports VTK format. - -VISOR supports VTK-based formats (including VTKHDF), which is the requirement for supporting 3D data along with the mixed OpenUSD and VTK formats from the [architecture board decision #29](https://github.com/ansys-internal/architecture-decision-records/blob/main/content/docs/adrs/0029-visualization-formats.md). Based on that decision, all legacy Ansys products need to support the mixed OpenUSD and VTK format, and for VTK they should be providing VTKHDF. This decision is based on achieving a common visualization format which covers the current needs and the upcoming view regarding OpenUSD format. Considerations for the legacy Ansys common data model may impact some of the decisions here but those discussions will need to make sure to include the requirements and constraints of the VISOR 3D viewer. - -## VISOR currently supports VTK - -VISOR directly supports VTK-based formats. - -_*Advantages*_: - -1. :heavy_check_mark: VISOR viewer as a visualization tool focuses on the visualization capabilities and technology stack which covers its functional and non-functional requirements as a visualization tool. -2. :heavy_check_mark: Avoids overlap with DPF and PyAnsys initiatives which are creating bindings from their formats to open formats such as VTK. -3. :heavy_check_mark: Existing tools such as DPF and PyAnsys bindings can be used to create workflows from Ansys flagship products to VTK format and with the usage of VTKHDF all the information should be encoded to the file format. Only additional information would be necessary for VISOR internal state management. -4. :heavy_check_mark: VISOR is required to enable high-performance workflows, but it is the responsibility of all the different components in the Solutions Application Visualization Workflow to cover the performance requirements of those. This is the reason VISOR is not adding these tools behind any of its APIs. -5. :heavy_check_mark: The developers creating a Solution Application are able to use PyAnsys products to also have post-processing capabilities that a viewer cannot have in its scope. -6. :heavy_check_mark: VISOR's release package and containers contain the minimum dependencies required for VISOR's functional capabilities in order to be aligned with the deployment KPIs of an Shared Technology Component. -7. :heavy_check_mark: VISOR doesn't need to spend development and testing resources for an all-formats-to-one-format. -8. :heavy_check_mark: The usage of open formats for rendering pipelines and formats allows VISOR to have less legacy Ansys proprietary and protected content in terms of visualization which can enable open sourcing part or the whole of the viewer and as such enabling a seamless integration with the PyAnsys initiative and greater adoption. The alignment with the PyAnsys initiative also drives the ability to have maintained and up-to-date APIs since legacy Ansys flagships and the PyAnsys community has put investment on that side which will be continuing in the foreseeable future. - -_*Disadvantages*_: - -1. :x: VISOR is not a single visualization component covering the visualization workflow. The Solution Application Developers need to be able to setup that workflow using PyAnsys APIs or DPF to create a workflow in a Solution. -2. :x: VISOR's input format is not supported by all legacy Ansys Simulation Flagships so the products themselves need to provide that optimized conversion to VTK format. The AVZ workflow has been offering transformations from all Ansys Flagship Simulation Products to its proprietary Ansys Visualization Format (AVZ). In this model, we switch to using VTK, which is an open standard and in that way the proprietary format is only limited to the products and whenever they do updates of their format they will also make sure to maintain their VTK export. - - -### Visualization workflows for Solution Applications - -VISOR is relying on legacy Ansys flagships to provide VTK format support as well as PyAnsys APIs to support flagships in order to be able to be used in Solutions Applications. These workflows allow better maintainability and adoption as PyAnsys has a specialized 3D viewer for examples and showcasing specific products capabilities which is also able to provide less effort for the community users in order to get functionality they are requiring. The flagship products will own the part of converting their internal format to the common format but that format is aligning with SimAI requirements and its also able to be optimized and maintained by them so there is no lag between their updates and the version actually used in the 3D viewer. - - - -### Consequences - -* Deprecation of libraries that were providing code for transforming Discovery and Fluent format to VTK. More specifically the following libraries are being *deprecated*: [visor-geometry-support](https://github.com/ansys-internal/theia-geometry/tree/main/examples) and [visor-fluent-support](https://github.com/ansys-internal/theia-fluent-support). These were created in order to provide proof of concepts and evaluations for VISOR usage but they were never aimed to go to production. -* VISOR will own any specialized formatting it needs which is more specialized than what the generic product support provides. -* PyAnsys initiative pyansys-visualization-tools and any relevant initiatives and PyAnsys APIs for specific products like PyGeometry need to have prioritization in terms of VISOR's technical stakeholders. -* PyAnsys APIs for specific products would benefit from supporting VISOR and having some less performant APIs like we have for PyGeometry until there is full support from products. -* DPF and pyDPF is able to support VISOR as it is and based on the fact that it supports VTKHDF and hierarchical data (assemblies) it can provide more performant bindings if they from their side implement the relevant transformations. - -### Examples and testing - -The [Reference Solution](https://github.com/ansys-internal/airfoil-explorer) created by the Task Force PI&E which targets the Control Plane Blueprint for desktop, on-prem and cloud-native targets is using VISOR for 3D visualization for geometry models created by the GeometryService through PyGeometry APIs. This solution application is also going to be used for testing the integration of these components to provide more long-term support. - - -## Notes - -### 4/7/2026 - -After the [architecture board decision #29](https://github.com/ansys-internal/architecture-decision-records/blob/main/content/docs/adrs/0029-visualization-formats.md) for the common graphics format, a lot of the context of the specific formats for each product is actually obsolete from this discussion as they are required to support the VTK format. The information that was previously outlined can be found here: - -|No | Product| File Extension/ Format | Priority | -----|--------|------------------------|-----------| -| 1 | Fluent |.cas.h5, .dat.h5, msh(.h5), | MVP | -| 2 | SpaceClaim | .scdoc(x) | MVP | -| 3 | Discovery | .dsco | MVP | -| 4 | AEDT | .aedt, .case | High | -| 5 | Mechanical | .cdb, .rst, .rth, .rstp, .rmg | High | -| 6 | Ansys Viewer (AVZ) | .avz | Medium | -| 7 | CFX | .res, .dat, .def | Low | -| 8 | HFSS | .obj, .aedtplt | Low | -| 9 | SIWave | .anf, ODB++, EDB, IPC-2581, DXF, GDSII, .snp | Low| -|10 | Maxwell | .ies, .ldt | Low | -| 11 | EnSight | .case, .encas | Low | - -(*) Note: This prioritization is based on our VISOR board and not on the document which is not currently being updated in terms of priorities but the requirements in terms of formats are still up to date as of 7/31/2025. - - - -## Options and recommended usage per format - -### Fluent - -Fluent formats are the following: .cas.h5, .dat.h5, msh(.h5). There are currently the following options for using VISOR in a Solutions with these formats. - -#### VISORFluentSupport - - -```python -converter = VisorFluentSupport() - -file, metadata = converter.to_vtk_file( - case_file=str(pathlib.Path.joinpath(dir, "input.cas.h5")), - data_file=str(pathlib.Path.joinpath(dir, "input.dat.h5")), - file_path=str(pathlib.Path.joinpath(dir, "output")), -) - -visualization = Visor(input=file, metadata=metadata, standalone=True) -visualization.start() -file, metadata = converter.to_vtk_file( - case_file=str(pathlib.Path.joinpath(dir, "input.cas.h5")), - data_file=str(pathlib.Path.joinpath(dir, "input.dat.h5")), - file_path=str(pathlib.Path.joinpath(dir, "output")), -) -visualization.update(input=file, metadata=metadata) - -visualization.stop() -``` - - -#### DPF/DataBridge - -VISOR will be able to accept files in its Pythonic APIs using DPF APIs as it has been done for DataBridge. If DPF is able to convert to single VtkDataset along with the existing capabilities of DPF to provide a metadata object then the in-memory API can also be used. - - - -### Ansys Geometry Format (SpaceClaim/Discovery/Geometry Service) - -Geometry format from Ansys products Discovery (SpaceClaim) and the Geometry Service support PyVista bindings in their PyAnsys bindings. Using those bindings we can convert to VTK formats and metadata objects and files which are compatible with VISOR. The Solutions Applications Visualization Workflow can use the Geometry Service directly or helper libraries to be able to convert to VTK format. Such a helper library is VisorGeometrySupport -in the following example: - -```python -converter = VisorGeometrySupport() -(model, metadata) = converter.to_vtk_file( - resolve_path("reactor.scdocx"), resolve_path("reactor") -) -visualizer = Visor(input=model, metadata=metadata, standalone=True) -visualizer.start() -``` - -### Ansys Discovery Physics - -Ansys Discovery supports exporting VTK format for geometry and physics but its not currently connected to the Python bindings. This -needs to be a feature request for adding the Python bindings. - -### Ansys Mechanical formats - -A conversion mechanism based on DPF can be used in order to be able to convert these formats to VTK files or datasets and to the metadata object. -This can be done currently and if necessary for ease of use helper libraries could be created as part of the Solutions Applications. - -### EnSight - -EnSight is able to export to VTK format compatible with VISOR and is able to be integrated with VISOR viewer. - -### AVZ - -Conversions from AVZ to VTK can be supported if that is actually proritized from the business cases. - -### Electronics formats - -In order to support formats: .aedt, .case, .res, .dat, .def, obj, .aedtplt, .anf, ODB++, EDB, IPC-2581, DXF, GDSII, .snp, .ies, .ldt we would need to leverage DPF and PyAnsys bindings which are offered from PyAEDT. This work hasn't been prioritized but the basis of capabilities currently exists -from PYAEDT. - - - - - - - diff --git a/doc/developer_docs/adrs/16-visor-saf-integration.md b/doc/developer_docs/adrs/16-visor-saf-integration.md deleted file mode 100644 index 2d42e501..00000000 --- a/doc/developer_docs/adrs/16-visor-saf-integration.md +++ /dev/null @@ -1,77 +0,0 @@ -## VISOR SAF Integration - -## Decision -Team approved - -## Context - -VISOR is aiming to be the Solutions' Applications 3D viewer and as such the primary platform that VISOR is going to be used is through Solutions Applications Framework (SAF). The tenets of the VISOR project can be found [here](https://github.com/ansys/visor/blob/3fdbf4413c63d1c7c2b2ef62792a638f8d6f2b85/doc/developer_docs/adrs/01-visor-tenets.md). Solutions Applications are required to be able to target desktop, on-premise deployment and cloud deployment through integration with SAF, REP and CISL/Cloud Burst platforms. VISOR as a Solutions Applications viewer is scoped to be aiming to the requirements of the Solutions Applications and ACE stakeholders rather than being a standalone application and as such it needs to comply with the requirements the Solutions Applications group, the Architecture Hub and the ACE stakeholders are providing. VISOR is not responsible for Authentication or Authorization of users, or directly deployments or running a service but its responsible for making sure that all the requirements set will be able to be implemented in VISOR through its architecture. - -VISOR is targeting desktop, on-prem and cloud deployments. In terms of MVP, VISOR is targeting desktop deployment and it will iteratively target on-prem and cloud deployments as a component of the Solutions Applications Framework and not as a standalone service. - -VISOR REST service is necessary for Solutions Applications to be able to connect to a running instance of VISOR from independent steps where VISOR is not a subsystem of GLOW. The VISOR service is managed by the SAF Product Instance Manager for its lifecycle and the Product Instance Configuration is expected to be able to be deployed along with SAF on the different deployment targets of the Solutions Applications Framework. - -### Desktop - -#### Requirements - -SAF is requiring that a server running for a session to be running using a [PIM (Product Instance Manager) configuration](https://saf.glow.docs.solutions.ansys.com/version/dev/user_guide/using_ansys_products/product_instance_management/index.html). The PIM configuration requires the following: -* An HTTP service with the following endpoints: -```\```: GET root -```\initialize```: POST initialization -```\start```: POST start -```\stop```: POST stop -```\health```: GET health endpoint - -* There is session management for the service -* PIM implementation of VISOR exposes the rest of the VISOR APIs through a VisorClient -* Stopping the service and it should clean up and remove any temporary directories that were created for the service to be running. -* A VISOR manager is included in [SAF Product Manager](https://github.com/ansys-internal/saf-product-manager/blob/main/src/ansys/saf/product_manager/theia/_theia_manager.py) -* A ``SAFVisorClient`` exposes APIs beyond the interface of the generic SAF Product Manager. As of 1.x version of VISOR it supports ``update`` functionality. -* SAF supported releases of VISOR are included in the SAF Product Configuration package: [saf-product-configuration](https://github.com/ansys-internal/saf-product-configuration/blob/main/src/ansys/saf/product_configuration/theia.py) -* There is an end-to-end test in [SAF Product Manager](https://github.com/ansys-internal/saf-product-manager/blob/main/tests/e2e/test_theia.py) which also tests the ``visordash`` API with the VISOR server configuration for Desktop. - -(*) Note: There is a bug in terms of the PIM Desktop configuration which doesn't allow VM rendering due to using localhost and local ports without API gateway. Issue tracking for these: [glow-engine#622](https://github.com/ansys-internal/glow-engine/issues/622), [saf-product-manager#34](https://github.com/ansys-internal/saf-product-manager/issues/34) - - -### Deployment on-premise and cloud - -VISOR is going to be using HPS for orchestrating on-premise and cloud deployment. VISOR is currently using Trame, which is a client-server architecture for a single session which sets up a web-socket connection based on the public session url of the web socket connection. - -In order for VISOR to support multiple users or sessions it needs to be containerized and deployed through the HPS which will be creating new instances to scale VISOR based on the users or sessions which are necessary for smoothly running the on-premise deployment. - -#### VISOR client-side rendering -Based on the current technology components of VISOR described [here](https://github.com/ansys/visor/blob/3fdbf4413c63d1c7c2b2ef62792a638f8d6f2b85/doc/developer_docs/adrs/02-visor-technology-components.md) it is using Trame VTK.WASM which is a client based rendering technology on the browser using VTK, Web assembly and OpenGL2.x and when its available WebGPU. This means that performance of VISOR is going to be impacted by network connectivity even though there is a websocket connecting directly to the client, it will still have the relative impact as there are models and data transferred to to the client. - -*Additional requirements:* -* Kubernetes-based containerized version of VISOR -* VISOR running in the same cluster where data for rendering are stored and processed (avoid waiting for loading big data files to a different machine and duplicating those data) -* There is an API Gateway which provides the route to the Trame server instance with a url which is able to be served to the ``visordash`` client running on the browser of the user. Issue tracked here: [glow engine #34](https://github.com/ansys-internal/saf-product-manager/issues/34) - - -#### VISOR server-side rendering - -VISOR will support server-side rendering with the same architecture and the only difference in terms of deployment is the requirement for running ParaView on the server. The ``visordash`` component will be using the same websocket connection client so it will require the publicly available websocket url for the Trame server (visualization server). - -*Additional requirements:* -* Kubernetes-based containerized version of VISOR -* VISOR running in the same cluster where data for rendering are stored and processed (avoid waiting for loading big data files to a different machine and duplicating those data) -* There is an API Gateway which provides the route to the Trame server instance with a url which is able to be served to the ``visordash`` client running on the browser of the user. Issue tracked here: [glow engine #34](https://github.com/ansys-internal/saf-product-manager/issues/34) -* ParaView included in the containerized version of VISOR -* GPU is heavily recommended (as the assumption is that large/complex models are passing through the VTK pipeline) - - -### Notes - -* For the first internal release the stop endpoint is not following OpenTelemetry requirements, it will only report if the VISOR server is healthy. At this point this covers the Trame server but not wslink running or the websocket connection health. - -* A revision of the error codes and the health endpoint to cover OpenTelemetry requirements will be done at a later stage ahead of a full delivery of VISOR. - -*29th of July 2025:* -As of now, an STC is not able to have a Product Instance Manager and Configuration included in GLOW. Only flagships have their configuration supported by the GLOW team. However, the [Product Instance Configuration repo](https://github.com/ansys-internal/saf-product-configuration) is the only that gets deployed along with SAF and has the testing support for PIM. The same goes for the [Product Instance Manager repo](https://github.com/ansys-internal/glow-engine/tree/main/src/ansys/saf/glow/solution/geometry). This repo contains testing which ensures that changes of the PIM configuration do not break the Product Instance Manager. - - -*23rd of December 2025:* -- The first release of VISOR is available and PIM light packages have the mechanisms to support VISOR for Desktop deployment (not containerized and without routing capabilities for external VPC connections) -- On-Premise and Cloud deployments are not yet implemented. The proposal is to use HPS as the orchestrator for VISOR since VISOR is using Trame server which implements a stateful, session based VTK rendering pipeline. -- VISOR is not currently providing a kubernetes based container. It only provides a Docker containerized version. \ No newline at end of file diff --git a/doc/developer_docs/adrs/17-remote-rendering-architecture.md b/doc/developer_docs/adrs/17-remote-rendering-architecture.md deleted file mode 100644 index 0d20c530..00000000 --- a/doc/developer_docs/adrs/17-remote-rendering-architecture.md +++ /dev/null @@ -1,203 +0,0 @@ -# ADR 17: Server-Authoritative State and Remote Rendering for VISOR - -## Status -Accepted (2026-07-21 by VISOR team) - -## Context -VISOR is a browser-based 3D scientific visualization platform built on Trame, with rendering currently performed -client-side via VTK.wasm (`trame-vtklocal`). This works well for datasets that fit comfortably in browser memory, -but the geometry must be serialized to the client and client hardware bounds performance. - -Remote rendering, in which the server owns the VTK pipeline and rendering and streams rendered frames to the browser, -is a product requirement. The local wasm mode still serves the case where no server GPU is available, and -where the dataset is small enough to fit in browser memory. The Trame framework supports both modes, -with `trame-vtklocal` and `trame-rca` as its packages for the local and remote paths respectively. - -Remote rendering needs the scene to live on the server. In VISOR today it does not: per-part visual state lives -in the browser and is never synced back, so the server's copy is stale by design after startup. Host solutions -drive VISOR through its Python service API, so those calls operate on that stale copy. Wasm-specific code is also -coupled through code on both frontend and backend, leaving no clean seam for a second rendering mode to attach. - -This ADR covers two coupled pieces of work: what the current application needs before a remote path can be added, -and the remote path itself. - -## Requirements -* **Server-authoritative state**: the scene lives on the server; a rendering mode with no client-side scene cannot -depend on client-owned state -* **Server-side rendering**: with pixel streaming to the browser, the existing UI overlaid, and camera and -interaction events forwarded to the server -* **One shared codebase for both modes**, minimizing divergence so that features and fixes apply to both paths -where possible -* **State behaviour identical across both modes**: Save/load state behaves the same regardless of rendering mode -* **API behaviour identical across both modes**: API-driven changes (`add_dataset`, `update_variables`, etc) behave -the same regardless of rendering mode -* **Support for datasets beyond client-side limits**: Rendering mode is selected when the application instance is -created, with the local wasm mode retained for no-server-GPU deployments; switching modes within a running -session is not a requirement. - -## Out of scope - -* Pre-existing issues that are not prerequisites for remote rendering. Performance and threading work is in scope -only where parity requires it. -* Multi-user and multi-tenancy -* In-session rendering mode switching -* Product-level performance targets (this work targets partiy) -* Production hardening of the remote path: session management, reconnect, encoder and quality controls. These -land under the performance epic, not in this ADR. - * A unified graceful auto-connect is deferred to the performance epic. -* Detailed design of the round-trip mechanism. We commit to round trips here, but it is designed separately; -see below. - -## Options Considered - -### Question 1: State ownership and stack - -**Option 1.1 Keep the current hybrid client-owned state model.** Cannot support a rendering mode with no client scene. -Also carries today's known costs: state changes push stale server state on `local_view.update()` before -reapplying state on the client, producing an inherent visible flicker. Not chosen. - -**Option 1.2 Move state fully client-side, off Trame.** A pure JS/VTK.wasm application with a -purpose-built service layer. This maximizes client-side simplicity, but is structurally incompatible with remote -rendering. To support the Python-driven APIs, it means building and owning transport, session, and sync layers -that Trame already provides. Not chosen. - -**Option 1.2 Server-authoritative state within Trame (chosen).** The server owns scene state; Trame remains the session -and delivery layer. This is the only option compatible with remote rendering, it resolves the state reconstruction -limitations noted above independent of rendering mode, and it keeps VISOR on infrastructure maintained upstream. -One cost is that the wasm path's end state requires round trips for state changes, which is the risk the second spike -was run to evaluate (see Spike Summary below). Chosen. - -### Question 2: How the two modes coexist - -**Option 2.1 Single stack with two renderers behind a shared `IRenderer` abstraction (chosen).** Python abstract class and -TypeScript interface; shared code programs against it, each mode implements it. The same React bundle serves both. - -**Option 2.2 Parallel remote path inside the existing app, no shared abstractions.** -The fastest to a first demo, but the current state model and a remote path's state model do not align, so -mode-specific branching spreads through backend and frontend and every feature is effectively built and maintained -twice in one codebase (not clean, difficult to maintain). Not chosen. - -**Option 2.3 Separate frontend for remote mode.** Maximal isolation between the modes, at the cost of two UIs to build -and maintain, a split user experience, and giving up the shared-codebase requirement. Not chosen. - -### Question 3: Remote transport within Trame -The trame-native options are the older image streaming widgets (`VtkRemoteView` / `VtkRemoteLocalView`, trame-vtk) and -`trame-rca` (Remote Controlled Area, Kitware's current purpose-built streaming layer). `trame-rca` was selected as -the current-generation tool, recommended by Kitware. - -### Question 4: Structure of the Trame application layer - -Rendering mode selection at startup is a requirement (see Requirements). The remaining structural question is whether -one Trame application class serves both modes, or each mode has its own. - -**Option 4.1 A single Trame application class serving both modes.** On its face, less total code. In practice the -two modes expose the same trigger contract but complete the triggers differently (apply and sync the WASM scene vs -apply and schedule a server-side render), so a merged class would branch on mode inside nearly every handler. -It also could not deliver in-session mode-switching alone, since a session's mode is decided when its scene, renderer, -and client objects are constructed, and switching is also not a requirement. Not chosen. - -**Option 4.2 One thin Trame application per mode (chosen).** `LocalApp` and `RemoteApp` are separate classes, -each decorated with `@TrameApp`, owning only their mode's web configuration. The frontend trigger contract is -common to both, and the shared Python logic lives in the scene layer beneath them (the scene base and the per-part -pipeline), so the application classes themselves stay thin. A session is unambiguously on one code path from -startup, which is more robust for the SAF integration path. Chosen. - - - -## Decision - -Adopt server-authoritative state within Trame, with dual rendering modes behind an `IRenderer` abstraction. - -* **`IRenderer` on both sides.** Python: scene coordination (dataset registry, state mapping, user-facing API) -no longer owns VTK pipeline objects; a renderer implementation owns the pipeline, render window, and frame delivery. -TypeScript: wasm-specific calls move behind the interface, and an RCA canvas component handles stream display -interaction forwarding in remote mode, with no wasm binary downloaded when running remote. -* **State model.** The server owns the VTK pipeline. Its objects are the authoritative record of scene state. -In local mode the client holds a wasm copy of the scene, so a change originating in the browser has to travel to the -server, be applied to the server's pipeline, and come back as the state the client renders. That round trip is -required by this model. In remote mode the client holds no scene and forwards events. On the wasm path, -local optimizations are preserved where latency demands them (cross-section drag applies locally and syncs on release, -for example). Service API calls, save/load, and refresh all act on the server's objects and behave identically -in both modes. -* **Shared per-part pipeline.** The per-part VTK pipeline (actor, mapper, geometry filter, clipping plane) is identical -in both modes. -* **Remote transport and rendering backends.** `trame-rca` only transports the pixels. The remote path is first built -and de-risked against a VTK off-screen render window with the OpenGL backend (Phase 4 below), which is enough to prove -the architecture. The Paraview `pvserver` is the production backend for large models, and is added as -the last step (Phase 5). -* **Rendering mode selected at startup.** Mode is fixed at instance creation, exposed on the entry point, and plumbed -through the service and CLI. Each mode has its own thin `@TrameApp` class (`LocalApp`, `RemoteApp`) owning only -that mode's web configuration. One unambiguous code path per session is a robust fit for the SAF integration path. - -### Round trips on the wasm path: committed, designed separately - -Committing to server-authoritative state commits us to round trips on the local path; that is a decision confirmed -in this ADR. What that mechanism looks like in detail is a question left out of the scope in this ADR. -Some open design questions include whether the client applies a change optimistically while its round trip is in -flight, whether changes need tracking and acknowledgement and what that would look like, and how camera interaction -behaves on the local path. -The design for the round-trip feature is left for its own ADR under the performance epic (see Implementation Plan -below), informed by the round-trip spike findings. - -## Implementation Plan -Below is the proposed implementation plan, broken into phases. The full user story breakdown is not tracked here. - -``` -Phase 1 (backend refactor)--- - | --> Phase 4 (remote rendering) --> Phase 5 (pveserver) - |--> Phase 3 (state inversion) | - | --> Phase 6 (round trips) - | (under performance epic, separate ADR) -Phase 2 (frontend refactor) -- -``` - -Each user story as part of the implementation plan is expected to leave VISOR in a working state, so that -parallel development work can continue while we incrementally move toward the final architecture. The phases are: - -* Phase 1: backend refactor extracting the renderer abstraction from the scene layer -* Phase 2: frontend refactor extracting wasm-specific code behind the renderer interface (parallel with Phase 1) -* Phase 3: state authority inversion: Incrementally move state from client to server, keeping VISOR functional -at each step. In this phase, the client interactions are still applied locally, but the state is synced back -to the server, and the server's VTK pipeline is updated accordingly. Some parts of the VTK pipeline will need to -be added on the server side. Note that this phase does not yet include the round-trip mechanism itself. -* Phase 4: remote path (RCA canvas, remote renderer, mode selection at VISOR startup). Includes moving geometry -picking server-side with highlights as actors in the server scene, which also adds occlusion. -* Phase 5: Add `pvserver`. Start with a timeboxed spike, followed by an implementation story based on the spike. -* Phase 6 (in parallel after Phase 3): the round trip mechanism. Will be a separate feature under the performance epic. -Requires an ADR for the design of the round-trip mechanism. Due to an upstream issue encountered on the round-trip -spike (see below under Accepted Costs), this phase needs to start with a user story to bump the versions of -`trame-vtklocal` and `vtk-wasm` to the latest versions. - - -## Consequences - -### Positive -* One codebase, two modes, no forked UI or state logic -* Server-authoritative state fixes save/load state and the inherent visible flicker on `local_view.update()` -* Local wasm mode retained for no-server-GPU deployments and small datasets -* Idiomatic to Trame, so maintenance stays aligned with upstream - -### Accepted Costs -* Session-thread affinity is the largest unresolved risk. The spikes surfaced (rather than introduced) a -threading issue. The framework expects VTK operations on the session's thread, and VISOR's API entry points arrive -from the host on other threads. Serializing state applies did not change crash frequency, so the root cause is -open. It affects API correctness after the server-owned model, so it is a parity prerequisite. -* The wasm path runs an interim model after Phase 3 (client-local application, one-way sync back) until the round-trip -feature lands. The end state arrives in two steps, the second under its own ADR. -* An upstream color table issue blocks round trips on the wasm path, with no viable local workaround. The spike branch -investigation found that the issue sits in the framework's state application layer (as opposed to VISOR's code). The -`trame-vtklocal` and `vtk-wasm` dependency bumps at the start of Phase 6 may resolve the issue. If the retest on -the latest versions fails, an upstream ticket would need to be filed to resolve it. (Note that this would leave the wasm -path on the interim model mentioned above - with the remote path being unaffected.) -* `pvserver` introduces a VTK version-pinning consideration (ParaView ships its own VTK), tracked in Phase 5. Pinning -is the initial approach, but we will also explore building a custom `pvserver` against our VTK version. - -## References -* Dual-mode architecture + remote rendering spike user story and spike findings document: - * https://github.com/ansys-internal/theia/issues/1051 -* Round-trip and optimization spike user story: - * https://github.com/ansys-internal/theia/issues/1128 -* User story breakdown for the phased implementation - * https://github.com/ansys-internal/theia/issues/1049 -* User story brekadown for performance epic (round trips, optimizations, and remote rendering production hardening) - * https://github.com/ansys-internal/theia/issues/1168 \ No newline at end of file diff --git a/doc/developer_docs/architecture/.structurizr/images/DashServerComponents-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/DashServerComponents-thumbnail.png deleted file mode 100644 index 151649f9..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/DashServerComponents-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/EndUserDeployment-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/EndUserDeployment-thumbnail.png deleted file mode 100644 index b262eed8..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/EndUserDeployment-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/GlowApiServerComponents-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/GlowApiServerComponents-thumbnail.png deleted file mode 100644 index 32cbf7ae..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/GlowApiServerComponents-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/GlowContainers-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/GlowContainers-thumbnail.png deleted file mode 100644 index b2855007..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/GlowContainers-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/GlowSystemContext-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/GlowSystemContext-thumbnail.png deleted file mode 100644 index 081b3eff..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/GlowSystemContext-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/MethodComponents-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/MethodComponents-thumbnail.png deleted file mode 100644 index abb93591..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/MethodComponents-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/PortalContainers-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/PortalContainers-thumbnail.png deleted file mode 100644 index c275f9ec..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/PortalContainers-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/PortalSystemContext-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/PortalSystemContext-thumbnail.png deleted file mode 100644 index b534bf10..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/PortalSystemContext-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/SystemLandscape-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/SystemLandscape-thumbnail.png deleted file mode 100644 index 40bda070..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/SystemLandscape-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/VisorClientComponents-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/VisorClientComponents-thumbnail.png deleted file mode 100644 index bbf67455..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/VisorClientComponents-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/VisorContainers-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/VisorContainers-thumbnail.png deleted file mode 100644 index 14aa9d7d..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/VisorContainers-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/VisorServerComponents-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/VisorServerComponents-thumbnail.png deleted file mode 100644 index cd3e78a2..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/VisorServerComponents-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/VisorSolutionApplicationContext-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/VisorSolutionApplicationContext-thumbnail.png deleted file mode 100644 index 61844b9f..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/VisorSolutionApplicationContext-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/VisorSystemContext-thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/VisorSystemContext-thumbnail.png deleted file mode 100644 index 62b45439..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/VisorSystemContext-thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/images/thumbnail.png b/doc/developer_docs/architecture/.structurizr/images/thumbnail.png deleted file mode 100644 index 40bda070..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/images/thumbnail.png and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/index/_0.cfe b/doc/developer_docs/architecture/.structurizr/index/_0.cfe deleted file mode 100644 index d042b2f4..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/index/_0.cfe and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/index/_0.cfs b/doc/developer_docs/architecture/.structurizr/index/_0.cfs deleted file mode 100644 index d70d7828..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/index/_0.cfs and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/index/_0.si b/doc/developer_docs/architecture/.structurizr/index/_0.si deleted file mode 100644 index dbc66db7..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/index/_0.si and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/index/segments_1 b/doc/developer_docs/architecture/.structurizr/index/segments_1 deleted file mode 100644 index 423b9170..00000000 Binary files a/doc/developer_docs/architecture/.structurizr/index/segments_1 and /dev/null differ diff --git a/doc/developer_docs/architecture/.structurizr/index/write.lock b/doc/developer_docs/architecture/.structurizr/index/write.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/developer_docs/architecture/visor.md b/doc/developer_docs/architecture/visor.md deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/developer_docs/architecture/workspace.dsl b/doc/developer_docs/architecture/workspace.dsl deleted file mode 100644 index e6d71f35..00000000 --- a/doc/developer_docs/architecture/workspace.dsl +++ /dev/null @@ -1,285 +0,0 @@ -workspace "Visor" "VISOR (Visual Interactive Simulation Object Renderer) 3D Visualization Web Components for Solutions Applications" { - !identifiers hierarchical - !impliedRelationships false - - model { - properties { - "structurizr.groupSeparator" "/" - } - end_user = person "End User" "A person who is using a Solution" "" - group "Ansys Corporate Client" { - - visor = softwareSystem "Visor" "3D Viewer for Solutions Applications" "" { - visor_dash = container "VISOR 3D Viewer Dash UI component" "VISOR 3D Viewer Dash wrapper with Python bindings for the VISOR web client library and client api" "Dash, Python, Typescript, React, VTK WASM JS viewer library" "" { - visor_dash_ui = component "VISOR Viewer web client library" "React TypeScript library of the VISOR client UI" "React,Typescript,Javascript" "#React,#Typescript,#JS" - visor_dash_api = component "VISOR Dash UI Component API" "Provides an API through the React interface available through the Dash component in order to allow for client side callbacks triggering specific functionality from the Dash application" "Dash,React,TypeScript" "#Dash,#JS,#Typescript,#React" - visor_client_api = component "VISOR client UI API" "Implements an API which interfaces and implements actions on the web UI, trame vtk module library and/or the scene component." "React,TypeScript" "#React,#TypeScript" - visor_js_library = component "VISOR JS library" "VISOR JS library implementing the visor client" "JavaScript,TypeScript,React" "#JavaScript,#TypeScript,#React" - } - group "VISOR Client" { - visor_client = container "VISOR JS client" "VISOR client implementing the viewer functionality on the frontend using Trame VTK.WASM module library." "Typescript,JavaScript,React,MJS,Trame,WASM,VTK.WASM" ""{ - visor_scene_component = component "VISOR Viewer Scene Graph component" "VISOR 3D Viewer scene graph component for visualization of the model topology" "VTK, Typescript, React" "#Typescript,#React,#VTK" - visor_ui_elements = component "VISOR UI Elements" "VISOR UI elements for the VISOR viewer" "React,Typescript" "#React,#Typescript" - visor_trame_functionality_api = component "VISOR Trame application sync and state manager" "VISOR API for interfacing with the VISOR defined Trame application on the server" "TypeScript,Trame,React" "" - } - trame_vtk_local_container = container "Trame VTK.WASM library" { - trame_wslink_connection = component "Trame WSLINK connection and WASM loader" "Trame WSLINK connection to the server and WASM loader" "Python, WebSocket, wslink" "#Python,#WebSocket,#wslink" - trame_wasm_handler = component "Trame Object Manager" "VTK Object manager for serializaton/deserialization of VTK C++ classes for VTK pipeline objects shared between the client and the Trame server" "VTK.WASM,JS,MJS" "#VTK,#WASM,#JS,#MJS" - } - } - - visor_server = container "VISOR server" "VISOR server supporting 3D rendering of models from Ansys flagship products" "" "" { - trame_server = component "Trame Server" "Trame server component supporting single session using a web socket connection" "Python, WebSocket, wslink" "#Python,#WebSocket,#wslink" - visor_http_api = component "VISOR Server Orchestration HTTP API" "Provides an HTTP API for controlling the lifecycle of the starting, stopping or updating the Trame server and the websocket connection." "OpenAPI,FastAPI" "#openapi,#fastapi" - visor_server_component = component "VISOR Server Component" "VISOR server component creating a Trame server for a single session for this VISOR application" "Python, WebSocket, wslink" "#Python,#WebSocket,#wslink" - } - - visor_app = container "VISOR Application Component" "VISOR Application controlling the choice of rendering engine, a VISOR Server instance and providing the Python API to the application" "Python" ""{ - visor_api = component "VISOR API" "VISOR API for controlling the lifecycle of the starting, stopping or updating the Trame server and the websocket connection." "OpenAPI, FastAPI" "#openapi,#fastapi" - trame_application = component "Trame application" "Trame application based on Trame vtk_local application utilizing VTK.WASM and a VTK Object Manager for client-server synchronization" "technology" "tags" - vtk_pipeline = component "VTK Pipeline and shared objects with the client side" "VTK pipeline setup for visualization on the server side which is synchronized with the VTK rendering on the client side" "VTK" "#VTK" - scene_graph = component "Scene graph" "Scene graph for supporting visualization of object hierarchies and scene attributes between the client and the server side" "Python, VTK" "#Python,#VTK" - visor_logmonitor = component "VISOR Logger and Monitor of the application, servers and services" "VISOR logger and monitor is the part of the VISOR application which implements the OpenTelemetry standards for VISOR" "Python,OpenTelemetry" "#Python,#OpenTelemetry" - } - visor_server.visor_http_api -> visor_app.visor_api "Provides an HTTP API for controlling the lifecycle of the starting, stopping or updating the Trame server and the websocket connection." "" "#openapi" - visor_app -> visor_server.visor_http_api "Provides an HTTP API for controlling the lifecycle of the starting, stopping or updating the Trame server and the websocket connection." "" "#openapi" - - visor_dash.visor_dash_ui -> visor.visor_dash.visor_js_library "Triggers functionality from the Dash client to the VISOR client" "" "#React,#Typescript,#JavaScript" - visor_app.trame_application -> visor_app.vtk_pipeline "Sets up the VTK pipeline for the server side and synchronizes with the client side" - visor_app.trame_application -> visor_app.scene_graph "Sets up the scene graph for the server side" - visor.visor_app.visor_logmonitor -> visor.visor_app.trame_application "Monitors the application and server" - visor.visor_app.visor_api -> visor.visor_app.trame_application "Manages the Trame application client and server side, along with the VTK pipeline, scene management and input management." - visor_app.scene_graph -> visor_client.visor_scene_component "Updates view and sends events to UI elements" - visor.visor_dash.visor_client_api -> visor.visor_client.visor_trame_functionality_api "Triggers functionality from the Dash client to the VISOR client library which is either a web UI functionality, or a Trame VTK.WASM functionality synchronized with the server and/or functionality on the scene component" - visor.visor_dash.visor_dash_api -> visor.visor_client.visor_scene_component "Triggers visualization updates" - visor.visor_dash.visor_dash_api -> visor.visor_client.visor_trame_functionality_api "Triggers functionality from the Dash client to the VISOR client library utilizing Trame VTK.WASM functionality on the client or server side." - visor_server.visor_server_component -> visor_server.trame_server "Lifecycle management of the Trame server" - visor_server.trame_server -> trame_vtk_local_container.trame_wasm_handler "Sends scene updates" - visor.visor_client.visor_trame_functionality_api -> visor_server.trame_server "Trigger VTK updates" - visor.trame_vtk_local_container.trame_wasm_handler -> visor_server.trame_server "Triggers VTK updates" - visor.trame_vtk_local_container.trame_wslink_connection -> visor_server.trame_server "Connects to running wslink session to setup a websocket connection." - visor_server.visor_http_api -> visor_server.visor_server_component "Controls server start, stop and state updates as well as monitoring tasks." - - - end_user -> visor.visor_dash.visor_dash_ui "Triggers 3D model view updates" "" "" - end_user -> visor.visor_dash.visor_dash_api "Triggers 3D model view updates" "" "" - visor.visor_client -> end_user "Visualization of 3D model data" "" "" - visor.visor_client -> end_user "Updates 3D model view" "" "" - visor.visor_client -> visor.visor_server "Requests model data" - visor.visor_server -> visor.visor_client "Sends model data" - } - - portal = softwareSystem "SAF Portal" "enables the user to create new project or select existing project then launch solution UI for project. Does not have responsibility for implementation of any aspect of the solution business logic or the services consumed by the solution." "" { - portal_server = container "Portal Server" "implements a REST API that is consumed by the Portal UI. The portal server consumes a small subset of the API provided by the GLOW API Server" "FastAPI" "#fastapi" - ui = container "Portal User Interface" "provides a view of the projects in the projects directory. enables the user to create or select a project then launch solution UI for the project" "React" - } - - product_instance_manager = softwareSystem "Product Instance Manager" "enables the startup and termination of Ansys Flagship Products or other stateful processes" "" { - url https://tfs.ansys.com:8443/tfs/ANSYS_Development/Extensibility/_git/Root?path=%2Fansys%2Finstancemanagement%2Flight - } - - product = softwareSystem "Ansys Flagship Product" "A stateful process that is required to implement a GLOW transaction method (typically an Ansys Flagship product which contains a simulation solver designed to be a desktop application)" "#external" - - glow = softwareSystem "Guided Low Code Workflow (GLOW)" "framework for vertical applications orientated towards a guided workflow user experience" { - url https://github.com/ansys-internal/glow-engine - dash_ui = container "Solution Dash UI" "A browser based client for the Dash server implemented in React Javascript that renders the UI defined by the Dash server" "React" - - group "API" { - - projects_directory = container "Projects Directory" "the file system directory containing project files" - api = container "API Server" "Provides a REST API specific to a given solution, which is consumed by the solution UI server." - projects_database = container "Projects Database" "stores instances of the solution schema" - - } - - dash = container "Dash Server" "a Flask server that services a React browser based UI defined using the Dash UI definition API" "Flask" "#Flask" { - dash_flask_server = component "Dash Flask Server" "a Flask server that services a React browser based UI defined using the Dash UI definition API" "Flask" { - url https://dash.plotly.com/ - } - solution_ui = component "Solution UI" "a python package which defines how the solution is rendered via the Dash UI definition API" "Python" "" - client_api = component "Client API" "a python package that provides a pythonic interface to a GLOW API server via REST" "Python"{ - url https://github.com/ansys-internal/glow-engine/tree/main/src/ansys/saf/glow/client - } - solution_definition_api = component "GLOW Solution definition API" "a python package that contains the set of python types required to define a GLOW solution" "Python" { - url https://github.com/ansys-internal/glow-engine/tree/main/src/ansys/saf/glow/solution - } - solution = component "Solution definition" "the definition of a solution's schema and business logic" "Python" "" - - solution_ui -> dash_flask_server "invoke rendering providing UI structure and callbacks" "" "#import" - solution_ui -> client_api "gets and sets data; and invokes methods via proxy objects" "" "#function" - client_api -> solution "obtains schema and method set" "" "#import" - solution -> solution_definition_api "obtains base types for solution definition" "" "#import" - client_api -> glow.api "calls" "REST" "#REST" - } - - method_process = container "Method Execution Process" "An OS process that implements a single call to a transaction method" "Python" "" { - method_runner = component "Method Runner" "implements a single call to a transaction method" "Python" { - url https://github.com/ansys-internal/glow-engine/blob/main/src/ansys/saf/glow/_executor/method_runner.py#L41 - } - solution_definition_api = component "Solution definition API" "a python package that contains the set of python types required to define a GLOW solution" "Python"{ - url https://github.com/ansys-internal/glow-engine/tree/main/src/ansys/saf/glow/solution - } - solution = component "Solution definition" "the definition of a solution's schema and business logic" "Python" "" - method_runner -> solution "executes method" "" "#function" - solution -> solution_definition_api "obtains base types for solution definition" "" "#import" - } - - - method_file_space = container "Method file space" "the temporary directory used by a method execution process that exists just for the duration of the process." "file sdystem directory" - product_instance_file_space = container "Product Instance file space" "the OS directory associated with a product instance" "file system directory" "#file" - - api -> method_process "starts & stops" "" "#process" - method_process -> product "executes method code" "gRPC" "#gRPC" - method_process -> product_instance_manager "queries product connection, requests product start & termination" "gRPC" "#gRPC" - method_process -> api "calls" "REST" "#REST" - method_process -> method_file_space "creates & deletes" "" "#file" - method_process -> product_instance_file_space "creates & deletes" "" "#file" - - api -> product_instance_manager "requests product termination (on shutdown)" "gRPC" "#gRPC" - api -> projects_database "gets, modifies & creates records in" "" "#file" - api -> projects_directory "reads and writes project files" "" "#file" - - product_instance_manager -> product "starts & kills" "" "#process" - product_instance_manager -> visor.visor_server "Starts, stops viewer" "" "#process" - visor.visor_server -> method_file_space "reads model data" "" "#file" - visor.visor_server -> product_instance_file_space "reads model data" "" "#file" - product -> method_file_space "writes 3D model" "" "#file" - product -> product_instance_file_space "writes 3D model" "" "#file" - - dash_ui -> dash "obtains code and state; signals user interface events" "REST" "#REST" - dash -> api "Call" "REST" "#REST" - dash_ui -> visor.visor_dash.visor_dash_ui "Signals user interface events" "" "" - dash_ui -> visor.visor_dash.visor_dash_api "Triggers visual events and requests data from the VISOR 3D viewer" "" "" - visor.visor_dash.visor_dash_api -> dash_ui "Sends data to the Dash UI and status responses based on user interaction" - - portal -> api "Call" "REST" "#REST" - - method_process -> api "uploads and downloads fields" "REST" "#REST" - method_process -> method_file_space "creates and deletes" "" "#file" - method_process -> product_instance_file_space "creates and deletes" "" "#file" - - api -> method_process "starts" "" "#process" - - end_user -> dash_ui "uses" "" "#user" - - - end_user -> portal "uses" "" "#user" - portal -> glow "launches UI for existing or new project" - glow -> product_instance_manager "requests start and termination of product instances" "gRPC" "#gRPC" - product_instance_manager -> visor "launches VISOR visualization" "" "" - glow -> visor "launches VISOR visualization" "" "" - portal -> glow.dash_ui "links to" "JavaScript Click Handler" "#link" - glow -> product "calls" "gRPC" "#gRPC" - product_writes_state = product -> glow.projects_directory "reads & writes product state" "" "#file" - end_user -> visor "Views and interacts with the 3D model" - } - } - - end_user_windows_pc_ = deploymentEnvironment "End User Windows Desktop PC" { - deploymentNode "Python Interpreter" "the python interpreter that runs the GLOW solution" "Python" "" 1 { - deploymentNode "Orchestrator" "the python module that starts and shutsdown the GLOW solution" "Python" "" 1 { - api_ = containerInstance glow.api "" { - } - dash_ = containerInstance glow.dash "" { - } - method_process_ = containerInstance glow.method_process "" { - } - portal_server_ = containerInstance portal.portal_server "" { - } - product_instance_manager_ = softwareSystemInstance product_instance_manager "" { - } - } - deploymentNode "pywebview" "a python and browser based engine for rendering web UIs as desktop application windows" "Python" "" 1 { - dash_ui_ = containerInstance glow.dash_ui "" { - } - portal_ui_ = containerInstance portal.ui "" { - } - visor_ui_ = containerInstance visor.visor_client "" "" - } - } - deploymentNode "File System" "the file system of a single Windows Desktop PC" "Windows" 1 { - deploymentNode "User Documents Directory" "the Documents directory of the end user" "Windows" 1 { - projects_directory_ = containerInstance glow.projects_directory - product_instance_file_space_ = containerInstance glow.product_instance_file_space - } - deploymentNode "APPDATA Directory" "the APPDATA directory of the end user" "Windows" 1 { - projects_database_ = containerInstance glow.projects_database " - } - } - } - - } - - views { - theme https://raw.githubusercontent.com/RVR06/cornifer-contrib/main/themes/semantic/theme.json - theme https://raw.githubusercontent.com/RVR06/cornifer-contrib/main/themes/heraldry/theme.json - branding { - logo https://raw.githubusercontent.com/RVR06/cornifer-contrib/main/assets/ansys.png - } - - systemLandscape "SystemLandscape" "VISOR integrated in Solution Application using GLOW" { - include * - autolayout lr - } - - systemContext visor "VisorSolutionApplicationContext" "VISOR Solution Application Context" { - include * - include glow - include portal - autolayout lr - } - - container visor "VisorContainers" "VISOR Containers" { - include end_user - include visor.visor_client - include visor.visor_server - } - - systemContext visor "VisorSystemContext" "VISOR Context" { - include * - autolayout lr - } - - - component visor.visor_server "VisorServerComponents" "VISOR Server Components" { - include * - include visor.visor_server.visor_http_api - include visor.visor_server.visor_server_component - include visor.visor_server.trame_server - autolayout lr - } - - component visor.visor_dash "VisorDashComponents" "VISOR Dash UI Components" { - include * - include visor.visor_dash.visor_dash_ui - include visor.visor_dash.visor_dash_api - include visor.visor_dash.visor_client_api - include visor.visor_dash.visor_js_library - autolayout lr - } - - component visor.visor_client "VisorClientComponents" "VISOR Client Component" { - include * - include visor.visor_client.visor_ui_elements - include visor.visor_client.visor_scene_component - include visor.visor_client.visor_trame_functionality_api - include visor.trame_vtk_local_container.trame_wasm_handler - include visor.trame_vtk_local_container.trame_wslink_connection - autolayout lr - } - - component visor.visor_app "VisorAppComponents" "VISOR Application Components" { - include * - include visor.visor_app.visor_api - include visor.visor_app.trame_application - include visor.visor_app.vtk_pipeline - include visor.visor_app.scene_graph - include visor.visor_app.visor_logmonitor - autolayout lr - } - - deployment * end_user_windows_pc_ "EndUserDeployment" "End User Deployment" { - include * - autolayout lr - } - } \ No newline at end of file diff --git a/doc/developer_docs/architecture/workspace.json b/doc/developer_docs/architecture/workspace.json deleted file mode 100644 index 961f1874..00000000 --- a/doc/developer_docs/architecture/workspace.json +++ /dev/null @@ -1,1875 +0,0 @@ -{ - "id" : 1, - "name" : "Visor", - "description" : "VISOR (Visual Interactive Simulation Object Renderer) 3D Visualization Web Components for Solutions Applications", - "lastModifiedDate" : "2025-05-22T12:28:08Z", - "lastModifiedAgent" : "structurizr-javascript", - "properties" : { - "structurizr.dsl" : "d29ya3NwYWNlICJUaGVpYSIgIlRoZWlhIChUaHJlZS1kaW1lbnNpb25hbCBFbmdpbmVlcmluZyBJbnRlcmFjdGl2ZSBBbmFseXNpcykgM0QgVmlzdWFsaXphdGlvbiBXZWIgQ29tcG9uZW50cyBmb3IgU29sdXRpb25zIEFwcGxpY2F0aW9ucyIgewoJIWlkZW50aWZpZXJzIGhpZXJhcmNoaWNhbAoJIWltcGxpZWRSZWxhdGlvbnNoaXBzIGZhbHNlCgkKCW1vZGVsIHsKCQlwcm9wZXJ0aWVzIHsKCQkJInN0cnVjdHVyaXpyLmdyb3VwU2VwYXJhdG9yIiAiLyIKCQl9CgkJZW5kX3VzZXIgPSBwZXJzb24gIkVuZCBVc2VyIiAiQSBwZXJzb24gd2hvIGlzIHVzaW5nIGEgU29sdXRpb24iICIiCgkJZ3JvdXAgIkFuc3lzIENvcnBvcmF0ZSBDbGllbnQiIHsKCQkJCgkJCXRoZWlhID0gc29mdHdhcmVTeXN0ZW0gIlRoZWlhIiAiM0QgVmlld2VyIGZvciBTb2x1dGlvbnMgQXBwbGljYXRpb25zIiAiIiB7CgkJCQl0aGVpYV9kYXNoID0gY29udGFpbmVyICJUaGVpYSAzRCBWaWV3ZXIgRGFzaCBVSSBjb21wb25lbnQiICJUaGVpYSAzRCBWaWV3ZXIgRGFzaCB3cmFwcGVyIHdpdGggUHl0aG9uIGJpbmRpbmdzIGZvciB0aGUgVGhlaWEgd2ViIGNsaWVudCBsaWJyYXJ5IGFuZCBjbGllbnQgYXBpIiAiRGFzaCwgUHl0aG9uLCBUeXBlc2NyaXB0LCBSZWFjdCwgVlRLIFdBU00gSlMgdmlld2VyIGxpYnJhcnkiICIiIHsKCQkJCQl0aGVpYV9kYXNoX3VpID0gY29tcG9uZW50ICJUaGVpYSBWaWV3ZXIgd2ViIGNsaWVudCBsaWJyYXJ5IiAiUmVhY3QgVHlwZVNjcmlwdCBsaWJyYXJ5IG9mIHRoZSBUaGVpYSBjbGllbnQgVUkiICJSZWFjdCxUeXBlc2NyaXB0LEphdmFzY3JpcHQiICIjUmVhY3QsI1R5cGVzY3JpcHQsI0pTIgoJCQkJCXRoZWlhX2Rhc2hfYXBpID0gY29tcG9uZW50ICJUaGVpYSBEYXNoIFVJIENvbXBvbmVudCBBUEkiICJQcm92aWRlcyBhbiBBUEkgdGhyb3VnaCB0aGUgUmVhY3QgaW50ZXJmYWNlIGF2YWlsYWJsZSB0aHJvdWdoIHRoZSBEYXNoIGNvbXBvbmVudCBpbiBvcmRlciB0byBhbGxvdyBmb3IgY2xpZW50IHNpZGUgY2FsbGJhY2tzIHRyaWdnZXJpbmcgc3BlY2lmaWMgZnVuY3Rpb25hbGl0eSBmcm9tIHRoZSBEYXNoIGFwcGxpY2F0aW9uIiAiRGFzaCxSZWFjdCxUeXBlU2NyaXB0IiAiI0Rhc2gsI0pTLCNUeXBlc2NyaXB0LCNSZWFjdCIKCQkJCQl0aGVpYV9jbGllbnRfYXBpID0gY29tcG9uZW50ICJUaGVpYSBjbGllbnQgVUkgQVBJIiAiSW1wbGVtZW50cyBhbiBBUEkgd2hpY2ggaW50ZXJmYWNlcyBhbmQgaW1wbGVtZW50cyBhY3Rpb25zIG9uIHRoZSB3ZWIgVUksIHRyYW1lIHZ0ayBtb2R1bGUgbGlicmFyeSBhbmQvb3IgdGhlIHNjZW5lIGNvbXBvbmVudC4iICJSZWFjdCxUeXBlU2NyaXB0IiAiI1JlYWN0LCNUeXBlU2NyaXB0IgoJCQkJCXRoZWlhX2pzX2xpYnJhcnkgID0gY29tcG9uZW50ICJUaGVpYSBKUyBsaWJyYXJ5IiAiVGhlaWEgSlMgbGlicmFyeSBpbXBsZW1lbnRpbmcgdGhlIHRoZWlhIGNsaWVudCIgIkphdmFTY3JpcHQsVHlwZVNjcmlwdCxSZWFjdCIgIiNKYXZhU2NyaXB0LCNUeXBlU2NyaXB0LCNSZWFjdCIKCQkJCX0KCQkJCWdyb3VwICJUaGVpYSBDbGllbnQiIHsKCQkJCQl0aGVpYV9jbGllbnQgPSBjb250YWluZXIgIlRoZWlhIEpTIGNsaWVudCIgIlRoZWlhIGNsaWVudCBpbXBsZW1lbnRpbmcgdGhlIHZpZXdlciBmdW5jdGlvbmFsaXR5IG9uIHRoZSBmcm9udGVuZCB1c2luZyBUcmFtZSBWVEsuV0FTTSBtb2R1bGUgbGlicmFyeS4iICJUeXBlc2NyaXB0LEphdmFTY3JpcHQsUmVhY3QsTUpTLFRyYW1lLFdBU00sVlRLLldBU00iICIiewoJCQkJCQl0aGVpYV9zY2VuZV9jb21wb25lbnQgPSAgY29tcG9uZW50ICJUaGVpYSBWaWV3ZXIgU2NlbmUgR3JhcGggY29tcG9uZW50IiAiVGhlaWEgM0QgVmlld2VyIHNjZW5lIGdyYXBoIGNvbXBvbmVudCBmb3IgdmlzdWFsaXphdGlvbiBvZiB0aGUgbW9kZWwgdG9wb2xvZ3kiICJWVEssIFR5cGVzY3JpcHQsIFJlYWN0IiAiI1R5cGVzY3JpcHQsI1JlYWN0LCNWVEsiCgkJCQkJCXRoZWlhX3VpX2VsZW1lbnRzID0gY29tcG9uZW50ICJUaGVpYSBVSSBFbGVtZW50cyIgIlRoZWlhIFVJIGVsZW1lbnRzIGZvciB0aGUgVGhlaWEgdmlld2VyIiAiUmVhY3QsVHlwZXNjcmlwdCIgIiNSZWFjdCwjVHlwZXNjcmlwdCIKCQkJCQkJdGhlaWFfdHJhbWVfZnVuY3Rpb25hbGl0eV9hcGkgID0gY29tcG9uZW50ICJUaGVpYSBUcmFtZSBhcHBsaWNhdGlvbiBzeW5jIGFuZCBzdGF0ZSBtYW5hZ2VyIiAiVGhlaWEgQVBJIGZvciBpbnRlcmZhY2luZyB3aXRoIHRoZSBUaGVpYSBkZWZpbmVkIFRyYW1lIGFwcGxpY2F0aW9uIG9uIHRoZSBzZXJ2ZXIiICJUeXBlU2NyaXB0LFRyYW1lLFJlYWN0IiAiIgoJCQkJCX0KCQkJCQl0cmFtZV92dGtfbG9jYWxfY29udGFpbmVyID0gY29udGFpbmVyICJUcmFtZSBWVEsuV0FTTSBsaWJyYXJ5IiB7CgkJCQkJCXRyYW1lX3dzbGlua19jb25uZWN0aW9uID0gY29tcG9uZW50ICJUcmFtZSBXU0xJTksgY29ubmVjdGlvbiBhbmQgV0FTTSBsb2FkZXIiICJUcmFtZSBXU0xJTksgY29ubmVjdGlvbiB0byB0aGUgc2VydmVyIGFuZCBXQVNNIGxvYWRlciIgIlB5dGhvbiwgV2ViU29ja2V0LCB3c2xpbmsiICIjUHl0aG9uLCNXZWJTb2NrZXQsI3dzbGluayIKCQkJCQkJdHJhbWVfd2FzbV9oYW5kbGVyID0gY29tcG9uZW50ICJUcmFtZSBPYmplY3QgTWFuYWdlciIgIlZUSyBPYmplY3QgbWFuYWdlciBmb3Igc2VyaWFsaXphdG9uL2Rlc2VyaWFsaXphdGlvbiBvZiBWVEsgQysrIGNsYXNzZXMgZm9yIFZUSyBwaXBlbGluZSBvYmplY3RzIHNoYXJlZCBiZXR3ZWVuIHRoZSBjbGllbnQgYW5kIHRoZSBUcmFtZSBzZXJ2ZXIiICJWVEsuV0FTTSxKUyxNSlMiICIjVlRLLCNXQVNNLCNKUywjTUpTIgoJCQkJCX0KCQkJCX0KCQkJCQoJCQkJdGhlaWFfc2VydmVyID0gY29udGFpbmVyICJUaGVpYSBzZXJ2ZXIiICJUaGVpYSBzZXJ2ZXIgc3VwcG9ydGluZyAzRCByZW5kZXJpbmcgb2YgbW9kZWxzIGZyb20gQW5zeXMgZmxhZ3NoaXAgcHJvZHVjdHMiICIiICIiIHsKCQkJCQl0cmFtZV9zZXJ2ZXIgPSBjb21wb25lbnQgIlRyYW1lIFNlcnZlciIgIlRyYW1lIHNlcnZlciBjb21wb25lbnQgc3VwcG9ydGluZyBzaW5nbGUgc2Vzc2lvbiB1c2luZyBhIHdlYiBzb2NrZXQgY29ubmVjdGlvbiIgIlB5dGhvbiwgV2ViU29ja2V0LCB3c2xpbmsiICIjUHl0aG9uLCNXZWJTb2NrZXQsI3dzbGluayIKCQkJCQl0aGVpYV9odHRwX2FwaSA9IGNvbXBvbmVudCAiVGhlaWEgU2VydmVyIE9yY2hlc3RyYXRpb24gSFRUUCBBUEkiICJQcm92aWRlcyBhbiBIVFRQIEFQSSBmb3IgY29udHJvbGxpbmcgdGhlIGxpZmVjeWNsZSBvZiB0aGUgc3RhcnRpbmcsIHN0b3BwaW5nIG9yIHVwZGF0aW5nIHRoZSBUcmFtZSBzZXJ2ZXIgYW5kIHRoZSB3ZWJzb2NrZXQgY29ubmVjdGlvbi4iICJPcGVuQVBJLEZhc3RBUEkiICIjb3BlbmFwaSwjZmFzdGFwaSIKCQkJCQl0aGVpYV9zZXJ2ZXJfY29tcG9uZW50ID0gY29tcG9uZW50ICJUaGVpYSBTZXJ2ZXIgQ29tcG9uZW50IiAiVGhlaWEgc2VydmVyIGNvbXBvbmVudCBjcmVhdGluZyBhIFRyYW1lIHNlcnZlciBmb3IgYSBzaW5nbGUgc2Vzc2lvbiBmb3IgdGhpcyBUaGVpYSBhcHBsaWNhdGlvbiIgIlB5dGhvbiwgV2ViU29ja2V0LCB3c2xpbmsiICIjUHl0aG9uLCNXZWJTb2NrZXQsI3dzbGluayIKCQkJCX0KCQkJCQoJCQkJdGhlaWFfYXBwID0gY29udGFpbmVyICJUaGVpYSBBcHBsaWNhdGlvbiBDb21wb25lbnQiICJUaGVpYSBBcHBsaWNhdGlvbiBjb250cm9sbGluZyB0aGUgY2hvaWNlIG9mIHJlbmRlcmluZyBlbmdpbmUsIGEgVGhlaWEgU2VydmVyIGluc3RhbmNlIGFuZCBwcm92aWRpbmcgdGhlIFB5dGhvbiBBUEkgdG8gdGhlIGFwcGxpY2F0aW9uIiAiUHl0aG9uIiAiInsKCQkJCQl0aGVpYV9hcGkgPSBjb21wb25lbnQgIlRoZWlhIEFQSSIgIlRoZWlhIEFQSSBmb3IgY29udHJvbGxpbmcgdGhlIGxpZmVjeWNsZSBvZiB0aGUgc3RhcnRpbmcsIHN0b3BwaW5nIG9yIHVwZGF0aW5nIHRoZSBUcmFtZSBzZXJ2ZXIgYW5kIHRoZSB3ZWJzb2NrZXQgY29ubmVjdGlvbi4iICJPcGVuQVBJLCBGYXN0QVBJIiAiI29wZW5hcGksI2Zhc3RhcGkiCgkJCQkJdHJhbWVfYXBwbGljYXRpb24gPSBjb21wb25lbnQgIlRyYW1lIGFwcGxpY2F0aW9uIiAiVHJhbWUgYXBwbGljYXRpb24gYmFzZWQgb24gVHJhbWUgdnRrX2xvY2FsIGFwcGxpY2F0aW9uIHV0aWxpemluZyBWVEsuV0FTTSBhbmQgYSBWVEsgT2JqZWN0IE1hbmFnZXIgZm9yIGNsaWVudC1zZXJ2ZXIgc3luY2hyb25pemF0aW9uIiAidGVjaG5vbG9neSIgInRhZ3MiCgkJCQkJdnRrX3BpcGVsaW5lID0gY29tcG9uZW50ICJWVEsgUGlwZWxpbmUgYW5kIHNoYXJlZCBvYmplY3RzIHdpdGggdGhlIGNsaWVudCBzaWRlIiAiVlRLIHBpcGVsaW5lIHNldHVwIGZvciB2aXN1YWxpemF0aW9uIG9uIHRoZSBzZXJ2ZXIgc2lkZSB3aGljaCBpcyBzeW5jaHJvbml6ZWQgd2l0aCB0aGUgVlRLIHJlbmRlcmluZyBvbiB0aGUgY2xpZW50IHNpZGUiICJWVEsiICIjVlRLIgoJCQkJCXNjZW5lX2dyYXBoICA9IGNvbXBvbmVudCAiU2NlbmUgZ3JhcGgiICJTY2VuZSBncmFwaCBmb3Igc3VwcG9ydGluZyB2aXN1bGl6YXRpb24gb2Ygb2JqZWN0IGhpZXJhcmNoaWVzIGFuZCBzY2VuZSBhdHRyaWJ1dGVzIGJldHdlZW4gdGhlIGNsaWVudCBhbmQgdGhlIHNlcnZlciBzaWRlIiAiUHl0aG9uLCBWVEsiICIjUHl0aG9uLCNWVEsiCgkJCQkJdGhlaWFfbG9nbW9uaXRvciA9IGNvbXBvbmVudCAiVGhlaWEgTG9nZ2VyIGFuZCBNb25pdG9yIG9mIHRoZSBhcHBsaWNhdGlvbiwgc2VydmVycyBhbmQgc2VydmljZXMiICJUaGVpYSBsb2dnZXIgYW5kIG1vbml0b3IgaXMgdGhlIHBhcnQgb2YgdGhlIFRoZWlhIGFwcGxpY2F0aW9uIHdoaWNoIGltcGxlbWVudHMgdGhlIE9wZW5UZWxlbWV0cnkgc3RhbmRhcmRzIGZvciBUaGVpYSIgIlB5dGhvbixPcGVuVGVsZW1ldHJ5IiAiI1B5dGhvbiwjT3BlblRlbGVtZXRyeSIKCQkJCX0KCQkJCXRoZWlhX3NlcnZlci50aGVpYV9odHRwX2FwaSAtPiB0aGVpYV9hcHAudGhlaWFfYXBpICJQcm92aWRlcyBhbiBIVFRQIEFQSSBmb3IgY29udHJvbGxpbmcgdGhlIGxpZmVjeWNsZSBvZiB0aGUgc3RhcnRpbmcsIHN0b3BwaW5nIG9yIHVwZGF0aW5nIHRoZSBUcmFtZSBzZXJ2ZXIgYW5kIHRoZSB3ZWJzb2NrZXQgY29ubmVjdGlvbi4iICIiICIjb3BlbmFwaSIKCQkJCXRoZWlhX2FwcCAtPiB0aGVpYV9zZXJ2ZXIudGhlaWFfaHR0cF9hcGkgIlByb3ZpZGVzIGFuIEhUVFAgQVBJIGZvciBjb250cm9sbGluZyB0aGUgbGlmZWN5Y2xlIG9mIHRoZSBzdGFydGluZywgc3RvcHBpbmcgb3IgdXBkYXRpbmcgdGhlIFRyYW1lIHNlcnZlciBhbmQgdGhlIHdlYnNvY2tldCBjb25uZWN0aW9uLiIgIiIgIiNvcGVuYXBpIgoJCQkJCgkJCQl0aGVpYV9kYXNoLnRoZWlhX2Rhc2hfdWkgLT4gdGhlaWEudGhlaWFfZGFzaC50aGVpYV9qc19saWJyYXJ5ICJUcmlnZ2VycyBmdW5jdGlvbmFsaXR5IGZyb20gdGhlIERhc2ggY2xpZW50IHRvIHRoZSBUaGVpYSBjbGllbnQiICIiICIjUmVhY3QsI1R5cGVzY3JpcHQsI0phdmFTY3JpcHQiCgkJCQl0aGVpYV9hcHAudHJhbWVfYXBwbGljYXRpb24gLT4gdGhlaWFfYXBwLnZ0a19waXBlbGluZSAiU2V0cyB1cCB0aGUgVlRLIHBpcGVsaW5lIGZvciB0aGUgc2VydmVyIHNpZGUgYW5kIHN5bmNocm9uaXplcyB3aXRoIHRoZSBjbGllbnQgc2lkZSIKCQkJCXRoZWlhX2FwcC50cmFtZV9hcHBsaWNhdGlvbiAtPiB0aGVpYV9hcHAuc2NlbmVfZ3JhcGggIlNldHMgdXAgdGhlIHNjZW5lIGdyYXBoIGZvciB0aGUgc2VydmVyIHNpZGUiCgkJCQl0aGVpYS50aGVpYV9hcHAudGhlaWFfbG9nbW9uaXRvciAtPiB0aGVpYS50aGVpYV9hcHAudHJhbWVfYXBwbGljYXRpb24gIk1vbml0b3JzIHRoZSBhcHBsaWNhdGlvbiBhbmQgc2VydmVyIgoJCQkJdGhlaWEudGhlaWFfYXBwLnRoZWlhX2FwaSAtPiB0aGVpYS50aGVpYV9hcHAudHJhbWVfYXBwbGljYXRpb24gIk1hbmFnZXMgdGhlIFRyYW1lIGFwcGxpY2F0aW9uIGNsaWVudCBhbmQgc2VydmVyIHNpZGUsIGFsb25nIHdpdGggdGhlIFZUSyBwaXBlbGluZSwgc2NlbmUgbWFuYWdlbWVudCBhbmQgaW5wdXQgbWFuYWdlbWVudC4iCgkJCQl0aGVpYV9hcHAuc2NlbmVfZ3JhcGggLT4gdGhlaWFfY2xpZW50LnRoZWlhX3NjZW5lX2NvbXBvbmVudCAiVXBkYXRlcyB2aWV3IGFuZCBzZW5kcyBldmVudHMgdG8gVUkgZWxlbWVudHMiCgkJCQl0aGVpYS50aGVpYV9kYXNoLnRoZWlhX2NsaWVudF9hcGkgLT4gdGhlaWEudGhlaWFfY2xpZW50LnRoZWlhX3RyYW1lX2Z1bmN0aW9uYWxpdHlfYXBpICJUcmlnZ2VycyBmdW5jdGlvbmFsaXR5IGZyb20gdGhlIERhc2ggY2xpZW50IHRvIHRoZSBUaGVpYSBjbGllbnQgbGlicmFyeSB3aGljaCBpcyBlaXRoZXIgYSB3ZWIgVUkgZnVuY3Rpb25hbGl0eSwgb3IgYSBUcmFtZSBWVEsuV0FTTSBmdW5jdGlvbmFsaXR5IHN5bmNocm9uaXplZCB3aXRoIHRoZSBzZXJ2ZXIgYW5kL29yIGZ1bmN0aW9uYWxpdHkgb24gdGhlIHNjZW5lIGNvbXBvbmVudCIKCQkJCXRoZWlhLnRoZWlhX2Rhc2gudGhlaWFfZGFzaF9hcGkgLT4gdGhlaWEudGhlaWFfY2xpZW50LnRoZWlhX3NjZW5lX2NvbXBvbmVudCAiVHJpZ2dlcnMgdmlzdWFsaXphdGlvbiB1cGRhdGVzIgoJCQkJdGhlaWEudGhlaWFfZGFzaC50aGVpYV9kYXNoX2FwaSAtPiB0aGVpYS50aGVpYV9jbGllbnQudGhlaWFfdHJhbWVfZnVuY3Rpb25hbGl0eV9hcGkgIlRyaWdnZXJzIGZ1bmN0aW9uYWxpdHkgZnJvbSB0aGUgRGFzaCBjbGllbnQgdG8gdGhlIFRoZWlhIGNsaWVudCBsaWJyYXJ5IHV0aWxpemluZyBUcmFtZSBWVEsuV0FTTSBmdW5jdGlvbmFsaXR5IG9uIHRoZSBjbGllbnQgb3Igc2VydmVyIHNpZGUuIgoJCQkJdGhlaWFfc2VydmVyLnRoZWlhX3NlcnZlcl9jb21wb25lbnQgLT4gdGhlaWFfc2VydmVyLnRyYW1lX3NlcnZlciAiTGlmZWN5Y2xlIG1hbmFnZW1lbnQgb2YgdGhlIFRyYW1lIHNlcnZlciIKCQkJCXRoZWlhX3NlcnZlci50cmFtZV9zZXJ2ZXIgLT4gdHJhbWVfdnRrX2xvY2FsX2NvbnRhaW5lci50cmFtZV93YXNtX2hhbmRsZXIgIlNlbmRzIHNjZW5lIHVwZGF0ZXMiCgkJCQl0aGVpYS50aGVpYV9jbGllbnQudGhlaWFfdHJhbWVfZnVuY3Rpb25hbGl0eV9hcGkgLT4gdGhlaWFfc2VydmVyLnRyYW1lX3NlcnZlciAiVHJpZ2dlciBWVEsgdXBkYXRlcyIKCQkJCXRoZWlhLnRyYW1lX3Z0a19sb2NhbF9jb250YWluZXIudHJhbWVfd2FzbV9oYW5kbGVyIC0+IHRoZWlhX3NlcnZlci50cmFtZV9zZXJ2ZXIgIlRyaWdnZXJzIFZUSyB1cGRhdGVzIgoJCQkJdGhlaWEudHJhbWVfdnRrX2xvY2FsX2NvbnRhaW5lci50cmFtZV93c2xpbmtfY29ubmVjdGlvbiAtPiB0aGVpYV9zZXJ2ZXIudHJhbWVfc2VydmVyICJDb25uZWN0cyB0byBydW5uaW5nIHdzbGluayBzZXNzaW9uIHRvIHNldHVwIGEgd2Vic29ja2V0IGNvbm5lY3Rpb24uIgoJCQkJdGhlaWFfc2VydmVyLnRoZWlhX2h0dHBfYXBpIC0+IHRoZWlhX3NlcnZlci50aGVpYV9zZXJ2ZXJfY29tcG9uZW50ICJDb250cm9scyBzZXJ2ZXIgc3RhcnQsIHN0b3AgYW5kIHN0YXRlIHVwZGF0ZXMgYXMgd2VsbCBhcyBtb25pdG9yaW5nIHRhc2tzLiIKCQkJCQoJCQkJCgkJCQllbmRfdXNlciAtPiB0aGVpYS50aGVpYV9kYXNoLnRoZWlhX2Rhc2hfdWkgIlRyaWdnZXJzIDNEIG1vZGVsIHZpZXcgdXBkYXRlcyIgIiIgIiIKCQkJCWVuZF91c2VyIC0+IHRoZWlhLnRoZWlhX2Rhc2gudGhlaWFfZGFzaF9hcGkgIlRyaWdnZXJzIDNEIG1vZGVsIHZpZXcgdXBkYXRlcyIgIiIgIiIKCQkJCXRoZWlhLnRoZWlhX2NsaWVudCAtPiBlbmRfdXNlciAiVmlzdWFsaXphdGlvbiBvZiAzRCBtb2RlbCBkYXRhIiAiIiAiIgoJCQkJdGhlaWEudGhlaWFfY2xpZW50IC0+IGVuZF91c2VyICJVcGRhdGVzIDNEIG1vZGVsIHZpZXciICIiICIiCgkJCQl0aGVpYS50aGVpYV9jbGllbnQgLT4gdGhlaWEudGhlaWFfc2VydmVyICJSZXF1ZXN0cyBtb2RlbCBkYXRhIgoJCQkJdGhlaWEudGhlaWFfc2VydmVyIC0+IHRoZWlhLnRoZWlhX2NsaWVudCAiU2VuZHMgbW9kZWwgZGF0YSIKCQkJfQoJCQkKCQkJcG9ydGFsID0gc29mdHdhcmVTeXN0ZW0gIlNBRiBQb3J0YWwiICJlbmFibGVzIHRoZSB1c2VyIHRvIGNyZWF0ZSBuZXcgcHJvamVjdCBvciBzZWxlY3QgZXhpc3RpbmcgcHJvamVjdCB0aGVuIGxhdW5jaCBzb2x1dGlvbiBVSSBmb3IgcHJvamVjdC4gIERvZXMgbm90IGhhdmUgcmVzcG9uc2liaWxpdHkgZm9yIGltcGxlbWVudGF0aW9uIG9mIGFueSBhc3BlY3Qgb2YgdGhlIHNvbHV0aW9uIGJ1c2luZXNzIGxvZ2ljIG9yIHRoZSBzZXJ2aWNlcyBjb25zdW1lZCBieSB0aGUgc29sdXRpb24uIiAiIiB7CgkJCQlwb3J0YWxfc2VydmVyID0gY29udGFpbmVyICAiUG9ydGFsIFNlcnZlciIgImltcGxlbWVudHMgYSBSRVNUIEFQSSB0aGF0IGlzIGNvbnN1bWVkIGJ5IHRoZSBQb3J0YWwgVUkuICBUaGUgcG9ydGFsIHNlcnZlciBjb25zdW1lcyBhIHNtYWxsIHN1YnNldCBvZiB0aGUgQVBJIHByb3ZpZGVkIGJ5IHRoZSBHTE9XIEFQSSBTZXJ2ZXIiICJGYXN0QVBJIiAiI2Zhc3RhcGkiCgkJCQl1aSA9IGNvbnRhaW5lciAiUG9ydGFsIFVzZXIgSW50ZXJmYWNlIiAicHJvdmlkZXMgYSB2aWV3IG9mIHRoZSBwcm9qZWN0cyBpbiB0aGUgcHJvamVjdHMgZGlyZWN0b3J5LiAgZW5hYmxlcyB0aGUgdXNlciB0byBjcmVhdGUgb3Igc2VsZWN0IGEgcHJvamVjdCB0aGVuIGxhdW5jaCBzb2x1dGlvbiBVSSBmb3IgdGhlIHByb2plY3QiICJSZWFjdCIKCQkJfQoJCQkKCQkJcHJvZHVjdF9pbnN0YW5jZV9tYW5hZ2VyID0gc29mdHdhcmVTeXN0ZW0gIlByb2R1Y3QgSW5zdGFuY2UgTWFuYWdlciIgImVuYWJsZXMgdGhlIHN0YXJ0dXAgYW5kIHRlcm1pbmF0aW9uIG9mIEFuc3lzIEZsYWdzaGlwIFByb2R1Y3RzIG9yIG90aGVyIHN0YXRlZnVsIHByb2Nlc3NlcyIgIiIgewoJCQkJdXJsIGh0dHBzOi8vdGZzLmFuc3lzLmNvbTo4NDQzL3Rmcy9BTlNZU19EZXZlbG9wbWVudC9FeHRlbnNpYmlsaXR5L19naXQvUm9vdD9wYXRoPSUyRmFuc3lzJTJGaW5zdGFuY2VtYW5hZ2VtZW50JTJGbGlnaHQKCQkJfQoJCQkKCQkJcHJvZHVjdCA9IHNvZnR3YXJlU3lzdGVtICJBbnN5cyBGbGFnc2hpcCBQcm9kdWN0IiAiQSBzdGF0ZWZ1bCBwcm9jZXNzIHRoYXQgaXMgcmVxdWlyZWQgdG8gaW1wbGVtZW50IGEgR0xPVyB0cmFuc2FjdGlvbiBtZXRob2QgKHR5cGljYWxseSBhbiBBbnN5cyBGbGFnc2hpcCBwcm9kdWN0IHdoaWNoIGNvbnRhaW5zIGEgc2ltdWxhdGlvbiBzb2x2ZXIgZGVzaWduZWQgdG8gYmUgYSBkZXNrdG9wIGFwcGxpY2F0aW9uKSIgIiNleHRlcm5hbCIKCQkJCgkJCWdsb3cgPSBzb2Z0d2FyZVN5c3RlbSAiR3VpZGVkIExvdyBDb2RlIFdvcmtmbG93IChHTE9XKSIgImZyYW1ld29yayBmb3IgdmVydGljYWwgYXBwbGljYXRpb25zIG9yaWVudGF0ZWQgdG93YXJkcyBhIGd1aWRlZCB3b3JrZmxvdyB1c2VyIGV4cGVyaWVuY2UiICB7CgkJCQl1cmwgaHR0cHM6Ly9naXRodWIuY29tL2Fuc3lzLWludGVybmFsL2dsb3ctZW5naW5lCgkJCQlkYXNoX3VpID0gY29udGFpbmVyICJTb2x1dGlvbiBEYXNoIFVJIiAiQSBicm93c2VyIGJhc2VkIGNsaWVudCBmb3IgdGhlIERhc2ggc2VydmVyIGltcGxlbWVudGVkIGluIFJlYWN0IEphdmFzY3JpcHQgdGhhdCByZW5kZXJzIHRoZSBVSSBkZWZpbmVkIGJ5IHRoZSBEYXNoIHNlcnZlciIgIlJlYWN0IgoJCQkJCgkJCQlncm91cCAiQVBJIiB7CgkJCQkJCgkJCQkJcHJvamVjdHNfZGlyZWN0b3J5ID0gY29udGFpbmVyICJQcm9qZWN0cyBEaXJlY3RvcnkiICJ0aGUgZmlsZSBzeXN0ZW0gZGlyZWN0b3J5IGNvbnRhaW5pbmcgcHJvamVjdCBmaWxlcyIKCQkJCQlhcGkgPSBjb250YWluZXIgIkFQSSBTZXJ2ZXIiICJQcm92aWRlcyBhIFJFU1QgQVBJIHNwZWNpZmljIHRvIGEgZ2l2ZW4gc29sdXRpb24sIHdoaWNoIGlzIGNvbnN1bWVkIGJ5IHRoZSBzb2x1dGlvbiBVSSBzZXJ2ZXIuIgoJCQkJCXByb2plY3RzX2RhdGFiYXNlID0gY29udGFpbmVyICJQcm9qZWN0cyBEYXRhYmFzZSIgInN0b3JlcyBpbnN0YW5jZXMgb2YgdGhlIHNvbHV0aW9uIHNjaGVtYSIKCQkJCQkKCQkJCX0KCQkJCQoJCQkJZGFzaCA9IGNvbnRhaW5lciAiRGFzaCBTZXJ2ZXIiICJhIEZsYXNrIHNlcnZlciB0aGF0IHNlcnZpY2VzIGEgUmVhY3QgYnJvd3NlciBiYXNlZCBVSSBkZWZpbmVkIHVzaW5nIHRoZSBEYXNoIFVJIGRlZmluaXRpb24gQVBJIiAiRmxhc2siICIjRmxhc2siIHsKCQkJCQlkYXNoX2ZsYXNrX3NlcnZlciA9IGNvbXBvbmVudCAiRGFzaCBGbGFzayBTZXJ2ZXIiICJhIEZsYXNrIHNlcnZlciB0aGF0IHNlcnZpY2VzIGEgUmVhY3QgYnJvd3NlciBiYXNlZCBVSSBkZWZpbmVkIHVzaW5nIHRoZSBEYXNoIFVJIGRlZmluaXRpb24gQVBJIiAiRmxhc2siIHsKCQkJCQkJdXJsIGh0dHBzOi8vZGFzaC5wbG90bHkuY29tLwoJCQkJCX0KCQkJCQlzb2x1dGlvbl91aSA9IGNvbXBvbmVudCAiU29sdXRpb24gVUkiICJhIHB5dGhvbiBwYWNrYWdlIHdoaWNoIGRlZmluZXMgaG93IHRoZSBzb2x1dGlvbiBpcyByZW5kZXJlZCB2aWEgdGhlIERhc2ggVUkgZGVmaW5pdGlvbiBBUEkiICJQeXRob24iICIiCgkJCQkJY2xpZW50X2FwaSA9IGNvbXBvbmVudCAiQ2xpZW50IEFQSSIgImEgcHl0aG9uIHBhY2thZ2UgdGhhdCBwcm92aWRlcyBhIHB5dGhvbmljIGludGVyZmFjZSB0byBhIEdMT1cgQVBJIHNlcnZlciB2aWEgUkVTVCIgIlB5dGhvbiJ7CgkJCQkJCXVybCBodHRwczovL2dpdGh1Yi5jb20vYW5zeXMtaW50ZXJuYWwvZ2xvdy1lbmdpbmUvdHJlZS9tYWluL3NyYy9hbnN5cy9zYWYvZ2xvdy9jbGllbnQKCQkJCQl9CgkJCQkJc29sdXRpb25fZGVmaW5pdGlvbl9hcGkgPSBjb21wb25lbnQgIkdMT1cgU29sdXRpb24gZGVmaW5pdGlvbiBBUEkiICJhIHB5dGhvbiBwYWNrYWdlIHRoYXQgY29udGFpbnMgdGhlIHNldCBvZiBweXRob24gdHlwZXMgcmVxdWlyZWQgdG8gZGVmaW5lIGEgR0xPVyBzb2x1dGlvbiIgIlB5dGhvbiIgewoJCQkJCQl1cmwgaHR0cHM6Ly9naXRodWIuY29tL2Fuc3lzLWludGVybmFsL2dsb3ctZW5naW5lL3RyZWUvbWFpbi9zcmMvYW5zeXMvc2FmL2dsb3cvc29sdXRpb24KCQkJCQl9CgkJCQkJc29sdXRpb24gPSBjb21wb25lbnQgIlNvbHV0aW9uIGRlZmluaXRpb24iICJ0aGUgZGVmaW5pdGlvbiBvZiBhIHNvbHV0aW9uJ3Mgc2NoZW1hIGFuZCBidXNpbmVzcyBsb2dpYyIgIlB5dGhvbiIgIiIKCQkJCQkKCQkJCQlzb2x1dGlvbl91aSAtPiBkYXNoX2ZsYXNrX3NlcnZlciAiaW52b2tlIHJlbmRlcmluZyBwcm92aWRpbmcgVUkgc3RydWN0dXJlIGFuZCBjYWxsYmFja3MiICIiICIjaW1wb3J0IgoJCQkJCXNvbHV0aW9uX3VpIC0+IGNsaWVudF9hcGkgImdldHMgYW5kIHNldHMgZGF0YTsgYW5kIGludm9rZXMgbWV0aG9kcyB2aWEgcHJveHkgb2JqZWN0cyIgIiIgIiNmdW5jdGlvbiIKCQkJCQljbGllbnRfYXBpIC0+IHNvbHV0aW9uICJvYnRhaW5zIHNjaGVtYSBhbmQgbWV0aG9kIHNldCIgIiIgIiNpbXBvcnQiCgkJCQkJc29sdXRpb24gLT4gc29sdXRpb25fZGVmaW5pdGlvbl9hcGkgIm9idGFpbnMgYmFzZSB0eXBlcyBmb3Igc29sdXRpb24gZGVmaW5pdGlvbiIgIiIgIiNpbXBvcnQiCgkJCQkJY2xpZW50X2FwaSAtPiBnbG93LmFwaSAiY2FsbHMiICJSRVNUIiAiI1JFU1QiCgkJCQl9CgkJCQkKCQkJCW1ldGhvZF9wcm9jZXNzID0gY29udGFpbmVyICJNZXRob2QgRXhlY3V0aW9uIFByb2Nlc3MiICJBbiBPUyBwcm9jZXNzIHRoYXQgaW1wbGVtZW50cyBhIHNpbmdsZSBjYWxsIHRvIGEgdHJhbnNhY3Rpb24gbWV0aG9kIiAiUHl0aG9uIiAiIiB7CgkJCQkJbWV0aG9kX3J1bm5lciA9IGNvbXBvbmVudCAiTWV0aG9kIFJ1bm5lciIgImltcGxlbWVudHMgYSBzaW5nbGUgY2FsbCB0byBhIHRyYW5zYWN0aW9uIG1ldGhvZCIgIlB5dGhvbiIgewoJCQkJCQl1cmwgaHR0cHM6Ly9naXRodWIuY29tL2Fuc3lzLWludGVybmFsL2dsb3ctZW5naW5lL2Jsb2IvbWFpbi9zcmMvYW5zeXMvc2FmL2dsb3cvX2V4ZWN1dG9yL21ldGhvZF9ydW5uZXIucHkjTDQxCgkJCQkJfQoJCQkJCXNvbHV0aW9uX2RlZmluaXRpb25fYXBpID0gY29tcG9uZW50ICJTb2x1dGlvbiBkZWZpbml0aW9uIEFQSSIgImEgcHl0aG9uIHBhY2thZ2UgdGhhdCBjb250YWlucyB0aGUgc2V0IG9mIHB5dGhvbiB0eXBlcyByZXF1aXJlZCB0byBkZWZpbmUgYSBHTE9XIHNvbHV0aW9uIiAiUHl0aG9uInsKCQkJCQkJdXJsIGh0dHBzOi8vZ2l0aHViLmNvbS9hbnN5cy1pbnRlcm5hbC9nbG93LWVuZ2luZS90cmVlL21haW4vc3JjL2Fuc3lzL3NhZi9nbG93L3NvbHV0aW9uCgkJCQkJfQoJCQkJCXNvbHV0aW9uID0gY29tcG9uZW50ICJTb2x1dGlvbiBkZWZpbml0aW9uIiAidGhlIGRlZmluaXRpb24gb2YgYSBzb2x1dGlvbidzIHNjaGVtYSBhbmQgYnVzaW5lc3MgbG9naWMiICJQeXRob24iICIiCgkJCQkJbWV0aG9kX3J1bm5lciAtPiBzb2x1dGlvbiAiZXhlY3V0ZXMgbWV0aG9kIiAiIiAiI2Z1bmN0aW9uIgoJCQkJCXNvbHV0aW9uIC0+IHNvbHV0aW9uX2RlZmluaXRpb25fYXBpICJvYnRhaW5zIGJhc2UgdHlwZXMgZm9yIHNvbHV0aW9uIGRlZmluaXRpb24iICIiICIjaW1wb3J0IgoJCQkJfQoJCQkJCgkJCQkKCQkJCW1ldGhvZF9maWxlX3NwYWNlID0gY29udGFpbmVyICJNZXRob2QgZmlsZSBzcGFjZSIgInRoZSB0ZW1wb3JhcnkgZGlyZWN0b3J5IHVzZWQgYnkgYSBtZXRob2QgZXhlY3V0aW9uIHByb2Nlc3MgdGhhdCBleGlzdHMganVzdCBmb3IgdGhlIGR1cmF0aW9uIG9mIHRoZSBwcm9jZXNzLiIgImZpbGUgc2R5c3RlbSBkaXJlY3RvcnkiCgkJCQlwcm9kdWN0X2luc3RhbmNlX2ZpbGVfc3BhY2UgPSBjb250YWluZXIgIlByb2R1Y3QgSW5zdGFuY2UgZmlsZSBzcGFjZSIgInRoZSBPUyBkaXJlY3RvcnkgYXNzb2NpYXRlZCB3aXRoIGEgcHJvZHVjdCBpbnN0YW5jZSIgImZpbGUgc3lzdGVtIGRpcmVjdG9yeSIgIiNmaWxlIgoJCQkJCgkJCQlhcGkgLT4gbWV0aG9kX3Byb2Nlc3MgInN0YXJ0cyAmIHN0b3BzIiAiIiAiI3Byb2Nlc3MiCgkJCQltZXRob2RfcHJvY2VzcyAtPiBwcm9kdWN0ICJleGVjdXRlcyBtZXRob2QgY29kZSIgImdSUEMiICIjZ1JQQyIKCQkJCW1ldGhvZF9wcm9jZXNzIC0+IHByb2R1Y3RfaW5zdGFuY2VfbWFuYWdlciAicXVlcmllcyBwcm9kdWN0IGNvbm5lY3Rpb24sIHJlcXVlc3RzIHByb2R1Y3Qgc3RhcnQgJiB0ZXJtaW5hdGlvbiIgImdSUEMiICIjZ1JQQyIKCQkJCW1ldGhvZF9wcm9jZXNzIC0+IGFwaSAiY2FsbHMiICJSRVNUIiAiI1JFU1QiCgkJCQltZXRob2RfcHJvY2VzcyAtPiBtZXRob2RfZmlsZV9zcGFjZSAiY3JlYXRlcyAmIGRlbGV0ZXMiICIiICIjZmlsZSIKCQkJCW1ldGhvZF9wcm9jZXNzIC0+IHByb2R1Y3RfaW5zdGFuY2VfZmlsZV9zcGFjZSAiY3JlYXRlcyAmIGRlbGV0ZXMiICIiICIjZmlsZSIKCQkJCQoJCQkJYXBpIC0+IHByb2R1Y3RfaW5zdGFuY2VfbWFuYWdlciAicmVxdWVzdHMgcHJvZHVjdCB0ZXJtaW5hdGlvbiAob24gc2h1dGRvd24pIiAiZ1JQQyIgIiNnUlBDIgoJCQkJYXBpIC0+IHByb2plY3RzX2RhdGFiYXNlICJnZXRzLCBtb2RpZmllcyAmIGNyZWF0ZXMgcmVjb3JkcyBpbiIgIiIgIiNmaWxlIgoJCQkJYXBpIC0+IHByb2plY3RzX2RpcmVjdG9yeSAicmVhZHMgYW5kIHdyaXRlcyBwcm9qZWN0IGZpbGVzIiAiIiAiI2ZpbGUiCgkJCQkKCQkJCXByb2R1Y3RfaW5zdGFuY2VfbWFuYWdlciAtPiBwcm9kdWN0ICJzdGFydHMgJiBraWxscyIgIiIgIiNwcm9jZXNzIgoJCQkJcHJvZHVjdF9pbnN0YW5jZV9tYW5hZ2VyIC0+IHRoZWlhLnRoZWlhX3NlcnZlciAiU3RhcnRzLCBzdG9wcyB2aWV3ZXIiICIiICIjcHJvY2VzcyIKCQkJCXRoZWlhLnRoZWlhX3NlcnZlciAtPiBtZXRob2RfZmlsZV9zcGFjZSAicmVhZHMgbW9kZWwgZGF0YSIgIiIgIiNmaWxlIgoJCQkJdGhlaWEudGhlaWFfc2VydmVyIC0+IHByb2R1Y3RfaW5zdGFuY2VfZmlsZV9zcGFjZSAicmVhZHMgbW9kZWwgZGF0YSIgIiIgIiNmaWxlIgoJCQkJcHJvZHVjdCAtPiBtZXRob2RfZmlsZV9zcGFjZSAid3JpdGVzIDNEIG1vZGVsIiAiIiAiI2ZpbGUiCgkJCQlwcm9kdWN0IC0+IHByb2R1Y3RfaW5zdGFuY2VfZmlsZV9zcGFjZSAid3JpdGVzIDNEIG1vZGVsIiAiIiAiI2ZpbGUiCgkJCQkKCQkJCWRhc2hfdWkgLT4gZGFzaCAib2J0YWlucyBjb2RlIGFuZCBzdGF0ZTsgc2lnbmFscyB1c2VyIGludGVyZmFjZSBldmVudHMiICJSRVNUIiAiI1JFU1QiCgkJCQlkYXNoIC0+IGFwaSAiQ2FsbCIgIlJFU1QiICIjUkVTVCIKCQkJCWRhc2hfdWkgLT4gdGhlaWEudGhlaWFfZGFzaC50aGVpYV9kYXNoX3VpICJTaWduYWxzIHVzZXIgaW50ZXJmYWNlIGV2ZW50cyIgIiIgIiIKCQkJCWRhc2hfdWkgLT4gdGhlaWEudGhlaWFfZGFzaC50aGVpYV9kYXNoX2FwaSAiVHJpZ2dlcnMgdmlzdWFsIGV2ZW50cyBhbmQgcmVxdWVzdHMgZGF0YSBmcm9tIHRoZSBUaGVpYSAzRCB2aWV3ZXIiICIiICIiCgkJCQl0aGVpYS50aGVpYV9kYXNoLnRoZWlhX2Rhc2hfYXBpIC0+IGRhc2hfdWkgIlNlbmRzIGRhdGEgdG8gdGhlIERhc2ggVUkgYW5kIHN0YXR1cyByZXNwb25zZXMgYmFzZWQgb24gdXNlciBpbnRlcmFjdGlvbiIKCQkJCQoJCQkJcG9ydGFsIC0+IGFwaSAiQ2FsbCIgIlJFU1QiICIjUkVTVCIKCQkJCQoJCQkJbWV0aG9kX3Byb2Nlc3MgLT4gYXBpICJ1cGxvYWRzIGFuZCBkb3dubG9hZHMgZmllbGRzIiAiUkVTVCIgIiNSRVNUIgoJCQkJbWV0aG9kX3Byb2Nlc3MgLT4gbWV0aG9kX2ZpbGVfc3BhY2UgImNyZWF0ZXMgYW5kIGRlbGV0ZXMiICIiICIjZmlsZSIKCQkJCW1ldGhvZF9wcm9jZXNzIC0+IHByb2R1Y3RfaW5zdGFuY2VfZmlsZV9zcGFjZSAiY3JlYXRlcyBhbmQgZGVsZXRlcyIgIiIgIiNmaWxlIgoJCQkJCgkJCQlhcGkgLT4gbWV0aG9kX3Byb2Nlc3MgInN0YXJ0cyIgIiIgIiNwcm9jZXNzIgoJCQkJCgkJCQllbmRfdXNlciAtPiBkYXNoX3VpICJ1c2VzIiAiIiAiI3VzZXIiCgkJCQkKCQkJCQoJCQkJZW5kX3VzZXIgLT4gcG9ydGFsICJ1c2VzIiAiIiAiI3VzZXIiCgkJCQlwb3J0YWwgLT4gZ2xvdyAibGF1bmNoZXMgVUkgZm9yIGV4aXN0aW5nIG9yIG5ldyBwcm9qZWN0IgoJCQkJZ2xvdyAtPiBwcm9kdWN0X2luc3RhbmNlX21hbmFnZXIgInJlcXVlc3RzIHN0YXJ0IGFuZCB0ZXJtaW5hdGlvbiBvZiBwcm9kdWN0IGluc3RhbmNlcyIgImdSUEMiICIjZ1JQQyIKCQkJCXByb2R1Y3RfaW5zdGFuY2VfbWFuYWdlciAtPiB0aGVpYSAibGF1bmNoZXMgVGhlaWEgdmlzdWFsaXphdGlvbiIgIiIgIiIKCQkJCWdsb3cgLT4gdGhlaWEgImxhdW5jaGVzIFRoZWlhIHZpc3VhbGl6YXRpb24iICIiICIiCgkJCQlwb3J0YWwgLT4gZ2xvdy5kYXNoX3VpICJsaW5rcyB0byIgIkphdmFTY3JpcHQgQ2xpY2sgSGFuZGxlciIgIiNsaW5rIgoJCQkJZ2xvdyAtPiBwcm9kdWN0ICJjYWxscyIgImdSUEMiICIjZ1JQQyIKCQkJCXByb2R1Y3Rfd3JpdGVzX3N0YXRlID0gcHJvZHVjdCAtPiBnbG93LnByb2plY3RzX2RpcmVjdG9yeSAicmVhZHMgJiB3cml0ZXMgcHJvZHVjdCBzdGF0ZSIgIiIgIiNmaWxlIgoJCQkJZW5kX3VzZXIgLT4gdGhlaWEgICJWaWV3cyBhbmQgaW50ZXJhY3RzIHdpdGggdGhlIDNEIG1vZGVsIgoJCQl9CgkJfQoJCQoJCWVuZF91c2VyX3dpbmRvd3NfcGNfID0gZGVwbG95bWVudEVudmlyb25tZW50ICJFbmQgVXNlciBXaW5kb3dzIERlc2t0b3AgUEMiIHsKCQkJZGVwbG95bWVudE5vZGUgIlB5dGhvbiBJbnRlcnByZXRlciIgInRoZSBweXRob24gaW50ZXJwcmV0ZXIgdGhhdCBydW5zIHRoZSBHTE9XIHNvbHV0aW9uIiAiUHl0aG9uIiAiIiAxIHsKCQkJCWRlcGxveW1lbnROb2RlICJPcmNoZXN0cmF0b3IiICJ0aGUgcHl0aG9uIG1vZHVsZSB0aGF0IHN0YXJ0cyBhbmQgc2h1dHNkb3duIHRoZSBHTE9XIHNvbHV0aW9uIiAiUHl0aG9uIiAiIiAxIHsKCQkJCQlhcGlfID0gY29udGFpbmVySW5zdGFuY2UgZ2xvdy5hcGkgIiIgewoJCQkJCX0KCQkJCQlkYXNoXyA9IGNvbnRhaW5lckluc3RhbmNlIGdsb3cuZGFzaCAiIiB7CgkJCQkJfQoJCQkJCW1ldGhvZF9wcm9jZXNzXyA9IGNvbnRhaW5lckluc3RhbmNlIGdsb3cubWV0aG9kX3Byb2Nlc3MgIiIgewoJCQkJCX0KCQkJCQlwb3J0YWxfc2VydmVyXyA9IGNvbnRhaW5lckluc3RhbmNlIHBvcnRhbC5wb3J0YWxfc2VydmVyICIiIHsKCQkJCQl9CgkJCQkJcHJvZHVjdF9pbnN0YW5jZV9tYW5hZ2VyXyA9IHNvZnR3YXJlU3lzdGVtSW5zdGFuY2UgcHJvZHVjdF9pbnN0YW5jZV9tYW5hZ2VyICIiIHsKCQkJCQl9CgkJCQl9CgkJCQlkZXBsb3ltZW50Tm9kZSAicHl3ZWJ2aWV3IiAiYSBweXRob24gYW5kIGJyb3dzZXIgYmFzZWQgZW5naW5lIGZvciByZW5kZXJpbmcgd2ViIFVJcyBhcyBkZXNrdG9wIGFwcGxpY2F0aW9uIHdpbmRvd3MiICJQeXRob24iICIiIDEgewoJCQkJCWRhc2hfdWlfID0gY29udGFpbmVySW5zdGFuY2UgZ2xvdy5kYXNoX3VpICIiIHsKCQkJCQl9CgkJCQkJcG9ydGFsX3VpXyA9IGNvbnRhaW5lckluc3RhbmNlIHBvcnRhbC51aSAiIiB7CgkJCQkJfQoJCQkJCXRoZWlhX3VpXyA9IGNvbnRhaW5lckluc3RhbmNlIHRoZWlhLnRoZWlhX2NsaWVudCAiIiAiIgoJCQkJfQoJCQl9CgkJCWRlcGxveW1lbnROb2RlICJGaWxlIFN5c3RlbSIgInRoZSBmaWxlIHN5c3RlbSBvZiBhIHNpbmdsZSBXaW5kb3dzIERlc2t0b3AgUEMiICJXaW5kb3dzIiAxIHsKCQkJCWRlcGxveW1lbnROb2RlICJVc2VyIERvY3VtZW50cyBEaXJlY3RvcnkiICJ0aGUgRG9jdW1lbnRzIGRpcmVjdG9yeSBvZiB0aGUgZW5kIHVzZXIiICJXaW5kb3dzIiAgMSB7CgkJCQkJcHJvamVjdHNfZGlyZWN0b3J5XyA9IGNvbnRhaW5lckluc3RhbmNlIGdsb3cucHJvamVjdHNfZGlyZWN0b3J5CgkJCQkJcHJvZHVjdF9pbnN0YW5jZV9maWxlX3NwYWNlXyA9IGNvbnRhaW5lckluc3RhbmNlIGdsb3cucHJvZHVjdF9pbnN0YW5jZV9maWxlX3NwYWNlCgkJCQl9CgkJCQlkZXBsb3ltZW50Tm9kZSAiQVBQREFUQSBEaXJlY3RvcnkiICJ0aGUgQVBQREFUQSBkaXJlY3Rvcnkgb2YgdGhlIGVuZCB1c2VyIiAiV2luZG93cyIgMSB7CgkJCQkJcHJvamVjdHNfZGF0YWJhc2VfID0gY29udGFpbmVySW5zdGFuY2UgZ2xvdy5wcm9qZWN0c19kYXRhYmFzZSAiCgkJCQl9CgkJCX0KCQl9CgkJCgl9CgkKCXZpZXdzIHsKCQl0aGVtZSBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vUlZSMDYvY29ybmlmZXItY29udHJpYi9tYWluL3RoZW1lcy9zZW1hbnRpYy90aGVtZS5qc29uCgkJdGhlbWUgaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1JWUjA2L2Nvcm5pZmVyLWNvbnRyaWIvbWFpbi90aGVtZXMvaGVyYWxkcnkvdGhlbWUuanNvbgoJCWJyYW5kaW5nIHsKCQkJbG9nbyBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vUlZSMDYvY29ybmlmZXItY29udHJpYi9tYWluL2Fzc2V0cy9hbnN5cy5wbmcKCQl9CgkJCgkJc3lzdGVtTGFuZHNjYXBlICJTeXN0ZW1MYW5kc2NhcGUiICJUaGVpYSBpbnRlZ3JhdGVkIGluIFNvbHV0aW9uIEFwcGxpY2F0aW9uIHVzaW5nIEdMT1ciIHsKCQkJaW5jbHVkZSAqCgkJCWF1dG9sYXlvdXQgbHIKCQl9CgkJCgkJc3lzdGVtQ29udGV4dCB0aGVpYSAiVGhlaWFTb2x1dGlvbkFwcGxpY2F0aW9uQ29udGV4dCIgIlRoZWlhIFNvbHV0aW9uIEFwcGxpY2F0aW9uIENvbnRleHQiIHsKCQkJaW5jbHVkZSAqCgkJCWluY2x1ZGUgZ2xvdwoJCQlpbmNsdWRlIHBvcnRhbAoJCQlhdXRvbGF5b3V0IGxyCgkJfQoJCQoJCWNvbnRhaW5lciB0aGVpYSAiVGhlaWFDb250YWluZXJzIiAiVGhlaWEgQ29udGFpbmVycyIgewoJCQlpbmNsdWRlIGVuZF91c2VyCgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfY2xpZW50CgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfc2VydmVyCgkJfQoJCQoJCXN5c3RlbUNvbnRleHQgdGhlaWEgIlRoZWlhU3lzdGVtQ29udGV4dCIgIlRoZWlhIENvbnRleHQiIHsKCQkJaW5jbHVkZSAqCgkJCWF1dG9sYXlvdXQgbHIKCQl9CgkJCgkJCgkJY29tcG9uZW50IHRoZWlhLnRoZWlhX3NlcnZlciAiVGhlaWFTZXJ2ZXJDb21wb25lbnRzIiAiVGhlaWEgU2VydmVyIENvbXBvbmVudHMiIHsKCQkJaW5jbHVkZSAqCgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfc2VydmVyLnRoZWlhX2h0dHBfYXBpCgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfc2VydmVyLnRoZWlhX3NlcnZlcl9jb21wb25lbnQKCQkJaW5jbHVkZSB0aGVpYS50aGVpYV9zZXJ2ZXIudHJhbWVfc2VydmVyCgkJCWF1dG9sYXlvdXQgbHIKCQl9CgkJCgkJY29tcG9uZW50IHRoZWlhLnRoZWlhX2Rhc2ggIlRoZWlhRGFzaENvbXBvbmVudHMiICJUaGVpYSBEYXNoIFVJIENvbXBvbmVudHMiIHsKCQkJaW5jbHVkZSAqCgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfZGFzaC50aGVpYV9kYXNoX3VpCgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfZGFzaC50aGVpYV9kYXNoX2FwaQoJCQlpbmNsdWRlIHRoZWlhLnRoZWlhX2Rhc2gudGhlaWFfY2xpZW50X2FwaQoJCQlpbmNsdWRlIHRoZWlhLnRoZWlhX2Rhc2gudGhlaWFfanNfbGlicmFyeQoJCQlhdXRvbGF5b3V0IGxyCgkJfQoJCQoJCWNvbXBvbmVudCB0aGVpYS50aGVpYV9jbGllbnQgIlRoZWlhQ2xpZW50Q29tcG9uZW50cyIgIlRoZWlhIENsaWVudCBDb21wb25lbnQiIHsKCQkJaW5jbHVkZSAqCgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfY2xpZW50LnRoZWlhX3VpX2VsZW1lbnRzCgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfY2xpZW50LnRoZWlhX3NjZW5lX2NvbXBvbmVudAoJCQlpbmNsdWRlIHRoZWlhLnRoZWlhX2NsaWVudC50aGVpYV90cmFtZV9mdW5jdGlvbmFsaXR5X2FwaQoJCQlpbmNsdWRlIHRoZWlhLnRyYW1lX3Z0a19sb2NhbF9jb250YWluZXIudHJhbWVfd2FzbV9oYW5kbGVyCgkJCWluY2x1ZGUgdGhlaWEudHJhbWVfdnRrX2xvY2FsX2NvbnRhaW5lci50cmFtZV93c2xpbmtfY29ubmVjdGlvbgoJCQlhdXRvbGF5b3V0IGxyCgkJfQoJCQoJCWNvbXBvbmVudCB0aGVpYS50aGVpYV9hcHAgIlRoZWlhQXBwQ29tcG9uZW50cyIgIlRoZWlhIEFwcGxpY2F0aW9uIENvbXBvbmVudHMiIHsKCQkJaW5jbHVkZSAqCgkJCWluY2x1ZGUgdGhlaWEudGhlaWFfYXBwLnRoZWlhX2FwaQoJCQlpbmNsdWRlIHRoZWlhLnRoZWlhX2FwcC50cmFtZV9hcHBsaWNhdGlvbgoJCQlpbmNsdWRlIHRoZWlhLnRoZWlhX2FwcC52dGtfcGlwZWxpbmUKCQkJaW5jbHVkZSB0aGVpYS50aGVpYV9hcHAuc2NlbmVfZ3JhcGgKCQkJaW5jbHVkZSB0aGVpYS50aGVpYV9hcHAudGhlaWFfbG9nbW9uaXRvcgoJCQlhdXRvbGF5b3V0IGxyCgkJfQoJCQoJCWRlcGxveW1lbnQgKiBlbmRfdXNlcl93aW5kb3dzX3BjXyAiRW5kVXNlckRlcGxveW1lbnQiICJFbmQgVXNlciBEZXBsb3ltZW50IiB7CgkJCWluY2x1ZGUgKgoJCQlhdXRvbGF5b3V0IGxyCgkJfQoJfQo=" - }, - "configuration" : { }, - "model" : { - "people" : [ { - "id" : "1", - "tags" : "Element,Person", - "properties" : { - "structurizr.dsl.identifier" : "end_user" - }, - "name" : "End User", - "description" : "A person who is using a Solution", - "relationships" : [ { - "id" : "42", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "78cf0108-dd79-464a-a883-2b8f31842cc4" - }, - "sourceId" : "1", - "destinationId" : "4", - "description" : "Triggers 3D model view updates" - }, { - "id" : "102", - "tags" : "Relationship,#user", - "properties" : { - "structurizr.dsl.identifier" : "f27f4964-64a6-45a0-bb1a-3e8b150985f6" - }, - "sourceId" : "1", - "destinationId" : "54", - "description" : "uses" - }, { - "id" : "103", - "tags" : "Relationship,#user", - "properties" : { - "structurizr.dsl.identifier" : "831ca93a-5340-4c86-bf75-ebc1ddf28603" - }, - "sourceId" : "1", - "destinationId" : "48", - "description" : "uses" - }, { - "id" : "43", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "04182223-e5a1-4a03-b353-ab4b7a39838f" - }, - "sourceId" : "1", - "destinationId" : "5", - "description" : "Triggers 3D model view updates" - }, { - "id" : "111", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "154d7a26-d58c-490c-a187-a79d71d0d205" - }, - "sourceId" : "1", - "destinationId" : "2", - "description" : "Views and interacts with the 3D model" - } ], - "location" : "Unspecified" - } ], - "softwareSystems" : [ { - "id" : "2", - "tags" : "Element,Software System", - "properties" : { - "structurizr.dsl.identifier" : "visor" - }, - "name" : "Visor", - "description" : "3D Viewer for Solutions Applications", - "group" : "Ansys Corporate Client", - "location" : "Unspecified", - "containers" : [ { - "id" : "8", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_client" - }, - "name" : "VISOR JS client", - "description" : "VISOR client implementing the viewer functionality on the frontend using Trame VTK.WASM module library.", - "relationships" : [ { - "id" : "44", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "6d8d1933-ef63-48c3-a320-06721693e22f" - }, - "sourceId" : "8", - "destinationId" : "1", - "description" : "Visualization of 3D model data" - }, { - "id" : "46", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "585379d4-0bad-4167-b5a6-6e7bf8d80b46" - }, - "sourceId" : "8", - "destinationId" : "15", - "description" : "Requests model data" - }, { - "id" : "45", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "9a712a84-c38d-452d-9e84-13c5a1dac9d4" - }, - "sourceId" : "8", - "destinationId" : "1", - "description" : "Updates 3D model view" - } ], - "group" : "VISOR Client", - "technology" : "Typescript,JavaScript,React,MJS,Trame,WASM,VTK.WASM", - "components" : [ { - "id" : "10", - "tags" : "Element,Component,#React,#Typescript", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_client.visor_ui_elements" - }, - "name" : "VISOR UI Elements", - "description" : "VISOR UI elements for the VISOR viewer", - "technology" : "React,Typescript", - "documentation" : { } - }, { - "id" : "9", - "tags" : "Element,Component,#Typescript,#React,#VTK", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_client.visor_scene_component" - }, - "name" : "VISOR Viewer Scene Graph component", - "description" : "VISOR 3D Viewer scene graph component for visualization of the model topology", - "technology" : "VTK, Typescript, React", - "documentation" : { } - }, { - "id" : "11", - "tags" : "Element,Component", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_client.visor_trame_functionality_api" - }, - "name" : "VISOR Trame application sync and state manager", - "description" : "VISOR API for interfacing with the VISOR defined Trame application on the server", - "relationships" : [ { - "id" : "38", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "c46e942a-d12f-420e-aec0-48b6d7b70e96" - }, - "sourceId" : "11", - "destinationId" : "16", - "description" : "Trigger VTK updates" - } ], - "technology" : "TypeScript,Trame,React", - "documentation" : { } - } ], - "documentation" : { } - }, { - "id" : "19", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_app" - }, - "name" : "VISOR Application Component", - "description" : "VISOR Application controlling the choice of rendering engine, a VISOR Server instance and providing the Python API to the application", - "relationships" : [ { - "id" : "26", - "tags" : "Relationship,#openapi", - "properties" : { - "structurizr.dsl.identifier" : "840e7b6f-651b-4c60-9253-c81dac235ba3" - }, - "sourceId" : "19", - "destinationId" : "17", - "description" : "Provides an HTTP API for controlling the lifecycle of the starting, stopping or updating the Trame server and the websocket connection." - } ], - "technology" : "Python", - "components" : [ { - "id" : "21", - "tags" : "Element,Component,tags", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_app.trame_application" - }, - "name" : "Trame application", - "description" : "Trame application based on Trame vtk_local application utilizing VTK.WASM and a VTK Object Manager for client-server synchronization", - "relationships" : [ { - "id" : "28", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "97527f45-f2f8-4141-94ca-3c20a62885f1" - }, - "sourceId" : "21", - "destinationId" : "22", - "description" : "Sets up the VTK pipeline for the server side and synchronizes with the client side" - }, { - "id" : "29", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "ce876782-2b7a-4b55-b968-77bf9143732c" - }, - "sourceId" : "21", - "destinationId" : "23", - "description" : "Sets up the scene graph for the server side" - } ], - "technology" : "technology", - "documentation" : { } - }, { - "id" : "22", - "tags" : "Element,Component,#VTK", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_app.vtk_pipeline" - }, - "name" : "VTK Pipeline and shared objects with the client side", - "description" : "VTK pipeline setup for visualization on the server side which is synchronized with the VTK rendering on the client side", - "technology" : "VTK", - "documentation" : { } - }, { - "id" : "20", - "tags" : "Element,Component,#openapi,#fastapi", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_app.visor_api" - }, - "name" : "VISOR API", - "description" : "VISOR API for controlling the lifecycle of the starting, stopping or updating the Trame server and the websocket connection.", - "relationships" : [ { - "id" : "31", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "d5f3a346-dc04-4d4a-8b7e-563ed5683f0e" - }, - "sourceId" : "20", - "destinationId" : "21", - "description" : "Manages the Trame application client and server side, along with the VTK pipeline, scene management and input management." - } ], - "technology" : "OpenAPI, FastAPI", - "documentation" : { } - }, { - "id" : "24", - "tags" : "Element,Component,#Python,#OpenTelemetry", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_app.visor_logmonitor" - }, - "name" : "VISOR Logger and Monitor of the application, servers and services", - "description" : "VISOR logger and monitor is the part of the VISOR application which implements the OpenTelemetry standards for VISOR", - "relationships" : [ { - "id" : "30", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "0ddf1ba3-56ae-4bd0-9546-52c7bbf73eef" - }, - "sourceId" : "24", - "destinationId" : "21", - "description" : "Monitors the application and server" - } ], - "technology" : "Python,OpenTelemetry", - "documentation" : { } - }, { - "id" : "23", - "tags" : "Element,Component,#Python,#VTK", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_app.scene_graph" - }, - "name" : "Scene graph", - "description" : "Scene graph for supporting visualization of object hierarchies and scene attributes between the client and the server side", - "relationships" : [ { - "id" : "32", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "7936a240-6d66-4c6c-ad7a-b47def82cd2f" - }, - "sourceId" : "23", - "destinationId" : "9", - "description" : "Updates view and sends events to UI elements" - } ], - "technology" : "Python, VTK", - "documentation" : { } - } ], - "documentation" : { } - }, { - "id" : "15", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_server" - }, - "name" : "VISOR server", - "description" : "VISOR server supporting 3D rendering of models from Ansys flagship products", - "relationships" : [ { - "id" : "89", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "f9285d4b-b30f-48bd-badd-0af458310da6" - }, - "sourceId" : "15", - "destinationId" : "76", - "description" : "reads model data" - }, { - "id" : "88", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "224a74b6-a463-4511-9f48-5edce6982c46" - }, - "sourceId" : "15", - "destinationId" : "75", - "description" : "reads model data" - }, { - "id" : "47", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "2846a074-dde3-47ba-b753-9e61d3672b8f" - }, - "sourceId" : "15", - "destinationId" : "8", - "description" : "Sends model data" - } ], - "components" : [ { - "id" : "18", - "tags" : "Element,Component,#Python,#WebSocket,#wslink", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_server.visor_server_component" - }, - "name" : "VISOR Server Component", - "description" : "VISOR server component creating a Trame server for a single session for this VISOR application", - "relationships" : [ { - "id" : "36", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "0bf8553e-9365-4e50-afce-0b6fc91121a7" - }, - "sourceId" : "18", - "destinationId" : "16", - "description" : "Lifecycle management of the Trame server" - } ], - "technology" : "Python, WebSocket, wslink", - "documentation" : { } - }, { - "id" : "16", - "tags" : "Element,Component,#Python,#WebSocket,#wslink", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_server.trame_server" - }, - "name" : "Trame Server", - "description" : "Trame server component supporting single session using a web socket connection", - "relationships" : [ { - "id" : "37", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "9309941b-2b11-40b2-a3f6-81b3c12493ba" - }, - "sourceId" : "16", - "destinationId" : "14", - "description" : "Sends scene updates" - } ], - "technology" : "Python, WebSocket, wslink", - "documentation" : { } - }, { - "id" : "17", - "tags" : "Element,Component,#openapi,#fastapi", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_server.visor_http_api" - }, - "name" : "VISOR Server Orchestration HTTP API", - "description" : "Provides an HTTP API for controlling the lifecycle of the starting, stopping or updating the Trame server and the websocket connection.", - "relationships" : [ { - "id" : "25", - "tags" : "Relationship,#openapi", - "properties" : { - "structurizr.dsl.identifier" : "18002b43-c7cc-44d6-80c7-5e3a8dee3ef8" - }, - "sourceId" : "17", - "destinationId" : "20", - "description" : "Provides an HTTP API for controlling the lifecycle of the starting, stopping or updating the Trame server and the websocket connection." - }, { - "id" : "41", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "fea92b37-afdb-4a21-b1fa-7b45fc0d8d2a" - }, - "sourceId" : "17", - "destinationId" : "18", - "description" : "Controls server start, stop and state updates as well as monitoring tasks." - } ], - "technology" : "OpenAPI,FastAPI", - "documentation" : { } - } ], - "documentation" : { } - }, { - "id" : "3", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_dash" - }, - "name" : "VISOR 3D Viewer Dash UI component", - "description" : "VISOR 3D Viewer Dash wrapper with Python bindings for the VISOR web client library and client api", - "technology" : "Dash, Python, Typescript, React, VTK WASM JS viewer library", - "components" : [ { - "id" : "6", - "tags" : "Element,Component,#React,#TypeScript", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_dash.visor_client_api" - }, - "name" : "VISOR client UI API", - "description" : "Implements an API which interfaces and implements actions on the web UI, trame vtk module library and/or the scene component.", - "relationships" : [ { - "id" : "33", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "29c996be-022d-4555-b493-cdf37c3ffbe3" - }, - "sourceId" : "6", - "destinationId" : "11", - "description" : "Triggers functionality from the Dash client to the VISOR client library which is either a web UI functionality, or a Trame VTK.WASM functionality synchronized with the server and/or functionality on the scene component" - } ], - "technology" : "React,TypeScript", - "documentation" : { } - }, { - "id" : "7", - "tags" : "Element,Component,#JavaScript,#TypeScript,#React", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_dash.visor_js_library" - }, - "name" : "VISOR JS library", - "description" : "VISOR JS library implementing the visor client", - "technology" : "JavaScript,TypeScript,React", - "documentation" : { } - }, { - "id" : "4", - "tags" : "Element,Component,#React,#Typescript,#JS", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_dash.visor_dash_ui" - }, - "name" : "VISOR Viewer web client library", - "description" : "React TypeScript library of the VISOR client UI", - "relationships" : [ { - "id" : "27", - "tags" : "Relationship,#React,#Typescript,#JavaScript", - "properties" : { - "structurizr.dsl.identifier" : "d4e3f0d6-f53f-4e68-8153-604e5281edb1" - }, - "sourceId" : "4", - "destinationId" : "7", - "description" : "Triggers functionality from the Dash client to the VISOR client" - } ], - "technology" : "React,Typescript,Javascript", - "documentation" : { } - }, { - "id" : "5", - "tags" : "Element,Component,#Dash,#JS,#Typescript,#React", - "properties" : { - "structurizr.dsl.identifier" : "visor.visor_dash.visor_dash_api" - }, - "name" : "VISOR Dash UI Component API", - "description" : "Provides an API through the React interface available through the Dash component in order to allow for client side callbacks triggering specific functionality from the Dash application", - "relationships" : [ { - "id" : "35", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "4205c7a5-e060-4613-a92b-0470dea3314f" - }, - "sourceId" : "5", - "destinationId" : "11", - "description" : "Triggers functionality from the Dash client to the VISOR client library utilizing Trame VTK.WASM functionality on the client or server side." - }, { - "id" : "96", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "ed78ec43-26b9-4d20-b65d-143bc070e5e5" - }, - "sourceId" : "5", - "destinationId" : "54", - "description" : "Sends data to the Dash UI and status responses based on user interaction" - }, { - "id" : "34", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "4614eed6-3107-4650-b9e1-000e9b4ac927" - }, - "sourceId" : "5", - "destinationId" : "9", - "description" : "Triggers visualization updates" - } ], - "technology" : "Dash,React,TypeScript", - "documentation" : { } - } ], - "documentation" : { } - }, { - "id" : "12", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "visor.trame_vtk_local_container" - }, - "name" : "Trame VTK.WASM library", - "group" : "VISOR Client", - "components" : [ { - "id" : "14", - "tags" : "Element,Component,#VTK,#WASM,#JS,#MJS", - "properties" : { - "structurizr.dsl.identifier" : "visor.trame_vtk_local_container.trame_wasm_handler" - }, - "name" : "Trame Object Manager", - "description" : "VTK Object manager for serializaton/deserialization of VTK C++ classes for VTK pipeline objects shared between the client and the Trame server", - "relationships" : [ { - "id" : "39", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "31f1f834-3494-4414-81c1-041b58015154" - }, - "sourceId" : "14", - "destinationId" : "16", - "description" : "Triggers VTK updates" - } ], - "technology" : "VTK.WASM,JS,MJS", - "documentation" : { } - }, { - "id" : "13", - "tags" : "Element,Component,#Python,#WebSocket,#wslink", - "properties" : { - "structurizr.dsl.identifier" : "visor.trame_vtk_local_container.trame_wslink_connection" - }, - "name" : "Trame WSLINK connection and WASM loader", - "description" : "Trame WSLINK connection to the server and WASM loader", - "relationships" : [ { - "id" : "40", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "c1819b9b-ce45-4228-ad1d-5608dc9537cb" - }, - "sourceId" : "13", - "destinationId" : "16", - "description" : "Connects to running wslink session to setup a websocket connection." - } ], - "technology" : "Python, WebSocket, wslink", - "documentation" : { } - } ], - "documentation" : { } - } ], - "documentation" : { } - }, { - "id" : "51", - "tags" : "Element,Software System", - "url" : "https://tfs.ansys.com:8443/tfs/ANSYS_Development/Extensibility/_git/Root?path=%2Fansys%2Finstancemanagement%2Flight", - "properties" : { - "structurizr.dsl.identifier" : "product_instance_manager" - }, - "name" : "Product Instance Manager", - "description" : "enables the startup and termination of Ansys Flagship Products or other stateful processes", - "relationships" : [ { - "id" : "86", - "tags" : "Relationship,#process", - "properties" : { - "structurizr.dsl.identifier" : "a757b535-32fe-45c9-9fc9-27bff39c5453" - }, - "sourceId" : "51", - "destinationId" : "52", - "description" : "starts & kills" - }, { - "id" : "106", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "b60c9503-2048-4d84-9a4d-a3c1dd269ef3" - }, - "sourceId" : "51", - "destinationId" : "2", - "description" : "launches VISOR visualization" - }, { - "id" : "87", - "tags" : "Relationship,#process", - "properties" : { - "structurizr.dsl.identifier" : "a8ddf3b6-6347-412f-be88-561ce6bb0238" - }, - "sourceId" : "51", - "destinationId" : "15", - "description" : "Starts, stops viewer" - } ], - "group" : "Ansys Corporate Client", - "location" : "Unspecified", - "documentation" : { } - }, { - "id" : "48", - "tags" : "Element,Software System", - "properties" : { - "structurizr.dsl.identifier" : "portal" - }, - "name" : "SAF Portal", - "description" : "enables the user to create new project or select existing project then launch solution UI for project. Does not have responsibility for implementation of any aspect of the solution business logic or the services consumed by the solution.", - "relationships" : [ { - "id" : "108", - "tags" : "Relationship,#link", - "properties" : { - "structurizr.dsl.identifier" : "4a2daf08-d0a0-4289-ae64-13bbe1abaa7e" - }, - "sourceId" : "48", - "destinationId" : "54", - "description" : "links to", - "technology" : "JavaScript Click Handler" - }, { - "id" : "97", - "tags" : "Relationship,#REST", - "properties" : { - "structurizr.dsl.identifier" : "a7835452-f305-4688-a194-98f921228e66" - }, - "sourceId" : "48", - "destinationId" : "56", - "description" : "Call", - "technology" : "REST" - }, { - "id" : "104", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "4cdd2e14-ad9a-4cc6-a255-cfc5815b2c7e" - }, - "sourceId" : "48", - "destinationId" : "53", - "description" : "launches UI for existing or new project" - } ], - "group" : "Ansys Corporate Client", - "location" : "Unspecified", - "containers" : [ { - "id" : "49", - "tags" : "Element,Container,#fastapi", - "properties" : { - "structurizr.dsl.identifier" : "portal.portal_server" - }, - "name" : "Portal Server", - "description" : "implements a REST API that is consumed by the Portal UI. The portal server consumes a small subset of the API provided by the GLOW API Server", - "technology" : "FastAPI", - "documentation" : { } - }, { - "id" : "50", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "portal.ui" - }, - "name" : "Portal User Interface", - "description" : "provides a view of the projects in the projects directory. enables the user to create or select a project then launch solution UI for the project", - "technology" : "React", - "documentation" : { } - } ], - "documentation" : { } - }, { - "id" : "52", - "tags" : "Element,Software System,#external", - "properties" : { - "structurizr.dsl.identifier" : "product" - }, - "name" : "Ansys Flagship Product", - "description" : "A stateful process that is required to implement a GLOW transaction method (typically an Ansys Flagship product which contains a simulation solver designed to be a desktop application)", - "relationships" : [ { - "id" : "91", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "48f55a90-865f-4819-a29f-ca7a9bfd504c" - }, - "sourceId" : "52", - "destinationId" : "76", - "description" : "writes 3D model" - }, { - "id" : "110", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "product_writes_state" - }, - "sourceId" : "52", - "destinationId" : "55", - "description" : "reads & writes product state" - }, { - "id" : "90", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "34c98483-7617-4a8d-887d-cbd72935586c" - }, - "sourceId" : "52", - "destinationId" : "75", - "description" : "writes 3D model" - } ], - "group" : "Ansys Corporate Client", - "location" : "Unspecified", - "documentation" : { } - }, { - "id" : "53", - "tags" : "Element,Software System", - "url" : "https://github.com/ansys-internal/glow-engine", - "properties" : { - "structurizr.dsl.identifier" : "glow" - }, - "name" : "Guided Low Code Workflow (GLOW)", - "description" : "framework for vertical applications orientated towards a guided workflow user experience", - "relationships" : [ { - "id" : "107", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "ac953e71-5a6b-45f4-aae8-06d1cb44dd61" - }, - "sourceId" : "53", - "destinationId" : "2", - "description" : "launches VISOR visualization" - }, { - "id" : "105", - "tags" : "Relationship,#gRPC", - "properties" : { - "structurizr.dsl.identifier" : "9cd50811-7c3b-48eb-ab8d-46a0ea995ec1" - }, - "sourceId" : "53", - "destinationId" : "51", - "description" : "requests start and termination of product instances", - "technology" : "gRPC" - }, { - "id" : "109", - "tags" : "Relationship,#gRPC", - "properties" : { - "structurizr.dsl.identifier" : "c02cb3bf-1712-41f1-a5f0-21404938a79a" - }, - "sourceId" : "53", - "destinationId" : "52", - "description" : "calls", - "technology" : "gRPC" - } ], - "group" : "Ansys Corporate Client", - "location" : "Unspecified", - "containers" : [ { - "id" : "76", - "tags" : "Element,Container,#file", - "properties" : { - "structurizr.dsl.identifier" : "glow.product_instance_file_space" - }, - "name" : "Product Instance file space", - "description" : "the OS directory associated with a product instance", - "technology" : "file system directory", - "documentation" : { } - }, { - "id" : "56", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "glow.api" - }, - "name" : "API Server", - "description" : "Provides a REST API specific to a given solution, which is consumed by the solution UI server.", - "relationships" : [ { - "id" : "77", - "tags" : "Relationship,#process", - "properties" : { - "structurizr.dsl.identifier" : "72363f91-5f97-4c68-991d-d0605af7af89" - }, - "sourceId" : "56", - "destinationId" : "69", - "description" : "starts & stops" - }, { - "id" : "101", - "tags" : "Relationship,#process", - "properties" : { - "structurizr.dsl.identifier" : "3b5e8a04-0b53-4d0c-aa07-4216046a778c" - }, - "sourceId" : "56", - "destinationId" : "69", - "description" : "starts" - }, { - "id" : "84", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "35737807-caa5-4b47-8c33-261f88ddf6ba" - }, - "sourceId" : "56", - "destinationId" : "57", - "description" : "gets, modifies & creates records in" - }, { - "id" : "83", - "tags" : "Relationship,#gRPC", - "properties" : { - "structurizr.dsl.identifier" : "1c69aaee-673a-472e-8936-572ace4d58ce" - }, - "sourceId" : "56", - "destinationId" : "51", - "description" : "requests product termination (on shutdown)", - "technology" : "gRPC" - }, { - "id" : "85", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "df363bcc-842a-4f98-b61b-bae7a0c5ce99" - }, - "sourceId" : "56", - "destinationId" : "55", - "description" : "reads and writes project files" - } ], - "group" : "API", - "documentation" : { } - }, { - "id" : "75", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "glow.method_file_space" - }, - "name" : "Method file space", - "description" : "the temporary directory used by a method execution process that exists just for the duration of the process.", - "technology" : "file sdystem directory", - "documentation" : { } - }, { - "id" : "55", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "glow.projects_directory" - }, - "name" : "Projects Directory", - "description" : "the file system directory containing project files", - "group" : "API", - "documentation" : { } - }, { - "id" : "57", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "glow.projects_database" - }, - "name" : "Projects Database", - "description" : "stores instances of the solution schema", - "group" : "API", - "documentation" : { } - }, { - "id" : "69", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "glow.method_process" - }, - "name" : "Method Execution Process", - "description" : "An OS process that implements a single call to a transaction method", - "relationships" : [ { - "id" : "98", - "tags" : "Relationship,#REST", - "properties" : { - "structurizr.dsl.identifier" : "165fcd02-6e54-4bd6-8873-9d9147492c60" - }, - "sourceId" : "69", - "destinationId" : "56", - "description" : "uploads and downloads fields", - "technology" : "REST" - }, { - "id" : "100", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "36cf8549-7e3c-40ea-bcfb-9be5f28e396a" - }, - "sourceId" : "69", - "destinationId" : "76", - "description" : "creates and deletes" - }, { - "id" : "82", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "f3cd4743-f9b7-4ec5-9cf6-d8a594c72a9b" - }, - "sourceId" : "69", - "destinationId" : "76", - "description" : "creates & deletes" - }, { - "id" : "99", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "1ae75136-a60d-4823-994f-ae08b47fe9e9" - }, - "sourceId" : "69", - "destinationId" : "75", - "description" : "creates and deletes" - }, { - "id" : "79", - "tags" : "Relationship,#gRPC", - "properties" : { - "structurizr.dsl.identifier" : "c2c025d7-617a-474c-b355-d56198a3e83e" - }, - "sourceId" : "69", - "destinationId" : "51", - "description" : "queries product connection, requests product start & termination", - "technology" : "gRPC" - }, { - "id" : "78", - "tags" : "Relationship,#gRPC", - "properties" : { - "structurizr.dsl.identifier" : "719626ea-3c34-4acb-a549-0eaac66bac96" - }, - "sourceId" : "69", - "destinationId" : "52", - "description" : "executes method code", - "technology" : "gRPC" - }, { - "id" : "80", - "tags" : "Relationship,#REST", - "properties" : { - "structurizr.dsl.identifier" : "42151dd2-0ad2-42f9-9ca0-3a5cccd0eabb" - }, - "sourceId" : "69", - "destinationId" : "56", - "description" : "calls", - "technology" : "REST" - }, { - "id" : "81", - "tags" : "Relationship,#file", - "properties" : { - "structurizr.dsl.identifier" : "dbd0b58d-7179-497e-9e5f-93c9137842b8" - }, - "sourceId" : "69", - "destinationId" : "75", - "description" : "creates & deletes" - } ], - "technology" : "Python", - "components" : [ { - "id" : "71", - "tags" : "Element,Component", - "url" : "https://github.com/ansys-internal/glow-engine/tree/main/src/ansys/saf/glow/solution", - "properties" : { - "structurizr.dsl.identifier" : "glow.method_process.solution_definition_api" - }, - "name" : "Solution definition API", - "description" : "a python package that contains the set of python types required to define a GLOW solution", - "technology" : "Python", - "documentation" : { } - }, { - "id" : "70", - "tags" : "Element,Component", - "url" : "https://github.com/ansys-internal/glow-engine/blob/main/src/ansys/saf/glow/_executor/method_runner.py#L41", - "properties" : { - "structurizr.dsl.identifier" : "glow.method_process.method_runner" - }, - "name" : "Method Runner", - "description" : "implements a single call to a transaction method", - "relationships" : [ { - "id" : "73", - "tags" : "Relationship,#function", - "properties" : { - "structurizr.dsl.identifier" : "01b38730-e7e3-4fa6-8405-2f2dd954a5fe" - }, - "sourceId" : "70", - "destinationId" : "72", - "description" : "executes method" - } ], - "technology" : "Python", - "documentation" : { } - }, { - "id" : "72", - "tags" : "Element,Component", - "properties" : { - "structurizr.dsl.identifier" : "glow.method_process.solution" - }, - "name" : "Solution definition", - "description" : "the definition of a solution's schema and business logic", - "relationships" : [ { - "id" : "74", - "tags" : "Relationship,#import", - "properties" : { - "structurizr.dsl.identifier" : "70dc40d0-4943-4f4e-a334-235450d99be4" - }, - "sourceId" : "72", - "destinationId" : "71", - "description" : "obtains base types for solution definition" - } ], - "technology" : "Python", - "documentation" : { } - } ], - "documentation" : { } - }, { - "id" : "54", - "tags" : "Element,Container", - "properties" : { - "structurizr.dsl.identifier" : "glow.dash_ui" - }, - "name" : "Solution Dash UI", - "description" : "A browser based client for the Dash server implemented in React Javascript that renders the UI defined by the Dash server", - "relationships" : [ { - "id" : "92", - "tags" : "Relationship,#REST", - "properties" : { - "structurizr.dsl.identifier" : "23fc0608-050d-4efd-ba56-6542c577b4a1" - }, - "sourceId" : "54", - "destinationId" : "58", - "description" : "obtains code and state; signals user interface events", - "technology" : "REST" - }, { - "id" : "94", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "e4eee6d2-4afc-4d6c-803a-28ddf3e59460" - }, - "sourceId" : "54", - "destinationId" : "4", - "description" : "Signals user interface events" - }, { - "id" : "95", - "tags" : "Relationship", - "properties" : { - "structurizr.dsl.identifier" : "b80fb8d4-95f7-42e4-bade-878fb1e3a1e5" - }, - "sourceId" : "54", - "destinationId" : "5", - "description" : "Triggers visual events and requests data from the VISOR 3D viewer" - } ], - "technology" : "React", - "documentation" : { } - }, { - "id" : "58", - "tags" : "Element,Container,#Flask", - "properties" : { - "structurizr.dsl.identifier" : "glow.dash" - }, - "name" : "Dash Server", - "description" : "a Flask server that services a React browser based UI defined using the Dash UI definition API", - "relationships" : [ { - "id" : "93", - "tags" : "Relationship,#REST", - "properties" : { - "structurizr.dsl.identifier" : "f9ca44bb-b9e9-4cde-a3c0-425d6dd49f98" - }, - "sourceId" : "58", - "destinationId" : "56", - "description" : "Call", - "technology" : "REST" - } ], - "technology" : "Flask", - "components" : [ { - "id" : "63", - "tags" : "Element,Component", - "properties" : { - "structurizr.dsl.identifier" : "glow.dash.solution" - }, - "name" : "Solution definition", - "description" : "the definition of a solution's schema and business logic", - "relationships" : [ { - "id" : "67", - "tags" : "Relationship,#import", - "properties" : { - "structurizr.dsl.identifier" : "17e717b6-cacc-4635-b689-342fb10a2083" - }, - "sourceId" : "63", - "destinationId" : "62", - "description" : "obtains base types for solution definition" - } ], - "technology" : "Python", - "documentation" : { } - }, { - "id" : "60", - "tags" : "Element,Component", - "properties" : { - "structurizr.dsl.identifier" : "glow.dash.solution_ui" - }, - "name" : "Solution UI", - "description" : "a python package which defines how the solution is rendered via the Dash UI definition API", - "relationships" : [ { - "id" : "64", - "tags" : "Relationship,#import", - "properties" : { - "structurizr.dsl.identifier" : "fc36a023-4430-4c65-9d63-d57f05e4116e" - }, - "sourceId" : "60", - "destinationId" : "59", - "description" : "invoke rendering providing UI structure and callbacks" - }, { - "id" : "65", - "tags" : "Relationship,#function", - "properties" : { - "structurizr.dsl.identifier" : "f81ba5e7-6280-4564-a60e-2eea048a3f8d" - }, - "sourceId" : "60", - "destinationId" : "61", - "description" : "gets and sets data; and invokes methods via proxy objects" - } ], - "technology" : "Python", - "documentation" : { } - }, { - "id" : "59", - "tags" : "Element,Component", - "url" : "https://dash.plotly.com/", - "properties" : { - "structurizr.dsl.identifier" : "glow.dash.dash_flask_server" - }, - "name" : "Dash Flask Server", - "description" : "a Flask server that services a React browser based UI defined using the Dash UI definition API", - "technology" : "Flask", - "documentation" : { } - }, { - "id" : "61", - "tags" : "Element,Component", - "url" : "https://github.com/ansys-internal/glow-engine/tree/main/src/ansys/saf/glow/client", - "properties" : { - "structurizr.dsl.identifier" : "glow.dash.client_api" - }, - "name" : "Client API", - "description" : "a python package that provides a pythonic interface to a GLOW API server via REST", - "relationships" : [ { - "id" : "68", - "tags" : "Relationship,#REST", - "properties" : { - "structurizr.dsl.identifier" : "10d9c09e-664f-4480-b81d-faf1967981af" - }, - "sourceId" : "61", - "destinationId" : "56", - "description" : "calls", - "technology" : "REST" - }, { - "id" : "66", - "tags" : "Relationship,#import", - "properties" : { - "structurizr.dsl.identifier" : "892a02f8-903f-4fbe-a414-9de1ecd72d90" - }, - "sourceId" : "61", - "destinationId" : "63", - "description" : "obtains schema and method set" - } ], - "technology" : "Python", - "documentation" : { } - }, { - "id" : "62", - "tags" : "Element,Component", - "url" : "https://github.com/ansys-internal/glow-engine/tree/main/src/ansys/saf/glow/solution", - "properties" : { - "structurizr.dsl.identifier" : "glow.dash.solution_definition_api" - }, - "name" : "GLOW Solution definition API", - "description" : "a python package that contains the set of python types required to define a GLOW solution", - "technology" : "Python", - "documentation" : { } - } ], - "documentation" : { } - } ], - "documentation" : { } - } ], - "deploymentNodes" : [ { - "id" : "131", - "tags" : "Element,Deployment Node,1", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.766479b0-ca64-4b58-bf66-013777ad47c2" - }, - "name" : "File System", - "description" : "the file system of a single Windows Desktop PC", - "environment" : "End User Windows Desktop PC", - "technology" : "Windows", - "instances" : "1", - "children" : [ { - "id" : "138", - "tags" : "Element,Deployment Node,1", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.766479b0-ca64-4b58-bf66-013777ad47c2.696158b9-9b7a-4053-9c95-645a8ed9fe7e" - }, - "name" : "APPDATA Directory", - "description" : "the APPDATA directory of the end user", - "environment" : "End User Windows Desktop PC", - "technology" : "Windows", - "instances" : "1", - "containerInstances" : [ { - "id" : "139", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.766479b0-ca64-4b58-bf66-013777ad47c2.696158b9-9b7a-4053-9c95-645a8ed9fe7e.projects_database_" - }, - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "57" - } ] - }, { - "id" : "132", - "tags" : "Element,Deployment Node,1", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.766479b0-ca64-4b58-bf66-013777ad47c2.7da33f8f-9925-4746-937b-32937a315ae8" - }, - "name" : "User Documents Directory", - "description" : "the Documents directory of the end user", - "environment" : "End User Windows Desktop PC", - "technology" : "Windows", - "instances" : "1", - "containerInstances" : [ { - "id" : "135", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.766479b0-ca64-4b58-bf66-013777ad47c2.7da33f8f-9925-4746-937b-32937a315ae8.product_instance_file_space_" - }, - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "76" - }, { - "id" : "133", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.766479b0-ca64-4b58-bf66-013777ad47c2.7da33f8f-9925-4746-937b-32937a315ae8.projects_directory_" - }, - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "55" - } ] - } ] - }, { - "id" : "112", - "tags" : "Element,Deployment Node", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2" - }, - "name" : "Python Interpreter", - "description" : "the python interpreter that runs the GLOW solution", - "environment" : "End User Windows Desktop PC", - "technology" : "Python", - "instances" : "1", - "children" : [ { - "id" : "126", - "tags" : "Element,Deployment Node", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.9a758bd9-e31b-45b4-94d3-cdbfaa9be8d6" - }, - "name" : "pywebview", - "description" : "a python and browser based engine for rendering web UIs as desktop application windows", - "environment" : "End User Windows Desktop PC", - "technology" : "Python", - "instances" : "1", - "containerInstances" : [ { - "id" : "127", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.9a758bd9-e31b-45b4-94d3-cdbfaa9be8d6.dash_ui_" - }, - "relationships" : [ { - "id" : "128", - "sourceId" : "127", - "destinationId" : "115", - "description" : "obtains code and state; signals user interface events", - "technology" : "REST", - "linkedRelationshipId" : "92" - } ], - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "54" - }, { - "id" : "130", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.9a758bd9-e31b-45b4-94d3-cdbfaa9be8d6.visor_ui_" - }, - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "8" - }, { - "id" : "129", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.9a758bd9-e31b-45b4-94d3-cdbfaa9be8d6.portal_ui_" - }, - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "50" - } ] - }, { - "id" : "113", - "tags" : "Element,Deployment Node", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.5b403c07-dfd5-443f-a6a8-62ba6421ad57" - }, - "name" : "Orchestrator", - "description" : "the python module that starts and shutsdown the GLOW solution", - "environment" : "End User Windows Desktop PC", - "technology" : "Python", - "instances" : "1", - "softwareSystemInstances" : [ { - "id" : "123", - "tags" : "Software System Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.5b403c07-dfd5-443f-a6a8-62ba6421ad57.product_instance_manager_" - }, - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "softwareSystemId" : "51" - } ], - "containerInstances" : [ { - "id" : "115", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.5b403c07-dfd5-443f-a6a8-62ba6421ad57.dash_" - }, - "relationships" : [ { - "id" : "116", - "sourceId" : "115", - "destinationId" : "114", - "description" : "Call", - "technology" : "REST", - "linkedRelationshipId" : "93" - } ], - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "58" - }, { - "id" : "114", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.5b403c07-dfd5-443f-a6a8-62ba6421ad57.api_" - }, - "relationships" : [ { - "id" : "125", - "sourceId" : "114", - "destinationId" : "123", - "description" : "requests product termination (on shutdown)", - "technology" : "gRPC", - "linkedRelationshipId" : "83" - }, { - "id" : "120", - "sourceId" : "114", - "destinationId" : "117", - "description" : "starts & stops", - "linkedRelationshipId" : "77" - }, { - "id" : "121", - "sourceId" : "114", - "destinationId" : "117", - "description" : "starts", - "linkedRelationshipId" : "101" - }, { - "id" : "134", - "sourceId" : "114", - "destinationId" : "133", - "description" : "reads and writes project files", - "linkedRelationshipId" : "85" - }, { - "id" : "140", - "sourceId" : "114", - "destinationId" : "139", - "description" : "gets, modifies & creates records in", - "linkedRelationshipId" : "84" - } ], - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "56" - }, { - "id" : "117", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.5b403c07-dfd5-443f-a6a8-62ba6421ad57.method_process_" - }, - "relationships" : [ { - "id" : "119", - "sourceId" : "117", - "destinationId" : "114", - "description" : "uploads and downloads fields", - "technology" : "REST", - "linkedRelationshipId" : "98" - }, { - "id" : "124", - "sourceId" : "117", - "destinationId" : "123", - "description" : "queries product connection, requests product start & termination", - "technology" : "gRPC", - "linkedRelationshipId" : "79" - }, { - "id" : "136", - "sourceId" : "117", - "destinationId" : "135", - "description" : "creates & deletes", - "linkedRelationshipId" : "82" - }, { - "id" : "118", - "sourceId" : "117", - "destinationId" : "114", - "description" : "calls", - "technology" : "REST", - "linkedRelationshipId" : "80" - }, { - "id" : "137", - "sourceId" : "117", - "destinationId" : "135", - "description" : "creates and deletes", - "linkedRelationshipId" : "100" - } ], - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "69" - }, { - "id" : "122", - "tags" : "Container Instance", - "properties" : { - "structurizr.dsl.identifier" : "end_user_windows_pc_.d67fbece-ba58-4e45-b92f-b0fedafe32f2.5b403c07-dfd5-443f-a6a8-62ba6421ad57.portal_server_" - }, - "environment" : "End User Windows Desktop PC", - "deploymentGroups" : [ "Default" ], - "instanceId" : 1, - "containerId" : "49" - } ] - } ] - } ], - "properties" : { - "structurizr.groupSeparator" : "/" - } - }, - "documentation" : { }, - "views" : { - "systemLandscapeViews" : [ { - "key" : "SystemLandscape", - "order" : 1, - "description" : "VISOR integrated in Solution Application using GLOW", - "automaticLayout" : { - "implementation" : "Graphviz", - "rankDirection" : "LeftRight", - "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : false - }, - "enterpriseBoundaryVisible" : true, - "elements" : [ { - "id" : "1", - "x" : 0, - "y" : 0 - }, { - "id" : "2", - "x" : 0, - "y" : 0 - }, { - "id" : "48", - "x" : 0, - "y" : 0 - }, { - "id" : "51", - "x" : 0, - "y" : 0 - }, { - "id" : "52", - "x" : 0, - "y" : 0 - }, { - "id" : "53", - "x" : 0, - "y" : 0 - } ], - "relationships" : [ { - "id" : "107" - }, { - "id" : "109" - }, { - "id" : "86" - }, { - "id" : "111" - }, { - "id" : "104" - }, { - "id" : "103" - }, { - "id" : "106" - }, { - "id" : "105" - } ] - } ], - "systemContextViews" : [ { - "key" : "VisorSystemContext", - "order" : 4, - "description" : "VISOR Context", - "softwareSystemId" : "2", - "automaticLayout" : { - "implementation" : "Graphviz", - "rankDirection" : "LeftRight", - "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : false - }, - "enterpriseBoundaryVisible" : true, - "elements" : [ { - "id" : "1", - "x" : 0, - "y" : 0 - }, { - "id" : "2", - "x" : 0, - "y" : 0 - }, { - "id" : "51", - "x" : 0, - "y" : 0 - }, { - "id" : "53", - "x" : 0, - "y" : 0 - } ], - "relationships" : [ { - "id" : "107" - }, { - "id" : "111" - }, { - "id" : "106" - }, { - "id" : "105" - } ] - }, { - "key" : "VisorSolutionApplicationContext", - "order" : 2, - "description" : "VISOR Solution Application Context", - "softwareSystemId" : "2", - "automaticLayout" : { - "implementation" : "Graphviz", - "rankDirection" : "LeftRight", - "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : false - }, - "enterpriseBoundaryVisible" : true, - "elements" : [ { - "id" : "1", - "x" : 0, - "y" : 0 - }, { - "id" : "2", - "x" : 0, - "y" : 0 - }, { - "id" : "48", - "x" : 0, - "y" : 0 - }, { - "id" : "51", - "x" : 0, - "y" : 0 - }, { - "id" : "53", - "x" : 0, - "y" : 0 - } ], - "relationships" : [ { - "id" : "107" - }, { - "id" : "111" - }, { - "id" : "104" - }, { - "id" : "103" - }, { - "id" : "106" - }, { - "id" : "105" - } ] - } ], - "containerViews" : [ { - "key" : "VisorContainers", - "order" : 3, - "description" : "VISOR Containers", - "softwareSystemId" : "2", - "paperSize" : "A5_Landscape", - "dimensions" : { - "width" : 2480, - "height" : 1748 - }, - "externalSoftwareSystemBoundariesVisible" : false, - "elements" : [ { - "id" : "1", - "x" : 60, - "y" : 410 - }, { - "id" : "15", - "x" : 710, - "y" : 955 - }, { - "id" : "8", - "x" : 0, - "y" : 0 - } ], - "relationships" : [ { - "id" : "44" - }, { - "id" : "45" - }, { - "id" : "46" - }, { - "id" : "47" - } ] - } ], - "componentViews" : [ { - "key" : "VisorClientComponents", - "order" : 7, - "description" : "VISOR Client Component", - "dimensions" : { - "width" : 890, - "height" : 3211 - }, - "automaticLayout" : { - "implementation" : "Graphviz", - "rankDirection" : "LeftRight", - "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : true - }, - "containerId" : "8", - "externalContainerBoundariesVisible" : false, - "elements" : [ { - "id" : "11", - "x" : 220, - "y" : 820 - }, { - "id" : "13", - "x" : 220, - "y" : 2020 - }, { - "id" : "14", - "x" : 220, - "y" : 2620 - }, { - "id" : "9", - "x" : 220, - "y" : 1420 - }, { - "id" : "10", - "x" : 220, - "y" : 220 - } ] - }, { - "key" : "VisorServerComponents", - "order" : 5, - "description" : "VISOR Server Components", - "dimensions" : { - "width" : 3120, - "height" : 811 - }, - "automaticLayout" : { - "implementation" : "Graphviz", - "rankDirection" : "LeftRight", - "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : true - }, - "containerId" : "15", - "externalContainerBoundariesVisible" : false, - "elements" : [ { - "id" : "16", - "x" : 2450, - "y" : 220 - }, { - "id" : "17", - "x" : 950, - "y" : 220 - }, { - "id" : "18", - "x" : 1700, - "y" : 220 - }, { - "id" : "19", - "x" : 200, - "y" : 220 - } ], - "relationships" : [ { - "id" : "26" - }, { - "id" : "36" - }, { - "id" : "41" - } ] - }, { - "key" : "VisorAppComponents", - "order" : 8, - "description" : "VISOR Application Components", - "dimensions" : { - "width" : 2390, - "height" : 1411 - }, - "automaticLayout" : { - "implementation" : "Graphviz", - "rankDirection" : "LeftRight", - "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : true - }, - "containerId" : "19", - "externalContainerBoundariesVisible" : false, - "elements" : [ { - "id" : "22", - "x" : 1719, - "y" : 819 - }, { - "id" : "23", - "x" : 1719, - "y" : 219 - }, { - "id" : "24", - "x" : 219, - "y" : 819 - }, { - "id" : "20", - "x" : 219, - "y" : 219 - }, { - "id" : "21", - "x" : 969, - "y" : 519 - } ], - "relationships" : [ { - "id" : "29" - }, { - "id" : "28" - }, { - "id" : "31" - }, { - "id" : "30" - } ] - }, { - "key" : "VisorDashComponents", - "order" : 6, - "description" : "VISOR Dash UI Components", - "dimensions" : { - "width" : 2320, - "height" : 2011 - }, - "automaticLayout" : { - "implementation" : "Graphviz", - "rankDirection" : "LeftRight", - "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : true - }, - "containerId" : "3", - "externalContainerBoundariesVisible" : false, - "elements" : [ { - "id" : "1", - "x" : 200, - "y" : 470 - }, { - "id" : "4", - "x" : 900, - "y" : 220 - }, { - "id" : "5", - "x" : 900, - "y" : 820 - }, { - "id" : "6", - "x" : 900, - "y" : 1420 - }, { - "id" : "7", - "x" : 1650, - "y" : 220 - } ], - "relationships" : [ { - "id" : "27" - }, { - "id" : "42" - }, { - "id" : "43" - } ] - } ], - "deploymentViews" : [ { - "key" : "EndUserDeployment", - "order" : 9, - "description" : "End User Deployment", - "automaticLayout" : { - "implementation" : "Graphviz", - "rankDirection" : "LeftRight", - "rankSeparation" : 300, - "nodeSeparation" : 300, - "edgeSeparation" : 0, - "vertices" : false, - "applied" : false - }, - "environment" : "End User Windows Desktop PC", - "elements" : [ { - "id" : "130", - "x" : 0, - "y" : 0 - }, { - "id" : "131", - "x" : 0, - "y" : 0 - }, { - "id" : "132", - "x" : 0, - "y" : 0 - }, { - "id" : "122", - "x" : 0, - "y" : 0 - }, { - "id" : "133", - "x" : 0, - "y" : 0 - }, { - "id" : "123", - "x" : 0, - "y" : 0 - }, { - "id" : "112", - "x" : 0, - "y" : 0 - }, { - "id" : "113", - "x" : 0, - "y" : 0 - }, { - "id" : "135", - "x" : 0, - "y" : 0 - }, { - "id" : "114", - "x" : 0, - "y" : 0 - }, { - "id" : "126", - "x" : 0, - "y" : 0 - }, { - "id" : "115", - "x" : 0, - "y" : 0 - }, { - "id" : "127", - "x" : 0, - "y" : 0 - }, { - "id" : "138", - "x" : 0, - "y" : 0 - }, { - "id" : "117", - "x" : 0, - "y" : 0 - }, { - "id" : "139", - "x" : 0, - "y" : 0 - }, { - "id" : "129", - "x" : 0, - "y" : 0 - } ], - "relationships" : [ { - "id" : "119" - }, { - "id" : "118" - }, { - "id" : "120" - }, { - "id" : "140" - }, { - "id" : "134" - }, { - "id" : "121" - }, { - "id" : "124" - }, { - "id" : "128" - }, { - "id" : "125" - }, { - "id" : "136" - }, { - "id" : "116" - }, { - "id" : "137" - } ] - } ], - "configuration" : { - "branding" : { - "logo" : "https://raw.githubusercontent.com/RVR06/cornifer-contrib/main/assets/ansys.png" - }, - "styles" : { }, - "themes" : [ "https://raw.githubusercontent.com/RVR06/cornifer-contrib/main/themes/semantic/theme.json", "https://raw.githubusercontent.com/RVR06/cornifer-contrib/main/themes/heraldry/theme.json" ], - "terminology" : { }, - "metadataSymbols" : "SquareBrackets", - "lastSavedView" : "VisorAppComponents" - } - } -} \ No newline at end of file diff --git a/doc/developer_docs/images/11_jupyter_notebook_1.png b/doc/developer_docs/images/11_jupyter_notebook_1.png deleted file mode 100644 index a63d5e9c..00000000 Binary files a/doc/developer_docs/images/11_jupyter_notebook_1.png and /dev/null differ diff --git a/doc/developer_docs/images/11_jupyter_notebook_2.png b/doc/developer_docs/images/11_jupyter_notebook_2.png deleted file mode 100644 index e0182383..00000000 Binary files a/doc/developer_docs/images/11_jupyter_notebook_2.png and /dev/null differ diff --git a/doc/developer_docs/images/env_vars.png b/doc/developer_docs/images/env_vars.png deleted file mode 100644 index a5cc7214..00000000 Binary files a/doc/developer_docs/images/env_vars.png and /dev/null differ diff --git a/doc/developer_docs/images/system_env_vars.png b/doc/developer_docs/images/system_env_vars.png deleted file mode 100644 index 3bff53d0..00000000 Binary files a/doc/developer_docs/images/system_env_vars.png and /dev/null differ diff --git a/doc/developer_docs/images/visor-state-model.png b/doc/developer_docs/images/visor-state-model.png deleted file mode 100644 index 25b5932a..00000000 Binary files a/doc/developer_docs/images/visor-state-model.png and /dev/null differ diff --git a/doc/source/contributing/index.rst b/doc/source/contributing/index.rst index 04814983..3df300cd 100644 --- a/doc/source/contributing/index.rst +++ b/doc/source/contributing/index.rst @@ -329,7 +329,7 @@ which requires authentication. - Click **Generate new token** and select the **Generate new token (classic)** option. - Check the ``repo`` scope only. - Click **Generate token** and copy the value. -- Activate the token for use in the Ansys and Ansys-internal organizations. +- Activate the token for use in the Ansys organization. - Set the token as an environment variable named ``GITHUB_TOKEN``. diff --git a/doc/source/user_guide/saf_integration/grid.rst b/doc/source/user_guide/saf_integration/grid.rst index 83639063..201ca170 100644 --- a/doc/source/user_guide/saf_integration/grid.rst +++ b/doc/source/user_guide/saf_integration/grid.rst @@ -14,32 +14,5 @@ See more information in the `PIM documentation`_. - -SAF integration examples ------------------------- - -Below are two examples of how to integrate VISOR into a SAF solution: - -#. **SAF VISOR POC**: - - `SAF VISOR POC`_ is a simple example that demonstrates how to integrate - VISOR into a SAF solution for interactive 3D visualization of engineering data. - -#. **SAF reference solution**: - - `Airfoil Explorer`_ is a reference solution application - showcasing integration of five key STCs through a guided workflow for defining airfoil geometry, generating a mesh, - running a 2D potential flow solve, and visualizing results. Ideal as a template for building engineering solution - apps. - - - - - - .. _PIM documentation: https://upgraded-carnival-wn6lkym.pages.github.io/version/stable/user_guide/backend/instance_management/index.html -.. _Airfoil Explorer: - https://github.com/ansys-internal/airfoil-explorer -.. _SAF VISOR POC: - https://github.com/ansys-internal/saf-theia-poc diff --git a/src/ansys/visor/viewer/core/visor_logging.py b/src/ansys/visor/viewer/core/visor_logging.py index 34b4df62..b69f13f2 100644 --- a/src/ansys/visor/viewer/core/visor_logging.py +++ b/src/ansys/visor/viewer/core/visor_logging.py @@ -22,9 +22,7 @@ class VisorLogger(Logger): level (int): Logging level. Default is logging.DEBUG. """ - # Logging format to comply with Ansys ADR: - # https://github.com/ansys-internal/architecture-decision-records/blob/main/content/ - # docs/adrs/0016-observability-strategy.md + # Logging format to comply with Ansys ADR: 0016-observability-strategy.md LOGGING_FORMAT = ( "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d %(funcName)s()] - %(message)s" ) diff --git a/src/ansys/visor/visor-client/src/components/UiScaffold.tsx b/src/ansys/visor/visor-client/src/components/UiScaffold.tsx index 34586c4d..cf57e810 100644 --- a/src/ansys/visor/visor-client/src/components/UiScaffold.tsx +++ b/src/ansys/visor/visor-client/src/components/UiScaffold.tsx @@ -68,7 +68,6 @@ export const UiScaffold: FC<{ lastPixelDensity = pixelDensity; const scale = pixelDensity > 0 ? scaffoldElem.offsetWidth / pixelDensity : 1; // Apply after-scaling to some UI panels. - // see https://github.com/ansys-internal/theia/pull/1004 const afterScale = 0.85; setScale(topLeftElem, scale * afterScale); setScale(topMiddleElem, scale); diff --git a/tools/awc_icon_generator/AWC-FONTS-ICONS-README.md b/tools/awc_icon_generator/AWC-FONTS-ICONS-README.md index 69420972..5e9d53fc 100644 --- a/tools/awc_icon_generator/AWC-FONTS-ICONS-README.md +++ b/tools/awc_icon_generator/AWC-FONTS-ICONS-README.md @@ -2,8 +2,6 @@ ## Overview -For the corresponding GitHub discussion, please visit https://github.com/ansys-internal/theia/issues/231 - In order to make use of AWC fonts and icons without installing the entire AWC React npm package, we need to import the static assets that the AWC React npm package uses. These assets include the following: - The default AWC UI font (Source Sans 3) and its corresponding CSS font-family stack diff --git a/tools/awc_icon_generator/awc-icon-generator.html b/tools/awc_icon_generator/awc-icon-generator.html index d6228bd9..15c92aee 100644 --- a/tools/awc_icon_generator/awc-icon-generator.html +++ b/tools/awc_icon_generator/awc-icon-generator.html @@ -65,8 +65,7 @@

AWC Icon Library Generator

This page loops through a list of AWC icon SVG file names, downloads each icon's SVG,
and dynamically builds a JavaScript object with methods that return each icon as an HTMLElement object.

For information on using this JavaScript generator, either see the readme at

- [VISOR Project Root]\src\ansys\visor\visor-client\utils\tools\AWC-FONTS-ICONS-README.md

- or see the GitHub discussion here. + [VISOR Project Root]\src\ansys\visor\visor-client\utils\tools\AWC-FONTS-ICONS-README.md

.