py
r"""
Installation requirements:
pip install trame trame-vuetify trame-vtk trame-components
"""
from pathlib import Path
from trame.app import get_server
from trame.ui.vuetify import SinglePageLayout
from trame.widgets import vuetify, html, trame, vtk as vtk_widgets
from vtkmodules.web.utils import mesh as vtk_mesh
from vtkmodules.vtkIOXML import vtkXMLPolyDataReader
from vtkmodules.vtkFiltersGeneral import vtkExtractSelectedFrustum
from vtkmodules.vtkFiltersCore import vtkThreshold
# -----------------------------------------------------------------------------
# Trame
# -----------------------------------------------------------------------------
server = get_server(client_type="vue2")
state, ctrl = server.state, server.controller
# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------
SCALE_P = 0.0001
SCALE_U = 0.01
VIEW_INTERACT = [
{"button": 1, "action": "Rotate"},
{"button": 2, "action": "Pan"},
{"button": 3, "action": "Zoom", "scrollEnabled": True},
{"button": 1, "action": "Pan", "alt": True},
{"button": 1, "action": "Zoom", "control": True},
{"button": 1, "action": "Pan", "shift": True},
{"button": 1, "action": "Roll", "alt": True, "shift": True},
]
VIEW_SELECT = [{"button": 1, "action": "Select"}]
# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------
data_directory = Path(__file__).parent.parent.parent.with_name("data")
f1_vtp = data_directory / "f1.vtp"
# print(f1_vtp)
reader = vtkXMLPolyDataReader()
reader.SetFileName(f1_vtp)
reader.Update()
f1_mesh = reader.GetOutput()
state.f1 = None
# Extract fieldParameters
fieldParameters = {"solid": {"range": [0, 1]}}
pd = f1_mesh.GetPointData()
nb_arrays = pd.GetNumberOfArrays()
for i in range(nb_arrays):
array = pd.GetArray(i)
name = array.GetName()
min, max = array.GetRange(-1)
fieldParameters[name] = {"name": name, "range": [min, max]}
# Frustrum extraction
extract = vtkExtractSelectedFrustum()
extract.SetInputConnection(reader.GetOutputPort())
threshold = vtkThreshold()
threshold.SetInputConnection(extract.GetOutputPort())
threshold.SetLowerThreshold(0)
threshold.SetInputArrayToProcess(0, 0, 0, 1, "vtkInsidedness") # 1 => cell
# -----------------------------------------------------------------------------
# Web App setup
# -----------------------------------------------------------------------------
state.trame__title = "F1 Probing"
state.update(
{
# Fields available
"fieldParameters": fieldParameters,
# picking controls
"modes": [
{"value": "hover", "icon": "mdi-magnify"},
{"value": "click", "icon": "mdi-cursor-default-click-outline"},
{"value": "select", "icon": "mdi-select-drag"},
],
# Picking feedback
"pickData": None,
"selectData": None,
"tooltip": "",
"coneVisibility": False,
"pixel_ratio": 2,
# Meshes
"f1Visible": True,
}
)
# -----------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------
@state.change("pickingMode")
def update_picking_mode(pickingMode, **kwargs):
mode = pickingMode
if mode is None:
state.update(
{
"tooltip": "",
"tooltipStyle": {"display": "none"},
"coneVisibility": False,
"interactorSettings": VIEW_INTERACT,
}
)
else:
state.interactorSettings = VIEW_SELECT if mode == "select" else VIEW_INTERACT
state.update(
{
"frustrum": None,
"selection": None,
"selectData": None,
}
)
@state.change("selectData")
def update_selection(selectData, **kwargs):
if selectData is None:
return
frustrum = selectData.get("frustrum")
vtk_frustrum = []
for xyz in frustrum:
vtk_frustrum += xyz
vtk_frustrum += [1]
extract.CreateFrustum(vtk_frustrum)
extract.ShowBoundsOn()
extract.PreserveTopologyOff()
extract.Update()
state.frustrum = vtk_mesh(extract.GetOutput())
extract.ShowBoundsOff()
extract.PreserveTopologyOn()
threshold.Update()
state.selection = vtk_mesh(threshold.GetOutput())
state.selectData = None
state.pickingMode = None
@state.change("pickData")
def update_tooltip(pickData, pixel_ratio, **kwargs):
state.tooltip = ""
state.tooltipStyle = {"display": "none"}
state.coneVisibility = False
data = pickData
if data and data["representationId"] == "f1":
xyx = data["worldPosition"]
idx = f1_mesh.FindPoint(xyx)
if idx > -1:
messages = []
cone_state = {
"resolution": 12,
"radius": pd.GetArray("p").GetValue(idx) * SCALE_P,
"center": f1_mesh.GetPoints().GetPoint(idx),
}
for i in range(nb_arrays):
array = pd.GetArray(i)
name = array.GetName()
nb_comp = array.GetNumberOfComponents()
value = array.GetValue(idx)
value_str = f"{array.GetValue(idx):.2f}"
norm_str = ""
if nb_comp == 3:
value = array.GetTuple3(idx)
norm = (value[0] ** 2 + value[1] ** 2 + value[2] ** 2) ** 0.5
norm_str = f" norm({norm:.2f})"
value_str = ", ".join([f"{v:.2f}" for v in value])
cone_state["height"] = SCALE_U * norm
cone_state["direction"] = [v / norm for v in value]
messages.append(f"{name}: {value_str} {norm_str}")
if "height" in cone_state:
new_center = [v for v in cone_state["center"]]
for i in range(3):
new_center[i] -= (
0.5 * cone_state["height"] * cone_state["direction"][i]
)
cone_state["center"] = new_center
if len(messages):
x, y, z = data["displayPosition"]
state.coneVisibility = True
state.tooltip = "\n".join(messages)
state.cone = cone_state
state.tooltipStyle = {
"position": "absolute",
"left": f"{(x / pixel_ratio )+ 10}px",
"bottom": f"{(y / pixel_ratio ) + 10}px",
"zIndex": 10,
"pointerEvents": "none",
}
with SinglePageLayout(server) as layout:
layout.title.set_text("F1 Probing")
# Let the server know the browser pixel ratio
trame.ClientTriggers(mounted="pixel_ratio = window.devicePixelRatio")
with layout.toolbar:
vuetify.VSpacer()
with vuetify.VBtnToggle(v_model=("pickingMode", "hover"), dense=True):
with vuetify.VBtn(value=("item.value",), v_for="item, idx in modes"):
vuetify.VIcon("{{item.icon}}")
vuetify.VSelect(
v_model=("field", "solid"),
items=(
"fields",
[
{"value": "solid", "text": "Solid color"},
{"value": "p", "text": "Pressure"},
{"value": "U", "text": "Velocity"},
],
),
classes="ml-8",
dense=True,
hide_details=True,
style="max-width: 140px",
)
vuetify.VSelect(
v_model=("colorMap", "erdc_rainbow_bright"),
# items=("''|vtkColorPresetItems",), # <= For trame-vtk<2.1
items=("trame.utils.vtk.vtkColorPresetItems('')",), # <= for trame-vtk>=2.1
classes="ml-8",
dense=True,
hide_details=True,
style="max-width: 200px",
)
vuetify.VSpacer()
with vuetify.VBtn(icon=True, click="f1Visible = !f1Visible"):
vuetify.VIcon("mdi-eye-outline", v_if="f1Visible")
vuetify.VIcon("mdi-eye-off-outline", v_if="!f1Visible")
with vuetify.VBtn(icon=True, click="$refs.view.resetCamera()"):
vuetify.VIcon("mdi-crop-free")
vuetify.VProgressLinear(
indeterminate=True, absolute=True, bottom=True, active=("trame__busy",)
)
with layout.content:
with vuetify.VContainer(
fluid=True, classes="pa-0 fill-height", style="position: relative;"
):
with vuetify.VCard(
style=("tooltipStyle", {"display": "none"}), elevation=2, outlined=True
):
with vuetify.VCardText():
html.Pre("{{ tooltip }}")
with vtk_widgets.VtkView(
ref="view",
picking_modes=("[pickingMode]",),
interactor_settings=("interactorSettings", VIEW_INTERACT),
click="pickData = $event",
hover="pickData = $event",
select="selectData = $event",
):
with vtk_widgets.VtkGeometryRepresentation(
id="f1",
v_if="f1",
color_map_preset=("colorMap",),
color_data_range=("fieldParameters[field].range",),
actor=("{ visibility: f1Visible }",),
mapper=(
"{ colorByArrayName: field, scalarMode: 3, interpolateScalarsBeforeMapping: true, scalarVisibility: field !== 'solid' }",
),
):
vtk_widgets.VtkMesh("f1", dataset=f1_mesh, point_arrays=["p", "U"])
with vtk_widgets.VtkGeometryRepresentation(
id="selection",
actor=("{ visibility: !!selection }",),
property=(
"{ color: [0.99,0.13,0.37], representation: 0, pointSize: Math.round(5 * pixel_ratio)}",
),
):
vtk_widgets.VtkMesh("selection", state=("selection", None))
with vtk_widgets.VtkGeometryRepresentation(
id="frustrum",
actor=("{ visibility: !!frustrum }",),
):
vtk_widgets.VtkMesh("frustrum", state=("frustrum", None))
with vtk_widgets.VtkGeometryRepresentation(
id="pointer",
property=("{ color: [1, 0, 0]}",),
actor=("{ visibility: coneVisibility }",),
):
vtk_widgets.VtkAlgorithm(
vtk_class="vtkConeSource",
state=("cone", {}),
)
# -----------------------------------------------------------------------------
# Jupyter
# -----------------------------------------------------------------------------
def show(**kwargs):
from trame.app import jupyter
jupyter.show(server, **kwargs)
# -----------------------------------------------------------------------------
# CLI
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()
r"""
Installation requirements:
pip install trame trame-vuetify trame-vtk trame-components
"""
from pathlib import Path
from trame.app import get_server
from trame.ui.vuetify import SinglePageLayout
from trame.widgets import vuetify, html, trame, vtk as vtk_widgets
from vtkmodules.web.utils import mesh as vtk_mesh
from vtkmodules.vtkIOXML import vtkXMLPolyDataReader
from vtkmodules.vtkFiltersGeneral import vtkExtractSelectedFrustum
from vtkmodules.vtkFiltersCore import vtkThreshold
# -----------------------------------------------------------------------------
# Trame
# -----------------------------------------------------------------------------
server = get_server(client_type="vue2")
state, ctrl = server.state, server.controller
# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------
SCALE_P = 0.0001
SCALE_U = 0.01
VIEW_INTERACT = [
{"button": 1, "action": "Rotate"},
{"button": 2, "action": "Pan"},
{"button": 3, "action": "Zoom", "scrollEnabled": True},
{"button": 1, "action": "Pan", "alt": True},
{"button": 1, "action": "Zoom", "control": True},
{"button": 1, "action": "Pan", "shift": True},
{"button": 1, "action": "Roll", "alt": True, "shift": True},
]
VIEW_SELECT = [{"button": 1, "action": "Select"}]
# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------
data_directory = Path(__file__).parent.parent.parent.with_name("data")
f1_vtp = data_directory / "f1.vtp"
# print(f1_vtp)
reader = vtkXMLPolyDataReader()
reader.SetFileName(f1_vtp)
reader.Update()
f1_mesh = reader.GetOutput()
state.f1 = None
# Extract fieldParameters
fieldParameters = {"solid": {"range": [0, 1]}}
pd = f1_mesh.GetPointData()
nb_arrays = pd.GetNumberOfArrays()
for i in range(nb_arrays):
array = pd.GetArray(i)
name = array.GetName()
min, max = array.GetRange(-1)
fieldParameters[name] = {"name": name, "range": [min, max]}
# Frustrum extraction
extract = vtkExtractSelectedFrustum()
extract.SetInputConnection(reader.GetOutputPort())
threshold = vtkThreshold()
threshold.SetInputConnection(extract.GetOutputPort())
threshold.SetLowerThreshold(0)
threshold.SetInputArrayToProcess(0, 0, 0, 1, "vtkInsidedness") # 1 => cell
# -----------------------------------------------------------------------------
# Web App setup
# -----------------------------------------------------------------------------
state.trame__title = "F1 Probing"
state.update(
{
# Fields available
"fieldParameters": fieldParameters,
# picking controls
"modes": [
{"value": "hover", "icon": "mdi-magnify"},
{"value": "click", "icon": "mdi-cursor-default-click-outline"},
{"value": "select", "icon": "mdi-select-drag"},
],
# Picking feedback
"pickData": None,
"selectData": None,
"tooltip": "",
"coneVisibility": False,
"pixel_ratio": 2,
# Meshes
"f1Visible": True,
}
)
# -----------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------
@state.change("pickingMode")
def update_picking_mode(pickingMode, **kwargs):
mode = pickingMode
if mode is None:
state.update(
{
"tooltip": "",
"tooltipStyle": {"display": "none"},
"coneVisibility": False,
"interactorSettings": VIEW_INTERACT,
}
)
else:
state.interactorSettings = VIEW_SELECT if mode == "select" else VIEW_INTERACT
state.update(
{
"frustrum": None,
"selection": None,
"selectData": None,
}
)
@state.change("selectData")
def update_selection(selectData, **kwargs):
if selectData is None:
return
frustrum = selectData.get("frustrum")
vtk_frustrum = []
for xyz in frustrum:
vtk_frustrum += xyz
vtk_frustrum += [1]
extract.CreateFrustum(vtk_frustrum)
extract.ShowBoundsOn()
extract.PreserveTopologyOff()
extract.Update()
state.frustrum = vtk_mesh(extract.GetOutput())
extract.ShowBoundsOff()
extract.PreserveTopologyOn()
threshold.Update()
state.selection = vtk_mesh(threshold.GetOutput())
state.selectData = None
state.pickingMode = None
@state.change("pickData")
def update_tooltip(pickData, pixel_ratio, **kwargs):
state.tooltip = ""
state.tooltipStyle = {"display": "none"}
state.coneVisibility = False
data = pickData
if data and data["representationId"] == "f1":
xyx = data["worldPosition"]
idx = f1_mesh.FindPoint(xyx)
if idx > -1:
messages = []
cone_state = {
"resolution": 12,
"radius": pd.GetArray("p").GetValue(idx) * SCALE_P,
"center": f1_mesh.GetPoints().GetPoint(idx),
}
for i in range(nb_arrays):
array = pd.GetArray(i)
name = array.GetName()
nb_comp = array.GetNumberOfComponents()
value = array.GetValue(idx)
value_str = f"{array.GetValue(idx):.2f}"
norm_str = ""
if nb_comp == 3:
value = array.GetTuple3(idx)
norm = (value[0] ** 2 + value[1] ** 2 + value[2] ** 2) ** 0.5
norm_str = f" norm({norm:.2f})"
value_str = ", ".join([f"{v:.2f}" for v in value])
cone_state["height"] = SCALE_U * norm
cone_state["direction"] = [v / norm for v in value]
messages.append(f"{name}: {value_str} {norm_str}")
if "height" in cone_state:
new_center = [v for v in cone_state["center"]]
for i in range(3):
new_center[i] -= (
0.5 * cone_state["height"] * cone_state["direction"][i]
)
cone_state["center"] = new_center
if len(messages):
x, y, z = data["displayPosition"]
state.coneVisibility = True
state.tooltip = "\n".join(messages)
state.cone = cone_state
state.tooltipStyle = {
"position": "absolute",
"left": f"{(x / pixel_ratio )+ 10}px",
"bottom": f"{(y / pixel_ratio ) + 10}px",
"zIndex": 10,
"pointerEvents": "none",
}
with SinglePageLayout(server) as layout:
layout.title.set_text("F1 Probing")
# Let the server know the browser pixel ratio
trame.ClientTriggers(mounted="pixel_ratio = window.devicePixelRatio")
with layout.toolbar:
vuetify.VSpacer()
with vuetify.VBtnToggle(v_model=("pickingMode", "hover"), dense=True):
with vuetify.VBtn(value=("item.value",), v_for="item, idx in modes"):
vuetify.VIcon("{{item.icon}}")
vuetify.VSelect(
v_model=("field", "solid"),
items=(
"fields",
[
{"value": "solid", "text": "Solid color"},
{"value": "p", "text": "Pressure"},
{"value": "U", "text": "Velocity"},
],
),
classes="ml-8",
dense=True,
hide_details=True,
style="max-width: 140px",
)
vuetify.VSelect(
v_model=("colorMap", "erdc_rainbow_bright"),
# items=("''|vtkColorPresetItems",), # <= For trame-vtk<2.1
items=("trame.utils.vtk.vtkColorPresetItems('')",), # <= for trame-vtk>=2.1
classes="ml-8",
dense=True,
hide_details=True,
style="max-width: 200px",
)
vuetify.VSpacer()
with vuetify.VBtn(icon=True, click="f1Visible = !f1Visible"):
vuetify.VIcon("mdi-eye-outline", v_if="f1Visible")
vuetify.VIcon("mdi-eye-off-outline", v_if="!f1Visible")
with vuetify.VBtn(icon=True, click="$refs.view.resetCamera()"):
vuetify.VIcon("mdi-crop-free")
vuetify.VProgressLinear(
indeterminate=True, absolute=True, bottom=True, active=("trame__busy",)
)
with layout.content:
with vuetify.VContainer(
fluid=True, classes="pa-0 fill-height", style="position: relative;"
):
with vuetify.VCard(
style=("tooltipStyle", {"display": "none"}), elevation=2, outlined=True
):
with vuetify.VCardText():
html.Pre("{{ tooltip }}")
with vtk_widgets.VtkView(
ref="view",
picking_modes=("[pickingMode]",),
interactor_settings=("interactorSettings", VIEW_INTERACT),
click="pickData = $event",
hover="pickData = $event",
select="selectData = $event",
):
with vtk_widgets.VtkGeometryRepresentation(
id="f1",
v_if="f1",
color_map_preset=("colorMap",),
color_data_range=("fieldParameters[field].range",),
actor=("{ visibility: f1Visible }",),
mapper=(
"{ colorByArrayName: field, scalarMode: 3, interpolateScalarsBeforeMapping: true, scalarVisibility: field !== 'solid' }",
),
):
vtk_widgets.VtkMesh("f1", dataset=f1_mesh, point_arrays=["p", "U"])
with vtk_widgets.VtkGeometryRepresentation(
id="selection",
actor=("{ visibility: !!selection }",),
property=(
"{ color: [0.99,0.13,0.37], representation: 0, pointSize: Math.round(5 * pixel_ratio)}",
),
):
vtk_widgets.VtkMesh("selection", state=("selection", None))
with vtk_widgets.VtkGeometryRepresentation(
id="frustrum",
actor=("{ visibility: !!frustrum }",),
):
vtk_widgets.VtkMesh("frustrum", state=("frustrum", None))
with vtk_widgets.VtkGeometryRepresentation(
id="pointer",
property=("{ color: [1, 0, 0]}",),
actor=("{ visibility: coneVisibility }",),
):
vtk_widgets.VtkAlgorithm(
vtk_class="vtkConeSource",
state=("cone", {}),
)
# -----------------------------------------------------------------------------
# Jupyter
# -----------------------------------------------------------------------------
def show(**kwargs):
from trame.app import jupyter
jupyter.show(server, **kwargs)
# -----------------------------------------------------------------------------
# CLI
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start()