diff --git a/.github/workflows/build_binder.yml b/.github/workflows/build_binder.yml
deleted file mode 100644
index fb6b6b29..00000000
--- a/.github/workflows/build_binder.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: Trigger Binder build
-on:
- push:
- branches:
- - main
-
-# Allow only one concurrent build, skipping runs queued between the run in-progress and latest queued.
-# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
-concurrency:
- group: "build_binder"
- cancel-in-progress: false
-
-jobs:
- trigger-binder-build:
- runs-on: ubuntu-latest
- steps:
- - uses: s-weigand/trigger-mybinder-build@v1
- with:
- target-repo: beamme-py/beamme/HEAD
- service-name: gh
- debug: true
diff --git a/.github/workflows/check_code.yml b/.github/workflows/check_code.yml
deleted file mode 100644
index 0d2ee751..00000000
--- a/.github/workflows/check_code.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Code quality
-
-on:
- schedule:
- - cron: '0 06 * * *'
- push:
- branches:
- - main
- pull_request:
- types:
- - opened
- - reopened
- - synchronize
- workflow_dispatch:
- type: choice
-
-jobs:
- code-check:
- name: Code check
- runs-on: ubuntu-latest
- defaults:
- run:
- shell: bash
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- - name: Setup virtual python environment
- uses: ./.github/actions/setup_virtual_python_environment
- - name: Code quality checks
- uses: ./.github/actions/code_check
diff --git a/.github/workflows/check_meshpy_redirects.yml b/.github/workflows/check_meshpy_redirects.yml
deleted file mode 100644
index 76040269..00000000
--- a/.github/workflows/check_meshpy_redirects.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Check MeshPy to BeamMe redirects
-
-on:
- schedule:
- - cron: "0 06 * * *"
-
-jobs:
- check-redirects:
- runs-on: ubuntu-latest
- steps:
- - name: Check GitHub repo redirect
- run: |
- final_url=$(curl -sL -o /dev/null -w "%{url_effective}" "https://github.com/imcs-compsim/meshpy/")
- echo "Repo redirect: $final_url"
- [ "$final_url" = "https://github.com/beamme-py/beamme" ] || {
- echo "Repo redirect failed"
- exit 1
- }
-
- - name: Check GitHub Pages meta redirect
- run: |
- redirect_url=$(curl -s "https://imcs-compsim.github.io/meshpy/" | grep -i 'http-equiv="refresh"' | sed -E 's/.*content="[0-9]+;\s*URL=([^"]*)".*/\1/I')
- echo "Pages redirect: $redirect_url"
- [ "$redirect_url" = "https://beamme-py.github.io/beamme/" ] || {
- echo "Pages redirect failed"
- exit 1
- }
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
deleted file mode 100644
index f0abf1ef..00000000
--- a/.github/workflows/documentation.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: Build documentation
-
-on:
- # Only build the documentation once the test suite has completed on the main branch
- workflow_run:
- workflows: [Protected test suite (can access cubit secrets)]
- types: [completed]
- branches: [main]
-
-jobs:
- build_documentation:
- name: Build documentation
- # Only run if a push to main occurred, do not run after nightly cron job
- if: ${{ github.event.workflow_run.event == 'push' }}
- runs-on: ubuntu-latest
- defaults:
- run:
- shell: bash
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- - name: Setup virtual python environment
- uses: ./.github/actions/setup_virtual_python_environment
- - name: Install dependencies
- run: |
- cd ${GITHUB_WORKSPACE}
- source python-workflow-venv/bin/activate
- pip install -e .[dev,fourc]
- - name: Build API documentation
- run: |
- source python-workflow-venv/bin/activate
- pdoc --math --docformat google --output-dir api-documentation src/beamme/
- - name: Upload API documentation artifact
- uses: actions/upload-artifact@v4
- with:
- name: api-documentation
- path: api-documentation/
diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
deleted file mode 100644
index b90926bd..00000000
--- a/.github/workflows/testing.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: Test suite
-
-on:
- schedule:
- - cron: '0 06 * * *'
- push:
- branches:
- - main
- pull_request:
- types:
- - opened
- - reopened
- - synchronize
- workflow_dispatch:
-
-jobs:
- beamme-testing:
- name: ${{ matrix.os-version }} python${{ matrix.python-version }}
- strategy:
- fail-fast: false
- matrix:
- os-version: [ubuntu-latest, macos-latest, windows-latest]
- python-version: ["3.10", "3.13"]
- runs-on: ${{ matrix.os-version }}
- steps:
- - name: Checkout PR code
- uses: actions/checkout@v4
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
- - name: Run the test suite
- uses: ./.github/actions/run_tests
- with:
- # Test coverage and editable install with Python 3.10, otherwise we use a
- # non-editable installation and turn of coverage, because the coverage
- # only works in editable mode.
- install-command: >-
- ${{ matrix.python-version == '3.10' && '-e .[dev,fourc]' || '.[dev,fourc]'}}
- # The single space in the empty string is required, otherwise GitHub
- # evaluates the if clause wrong.
- additional-pytest-flags: >-
- ${{ matrix.python-version == '3.10' && ' ' || '--no-cov' }}
- - name: Upload test results on failure
- if: failure()
- uses: actions/upload-artifact@v4
- with:
- name: ${{ github.job }}-${{ matrix.os-version }}-python${{ matrix.python-version }}-${{ github.run_number }}
- path: ${{ env.PYTEST_TMPDIR }}
diff --git a/.github/workflows/testing_protected.yml b/.github/workflows/testing_protected.yml
deleted file mode 100644
index 1e5c775b..00000000
--- a/.github/workflows/testing_protected.yml
+++ /dev/null
@@ -1,120 +0,0 @@
-name: Protected test suite (can access cubit secrets)
-
-on:
- schedule:
- - cron: '0 06 * * *'
- push:
- branches:
- - main
- pull_request_target:
- types:
- - opened
- - reopened
- - synchronize
- workflow_dispatch:
-
-env:
- CUBIT_DOWNLOAD_URL: https://f002.backblazeb2.com/file/cubit-downloads/Coreform-Cubit/Releases/Linux/Coreform-Cubit-2025.8%2B61943-Lin64.tar.gz
-
-jobs:
- beamme-testing-all-dependencies:
- name: ubuntu-latest with all dependencies
- runs-on: ubuntu-latest
- environment:
- # Use the trusted environment only for PRs authored by an COLLABORATOR (no approval needed); all others use the untrusted environment (someone has to approve the workflow run).
- # Otherwise (scheduled or merge triggers) use the trusted environment (no approval needed).
- name: ${{ github.event_name == 'pull_request_target' && (github.event.pull_request.author_association == 'COLLABORATOR') && 'cubit_secrets_trusted' || (github.event_name == 'pull_request_target' && 'cubit_secrets_untrusted' || 'cubit_secrets_trusted') }}
- container:
- image: ghcr.io/4c-multiphysics/4c:main
- defaults:
- run:
- shell: bash
- steps:
- - name: Checkout PR code
- uses: actions/checkout@v4
- with:
- submodules: true
- # For PR runs, checkout PR code; otherwise fallback to default branch or ref
- ref: ${{ github.event.pull_request.head.ref || github.ref }}
- repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
- - name: Setup cubit
- id: cubit
- uses: ./.github/actions/cubit_setup
- with:
- cubit_download_url: ${{ env.CUBIT_DOWNLOAD_URL }}
- cubit_email: ${{ secrets.CUBIT_EMAIL }}
- cubit_password: ${{ secrets.CUBIT_PASSWORD }}
- - name: Setup virtual python environment
- uses: ./.github/actions/setup_virtual_python_environment
- - name: Build ArborX geometric search
- uses: ./.github/actions/build_arborx_geometric_search
- - name: Run the test suite
- uses: ./.github/actions/run_tests
- with:
- source-command: "source python-workflow-venv/bin/activate"
- install-command: "-e .[cubitpy,dev,fourc]"
- additional-pytest-flags: "--4C --ArborX --CubitPy --cov-fail-under=93"
- cubit-root: ${{ steps.cubit.outputs.cubit_root }}
- - name: Free cubit license
- if: ${{ always() }}
- uses: ./.github/actions/cubit_finalize
- with:
- cubit-root: ${{ steps.cubit.outputs.cubit_root }}
- - name: Upload test results on failure
- if: failure()
- uses: actions/upload-artifact@v4
- with:
- name: ${{ github.job }}-${{ github.run_number }}
- path: ${{ env.PYTEST_TMPDIR }}
- - name: Coverage badge and report
- uses: ./.github/actions/coverage
-
- beamme-performance-testing:
- needs: beamme-testing-all-dependencies
- name: performance tests
- continue-on-error: true
- runs-on: ubuntu-latest
- environment:
- # Use the trusted environment only for PRs authored by an COLLABORATOR (no approval needed); all others use the untrusted environment (someone has to approve the workflow run).
- # Otherwise (scheduled or merge triggers) use the trusted environment (no approval needed).
- name: ${{ github.event_name == 'pull_request_target' && (github.event.pull_request.author_association == 'COLLABORATOR') && 'cubit_secrets_trusted' || (github.event_name == 'pull_request_target' && 'cubit_secrets_untrusted' || 'cubit_secrets_trusted') }}
- container:
- image: ghcr.io/4c-multiphysics/4c:main
- defaults:
- run:
- shell: bash
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- submodules: true
- # For PR runs, checkout PR code; otherwise fallback to default branch or ref
- ref: ${{ github.event.pull_request.head.ref || github.ref }}
- repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
- - name: Setup cubit
- id: cubit
- uses: ./.github/actions/cubit_setup
- with:
- cubit_download_url: ${{ env.CUBIT_DOWNLOAD_URL }}
- cubit_email: ${{ secrets.CUBIT_EMAIL }}
- cubit_password: ${{ secrets.CUBIT_PASSWORD }}
- - name: Setup virtual python environment
- uses: ./.github/actions/setup_virtual_python_environment
- - name: Run the test suite
- uses: ./.github/actions/run_tests
- with:
- install-command: ".[cubitpy,dev,fourc]"
- source-command: "source python-workflow-venv/bin/activate"
- additional-pytest-flags: "--performance-tests --exclude-standard-tests -s --no-cov"
- cubit-root: ${{ steps.cubit.outputs.cubit_root }}
- - name: Free cubit license
- if: ${{ always() }}
- uses: ./.github/actions/cubit_finalize
- with:
- cubit-root: ${{ steps.cubit.outputs.cubit_root }}
- - name: Upload test results on failure
- if: failure()
- uses: actions/upload-artifact@v4
- with:
- name: ${{ github.job }}-${{ github.run_number }}
- path: ${{ env.PYTEST_TMPDIR }}
diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index 7b566a33..0b47e1fc 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -1,22 +1,21 @@
name: Build and deploy website
on:
- # Only build and deploy the website once the documentation is built (and the test suite is completed => coverage report and badge are necessary)
- workflow_run:
- workflows: [Build documentation]
- types: [completed]
- branches: [main]
-
-# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
-# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
-concurrency:
- group: "pages"
- cancel-in-progress: false
+ schedule:
+ - cron: '0 06 * * *'
+ push:
+ branches:
+ - main
+ pull_request:
+ types:
+ - opened
+ - reopened
+ - synchronize
+ workflow_dispatch:
jobs:
build_website:
# Only run if documentation was built successfully (if it is skipped, it is not successful)
- if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -27,62 +26,23 @@ jobs:
python-version: '3.13'
- name: Install dependencies
run: |
+ sudo apt-get install -y libosmesa6-dev
+ sudo apt-get install -y pandoc
+ sudo apt-get install -y xvfb
python -m pip install --upgrade pip
pip install -r website/requirements.txt
+ pip install -e .[dev,fourc]
- name: Prepare docs for the website
run: |
python website/docs/prepare_docs.py
- name: Build Sphinx website
run: |
- sphinx-build -b html website/docs/source website/docs/build
+ export PYVISTA_OFF_SCREEN=true
+ export PYVISTA_USE_EGL=true
+ export VTK_DEFAULT_RENDER_WINDOW_OFFSCREEN=1
+ xvfb-run -a sphinx-build -b html website/docs/source website/docs/build
- name: Upload website artifact
uses: actions/upload-artifact@v4
with:
name: website
path: website/docs/build/
-
- deploy:
- environment:
- name: Website
- # TODO Check if this url is really necessary or if it works without it
- url: http://beamme-py.github.io/beamme
- runs-on: ubuntu-latest
- needs: build_website
- permissions:
- pages: write
- id-token: write
- contents: read
- steps:
- - name: Download website artifact
- uses: actions/download-artifact@v4
- with:
- name: website
- path: ${{ github.workspace }}
- - name: Download API documentation artifact
- uses: dawidd6/action-download-artifact@v8
- with:
- workflow: documentation.yml
- name: api-documentation
- path: ${{ github.workspace }}/api-documentation
- branch: ${{ github.event.repository.default_branch }}
- - name: Download coverage report artifact
- uses: dawidd6/action-download-artifact@v8
- with:
- workflow: testing_protected.yml
- name: coverage-report
- path: ${{ github.workspace }}/coverage-report
- branch: ${{ github.event.repository.default_branch }}
- - name: Download coverage badge artifact
- uses: dawidd6/action-download-artifact@v8
- with:
- workflow: testing_protected.yml
- name: coverage-badge
- path: ${{ github.workspace }}/coverage-badge
- branch: ${{ github.event.repository.default_branch }}
- - name: Upload all pages artifacts (website, coverage-report, coverage-badge, documentation)
- uses: actions/upload-pages-artifact@v3
- id: deployment
- with:
- path: ${{ github.workspace }}
- - name: Deploy to GitHub Pages
- uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
index 5099893d..92feec23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ api-documentation/*
# website folder which contains automatically copied files
website/docs/source/md/*
+website/docs/source/examples/*
### Automatically created ignores ###
diff --git a/examples/example_1_finite_rotations.ipynb b/examples/example_1_finite_rotations.ipynb
index 28aec940..e1d634f0 100644
--- a/examples/example_1_finite_rotations.ipynb
+++ b/examples/example_1_finite_rotations.ipynb
@@ -4,15 +4,22 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Example 1: Introduction to finite rotations in BeamMe\n",
- "$\n",
+ "$$\n",
"% Define TeX macros for this document\n",
"\\def\\vv#1{\\boldsymbol{#1}}\n",
"\\def\\mm#1{\\boldsymbol{#1}}\n",
"\\def\\R#1{\\mathbb{R}^{#1}}\n",
"\\def\\SO{SO(3)}\n",
"\\def\\triad{\\mm{\\Lambda}}\n",
- "$\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Example 1: Introduction to finite rotations in BeamMe\n",
+ "\n",
"When working with Cosserat continua in 3D, the mathematical treatment of finite rotations is required.\n",
"This example gives an overview of the finite rotation functionality in BeamMe.\n",
"For a more comprehensive and theoretical overview of finite rotations, the interested reader is referred to:\n",
@@ -217,12 +224,6 @@
"metadata": {},
"outputs": [],
"source": [
- "import vtk # We need to import vtk before pyvista for the TeX labels to work # noqa: F401, I001\n",
- "import pyvista as pv\n",
- "\n",
- "pv.set_jupyter_backend(\"trame\")\n",
- "\n",
- "\n",
"# Utility functionality for this example\n",
"from utils.example_1_utils import (\n",
" PyVistaPlotter,\n",
diff --git a/examples/example_2_core_mesh_generation_functions.ipynb b/examples/example_2_core_mesh_generation_functions.ipynb
index bc56e011..641d79b7 100644
--- a/examples/example_2_core_mesh_generation_functions.ipynb
+++ b/examples/example_2_core_mesh_generation_functions.ipynb
@@ -4,11 +4,18 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Example 2: Basic mesh generation functions\n",
- "$\n",
+ "$$\n",
"% Define TeX macros for this document\n",
"\\def\\vv#1{\\boldsymbol{#1}}\n",
- "$\n",
+ "$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Example 2: Basic mesh generation functions\n",
+ "\n",
"This example shall showcase the core mesh generation functions provided in BeamMe."
]
},
diff --git a/examples/utils/example_1_utils.py b/examples/utils/example_1_utils.py
index 72bb1afc..fb8eec19 100644
--- a/examples/utils/example_1_utils.py
+++ b/examples/utils/example_1_utils.py
@@ -21,12 +21,15 @@
# THE SOFTWARE.
"""This file contains utility functions for the first example."""
+# We need to import vtk before pyvista for the TeX labels to work
+import vtk # noqa: F401, I001
+
import numpy as np
import pyvista as pv
+import sys
from beamme.utils.environment import is_testing
-
-from .general_utils import reset_print_out
+import beamme.utils.visualization as visualization
def print_matrix(name, matrix):
@@ -112,6 +115,10 @@ def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
+ # PyVista changes the printout, so store the original print out
+ # and restore it when done.
+ self.stdout = sys.stdout
+
def __enter__(self):
"""Return the plotter with the given arguments."""
@@ -125,5 +132,5 @@ def __exit__(self, exc_type, exc_value, traceback):
console print out).
"""
if not is_testing():
- self.plotter.show()
- reset_print_out()
+ visualization.show_plotter(self.plotter, nbsphinx_export_3d_view=False)
+ sys.stdout = self.stdout
diff --git a/examples/utils/general_utils.py b/examples/utils/general_utils.py
deleted file mode 100644
index 2a313393..00000000
--- a/examples/utils/general_utils.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# The MIT License (MIT)
-#
-# Copyright (c) 2018-2025 BeamMe Authors
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-"""This file contains utility functions for the examples."""
-
-import sys
-
-import pyvista as pv
-
-from beamme.utils.environment import is_mybinder
-
-# Store the default system out, so we can reset it after pyvista changes it
-stdout = sys.stdout
-
-# If we are on mybinder, set to server side rendering
-if is_mybinder():
- pv.start_xvfb()
-
-
-def reset_print_out():
- """PyVista changes the printout, this resets it to the default."""
- sys.stdout = stdout
diff --git a/src/beamme/core/mesh.py b/src/beamme/core/mesh.py
index 553342e5..fdb909ad 100644
--- a/src/beamme/core/mesh.py
+++ b/src/beamme/core/mesh.py
@@ -68,6 +68,7 @@
from beamme.utils.nodes import get_nodal_coordinates as _get_nodal_coordinates
from beamme.utils.nodes import get_nodal_quaternions as _get_nodal_quaternions
from beamme.utils.nodes import get_nodes_by_function as _get_nodes_by_function
+from beamme.utils.visualization import show_plotter as _show_plotter
class Mesh:
@@ -999,7 +1000,7 @@ def display_pyvista(
plotter.add_mesh(solid_grid, color="white", show_edges=True, opacity=0.5)
if not _is_testing():
- plotter.show()
+ _show_plotter(plotter)
else:
return plotter
diff --git a/src/beamme/utils/environment.py b/src/beamme/utils/environment.py
index 4dbd05de..eebb9cf7 100644
--- a/src/beamme/utils/environment.py
+++ b/src/beamme/utils/environment.py
@@ -48,6 +48,11 @@ def is_mybinder():
return "BINDER_LAUNCH_HOST" in _os.environ.keys()
+def is_nbsphinx():
+ """Check if the current environment is running in nbsphinx."""
+ return "IS_NBSPHINX" in _os.environ
+
+
def is_testing():
"""Check if the current environment is a pytest testing run."""
return "PYTEST_CURRENT_TEST" in _os.environ
diff --git a/src/beamme/utils/visualization.py b/src/beamme/utils/visualization.py
new file mode 100644
index 00000000..c84614cf
--- /dev/null
+++ b/src/beamme/utils/visualization.py
@@ -0,0 +1,92 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2018-2025 BeamMe Authors
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+"""Helper functions for visualizations."""
+
+import os as _os
+import uuid as _uuid
+from pathlib import Path as _Path
+
+import ipywidgets as _widgets
+import pyvista as _pv
+from IPython.display import HTML as _HTML
+from IPython.display import IFrame as _IFrame
+from IPython.display import display as _display
+
+from beamme.utils.environment import is_mybinder as _is_mybinder
+from beamme.utils.environment import is_nbsphinx as _is_nbsphinx
+
+# Start virtual framebuffer for MyBinder environments
+if _is_mybinder():
+ _pv.start_xvfb()
+
+
+def show_plotter(plotter: _pv.Plotter, *, nbsphinx_export_3d_view: bool = True) -> None:
+ """Show a PyVista plotter.
+
+ This wrapper either directly displays the plotter, default
+ development use for BeamMe. For the website representation of the
+ examples, we export static and interactive versions of the plotter
+ that will be embedded in the documentation.
+
+ Args:
+ plotter: The PyVista plotter to show.
+ nbsphinx_export_3d_view: For nbsphinx documentation, whether to
+ export an interactive 3D view alongside the static image.
+ """
+
+ if not _is_nbsphinx():
+ plotter.show()
+ else:
+ # Path where static documents are stored for the website.
+ static_doc_path = _Path(_os.environ["PYVISTA_DOCS_STATIC"])
+
+ # Get a unique identifier for the current plotter
+ plotter_uid = str(_uuid.uuid4().hex)
+
+ # Export a screenshot of the plotter
+ plotter.screenshot(static_doc_path / f"{plotter_uid}.png")
+ static_frame_html = f"""
+
+ """
+
+ if nbsphinx_export_3d_view:
+ # Export a html representation of the plotter
+ plotter.export_html(static_doc_path / f"{plotter_uid}.html")
+
+ interactive_frame = _IFrame(
+ src=f"../_static/pyvista/{plotter_uid}.html",
+ width="100%",
+ height=600,
+ )
+ tab = _widgets.Tab(
+ children=[
+ _widgets.HTML(static_frame_html),
+ _widgets.HTML(interactive_frame._repr_html_()),
+ ]
+ )
+ tab.set_title(0, "Static Scene")
+ tab.set_title(1, "Interactive Scene")
+
+ _display(tab)
+ else:
+ _display(_HTML(static_frame_html))
diff --git a/website/README.md b/website/README.md
index f4b8a956..875f784f 100644
--- a/website/README.md
+++ b/website/README.md
@@ -18,7 +18,7 @@ In the source directory of BeamMe simply execute
python website/docs/prepare_docs.py
```
-to prepare the documents for the website build (currently only the readme gets copied into the website build directory).
+to prepare the documents for the website build.
Then build the website with
diff --git a/website/docs/prepare_docs.py b/website/docs/prepare_docs.py
index 51444d77..01b72213 100644
--- a/website/docs/prepare_docs.py
+++ b/website/docs/prepare_docs.py
@@ -29,17 +29,33 @@
def prepare_docs():
"""Prepare documentation for the website.
- Currently, this only copies the README.md file to the documentation
- directory.
- """
+ This function copies all relevant files for the website.
- markdown_dir = Path("website/docs/source/md")
+ Note: We remove the target directories before copying the files to ensure
+ that no stale files remain.
+ """
# create directory which contains all the markdown files
+ markdown_dir = Path("website/docs/source/md")
+ if markdown_dir.exists():
+ shutil.rmtree(markdown_dir)
os.makedirs(markdown_dir, exist_ok=True)
# copy readme
- shutil.copy("README.md", os.path.join(markdown_dir, "README.md"))
+ shutil.copy("README.md", markdown_dir / "README.md")
+
+ # create directory which contains all the example files
+ examples_source_dir = Path("examples")
+ examples_target_dir = Path("website/docs/source/examples")
+ if examples_target_dir.exists():
+ shutil.rmtree(examples_target_dir)
+ file_extensions_to_copy = {".ipynb", ".py"}
+ for file in examples_source_dir.rglob("*"):
+ if file.is_file() and file.suffix.lower() in file_extensions_to_copy:
+ rel_path = file.relative_to(examples_source_dir)
+ dest_path = examples_target_dir / rel_path
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy(file, dest_path)
if __name__ == "__main__":
diff --git a/website/docs/source/conf.py b/website/docs/source/conf.py
index 8140e4f8..ae68cc06 100644
--- a/website/docs/source/conf.py
+++ b/website/docs/source/conf.py
@@ -20,6 +20,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
+import os
+from pathlib import Path
+
# general configuration
project = "BeamMe"
copyright = "2025, BeamMe Authors"
@@ -36,6 +39,7 @@
# extensions
extensions = [
"myst_parser", # to enable Markdown support
+ "nbsphinx", # to enable Jupyter Notebook support
"sphinxcontrib.jquery", # to enable custom JavaScript (open links in new empty tab)
]
@@ -49,3 +53,38 @@
# JavaScript configuration (to open links in new tabs)
html_js_files = ["js/custom.js"]
html_static_path = ["static"]
+
+# Always execute notebooks to ensure outputs are up-to-date
+nbsphinx_execute = "always"
+
+# Prolog for nbsphinx to add a note with a link to the GitHub source and Binder
+# at the top of each rendered html page
+nbsphinx_prolog = r"""
+{% set docname = env.doc2path(env.docname, base=False)|string %}
+{% set binder_path = "/doc/tree/" ~ docname %}
+{% set binder_urlpath = binder_path | urlencode %}
+
+.. raw:: html
+
+