Full-code customization tutorial: Camera app#
This tutorial shows how to create a full code customization of the web app. It is intended for advanced users for whom the default customization options are not sufficient.
Note
This tutorial assumes that you have some knowledge of Python and the Python-based UI framework, Dash. For information on Dash, see the Dash documentation.
- Code
You can access the full code at the end of this document or download it
here
.- Project:
You can download the optiSLang project used in this tutorial
here
.The simplified tutorial project consists of nodes that run Python code without calling any other Ansys software. The aim is to mimic the behavior of an optiSLang project that runs an optical simulation, where a picture and report file are generated at the end.
- Customized UI:
When you complete this tutorial, the customized UI will look like this:
In the final customized UI, you can see that:
On the left, the tree has been edited to add a new item called Camera App, while the usual monitoring pages have been removed.
On the main page, there are several tables, a button to download result files, and a picture which is expected to be generated by optiSLang on project completion.
The following sections describe how to create this custom UI step by step, starting from the default example solution created from the supplied optiSLang project.
Create the default solution#
The first step is to create a new solution from the supplied optiSLang project Camera_App.opf
:
Open the project in optiSLang and save it as Ansys Web App.
Set up the development environment and run the app as described in Test the changes.
When you run this default solution, it looks like this:

Add a custom icon#
Add a custom icon to use in the tree view.
- Add the icon to the
Icon
class. Open the file
src\ansys\solutions\camera_app\ui\utils\icons.py
.Add the following line, making sure that the filename is the same as the name of the file you added:
12class Icon(Enum): 13 PLAY = "play-solid.svg" 14 ROOT_NODE = "account_tree.svg" 15 RESET = "backward-fast-solid.svg" 16 STOP = "hand-solid.svg" 17 ABORT = "stop-solid.svg" 18 SHUTDOWN = "power-off-solid.svg" 19 DOWNLOAD = "file-export-solid.svg" 20 SYSTEM = "workspaces_filled.svg" 21 NODE = "workspaces_outline.svg" 22 SETTINGS = "sliders-solid.svg" 23 CAMERA = "camera_custom.svg"
- Add the icon to the
This adds a new icon to the Icon
class so it can be used in the tree view.
Update the tree view#
Add the Camera app to the tree view and remove the default monitoring pages so that the tree will look like this:

