-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
Hi, i'm trying to communicate some data from vue3 component to trame but it's seems is not working.
Following vue3 component:
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import sharedConfig from '@pomini-apps/shared/src/core/sharedConfig'
import { ModuleMediaRequest } from '@pomini-apps/shared/src/types/api-mediaservice'
interface Props {
req: ModuleMediaRequest
}
const props = defineProps<Props>()
const iframeRef = ref<HTMLIFrameElement | null>(null)
const receivedMessages = ref<string[]>([])
const iframeSrc = computed(() => {
if (sharedConfig.nodeEnv === 'development') return sharedConfig.urlMfePyTrame
if (sharedConfig.nodeEnv === 'production') return `${window.location.origin}/pytrame/`
throw new Error('getUrlPyTrame nodeEnv not provided :' + sharedConfig.nodeEnv)
})
const sendParametersToIframe = () => {
if (!iframeRef.value?.contentWindow) return
const messageObject = {
type: 'VTU_PARAMETERS',
data: {
id: props.req.id,
plantId: props.req.plantId,
productionId: props.req.productionId,
machineId: props.req.machineId,
module: props.req.module,
mediaType: props.req.mediaType,
mediaFormat: props.req.mediaFormat,
language: props.req.language,
presetId: props.req.presetId,
uiInfo: props.req.uiInfo,
timestamp: new Date().toISOString()
}
}
iframeRef.value.contentWindow.postMessage(
{
emit: 'parent_to_child', // Matches Trame communicator event
value: messageObject
},
'*'
)
console.log('Parameters sent to iframe:', messageObject)
}
// Handle messages FROM Trame iframe
const handleMessageFromIframe = (event: MessageEvent) => {
// Security: Check origin in production
// if (event.origin !== 'https://your-trame-domain.com') return;
if (!event.data || !event.data.emit) return
console.log('π¨ Message received from iframe:', event.data)
const message = `[${new Date().toLocaleTimeString()}] ${event.data.emit}: ${JSON.stringify(event.data.value)}`
receivedMessages.value.unshift(message)
if (receivedMessages.value.length > 10) receivedMessages.value.pop()
switch (event.data.emit) {
case 'child_to_parent':
console.log('Child to parent message:', event.data.value)
break
case 'app_ready':
console.log('π Trame app is ready!')
sendParametersToIframe() // Only send after Trame signals ready
break
case 'vtu_loaded':
console.log('β
VTU data loaded:', event.data.value)
break
case 'file_changed':
console.log('π File changed:', event.data.value)
break
}
}
onMounted(() => {
window.addEventListener('message', handleMessageFromIframe)
})
onUnmounted(() => {
window.removeEventListener('message', handleMessageFromIframe)
})
</script>
<template>
<div class="flex flex-col w-full h-full">
<!-- Iframe container -->
<div class="flex-1 relative">
<iframe ref="iframeRef" :src="iframeSrc" class="w-full h-full border-none" title="VTU player" />
</div>
</div>
</template>Following the python code:
"""Main VTU Player application class."""
import os
import sys
import signal
from typing import List, Dict, Optional, Tuple
from loguru import logger
from trame.app import get_server
from config.settings import config
from dataloader.dataloader import DataLoader
from visualization.vtk_pipeline import VTKPipeline
from visualization.enums import RenderMode, ColorMap
from ui.components import UIComponents
from ui.callbacks import CallbackManager
class VTUViewerApp:
"""Main VTU Player application."""
def __init__(self):
# --- Core services ---
self.data_loader = DataLoader(config)
self.vtk_pipeline = VTKPipeline(config)
# --- Trame server ---
self.server = get_server(client_type="vue3")
self.state = self.server.state # type: ignore
self.ctrl = self.server.controller # type: ignore
# --- UI & callbacks ---
self.ui_components = UIComponents(self.server, self.vtk_pipeline, config)
self.callback_manager = CallbackManager(
self.server, self.data_loader, self.vtk_pipeline
)
# --- Internal state ---
self.dataset_arrays: List[Dict] = []
self.default_array_range: Tuple[float, float] = (0.0, 1.0)
self.contour_value: float = 0.5
# --- Init ---
logger.info('INIT')
signal.signal(signal.SIGINT, self._signal_handler)
self._init_state()
# -------------------------------------------------------------------------
# Init helpers
# -------------------------------------------------------------------------
def _init_state(self):
"""Initialize application state with defaults."""
self.state.data_source = "remote"
self.state.selected_file = ""
self.state.available_files = []
self.state.msg_received = [] # ensure it's always a list
# Other initialization here...
# -------------------------------------------------------------------------
# Data initialization
# -------------------------------------------------------------------------
# Functions here for initialize data...
# -------------------------------------------------------------------------
# Communication handlers
# -------------------------------------------------------------------------
def parent_receive_msg(self, msg):
"""
Handle messages received from parent iframe.
Automatically unwraps the 'value' field sent from Vue.
"""
# Unwrap value if message is wrapped
if isinstance(msg, dict) and 'value' in msg:
msg = msg['value']
logger.info(f"π© Received message from parent: {msg}")
# Keep last 10 messages
if len(self.state.msg_received) >= 10:
self.state.msg_received.pop()
self.state.msg_received.insert(0, f"{msg}")
# Trigger VTU parameter handler
if isinstance(msg, dict) and msg.get("type") == "VTU_PARAMETERS":
self._handle_vtu_parameters(msg.get("data", {}))
self.state.dirty("msg_received")
def _notify_parent_ready(self):
"""Notify parent window that app is ready."""
logger.info("π App ready, notifying parent window")
self._send_to_parent({
"type": "app_ready", # This will be in event.data.value.type
"data": {
"selected_file": self.state.selected_file,
"available_files": self.state.available_files,
"timestamp": self._get_timestamp()
}
})
def _handle_vtu_parameters(self, parameters: Dict):
"""Handle VTU parameters and load the corresponding data."""
logger.info(f"β
VTU Parameters received: {parameters}")
# Store parameters
self.state.vtu_parameters = parameters
# Load data based on parameters
self.initialize_data_based_on_params(parameters)
# Send acknowledgment back to parent
self._send_to_parent({
"type": "vtu_loaded",
"data": {
"status": "success",
"plantId": parameters.get('plantId'),
"productionId": parameters.get('productionId'),
"selected_file": self.state.selected_file,
"timestamp": self._get_timestamp()
}
})
def _get_timestamp(self) -> str:
"""Get current timestamp string."""
from datetime import datetime
return datetime.now().isoformat()
def _send_to_parent(self, message):
"""Send message to parent iframe."""
if hasattr(self.ctrl, 'child_post_message'):
try:
# The event name should match what the Vue component expects
self.ctrl.child_post_message([{
"emit": "child_to_parent", # This is what Vue listens for
"value": message
}])
logger.info(f"π€ Sent to parent: {message.get('type', 'Unknown')}")
except Exception as e:
logger.warning(f"β οΈ Could not send to parent: {e}")
# -------------------------------------------------------------------------
# UI & Callbacks
# -------------------------------------------------------------------------
def _update_ui_state_after_loading(self):
self.state.contour_value = self.contour_value
self.state.contour_min = self.default_array_range[0]
self.state.contour_max = self.default_array_range[1]
self.state.contour_step = 0.01 * (
self.default_array_range[1] - self.default_array_range[0]
)
self.state.array_list = self.dataset_arrays
def setup_callbacks(self):
self.callback_manager.setup_callbacks()
@self.state.change("selected_file")
def on_file_selected(selected_file, **_):
if selected_file:
try:
self.initialize_data(
use_local=self.state.data_source == "local",
filename=selected_file
)
self.ctrl.view_update()
# Notify parent window of file change
self._send_to_parent({
"type": "file_changed",
"data": {"selected_file": selected_file}
})
except Exception as e:
logger.error(f"β Error loading {selected_file}: {e}")
def create_ui(self):
"""Create UI and attach iframe communicator."""
# Pass the message handler to UIComponents
self.ui_components.create_layout(
iframe_message_handler=self.parent_receive_msg
)
# -------------------------------------------------------------------------
# Runtime
# -------------------------------------------------------------------------
def run(self, use_local=True, filename=None, **server_kwargs):
"""Run the application."""
try:
# Default init (params may override later via postMessage)
self.state.available_files = self.data_loader.get_available_files()
self.initialize_data(use_local=use_local, filename=filename)
self.create_ui()
self.setup_callbacks()
# Notify parent after UI is created
self._notify_parent_ready()
self.server.start(**server_kwargs)
except KeyboardInterrupt:
logger.info("π User interrupted")
except Exception as e:
logger.error(f"π₯ Fatal error: {e}")
raise
# -------------------------------------------------------------------------
# Shutdown
# -------------------------------------------------------------------------
def _signal_handler(self, *_):
logger.info("π Shutdown requested")
if self.vtk_pipeline.temp_file_path and os.path.exists(self.vtk_pipeline.temp_file_path):
try:
os.unlink(self.vtk_pipeline.temp_file_path)
except Exception:
pass
self.data_loader.clear_cache()
sys.exit(0)Last the UI Component
"""UI component creation for the VTU Player interface."""
from trame.widgets import vuetify3 as vuetify, vtk, trame, iframe, html
from trame.ui.vuetify3 import SinglePageWithDrawerLayout
from config.settings import AppConfig
from visualization.enums import RenderMode, ColorMap
from loguru import logger
class UIComponents:
"""Creates and manages UI components for the VTU Player."""
def __init__(self, server, vtk_pipeline, config: AppConfig):
self.server = server
self.state = server.state
self.ctrl = server.controller
self.vtk_pipeline = vtk_pipeline
self.config = config
self.iframe_message_handler = None
def create_layout(self, iframe_message_handler=None):
"""Create the main application layout with iframe communication."""
self.iframe_message_handler = iframe_message_handler
with SinglePageWithDrawerLayout(self.server) as layout:
layout.title.set_text("π¬ VTU Player")
with layout.drawer as drawer:
drawer.width = self.config.drawer_width
self._create_drawer_content()
with layout.content:
self._create_main_content()
def _create_main_content(self):
"""Create the main 3D viewport content."""
with vuetify.VContainer(fluid=True, classes="pa-0 fill-height"):
view = vtk.VtkLocalView(self.vtk_pipeline.render_window)
self.ctrl.view_update = view.update
self.ctrl.view_reset_camera = view.reset_camera
def _add_communication_components(self, message_handler):
"""Add iframe communication components to the layout."""
# Create communicator for iframe events
comm = iframe.Communicator(
event_names=["parent_to_child"],
parent_to_child=(message_handler, "[$event]"),
)
self.ctrl.child_post_message = comm.post_message
logger.info('β
Iframe communicator created and attached to controller')
def _create_drawer_content(self):
"""Create the drawer sidebar content."""
vuetify.VDivider(classes="mt-4")
# File selection
vuetify.VSelect(
v_model=("selected_file", ""),
items=("available_files", []),
label="VTU File",
density="compact",
variant="outlined",
hide_details=True,
)
vuetify.VDivider(classes="mb-2")
self._create_pipeline_widget()
vuetify.VDivider(classes="mb-2")
self._create_mesh_card()
self._create_contour_card()
def _create_pipeline_widget(self):
"""Create the pipeline visibility tree widget."""
def actives_change(ids):
active_id = ids[0] if ids else None
if active_id == "1":
self.state.active_ui = "mesh"
elif active_id == "2":
self.state.active_ui = "contour"
else:
self.state.active_ui = "nothing"
def visibility_change(event):
_id = event.get("id")
visible = event.get("visible", True)
if _id == "1" and self.vtk_pipeline.mesh_actor:
self.vtk_pipeline.mesh_actor.SetVisibility(visible)
elif _id == "2" and self.vtk_pipeline.contour_actor:
self.vtk_pipeline.contour_actor.SetVisibility(visible)
self.ctrl.view_update()
trame.GitTree(
sources=(
"pipeline",
[
{"id": "1", "parent": "0", "visible": 1, "name": "π· Mesh"},
{"id": "2", "parent": "1", "visible": 1, "name": "π Contour"},
],
),
actives_change=(actives_change, "[$event]"),
visibility_change=(visibility_change, "[$event]"),
)
def _create_ui_card(self, title: str, ui_name: str):
"""Create a collapsible UI card."""
with vuetify.VCard(v_show=f"active_ui == '{ui_name}'"):
vuetify.VCardTitle(
title,
classes="grey lighten-1 py-1 grey--text text--darken-3",
style="user-select: none; cursor: pointer",
hide_details=True,
dense=True,
)
return vuetify.VCardText(classes="py-2")
def _create_mesh_card(self):
"""Create the mesh configuration card."""
with self._create_ui_card("π· Mesh Configuration", "mesh"):
# Representation selection
vuetify.VSelect(
v_model=("mesh_representation", RenderMode.SURFACE),
items=("representations", self._get_representations()),
label="Representation",
prepend_icon="mdi-shape",
hide_details=True,
density="compact",
variant="outlined",
classes="pt-1",
)
# Color mapping controls
with vuetify.VRow(classes="pt-2", dense=True):
with vuetify.VCol(cols="6"):
vuetify.VSelect(
label="Color by",
v_model=("mesh_color_array_idx", 0),
items=("array_list", []),
prepend_icon="mdi-palette",
hide_details=True,
density="compact",
variant="outlined",
classes="pt-1",
)
with vuetify.VCol(cols="6"):
vuetify.VSelect(
label="Colormap",
v_model=("mesh_color_preset", ColorMap.RAINBOW),
items=("colormaps", self._get_colormaps()),
prepend_icon="mdi-format-color-fill",
hide_details=True,
density="compact",
variant="outlined",
classes="pt-1",
)
# Opacity control
vuetify.VSlider(
v_model=("mesh_opacity", self.config.default_opacity),
min=0,
max=1,
step=0.1,
label="Opacity",
prepend_icon="mdi-opacity",
classes="mt-1",
hide_details=True,
density="compact",
)
def _create_contour_card(self):
"""Create the contour configuration card."""
with self._create_ui_card("π Contour Configuration", "contour"):
# Contour array selection
vuetify.VSelect(
label="Contour by",
v_model=("contour_by_array_idx", 0),
items=("array_list", []),
prepend_icon="mdi-chart-line",
hide_details=True,
density="compact",
variant="outlined",
classes="pt-1",
)
# Contour value slider
vuetify.VSlider(
v_model=("contour_value", 0.5),
min=("contour_min", 0.0),
max=("contour_max", 1.0),
step=("contour_step", 0.01),
label="Contour Value",
prepend_icon="mdi-tune",
classes="my-1",
hide_details=True,
density="compact",
)
# Representation selection
vuetify.VSelect(
v_model=("contour_representation", RenderMode.SURFACE),
items=("representations", self._get_representations()),
label="Representation",
prepend_icon="mdi-shape",
hide_details=True,
density="compact",
variant="outlined",
classes="pt-1",
)
# Color mapping controls
with vuetify.VRow(classes="pt-2", dense=True):
with vuetify.VCol(cols="6"):
vuetify.VSelect(
label="Color by",
v_model=("contour_color_array_idx", 0),
items=("array_list", []),
prepend_icon="mdi-palette",
hide_details=True,
density="compact",
variant="outlined",
classes="pt-1",
)
with vuetify.VCol(cols="6"):
vuetify.VSelect(
label="Colormap",
v_model=("contour_color_preset", ColorMap.RAINBOW),
items=("colormaps", self._get_colormaps()),
prepend_icon="mdi-format-color-fill",
hide_details=True,
density="compact",
variant="outlined",
classes="pt-1",
)
# Opacity control
vuetify.VSlider(
v_model=("contour_opacity", self.config.default_opacity),
min=0,
max=1,
step=0.1,
label="Opacity",
prepend_icon="mdi-opacity",
classes="mt-1",
hide_details=True,
density="compact",
)
def _get_representations(self):
"""Get available representation modes."""
return [
{"title": "Points", "value": RenderMode.POINTS},
{"title": "Wireframe", "value": RenderMode.WIREFRAME},
{"title": "Surface", "value": RenderMode.SURFACE},
{"title": "Surface + Edges", "value": RenderMode.SURFACE_WITH_EDGES},
]
def _get_colormaps(self):
"""Get available color maps."""
return [
{"title": "π Rainbow", "value": ColorMap.RAINBOW},
{"title": "π Inv Rainbow", "value": ColorMap.INVERTED_RAINBOW},
{"title": "β¬ Greyscale", "value": ColorMap.GREYSCALE},
{"title": "β¬ Inv Greyscale", "value": ColorMap.INVERTED_GREYSCALE},
]
def setup_state_defaults(self):
"""Setup default state values for UI components."""
# This method can be called to ensure UI state is properly initialized
if not hasattr(self.state, "representations"):
self.state.representations = self._get_representations()
if not hasattr(self.state, "colormaps"):
self.state.colormaps = self._get_colormaps()
# Ensure array_list is initialized
if not hasattr(self.state, "array_list"):
self.state.array_list = []It's seems that there is no communication the two project. The vue app is on localhost:3000 while the trame app is on localhost:3015. Is there any solution? Thanks in advice
Metadata
Metadata
Assignees
Labels
No labels