puppy2333 commited on
Commit
32e7816
·
1 Parent(s): 0e3c6b7

Web visualizer initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .DS_Store
2
+ node_modules
3
+ .venv
4
+
5
+ # local env files
6
+ .env.local
7
+ .env.*.local
8
+
9
+ # Log files
10
+ npm-debug.log*
11
+ yarn-debug.log*
12
+ yarn-error.log*
13
+ pnpm-debug.log*
14
+
15
+ # Editor directories and files
16
+ .idea
17
+ .vscode
18
+ *.suo
19
+ *.ntvs*
20
+ *.njsproj
21
+ *.sln
22
+ *.sw?
23
+
24
+ __pycache__
25
+ *egg-info
26
+ *pyc
CONTRIBUTING.rst ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ =====================================
2
+ Contributing to Wind Field Visualizer
3
+ =====================================
4
+
5
+ #. Clone the repository using ``git clone``
6
+ #. Make changes to the code, and commit your changes to a separate branch
7
+ #. Create a fork of the repository on GitHub
8
+ #. Push your branch to your fork, and open a pull request
LICENSE ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BSD 3-Clause
2
+
3
+ Copyright (c) 2025, Xuanchen Zhou
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification,
7
+ are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice, this
13
+ list of conditions and the following disclaimer in the documentation and/or
14
+ other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from this
18
+ software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
27
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
28
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29
+ OF THE POSSIBILITY OF SUCH DAMAGE.
README.rst ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Wind Field Visualizer
2
+ ----------------------------------------
3
+
4
+ A web-based wind field visualizer
5
+
6
+ License
7
+ ----------------------------------------
8
+
9
+ This library is OpenSource and follow the BSD License
10
+
11
+ Installation
12
+ ----------------------------------------
13
+
14
+ Install the application/library
15
+
16
+ .. code-block:: console
17
+
18
+ pip install wind-field-visualizer
19
+
20
+ Run the application
21
+
22
+ .. code-block:: console
23
+
24
+ wind-field-visualizer
25
+
26
+ Developement setup
27
+ ----------------------------------------
28
+
29
+ We recommend using uv for setting up and managing a virtual environement for your development.
30
+
31
+ .. code-block:: console
32
+
33
+ # Create venv and install all dependencies
34
+ uv sync --all-extras --dev
35
+
36
+ # Activate environement
37
+ source .venv/bin/activate
38
+
39
+
40
+ # Allow live code edit
41
+ uv pip install -e .
42
+
43
+
44
+
45
+
46
+ Professional Support
47
+ ----------------------------------------
48
+
49
+ * `Training <https://www.kitware.com/courses/trame/>`_: Learn how to confidently use trame from the expert developers at Kitware.
50
+ * `Support <https://www.kitware.com/trame/support/>`_: Our experts can assist your team as you build your web application and establish in-house expertise.
51
+ * `Custom Development <https://www.kitware.com/trame/support/>`_: Leverage Kitware’s 25+ years of experience to quickly build your web application.
bundles/desktop/README.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Desktop
2
+
3
+ This relies on [pyinstaller](https://pyinstaller.org/en/stable/) to bundle your trame application into a standalone desktop application.
4
+
5
+ In our current setup, we'll rely on the current virtual environment to provide the code content.
6
+
7
+ ## Building the bundle
8
+
9
+ First, run `pip install -r requirements.txt`. This includes `pyinstaller` and `pywebview`, which is required for trame desktop applications.
10
+
11
+ Next, take a look at the `run.py` file. This should be just a regular python script that starts your trame desktop application. It can be tested via `python run.py`. Due to multiprocessing in some applications, `multiprocessing.freeze_support()` may be required to avoid an infinite recursion of running the script.
12
+
13
+ Once `python run.py` seems to be working properly, take a look at `create_exe.bat`. This is the pyinstaller command that we will use. Explanations for each option are provided:
14
+
15
+ 1. `--hidden-import vtkmodules.all`: pyinstaller tries to find every dependency, but sometimes it is not able to. You can add additional python dependencies via the `--hidden-import` option, which may be specified multiple times.
16
+
17
+ For VTK applications, we can include all VTK modules, or to reduce the final binary size, only include the VTK modules that are actually used. The VTK hooks in pyinstaller should be updated in the future so that this is not necessary; see [pyinstaller/pyinstaller-hooks-contrib#327](https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/327) for details.
18
+
19
+ 2. `--collect-data pywebvue`: normally, pyinstaller will only install python dependencies. For trame, however, we need some javascript files too. Adding this command will get pyinstaller to install the `pywebvue` javascript files as well.
20
+
21
+ 3. `--onefile`: create a single executable for the output. `pyinstaller` by default creates a folder that contains the executable. The single executable is stand-alone and easy to move between computers. The only downside is that it takes a little longer to start and stop compared to the folder executable, because it must unpack itself before starting.
22
+
23
+ Note: if you are debugging your `pyinstaller` application, consider removing this option. It is much easier to see whether files/folders ended up in the right place in the folder executable mode.
24
+
25
+ 4. `--windowed`: on Mac and Windows, this prevents the terminal/command prompt from being opened when the application is started.
26
+
27
+ 5. `--icon wind-field-visualizer.ico`: the icon file to use for the application.
28
+
29
+ After running `pyinstaller`, the standalone application may be found inside the `dist` directory that is created.
30
+
31
+ ## MacOS
32
+
33
+ ```bash
34
+ pip install -r ./requirements.txt
35
+ ./create_mac.sh
36
+ open ./dist/run.app
37
+ ```
38
+
39
+ ## Linux
40
+
41
+ ```bash
42
+ pip install -r ./requirements.txt
43
+ ./create_linux.sh
44
+ ./dist/run
45
+ ```
46
+
47
+ ## Windows
48
+
49
+ ```bash
50
+ pip install -r requirements.txt
51
+ ./create_exe.bat
52
+ ./dist/run/run.exe
53
+ ```
bundles/desktop/create_exe.bat ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ python -m PyInstaller ^
2
+ --hidden-import pkgutil ^
3
+ --windowed ^
4
+ --collect-data trame_vuetify ^
5
+ --collect-data trame_vtk ^
6
+ --collect-data trame_client ^
7
+ .\run.py
bundles/desktop/create_linux.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ python -m PyInstaller \
4
+ --clean --noconfirm \
5
+ --hidden-import pkgutil \
6
+ --collect-data trame_vuetify \
7
+ --collect-data trame_vtk \
8
+ --collect-data trame_client \
9
+ ./run.py
bundles/desktop/create_mac.sh ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ python -m PyInstaller \
4
+ --clean --noconfirm \
5
+ --windowed \
6
+ --hidden-import pkgutil \
7
+ --collect-data trame_vuetify \
8
+ --collect-data trame_vtk \
9
+ --collect-data trame_client \
10
+ ./run.py
bundles/desktop/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ pyinstaller
2
+ pywebview
bundles/desktop/run.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import multiprocessing
2
+
3
+ from wind_field_visualizer.app.main import main
4
+
5
+ if __name__ == "__main__":
6
+ multiprocessing.freeze_support()
7
+ main(exec_mode="desktop")
bundles/docker/DEPLOY.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Trame deployment
2
+
3
+ Trame applications can be deployed following different patterns.
4
+ The one describe below is the simplest one and will only scale up to what the hardware is capable of handling.
5
+ For infinite scaling feel free to consult [Kitware](https://www.kitware.com/contact/) for more guidance.
6
+
7
+ ## Docker
8
+
9
+ This directory provide the core infrastructure for building docker images that can then be deployed your own way.
10
+ But if you are looking for something more Heroku like we suggest using [CapRover](https://caprover.com/).
11
+
12
+ ## Caprover
13
+
14
+ For that section we will assumed you've setup your own CapRover and you are just aiming to deploy your trame application using the caprover cli from npm.
15
+
16
+ Before any deployment, you need to create an application from the web interface and check the __Websocket Support__.
17
+
18
+ Then within the directory that contain this `DEPLOY.md`, you should run the following:
19
+
20
+ ```bash
21
+ rm -rf ./server
22
+ ./scripts/build_server.sh
23
+ tar -cvf trame-app.tar captain-definition Dockerfile server
24
+ caprover deploy -t trame-app.tar
25
+ ```
26
+
27
+ It might be necessary to increase the nginx __client_max_body_size__ for your app upload.
28
+ To do that go in: __CapRover > Settings > NGINX Configurations > /etc/nginx/conf.d/captain-root.conf__
29
+
30
+
31
+ ```json
32
+ # Captain dashboard at captain.captainroot.domain.com
33
+ server {
34
+ client_max_body_size 500m;
35
+ ```
36
+
37
+ For GPU access you will need to create a `/etc/docker/daemon.json` with the following content where `GPU-f0ada461` come from the command line below
38
+
39
+ ```bash
40
+ nvidia-smi -a | grep GPU-UUID
41
+ GPU UUID : GPU-f0ada461-5941-6211-dad3-a5003817fb59
42
+ ```
43
+
44
+ => `/etc/docker/daemon.json`
45
+ ```json
46
+ {
47
+ "runtimes": {
48
+ "nvidia": {
49
+ "path": "/usr/bin/nvidia-container-runtime",
50
+ "runtimeArgs": []
51
+ }
52
+ },
53
+ "default-runtime": "nvidia",
54
+ "node-generic-resources": [
55
+ "NVIDIA-GPU=GPU-f0ada461"
56
+ ]
57
+ }
58
+ ```
bundles/docker/Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM kitware/trame:uv
2
+
3
+ # Set Python and Vue versions
4
+ ENV TRAME_PYTHON=3.13
5
+ ENV TRAME_CLIENT_TYPE=vue3
6
+
7
+ # Copy the local app and the deploy directory
8
+ COPY --chown=trame-user:trame-user . /local-app
9
+ COPY --chown=trame-user:trame-user ./bundles/docker /deploy
10
+
11
+ RUN /opt/trame/entrypoint.sh build
bundles/docker/README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Docker Bundle
2
+
3
+ Docker bundle aims to provide an easy step to bundle and deploy your application into a Docker image that can easily be deployed in the cloud as a service and will naturally support multi-users.
4
+
5
+ ## Building the server directory
6
+
7
+ The **server** directory capture all the key elements of your application and can be used with our generic docker image.
8
+
9
+ To generate that directory, just run the **scripts/build_server.sh** script.
10
+
11
+ ## Building your docker image
12
+
13
+ Your docker image is simply bundling the **server** directory into a docker image to simplify possible deployment.
14
+
15
+ To generate that image, just run the **scripts/build_image.sh** script.
16
+
17
+ ## Running your bundle
18
+
19
+ Just run either `scripts/run_server.sh` or `scripts/run_image.sh` and open your browser to `http://localhost:8080/wind-field-visualizer.html`
bundles/docker/captain-definition ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "schemaVersion": 2,
3
+ "dockerfilePath": "./Dockerfile"
4
+ }
bundles/docker/scripts/build_image.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ CURRENT_DIR=`dirname "$0"`
3
+
4
+ cd $CURRENT_DIR/../../..
5
+
6
+ docker build -t wind-field-visualizer -f ./bundles/docker/Dockerfile .
bundles/docker/scripts/run_image.sh ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ #!/usr/bin/env bash
2
+ docker run -it --rm -p 8080:80 wind-field-visualizer
bundles/docker/setup/apps.yml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ trame: # Default app under /index.html
2
+ app: wind-field-visualizer
3
+ wind-field-visualizer: # /wind-field-visualizer.html
4
+ app: wind-field-visualizer
bundles/docker/setup/initialize.sh ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Install app from local directory
2
+ uv pip install /local-app
bundles/docker/setup/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Use published version instead of local-app from initialize.sh
2
+ # wind-field-visualizer
pyproject.toml ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "wind-field-visualizer"
3
+ version = "1.0.0"
4
+ description = "A web-based wind field visualizer"
5
+ authors = [
6
+ {name = "Xuanchen Zhou"},
7
+ ]
8
+ dependencies = [
9
+ "trame>=3.12",
10
+ "trame-vuetify",
11
+ "trame-vtk",
12
+ ]
13
+ requires-python = ">=3.9"
14
+ readme = "README.rst"
15
+ license = {text = "BSD License"}
16
+ keywords = ["Python", "Interactive", "Web", "Application", "Framework"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Web Environment",
20
+ "License :: OSI Approved :: BSD License",
21
+ "Natural Language :: English",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ app = [
30
+ "pywebview",
31
+ ]
32
+ jupyter = [
33
+ "jupyterlab",
34
+ ]
35
+ dev = [
36
+ "pre-commit",
37
+ "ruff",
38
+ "pytest >=6",
39
+ "pytest-cov >=3",
40
+ "nox",
41
+ ]
42
+
43
+ [project.scripts]
44
+ wind-field-visualizer = "wind_field_visualizer.app:main"
45
+
46
+ [build-system]
47
+ requires = ["hatchling"]
48
+ build-backend = "hatchling.build"
49
+
50
+
51
+ [tool.hatch.build]
52
+ include = [
53
+ "/src/wind_field_visualizer/**/*.py",
54
+ "/src/wind_field_visualizer/**/*.js",
55
+ "/src/wind_field_visualizer/**/*.css",
56
+ ]
57
+
58
+ [tool.hatch.build.targets.wheel]
59
+ packages = [
60
+ "src/wind_field_visualizer",
61
+ ]
62
+
63
+ [tool.ruff]
64
+
65
+ [tool.ruff.lint]
66
+ extend-select = [
67
+ "ARG", # flake8-unused-arguments
68
+ "B", # flake8-bugbear
69
+ "C4", # flake8-comprehensions
70
+ "EM", # flake8-errmsg
71
+ "EXE", # flake8-executable
72
+ "G", # flake8-logging-format
73
+ "I", # isort
74
+ "ICN", # flake8-import-conventions
75
+ "NPY", # NumPy specific rules
76
+ "PD", # pandas-vet
77
+ "PGH", # pygrep-hooks
78
+ "PIE", # flake8-pie
79
+ "PL", # pylint
80
+ "PT", # flake8-pytest-style
81
+ "PTH", # flake8-use-pathlib
82
+ "RET", # flake8-return
83
+ "RUF", # Ruff-specific
84
+ "SIM", # flake8-simplify
85
+ "T20", # flake8-print
86
+ "UP", # pyupgrade
87
+ "YTT", # flake8-2020
88
+ ]
89
+ ignore = [
90
+ "PLR09", # Too many <...>
91
+ "PLR2004", # Magic value used in comparison
92
+ "ISC001", # Conflicts with formatter
93
+ ]
94
+ isort.required-imports = []
95
+
96
+ [tool.ruff.lint.per-file-ignores]
97
+ "tests/**" = ["T20"]
98
+ "noxfile.py" = ["T20"]
99
+ "src/**" = ["SIM117"]
src/wind_field_visualizer/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = "1.0.0"
src/wind_field_visualizer/app/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from .main import main
2
+
3
+ __all__ = [
4
+ "main",
5
+ ]
src/wind_field_visualizer/app/main.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from trame.app import get_server
3
+ from trame.ui.vuetify import SinglePageLayout
4
+ from trame.widgets import vuetify
5
+ from trame.widgets import vtk as vtk_widget
6
+ import vtk
7
+ import xarray as xr
8
+ import pyvista as pv
9
+ import numpy as np
10
+
11
+ import matplotlib.pyplot as plt
12
+ from vtkmodules.vtkCommonCore import vtkLookupTable
13
+
14
+ from vtkmodules.vtkCommonColor import vtkNamedColors
15
+ from vtkmodules.vtkCommonDataModel import vtkPlane
16
+ from vtkmodules.vtkFiltersCore import vtkCutter
17
+ from vtkmodules.vtkFiltersModeling import vtkOutlineFilter
18
+ from vtkmodules.vtkRenderingCore import (
19
+ vtkActor,
20
+ vtkPolyDataMapper,
21
+ vtkRenderer,
22
+ vtkRenderWindow,
23
+ vtkRenderWindowInteractor,
24
+ )
25
+
26
+ def main(server=None, **kwargs):
27
+ CURRENT_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
28
+
29
+ # -----------------------------------------------------------------------------
30
+ # NetCDF to VTK
31
+ # -----------------------------------------------------------------------------
32
+ ds = xr.open_dataset(os.path.join(CURRENT_DIRECTORY, "../../../../../Data/Field_20251119_140509_BuildingGroupFloor.nc"))
33
+
34
+ # ----- Load fluid field -----
35
+ # Vtk uses right-handed coordinate system, while unity uses left-handed coordinate system. So the
36
+ # z asix needs to be reversed here.
37
+ velFieldRaw = ds["velField"][:, ::-1, :, :]
38
+ nTime, nz, ny, nx, _ = velFieldRaw.shape
39
+ print(f"Fluid field resolution: {nx=}, {ny=}, {nz=}")
40
+
41
+ # ----- Load dx -----
42
+ velXCoor = ds["velX"]
43
+ dx = float(velXCoor.values[1] - velXCoor.values[0])
44
+ print(f"{dx=}")
45
+
46
+ # ----- Load dt -----
47
+ timeList = ds["time"]
48
+ dt = float(timeList.values[1] - timeList.values[0])
49
+ dt = round(dt, 2)
50
+ print(f"{dt=}")
51
+
52
+ # ----- Fluid field at the first stored time step -----
53
+ velField0 = velFieldRaw[0, :, :, :, :3]
54
+ velFieldMag0 = np.linalg.norm(velField0.values, axis=-1).ravel()
55
+
56
+ # ----- Build vtk data -----
57
+ grid = pv.ImageData()
58
+ grid.dimensions = (nx, ny, nz)
59
+ grid.origin = (-(nx-1) * dx / 2, -(ny-1) * dx / 2 + 10, -(nz-1) * dx / 2)
60
+ grid.spacing = (dx, dx, dx)
61
+ grid.point_data["vel_mag"] = velFieldMag0
62
+
63
+ vtk_producer = vtk.vtkTrivialProducer()
64
+ vtk_producer.SetOutput(grid)
65
+
66
+ # -----------------------------------------------------------------------------
67
+ # PLY Reader (for building model)
68
+ # -----------------------------------------------------------------------------
69
+
70
+ modelReader = vtk.vtkPLYReader()
71
+ # modelReader.SetFileName("Data/SmallBuilding3.ply")
72
+ modelReader.SetFileName(os.path.join(CURRENT_DIRECTORY, "../../../../Data/Buildings-GroupAll.ply"))
73
+ modelReader.Update()
74
+
75
+ modelMapper = vtk.vtkPolyDataMapper()
76
+ modelMapper.SetInputConnection(modelReader.GetOutputPort())
77
+
78
+ modelActor = vtk.vtkActor()
79
+ modelActor.SetMapper(modelMapper)
80
+
81
+ transform = vtk.vtkTransform()
82
+
83
+ transform.PostMultiply()
84
+
85
+ # Small building 3
86
+ # transform.RotateX(-90.0)
87
+ # transform.RotateY(50.0)
88
+
89
+ # Building Group
90
+ transform.Translate(25, 0.2, -12)
91
+ transform.RotateX(180.0)
92
+ transform.RotateY(180.0)
93
+
94
+ modelActor.SetUserTransform(transform)
95
+
96
+ # -----------------------------------------------------------------------------
97
+ # Render VTK fluid field
98
+ # -----------------------------------------------------------------------------
99
+
100
+ renderer = vtkRenderer()
101
+ renderWindow = vtkRenderWindow()
102
+ renderWindow.AddRenderer(renderer)
103
+
104
+ renderWindowInteractor = vtkRenderWindowInteractor()
105
+ renderWindowInteractor.SetRenderWindow(renderWindow)
106
+ renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
107
+
108
+ # ----- Render outline -----
109
+
110
+ colors = vtkNamedColors()
111
+
112
+ outline = vtkOutlineFilter()
113
+ outline.SetInputConnection(vtk_producer.GetOutputPort())
114
+
115
+ outlineMapper = vtkPolyDataMapper()
116
+ outlineMapper.SetInputConnection(outline.GetOutputPort())
117
+
118
+ outlineActor = vtkActor()
119
+ outlineActor.SetMapper(outlineMapper)
120
+ outlineActor.GetProperty().SetColor(colors.GetColor3d("White"))
121
+
122
+ # ----- Colormap -----
123
+ def create_vtk_lut_from_matplotlib(cmap_name, num_colors=256):
124
+ mpl_cmap = plt.get_cmap(cmap_name)
125
+
126
+ lut = vtkLookupTable()
127
+ lut.SetNumberOfTableValues(num_colors)
128
+
129
+ for i in range(num_colors):
130
+ rgba = mpl_cmap(i / (num_colors - 1))
131
+ lut.SetTableValue(i, rgba[0], rgba[1], rgba[2], 1.0)
132
+
133
+ lut.SetRange(0, 15)
134
+ lut.Build()
135
+ return lut
136
+
137
+ turbo_lut = create_vtk_lut_from_matplotlib("turbo")
138
+
139
+ # ----- Render a slice -----
140
+ center_x = grid.center[0]
141
+ center_y = grid.center[1]
142
+ center_z = grid.center[2]
143
+ plane = vtkPlane()
144
+ plane.SetOrigin(center_x, center_y, center_z)
145
+ plane.SetNormal(0, 1, 0)
146
+ print(f"{grid.center[1]=}")
147
+
148
+ cutter = vtkCutter()
149
+ cutter.SetInputConnection(vtk_producer.GetOutputPort())
150
+ cutter.SetCutFunction(plane)
151
+ cutter.Update()
152
+
153
+ slice_mapper = vtkPolyDataMapper()
154
+ slice_mapper.SetInputConnection(cutter.GetOutputPort())
155
+ slice_mapper.SetScalarModeToUsePointFieldData()
156
+ slice_mapper.SelectColorArray("vel_mag")
157
+ slice_mapper.SetLookupTable(turbo_lut)
158
+ slice_mapper.SetScalarRange(0, 15)
159
+
160
+ slice_actor = vtkActor()
161
+ slice_actor.SetMapper(slice_mapper)
162
+
163
+ # Add the actors to the renderer
164
+ renderer.AddActor(outlineActor)
165
+ renderer.AddActor(slice_actor)
166
+ renderer.AddActor(modelActor)
167
+ renderer.ResetCamera()
168
+ renderWindow.Render()
169
+
170
+ # -----------------------------------------------------------------------------
171
+ # GUI
172
+ # -----------------------------------------------------------------------------
173
+
174
+ server = get_server(client_type = "vue2")
175
+ ctrl = server.controller
176
+ state = server.state
177
+
178
+ @state.change("time")
179
+ def update_time(time, **kwargs):
180
+ time_index = round(time / dt) - 1
181
+ newVelField = velFieldRaw[time_index, :, :, :, :3]
182
+ newVelMagFlat = np.linalg.norm(newVelField.values, axis=-1).ravel()
183
+ grid.point_data["vel_mag"] = newVelMagFlat
184
+ grid.Modified()
185
+ ctrl.view_update()
186
+
187
+ @state.change("ypos")
188
+ def update_ypos(ypos, **kwargs):
189
+ plane.SetOrigin(center_x, ypos, center_z)
190
+ ctrl.view_update()
191
+
192
+ def reset_ypos():
193
+ state.ypos = center_y
194
+
195
+ with SinglePageLayout(server) as layout:
196
+ layout.title.set_text("Fluid Field Visualizer")
197
+
198
+ with layout.toolbar:
199
+ vuetify.VSpacer()
200
+
201
+ # Time slider
202
+ vuetify.VSlider(
203
+ label="Time (s)",
204
+ v_model=("time", 0),
205
+ min=dt,
206
+ max=nTime * dt,
207
+ step=dt,
208
+ thumb_label=True,
209
+ hide_details=True,
210
+ dense=True,
211
+ style="max-width: 300px; margin-top: 40px; margin-bottom: 10px;",
212
+ )
213
+ vuetify.VDivider(vertical=True, classes="mx-2")
214
+
215
+ # Slice position silder
216
+ vuetify.VSlider(
217
+ label="Y Position (m)",
218
+ v_model=("ypos", center_y),
219
+ min=grid.bounds[2] + dx,
220
+ max=grid.bounds[3] - dx,
221
+ step=dx,
222
+ thumb_label=True,
223
+ hide_details=True,
224
+ dense=True,
225
+ style="max-width: 300px; margin-top: 40px; margin-bottom: 10px;",
226
+ )
227
+ with vuetify.VBtn(icon=True, click=reset_ypos):
228
+ vuetify.VIcon("mdi-restore")
229
+ vuetify.VDivider(vertical=True, classes="mx-2")
230
+
231
+ with layout.content:
232
+ with vuetify.VContainer(
233
+ fluid=True,
234
+ classes="pa-0 fill-height",
235
+ ):
236
+ view = vtk_widget.VtkLocalView(renderWindow)
237
+ ctrl.view_update = view.update
238
+
239
+ server.start()
240
+
241
+ if __name__ == "__main__":
242
+ main()