Camera app tree view#
- Import the app layout.
Open the file
src/ansys/solutions/camera_app/ui/pages/page.py
.Add the following line:
from ansys.solutions.camera_app.ui.components import event_listeners from ansys.solutions.camera_app.ui.customization import camera_app from ansys.solutions.camera_app.ui.pages import monitoring_page, problem_setup_page
Note
Note that
create ansys.solutions.camera_app.ui.customization.camera_app
isn’t yet created. This happens later, when you create the app layout.
- Add the app to the tree view.
Add a new item to the
get_treeview_items
function so the item is displayed in the navigation tree.In this case, you’re adding
icon=Icon.CAMERA
, which is the icon you added previously.74def get_treeview_items(project: Camera_AppSolution) -> list[dict]: 75 """Get the treeview items for the navigation tree.""" 76 77 problem_setup_step_item = AwcDashTreeViewItem( 78 id="problem_setup_step", 79 text="Problem Setup", 80 icon=Icon.SETTINGS, 81 ) 82 83 tree_items = [problem_setup_step_item] 84 85 if DataAccess(project).get_osl_instance_started(): 86 camera_app_item = AwcDashTreeViewItem( 87 id="camera_app", 88 text="Camera App", 89 icon=Icon.CAMERA, 90 ) 91 92 tree_items.append(camera_app_item) 93 94 osl_project_tree = DataAccess(project).get_project_tree() 95 96 if not osl_project_tree.is_uninitialized: 97 tree_items.append(AwcDashTreeViewItem.from_actor_tree_node(osl_project_tree)) 98 99 return AwcDashTreeView(tree_items=tree_items).create()
This results in the following tree view:
- Remove the default monitoring pages.
Remove the following lines to remove the standard monitoring pages from the tree:
74def get_treeview_items(project: Camera_AppSolution) -> list[dict]: 75 """Get the treeview items for the navigation tree.""" 76 77 problem_setup_step_item = AwcDashTreeViewItem( 78 id="problem_setup_step", 79 text="Problem Setup", 80 icon=Icon.SETTINGS, 81 ) 82 83 tree_items = [problem_setup_step_item] 84 85 if DataAccess(project).get_osl_instance_started(): 86 camera_app_item = AwcDashTreeViewItem( 87 id="camera_app", 88 text="Camera App", 89 icon=Icon.CAMERA, 90 ) 91 92 tree_items.append(camera_app_item) 93 94- osl_project_tree = DataAccess(project).get_project_tree() 95- 96- if not osl_project_tree.is_uninitialized: 97- tree_items.append(AwcDashTreeViewItem.from_actor_tree_node(osl_project_tree)) 98- 99 return AwcDashTreeView(tree_items=tree_items).create()
- Display the app when clicked.
Update the
get_page_layout_by_clicked_tree_item
function to display the Camera app when the item is clicked in the navigation tree.Check if the
clicked_tree_item_id
isbolt_app
. If so, it should return the app layout.85 if DataAccess(project).get_osl_instance_started(): 86 camera_app_item = AwcDashTreeViewItem( 87 id="camera_app", 88 text="Camera App", 89 icon=Icon.CAMERA, 90 ) 91 92 tree_items.append(camera_app_item) 93 94 return AwcDashTreeView(tree_items=tree_items).create() 95 96 97def get_page_layout_by_clicked_tree_item(clicked: str | None, project: Camera_AppSolution) -> html.Div: 98 """Get the page layout for the clicked tree item.""" 99 if clicked is None: 100 return html.Div(html.H1("Welcome!")) 101 elif clicked == "problem_setup_step": 102 return problem_setup_page.layout(project) 103 elif clicked == "camera_app": 104 return camera_app.layout 105 else: 106 return monitoring_page.layout(selected_actor=clicked, project=project)
If the project has been initialized, this modified function adds a new item to the tree view with the text “Camera App” and the camera icon you added previously. The
id
is used to identify the item in the tree view — for example, to display the app when the item is clicked.If the project hasn’t been initialized yet, the tree view doesn’t contain the Camera App item, only the standard Problem Setup item.
Create the app layout#
Create the layout for the Camera app.
- Create the app layout file.
In the folder
src/ansys/solutions/camera_app/ui
, create a folder namedcustomization
.In this folder, create a file named
camera_app.py
.Open the file for editing.
- Add the imports.
Add the imports to be used in the app:
3import base64 4 5from dash.exceptions import PreventUpdate 6import dash_bootstrap_components as dbc 7from dash_extensions.enrich import Input, Output, State, Trigger, dash_table, dcc, html 8import pandas as pd 9 10from ansys.saf.glow.client import DashClient, callback 11from ansys.solutions.camera_app.datamodel.shared.models import ActorAndHid, DesignFields 12from ansys.solutions.camera_app.datamodel.shared.websocket_streams import WebsocketStreamListeners 13from ansys.solutions.camera_app.solution.data_access import DataAccess 14from ansys.solutions.camera_app.solution.definition import Camera_AppSolution
Note
If you use the case built from the supplied project, the namespace is
ansys.solutions.camera_app
and the solution name isCamera_AppSolution
.These names are derived from the name of the solution
.awa
file, so you need to adjust them when the solution has been created from a different optiSLang project or when you specified a different filename during the solution creation process.You can use the imports in
src/ansys/solutions/camera_app/ui/pages/page.py
as a reference to find the correct names.
- Add the tables.
In the customized app, there are to be four tables: Actor Status, Project Status, Project Information, and Parameters.
Create these tables using the standard
dash_table.DataTable
:19actor_status_table = dash_table.DataTable(id="actor-status-table",) 20project_status_table = dash_table.DataTable(id="project-status") 21project_information_table = dash_table.DataTable(id="project-table") 22parameters_table = dash_table.DataTable(id="parameter-table")
Note
At the moment, these tables are just placeholders which don’t contain any data. The data is to be filled in by callbacks at a later stage, as described in Add the callbacks. The styling of the tables is also applied later, as described in Style the tables.
- Add the results button and image.
Add a button for downloading results and an image from the project results (once the results become available):
91results_download_button = dbc.Button( 92 id="results-button", disabled=True, children="Download Result files", color="dark", outline=True 93) 94result_download = dcc.Download(id="result-download") 95 96result_image = html.Img(id="result-image", style={"width": "200px"}) 97result_image_loading = dcc.Loading(id="image-loading", type="circle", display="show", children=result_image)
- Add the page layout.
Create a simple linear page layout using a single
div
element that contains the tables and other components:101layout = html.Div( 102 [ 103 html.H4("Analysis Status"), 104 actor_status_table, 105 html.Br(), 106 html.H4("Status"), 107 project_status_table, 108 html.Br(), 109 html.H4("Project"), 110 project_information_table, 111 html.Br(), 112 html.H4("Parameters"), 113 parameters_table, 114 html.Br(), 115 html.H4("Results"), 116 results_download_button, 117 html.Br(), 118 result_download, 119 html.Br(), 120 html.Br(), 121 html.Br(), 122 result_image_loading, 123 ], 124)
- Add the root node constant.
Define the constant for the
ROOT_NODE_HID
. This is used later to get the root node of the project:16ROOT_NODE_HID = "0" 17"""For this project we expect the root node states to always be a single state with hid "0"."""
Add the callbacks#
The tables are now added to the layout, but they don’t yet contain any data. Create the callbacks to fill in and update the data used in the tables.
- Add the project status table callback.
In this callback, the data for the Project Status table is retrieved from the server. The table should have a single row containing the string “Project Status” in the first column and the current project status in the second.
Create the callback using the following code:
165@callback( 166 Output("project-status", "data"), 167 Trigger(WebsocketStreamListeners.PROJECT_STATE.value, "message"), 168 State("url", "pathname"), 169) 170def update_project_status(pathname): 171 project = DashClient[Camera_AppSolution].get_project(pathname) 172 173 monitoring_data = DataAccess(project).monitoring_data 174 175 project_status = monitoring_data.get_osl_project_state() 176 177 df = pd.DataFrame([["Project status", project_status]], columns=["Name", "Value"]) 178 return df.to_dict("records")
About this code:
The
@callback
decorator is used to define a callback function in Dash.The
Output
specifies the component and property to update — in this case, thedata
property of theproject-status
table.The
Trigger
specifies the event that triggers the callback — in this case, a message from theWebsocketStreamListeners.PROJECT_STATE
stream. This stream sends a message whenever the project state changes.The
State
specifies the current value of thepathname
property of theurl
component, which is used to access the current project data within the callback.The
DashClient
is used to retrieve theproject
. With this project, aDataAccess
object can be created. TheDataAccess
object provides access to the data in the project.The
monitoring_data
object is used to get information about the project and its actors.The
get_osl_project_state
method is used to retrieve the current project state, which is a string with values such as “RUNNING,” “FINISHED,” “FAILED,” and so on.Finally, a
pandas
data frame is created with the project state and returned as a dictionary. Theto_dict("records")
method converts the data frame to a list of dictionaries, which is the format expected by thedata
property of the table.
- Add the project information table callback.
The second callback is for the Project Information table. This table contains information about the project, as one would get from
get_full_project_tree
from the optiSLang server.Create the callback using the following code:
181@callback( 182 Output("project-table", "data"), 183 Trigger(WebsocketStreamListeners.PROJECT_INFORMATION.value, "message"), 184 State("url", "pathname"), 185) 186def update_project_information(pathname): 187 project = DashClient[Camera_AppSolution].get_project(pathname) 188 189 monitoring_data = DataAccess(project).monitoring_data 190 191 project_information = monitoring_data.get_project_information() 192 project_information_as_dict = project_information.to_dict_by_title() 193 194 df = pd.DataFrame(list(project_information_as_dict.items()), columns=["Name", "Value"]) 195 196 return df.to_dict("records")
This callback is similar to the previous one, but in this case:
The trigger is the
PROJECT_INFORMATION
stream, which updates when the project information changes.Instead of getting the project state, the project information is returned. The
get_project_information
method returns an object with the project information, which is converted to a dict usingto_dict_by_title
.
- Add actor status table callback.
The Actor Status table contains information about the actors in the project. This is a bit more complicated, as the status and the actor name for each actor need to be retrieved.
Create the callback using the following code:
127@callback( 128 Output("actor-status-table", "data"), 129 Trigger(WebsocketStreamListeners.ACTOR_DATA.value, "message"), 130 State("url", "pathname"), 131) 132def update_actor_status(pathname): 133 project = DashClient[Camera_AppSolution].get_project(pathname) 134 135 data_access = DataAccess(project) 136 monitoring_data = data_access.monitoring_data 137 138 root_system = data_access.get_project_tree() 139 140 if root_system.is_uninitialized: 141 # The project is not initialized yet 142 raise PreventUpdate 143 144 status_by_name = {} 145 146 for node in root_system.children: 147 if node.is_system: 148 continue 149 150 name = node.name 151 uid = node.uid 152 actor_states = monitoring_data.get_actor_states_ids(uid) 153 154 state_id = actor_states.states_ids[0] 155 actor_and_hid = ActorAndHid(actor_uid=uid, hid=state_id) 156 actor_info = monitoring_data.get_actor_information_actor(actor_and_hid) 157 status = actor_info.processing_state 158 159 status_by_name[name] = status 160 161 df = pd.DataFrame(status_by_name.items(), columns=["Name", "Value"]) 162 return df.to_dict("records")
About this code:
The stream
WebsocketStreamListeners.ACTOR_DATA
is used to trigger this callback when there are updates to the actor data available.In the callback, the data access module provides the root system of the project. This is the top-level system in the project and contains all the other systems and actors.
If the project is not initialized, a
PreventUpdate
exception is raised, which prevents the callback from updating the table. If it is initialized, the children of the root system are iterated over. These are the direct descendants of the root system, which can be systems or actors.All system children are skipped. This is an defensive measure because for the optiSLang project used, no direct descendants of the root of system type are expected.
For all actor children, the name and unique identifier of the actor are stored and the actor states are retrieved using
get_actor_states_ids
. This function returns a list of states for the given actor. A state is a unique identifier for a particular state. A state can, for example, be0.1
,0.2
, and so on. In this case, only a single state is expected for each actor, so the first state is used in the following:Using the first state, an
ActorAndHid
object is created with the unique identifier and state. The actor information is then retrieved usingget_actor_information_actor
. The processing state is obtained from this object.Finally, a data frame with the actor name and status is created and is returned as a list of dictionaries using
to_dict("record")
.
- Add the parameters table callback.
For the Parameters table callback, you’ll use some additional capabilities from the
DataAccess
module to get the design points for the project and the parameters for each design point.Create the callback using the following code:
199@callback( 200 Output("parameter-table", "data"), 201 Output("parameter-table", "columns"), 202 Trigger(WebsocketStreamListeners.ACTOR_DATA.value, "message"), 203 State("url", "pathname"), 204) 205def update_parameter_status(pathname): 206 project = DashClient[Camera_AppSolution].get_project(pathname) 207 data_access = DataAccess(project) 208 root_system = data_access.get_project_tree() 209 210 if root_system.is_uninitialized: 211 # The project is not initialized yet 212 raise PreventUpdate 213 actor_and_hid = ActorAndHid(actor_uid=root_system.uid, hid=ROOT_NODE_HID) 214 design_points = data_access.monitoring_data.get_design_points(actor_and_hid) 215 parameters_dict = design_points.to_dict_by_states(DesignFields.PARAMETERS) 216 if not parameters_dict: 217 raise PreventUpdate 218 219 states = design_points.get_states() 220 221 if not len(states) == 1: 222 # For this case, we assume only one state for the root node 223 raise ValueError(f"Expected only one state, got {len(states)}: {states}") 224 225 state = states[0] 226 227 df = pd.DataFrame(parameters_dict) 228 229 # Rename the column "state" to "Value" 230 df = df.rename(columns={state: "Value"}) 231 columns = [{"name": i, "id": i} for i in df.columns] 232 233 return df.to_dict("records"), columns
About this code:
A
DataAccess
object is created, and the root system is retrieved. If the project is not initialized, aPreventUpdate
exception is raised, as before.Using the root system unique identifier and the
ROOT_NODE_HID
, anActorAndHid
object is created. This is used to get the design points for the root system of the project.The design points object can be converted to a dictionary in different ways. In this case, the
to_dict_by_states
method is used, which returns a dictionary with the design points states as keys and the parameter names as values.This is useful when the parameters are to be the rows in the table. The “state” column is renamed to Value and a list of table columns is created.
Finally, a data frame is created and returned a list of dictionaries, and the columns as a list of dictionaries.
Style the tables#
The project as described up to this point works, but is a bit bare:

Make the following modifications to improve the presentation of the tables:
- Align table widths.
Use the following setting to align the width of all the tables:
21style_cell_conditional = [ 22 {"if": {"column_id": "Name"}, "width": "30%", "textAlign": "left"}, 23 {"if": {"column_id": "Value"}, "width": "70%", "textAlign": "left"}, 24]
- Apply conditional formatting.
Use these settings to introduce conditional formatting to change the foreground color for project and actor status tables based on the project state:
26style_actor_status_data_conditional = [ 27 { 28 "if": {"column_id": "Value", "filter_query": '{Value} eq "Succeeded"'}, 29 "color": "green", 30 }, 31 { 32 "if": {"column_id": "Value", "filter_query": '{Value} eq "Failed"'}, 33 "color": "red", 34 }, 35 { 36 "if": {"column_id": "Value", "filter_query": '{Value} eq "Predecessor failed"'}, 37 "color": "grey", 38 }, 39] 40 41style_project_status_data_conditional = [ 42 { 43 "if": {"column_id": "Value", "filter_query": '{Value} eq "FINISHED"'}, 44 "color": "green", 45 }, 46 { 47 "if": {"column_id": "Value", "filter_query": '{Value} eq "RUNNING"'}, 48 "color": "black", 49 }, 50 { 51 "if": {"column_id": "Value", "filter_query": '{Value} eq "FAILED"'}, 52 "color": "red", 53 }, 54 { 55 "if": {"column_id": "Value", "filter_query": '{Value} eq "NOT STARTED"'}, 56 "color": "grey", 57 }, 58] 59 60common_data_table_kwargs = { 61 "style_cell_conditional": style_cell_conditional, 62 "style_as_list_view": True, 63 "cell_selectable": False, 64 "data": [{"Name": "", "Value": "NOT STARTED"}], 65 "columns": [{"name": "Name", "id": "Name"}, {"name": "Value", "id": "Value"}], 66} 67 68# Needed to get the conditional coloring of text to work correctly 69COMMON_CSS = [{"selector": "div", "rule": "color: inherit"}]
- Hide table headers.
Use this setting to hide the header for the actor and project state tables:
71# For hiding the header of the first row 72HIDE_HEADER_CSS = COMMON_CSS + [{"selector": "tr:first-child", "rule": "display: none"}] 73
- Apply modifications.
Apply your customizations to the tables as follows:
76actor_status_table = dash_table.DataTable( 77 id="actor-status-table", 78 css=HIDE_HEADER_CSS, 79 style_data_conditional=style_actor_status_data_conditional, 80 **common_data_table_kwargs, 81) 82project_status_table = dash_table.DataTable( 83 id="project-status", 84 css=HIDE_HEADER_CSS, 85 style_data_conditional=style_project_status_data_conditional, 86 **common_data_table_kwargs, 87) 88project_information_table = dash_table.DataTable(id="project-table", css=COMMON_CSS, **common_data_table_kwargs) 89parameters_table = dash_table.DataTable(id="parameter-table", css=COMMON_CSS, **common_data_table_kwargs)
This yields the desired final customized look of the Camera app:

Full code#
Here is the full code for the Camera app after the customizations are complete:
1# ©2025, ANSYS Inc. Unauthorized use, distribution or duplication is prohibited.
2
3import base64
4
5from dash.exceptions import PreventUpdate
6import dash_bootstrap_components as dbc
7from dash_extensions.enrich import Input, Output, State, Trigger, dash_table, dcc, html
8import pandas as pd
9
10from ansys.saf.glow.client import DashClient, callback
11from ansys.solutions.camera_app.datamodel.shared.models import ActorAndHid, DesignFields
12from ansys.solutions.camera_app.datamodel.shared.websocket_streams import WebsocketStreamListeners
13from ansys.solutions.camera_app.solution.data_access import DataAccess
14from ansys.solutions.camera_app.solution.definition import Camera_AppSolution
15
16ROOT_NODE_HID = "0"
17"""For this project we expect the root node states to always be a single state with hid "0"."""
18
19# Styles
20
21style_cell_conditional = [
22 {"if": {"column_id": "Name"}, "width": "30%", "textAlign": "left"},
23 {"if": {"column_id": "Value"}, "width": "70%", "textAlign": "left"},
24]
25
26style_actor_status_data_conditional = [
27 {
28 "if": {"column_id": "Value", "filter_query": '{Value} eq "Succeeded"'},
29 "color": "green",
30 },
31 {
32 "if": {"column_id": "Value", "filter_query": '{Value} eq "Failed"'},
33 "color": "red",
34 },
35 {
36 "if": {"column_id": "Value", "filter_query": '{Value} eq "Predecessor failed"'},
37 "color": "grey",
38 },
39]
40
41style_project_status_data_conditional = [
42 {
43 "if": {"column_id": "Value", "filter_query": '{Value} eq "FINISHED"'},
44 "color": "green",
45 },
46 {
47 "if": {"column_id": "Value", "filter_query": '{Value} eq "RUNNING"'},
48 "color": "black",
49 },
50 {
51 "if": {"column_id": "Value", "filter_query": '{Value} eq "FAILED"'},
52 "color": "red",
53 },
54 {
55 "if": {"column_id": "Value", "filter_query": '{Value} eq "NOT STARTED"'},
56 "color": "grey",
57 },
58]
59
60common_data_table_kwargs = {
61 "style_cell_conditional": style_cell_conditional,
62 "style_as_list_view": True,
63 "cell_selectable": False,
64 "data": [{"Name": "", "Value": "NOT STARTED"}],
65 "columns": [{"name": "Name", "id": "Name"}, {"name": "Value", "id": "Value"}],
66}
67
68# Needed to get the conditional coloring of text to work correctly
69COMMON_CSS = [{"selector": "div", "rule": "color: inherit"}]
70
71# For hiding the header of the first row
72HIDE_HEADER_CSS = COMMON_CSS + [{"selector": "tr:first-child", "rule": "display: none"}]
73
74# Elements with data which will be automatically updated by callbacks
75
76actor_status_table = dash_table.DataTable(
77 id="actor-status-table",
78 css=HIDE_HEADER_CSS,
79 style_data_conditional=style_actor_status_data_conditional,
80 **common_data_table_kwargs,
81)
82project_status_table = dash_table.DataTable(
83 id="project-status",
84 css=HIDE_HEADER_CSS,
85 style_data_conditional=style_project_status_data_conditional,
86 **common_data_table_kwargs,
87)
88project_information_table = dash_table.DataTable(id="project-table", css=COMMON_CSS, **common_data_table_kwargs)
89parameters_table = dash_table.DataTable(id="parameter-table", css=COMMON_CSS, **common_data_table_kwargs)
90
91results_download_button = dbc.Button(
92 id="results-button", disabled=True, children="Download Result files", color="dark", outline=True
93)
94result_download = dcc.Download(id="result-download")
95
96result_image = html.Img(id="result-image", style={"width": "200px"})
97result_image_loading = dcc.Loading(id="image-loading", type="circle", display="show", children=result_image)
98
99# Forming the layout
100
101layout = html.Div(
102 [
103 html.H4("Analysis Status"),
104 actor_status_table,
105 html.Br(),
106 html.H4("Status"),
107 project_status_table,
108 html.Br(),
109 html.H4("Project"),
110 project_information_table,
111 html.Br(),
112 html.H4("Parameters"),
113 parameters_table,
114 html.Br(),
115 html.H4("Results"),
116 results_download_button,
117 html.Br(),
118 result_download,
119 html.Br(),
120 html.Br(),
121 html.Br(),
122 result_image_loading,
123 ],
124)
125
126
127@callback(
128 Output("actor-status-table", "data"),
129 Trigger(WebsocketStreamListeners.ACTOR_DATA.value, "message"),
130 State("url", "pathname"),
131)
132def update_actor_status(pathname):
133 project = DashClient[Camera_AppSolution].get_project(pathname)
134
135 data_access = DataAccess(project)
136 monitoring_data = data_access.monitoring_data
137
138 root_system = data_access.get_project_tree()
139
140 if root_system.is_uninitialized:
141 # The project is not initialized yet
142 raise PreventUpdate
143
144 status_by_name = {}
145
146 for node in root_system.children:
147 if node.is_system:
148 continue
149
150 name = node.name
151 uid = node.uid
152 actor_states = monitoring_data.get_actor_states_ids(uid)
153
154 state_id = actor_states.states_ids[0]
155 actor_and_hid = ActorAndHid(actor_uid=uid, hid=state_id)
156 actor_info = monitoring_data.get_actor_information_actor(actor_and_hid)
157 status = actor_info.processing_state
158
159 status_by_name[name] = status
160
161 df = pd.DataFrame(status_by_name.items(), columns=["Name", "Value"])
162 return df.to_dict("records")
163
164
165@callback(
166 Output("project-status", "data"),
167 Trigger(WebsocketStreamListeners.PROJECT_STATE.value, "message"),
168 State("url", "pathname"),
169)
170def update_project_status(pathname):
171 project = DashClient[Camera_AppSolution].get_project(pathname)
172
173 monitoring_data = DataAccess(project).monitoring_data
174
175 project_status = monitoring_data.get_osl_project_state()
176
177 df = pd.DataFrame([["Project status", project_status]], columns=["Name", "Value"])
178 return df.to_dict("records")
179
180
181@callback(
182 Output("project-table", "data"),
183 Trigger(WebsocketStreamListeners.PROJECT_INFORMATION.value, "message"),
184 State("url", "pathname"),
185)
186def update_project_information(pathname):
187 project = DashClient[Camera_AppSolution].get_project(pathname)
188
189 monitoring_data = DataAccess(project).monitoring_data
190
191 project_information = monitoring_data.get_project_information()
192 project_information_as_dict = project_information.to_dict_by_title()
193
194 df = pd.DataFrame(list(project_information_as_dict.items()), columns=["Name", "Value"])
195
196 return df.to_dict("records")
197
198
199@callback(
200 Output("parameter-table", "data"),
201 Output("parameter-table", "columns"),
202 Trigger(WebsocketStreamListeners.ACTOR_DATA.value, "message"),
203 State("url", "pathname"),
204)
205def update_parameter_status(pathname):
206 project = DashClient[Camera_AppSolution].get_project(pathname)
207 data_access = DataAccess(project)
208 root_system = data_access.get_project_tree()
209
210 if root_system.is_uninitialized:
211 # The project is not initialized yet
212 raise PreventUpdate
213 actor_and_hid = ActorAndHid(actor_uid=root_system.uid, hid=ROOT_NODE_HID)
214 design_points = data_access.monitoring_data.get_design_points(actor_and_hid)
215 parameters_dict = design_points.to_dict_by_states(DesignFields.PARAMETERS)
216 if not parameters_dict:
217 raise PreventUpdate
218
219 states = design_points.get_states()
220
221 if not len(states) == 1:
222 # For this case, we assume only one state for the root node
223 raise ValueError(f"Expected only one state, got {len(states)}: {states}")
224
225 state = states[0]
226
227 df = pd.DataFrame(parameters_dict)
228
229 # Rename the column "state" to "Value"
230 df = df.rename(columns={state: "Value"})
231 columns = [{"name": i, "id": i} for i in df.columns]
232
233 return df.to_dict("records"), columns
234
235
236@callback(
237 Output("results-button", "disabled"),
238 Trigger(WebsocketStreamListeners.PROJECT_STATE.value, "message"),
239 State("url", "pathname"),
240)
241def update_results_button_disabled(pathname):
242 project = DashClient[Camera_AppSolution].get_project(pathname)
243
244 root_uid = DataAccess(project).get_project_tree().uid
245 actor_and_hid = ActorAndHid(actor_uid=root_uid, hid=ROOT_NODE_HID)
246
247 results = DataAccess(project).results
248 file_reference = results.get_file_reference_by_name("Result_files.zip", actor_and_hid)
249 return file_reference is None
250
251
252@callback(
253 Output("result-download", "data"),
254 Input("results-button", "n_clicks"),
255 State("url", "pathname"),
256)
257def download_file(n_clicks, pathname):
258 if n_clicks is None:
259 raise PreventUpdate
260
261 project = DashClient[Camera_AppSolution].get_project(pathname)
262
263 root_uid = DataAccess(project).get_project_tree().uid
264 actor_and_hid = ActorAndHid(actor_uid=root_uid, hid=ROOT_NODE_HID)
265
266 results = DataAccess(project).results
267 content = results.get_file_content_by_name("Result_files.zip", actor_and_hid)
268
269 if content is None:
270 return dcc.send_string("No result files available", "Error.txt")
271
272 return dcc.send_bytes(content, "Result_files.zip")
273
274
275@callback(
276 Output("image-loading", "display"),
277 Output("result-image", "src"),
278 Trigger(WebsocketStreamListeners.PROJECT_STATE.value, "message"),
279 Trigger(WebsocketStreamListeners.ACTOR_DATA.value, "message"),
280 State("url", "pathname"),
281)
282def update_images(pathname):
283 project = DashClient[Camera_AppSolution].get_project(pathname)
284
285 root_uid = DataAccess(project).get_project_tree().uid
286 actor_and_hid = ActorAndHid(actor_uid=root_uid, hid=ROOT_NODE_HID)
287
288 results = DataAccess(project).results
289 file_content = results.get_file_content_by_name("Camera.svg", actor_and_hid)
290
291 if file_content is None:
292 return "show", None
293
294 b64_encoded_image = base64.b64encode(file_content)
295 decoded = b64_encoded_image.decode("ascii")
296 img_src = f"data:image/svg+xml;base64,{decoded}"
297
298 return "hide", img_src