sensotwin.lifetime_extrapolation.ui

  1import ipywidgets as widgets
  2from . import configuration
  3from . import style
  4from .. import global_style
  5from .. import store_connection
  6from ..defect_placement import configuration as defect_config
  7from ..stress_selection import configuration as stress_config
  8from ..temperature_selection import configuration as temperature_config
  9import pyvista as pv
 10import numpy as np
 11import matplotlib.pyplot as plt
 12import matplotlib.colors as pltcolor
 13from IPython.display import display
 14import copy
 15from traitlets.utils.bunch import Bunch
 16from vtkmodules.vtkRenderingCore import vtkActor2D
 17import math
 18
 19
 20def create_d_cmap():
 21    """Create matplotlib color map for d plot in result.
 22    
 23    Returns:
 24        matplotlib Colormap
 25    """
 26    base_cmap = plt.get_cmap('jet')
 27    damage_cmap = pltcolor.LinearSegmentedColormap.from_list(
 28            'trunc({n},{a:.2f},{b:.2f})'.format(n=base_cmap.name, a=0.30, b=1.0),
 29            base_cmap(np.linspace(0.30, 1.0, 100)))
 30    return damage_cmap
 31
 32def create_t_cmap():
 33    """Create matplotlib color map for t plot in result.
 34    
 35    Returns:
 36        matplotlib Colormap
 37    """
 38    base_cmap = plt.get_cmap('jet')
 39    damage_cmap = pltcolor.LinearSegmentedColormap.from_list(
 40            'trunc({n},{a:.2f},{b:.2f})'.format(n=base_cmap.name, a=0.30, b=1.0),
 41            base_cmap(np.linspace(0.30, 1.0, 100)))
 42    return "jet"
 43
 44
 45class LifetimeExtrapolationUIElements:
 46    """Contains all UI functionality for executing simulation and viewing results (Step 4)."""
 47    LAYER_ID_KEY = "LAYID"
 48    LAYER_THICKNESS_KEY = 'THICKMM'
 49    POINT_WARP_KEY = "U_AEROCENT"
 50    CURING_MESH_ID = 0
 51    STRUCTURE_MESH_ID = 1
 52
 53    STRUCTURE_DISPLAY_DEFINITIONS = {
 54            "DFAT": {
 55                "clim": [0.000000001, 1],
 56                "cmap": create_d_cmap(),
 57                "log_scale": True
 58            },
 59            "TFAT": {
 60                "clim": [0.001, 100],
 61                "cmap": create_t_cmap(),
 62                "log_scale": True
 63            },
 64            "THICKNESS": {
 65                "cmap": "terrain",
 66                "log_scale": False
 67            }
 68        }
 69    
 70    structure_limit_actor = None
 71    curing_limit_actor = None
 72
 73    def __init__(self):
 74        """Initialize UI objects and associated UI functionality."""
 75        self.init_input_set_selection_UI()
 76        self.init_job_overview_UI()
 77        self.init_result_display_UI()
 78
 79    def init_input_set_selection_UI(self):
 80        """Initialize first UI of notebook, choosing data source and selecting input data sets."""
 81        temperature_input_selection_label = widgets.Label(value="Material Hardening Input:").add_class("global_headline")
 82        self.temperature_input_selection = widgets.Select(
 83            options=['No data in source'],
 84            value='No data in source',
 85            rows=10,
 86            description='Input sets:',
 87            disabled=False
 88        ).add_class("global_input_set_selection").add_class("global_basic_input")
 89        defect_input_selection_label = widgets.Label(value="Defect Placement Input Set:").add_class("global_headline")
 90        self.defect_input_selection = widgets.Select(
 91            options=['No data in source'],
 92            value='No data in source',
 93            rows=10,
 94            description='Input sets:',
 95            disabled=False
 96        ).add_class("global_input_set_selection").add_class("global_basic_input")
 97        stress_input_selection_label = widgets.Label(value="Stress/Strain Input Set:").add_class("global_headline")
 98        self.stress_input_selection = widgets.Select(
 99            options=['No data in source'],
100            value='No data in source',
101            rows=10,
102            description='Input sets:',
103            disabled=False
104        ).add_class("global_input_set_selection").add_class("global_basic_input")
105        start_simulation_button = widgets.Button(
106            description='Start simulation with selected input data sets',
107            disabled=False,
108            tooltip='Start simulation with selected input data sets',
109            icon='play',
110        ).add_class("global_save_input_set_button").add_class("global_basic_button")
111        start_simulation_button.on_click(self.start_simulation)
112
113        simulation_temperature_selection = widgets.VBox([
114            temperature_input_selection_label,
115            self.temperature_input_selection
116        ]).add_class("life_extra_input_column_container")
117        simulation_defect_placement = widgets.VBox([
118            defect_input_selection_label,
119            self.defect_input_selection
120        ]).add_class("life_extra_input_column_container")
121        simulation_stress_selection = widgets.VBox([
122            stress_input_selection_label,
123            self.stress_input_selection
124        ]).add_class("life_extra_input_column_container")
125        simulation_box = widgets.VBox([
126            widgets.HBox([
127                simulation_temperature_selection,
128                simulation_defect_placement,
129                simulation_stress_selection
130            ]).add_class("life_extra_input_row_container"),
131            start_simulation_button
132        ])
133
134        local_database_label = widgets.Label(value="Use local owlready2 database:").add_class("global_headline")
135        use_local_data_button = widgets.Button(
136            description='Use local database',
137            disabled=False,
138            tooltip='Use local database',
139            icon='play',
140        ).add_class("global_load_data_button").add_class("global_basic_button")
141        use_local_data_button.on_click(self.load_local_data)
142        remote_database_label = widgets.Label(value="Use remote Apache Jena Fuseki database:").add_class("global_headline")
143        self.remote_database_input = widgets.Text(
144            value=None,
145            placeholder="insert SPARQL url here",
146            description="SPARQL Endpoint:",
147            disabled=False   
148        ).add_class("global_url_input").add_class("global_basic_input")
149        use_remote_data_button = widgets.Button(
150            description='Use remote database',
151            disabled=False,
152            tooltip='Use remote database',
153            icon='play',
154        ).add_class("global_load_data_button").add_class("global_basic_button")
155        use_remote_data_button.on_click(self.load_remote_data)
156        local_data_box = widgets.VBox([
157            local_database_label,
158            use_local_data_button
159        ])
160        remote_data_box = widgets.VBox([
161            remote_database_label,
162            self.remote_database_input,
163            use_remote_data_button
164        ])
165        data_source_box = widgets.VBox([
166            local_data_box,
167            remote_data_box
168        ]).add_class("global_data_tab_container")
169
170        self.input_dashboard = widgets.Tab().add_class("global_tab_container")
171        self.input_dashboard.children = [
172            data_source_box,
173            simulation_box
174        ]
175        tab_titles = ['Input', 'Simulation']
176        for i in range(len(tab_titles)):
177            self.input_dashboard.set_title(i, tab_titles[i])
178
179    def start_simulation(self, ui_element=None):
180        """Start simulation with the 3 currently selected input sets.
181
182        Args:
183            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
184        """
185        configuration.execute_job(
186            self.pyiron_project,
187            self.temperature_input_set_data[self.temperature_input_selection.value],
188            self.defect_input_set_data[self.defect_input_selection.value],
189            self.stress_input_set_data[self.stress_input_selection.value],
190            self.conn
191        )
192
193    def init_job_overview_UI(self):
194        """Initialize second UI of notebook, viewing the pyiron job table."""
195        self.pyiron_project = configuration.get_pyiron_project("jobs_test")
196        self.job_dashboard = widgets.Output(layout={'border': '1px solid black'})
197        self.update_job_list_display()
198
199    def update_job_list_display(self):
200        """Reload job table from pyiron project."""
201        if self.job_dashboard:
202            self.job_dashboard.clear_output()
203            columns_to_hide = ["projectpath","chemicalformula","parentid","masterid","hamversion"]
204            with self.job_dashboard:
205                display(configuration.get_job_table(self.pyiron_project).drop(columns=columns_to_hide))
206
207    def init_result_display_UI(self):
208        """Initialize third UI of notebook, displaying result of a simulation."""
209        result_selection_label = widgets.Label(value="Completed simulation:").add_class("global_headline")
210        self.result_selection = widgets.Select(
211            options=['Load data first'],
212            value='Load data first',
213            rows=10,
214            description='Input sets:',
215            disabled=False
216        ).add_class("global_input_set_selection").add_class("global_basic_input")
217        display_result_button = widgets.Button(
218            description='Display Result',
219            disabled=False,
220            tooltip='Display Result',
221            icon='play',
222        ).add_class("global_basic_button")
223        display_result_button.on_click(self.display_result)
224        result_display_selection_box = widgets.VBox([
225            result_selection_label,
226            self.result_selection,
227            display_result_button
228        ])
229
230        structure_render_label = widgets.Label(value="Structure simulation:").add_class("global_headline")
231        self.structure_deformation_toggle = widgets.ToggleButton(
232            value=False,
233            description='Toggle Deformation',
234            tooltip='Toggle Deformation',
235        ).add_class("global_basic_button")
236        self.structure_deformation_toggle.observe(self.on_structure_deformation_changed, names=['value'])
237        self.structure_layer_select = widgets.Dropdown(
238            options=['Load Result'],
239            value='Load Result',
240            description='Layer:',
241            disabled=False,
242        ).add_class("global_basic_input")
243        self.structure_layer_select.observe(self.on_structure_layer_changed, names=['value'])
244        self.structure_scalar_select = widgets.Dropdown(
245            options=['Select Layer'],
246            value='Select Layer',
247            description='Value:',
248            disabled=False,
249        ).add_class("global_basic_input")
250        self.structure_scalar_select.observe(self.on_structure_scalar_changed, names=['value'])
251        self.structure_cell_info = widgets.HTML().add_class("life_extra_cell_data_column_container")
252        
253        curing_render_label = widgets.Label(value="Curing simulation:").add_class("global_headline")
254        self.curing_deformation_toggle = widgets.ToggleButton(
255            value=False,
256            description='Toggle Deformation',
257            tooltip='Toggle Deformation',
258        ).add_class("global_basic_button")
259        self.curing_deformation_toggle.observe(self.on_curing_deformation_changed, names=['value'])
260        self.curing_layer_select = widgets.Dropdown(
261            options=['Load Result'],
262            value='Load Result',
263            description='Layer:',
264            disabled=False,
265        ).add_class("global_basic_input")
266        self.curing_layer_select.observe(self.on_curing_layer_changed, names=['value'])
267        self.curing_scalar_select = widgets.Dropdown(
268            options=['Select Layer'],
269            value='Select Layer',
270            description='Value:',
271            disabled=False,
272        ).add_class("global_basic_input")
273        self.curing_scalar_select.observe(self.on_curing_scalar_changed, names=['value'])
274        self.curing_cell_info = widgets.HTML().add_class("life_extra_cell_data_column_container")
275
276        result_display_controls = widgets.VBox([
277            widgets.VBox([
278                curing_render_label,
279                self.curing_deformation_toggle,
280                self.curing_layer_select,
281                self.curing_scalar_select
282            ]).add_class("life_extra_output_column_container"), 
283            widgets.VBox([
284                structure_render_label,
285                self.structure_deformation_toggle,
286                self.structure_layer_select,
287                self.structure_scalar_select
288            ]).add_class("life_extra_output_column_container")
289        ]).add_class("life_extra_output_column_container")
290
291        PYVISTA_OUTPUT_RENDER_HEIGHT = 1100
292        PYVISTA_OUTPUT_RENDER_WIDTH = 900
293        pyvista_render_widget = widgets.Output(layout={'height': '{}px'.format(PYVISTA_OUTPUT_RENDER_HEIGHT+15), 
294                                                           'width': '{}px'.format(PYVISTA_OUTPUT_RENDER_WIDTH+10)})
295
296        result_cell_info_box = widgets.VBox([
297            self.curing_cell_info,
298            self.structure_cell_info
299        ]).add_class("life_extra_output_column_container")
300        result_display_box = widgets.HBox([
301            widgets.VBox([
302                pyvista_render_widget
303            ]),
304            result_display_controls,
305            result_cell_info_box
306        ])
307
308        self.plotter = pv.Plotter(shape=(2,1))
309        self.plotter.window_size = [PYVISTA_OUTPUT_RENDER_WIDTH, PYVISTA_OUTPUT_RENDER_HEIGHT]
310        self.plotter.enable_element_picking(callback=self.selection_callback, show_message=False)
311        with pyvista_render_widget:
312            self.plotter.show(jupyter_backend='trame')
313
314        self.result_dashboard = widgets.Tab().add_class("global_tab_container")
315        self.result_dashboard.children = [
316            result_display_selection_box,
317            result_display_box
318        ]
319        tab_titles = ['Select Result', 'Result Display']
320        for i in range(len(tab_titles)):
321            self.result_dashboard.set_title(i, tab_titles[i])
322
323    def update_result_list_display(self):
324        """Update list of completed jobs in result display selection list."""
325        if self.result_dashboard:
326            options = []
327            for _, row in configuration.get_job_table(self.pyiron_project).iterrows():
328                if row["hamilton"] == "StructureSimulation" and row["status"] == "finished":
329                    options.append((row["job"], row["id"]))
330            if options:
331                self.result_selection.options = options
332                self.result_selection.value = options[0][1]
333            
334    def display_result(self, ui_element=None):
335        """Load currently selected result from list of finished jobs.
336
337        Args:
338            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
339        """
340        all_jobs = configuration.get_job_table(self.pyiron_project)
341        result = all_jobs.loc[all_jobs.id == self.result_selection.value].iloc[0]
342        result_job = self.pyiron_project.load(result["job"])
343        self.init_pyvista_render(result_job.output.curing_mesh,result_job.output.mesh)
344
345    def init_pyvista_render(self, curing_mesh, structure_mesh):
346        """Load result mesh from job into PyVista display.
347        
348        Args:
349            structure_mesh: Relative file path to result mesh
350        """
351        initial_camera_position = [(-30.765771120379302, -28.608772602676154, 39.46235706090557),
352             (0.9572034500000001, 0.0005481500000000805, -30.4),
353             (-0.16976192468426815, -0.8825527410211623, -0.43849919982085056)]
354
355        # render will be triggered by the scalar widget change event when setting new options
356        self.set_plotter_to_curing()
357        self.curing_mesh_data = self.create_curing_mesh_data(curing_mesh)
358        self.plotter.camera_position = initial_camera_position
359        self.relevant_curing_cell_fields = self.get_relevant_curing_point_fields()
360        self.update_available_curing_layers()
361        
362        # render will be triggered by the scalar widget change event when setting new options
363        self.set_plotter_to_structure()
364        self.structure_mesh_data = self.create_structure_mesh_data(structure_mesh)
365        self.plotter.camera_position = initial_camera_position
366        self.relevant_structure_cell_fields = self.get_relevant_structure_cell_fields()
367        self.update_available_structure_layers()
368
369        self.plotter.update()
370
371    def create_structure_mesh_data(self, mesh_path: str) -> dict:
372        """Read structure 3D mesh and split into information necessary for display purposes.
373        
374        Args:
375            mesh_path: Path to vtu file on file system
376
377        Returns:
378            dictionary containing parsed mesh data for given file with placeholders for display PyVista actors
379        """
380        mesh = self.reduce_cell_points(pv.read(mesh_path).cast_to_unstructured_grid(), self.STRUCTURE_MESH_ID)
381        layers = np.unique(mesh[self.LAYER_ID_KEY])
382        mesh_data = {}
383        for layer_id in layers:
384            input_mesh = mesh.remove_cells(np.argwhere(mesh[self.LAYER_ID_KEY] != layer_id))
385            input_mesh['color'] = [0 for x in range(len(input_mesh[self.LAYER_THICKNESS_KEY]))]
386            mesh_data[layer_id] = {
387                "input": input_mesh,
388                "warped": input_mesh.warp_by_vector(vectors=self.POINT_WARP_KEY),
389                "scalar_bar_name": None,
390                "output": None
391            }
392        return mesh_data
393
394    def create_curing_mesh_data(self, mesh_path: str) -> dict:
395        """Read structure 3D mesh and split into information necessary for display purposes.
396        
397        Args:
398            mesh_path: Path to vtu file on file system
399
400        Returns:
401            dictionary containing parsed mesh data for given file with placeholders for display PyVista actors
402        """
403        mesh = self.reduce_cell_points(pv.read(mesh_path).cast_to_unstructured_grid(), self.CURING_MESH_ID)
404        layers = np.unique(mesh[self.LAYER_ID_KEY])
405        mesh_data = {}
406        for layer_id in layers:
407            input_mesh = mesh.remove_cells(np.argwhere(mesh[self.LAYER_ID_KEY] != layer_id))
408            input_mesh['color'] = [0 for x in range(len(input_mesh[self.LAYER_THICKNESS_KEY]))]
409            mesh_data[layer_id] = {
410                "input": input_mesh,
411                "warped": input_mesh.warp_by_vector(vectors=self.POINT_WARP_KEY),
412                "scalar_bar_name": None,
413                "output": None
414            }
415        return mesh_data
416
417    def reduce_cell_points(self, mesh: pv.UnstructuredGrid, key: int) -> pv.UnstructuredGrid:
418        """Reduces 3D cells of structure simulation result to 2D polygons for display purposes.
419        
420        Args:
421            mesh: PyVista mesh read directly from simulation result file
422            key: key to add to cells for later identification of meshes
423
424        Returns:
425            3D mesh with same cell layout and cell data content as input, but reduced in dimension (20 to 4 points per cell)
426        """
427        full_cell_data = mesh.cell_data
428        full_point_data = mesh.point_data
429        cell_ids = mesh.cells
430        cell_ids = np.split(cell_ids, len(cell_ids) / 21)
431        
432        new_points = []
433        new_ids = []
434        point_scalar_slice = []
435        for cell_id in range(mesh.n_cells):
436            cell = mesh.get_cell(cell_id)
437            new_ids.append(4)
438            for i, point in enumerate(cell.points[:4]):
439                new_points.append(point)
440                point_scalar_slice.append(cell_ids[cell_id][i+1])
441                new_ids.append(len(new_points) - 1)
442        
443        point_scalars = full_point_data[self.POINT_WARP_KEY][point_scalar_slice]
444        
445        reduced_mesh = pv.UnstructuredGrid(new_ids, [pv.CellType.POLYGON for _ in range(mesh.n_cells)], new_points)
446        for prop in full_cell_data:
447            reduced_mesh[prop] = full_cell_data[prop]
448        reduced_mesh["mesh_id"] = [key] * len(cell_ids)
449        reduced_mesh.point_data[self.POINT_WARP_KEY] = point_scalars
450        reduced_mesh.point_data["{}_total".format(self.POINT_WARP_KEY)] = [math.sqrt(x*x+y*y+z*z) for [x,y,z] in point_scalars]
451        for i in range(len(point_scalars[0])):
452            reduced_mesh.point_data["{}_{}".format(self.POINT_WARP_KEY, i)] = [x[i] for x in point_scalars]
453
454        return reduced_mesh
455
456    def get_relevant_structure_cell_fields(self) -> list:
457        """Get all fields that should ne filter-able for structure mesh.
458        
459        Returns:
460            list of string names of scalar fields that should be toggleable for display
461        """
462        relevant_cells = ["THICKMM"]
463        for x in self.structure_mesh_data[list(self.structure_mesh_data.keys())[0]]["input"].cell_data:
464            if x.startswith("DFAT_") or x.startswith("TFAT_"):
465                relevant_cells.append(x)
466        return relevant_cells
467
468    def get_relevant_curing_point_fields(self) -> list:
469        """Get all fields that should ne filter-able for curing mesh.
470        
471        Returns:
472            list of string names of scalar fields that should be toggleable for display
473        """
474        relevant_cells = []
475        for x in self.curing_mesh_data[list(self.curing_mesh_data.keys())[0]]["input"].point_data:
476            if x.startswith("{}_".format(self.POINT_WARP_KEY)):
477                relevant_cells.append(x)
478        return relevant_cells
479
480    def update_available_structure_layers(self):
481        """Update available layers for display in structure dropdown.
482        
483        This causes an update in PyVista rendering by triggering the onchange event of 
484        the layer select widget.
485        """
486        self.structure_layer_select.options = self.structure_mesh_data.keys()
487        self.structure_layer_select.value = list(self.structure_mesh_data.keys())[0]
488
489    def update_available_curing_layers(self):
490        """Update available layers for display in curing dropdown.
491        
492        This causes an update in PyVista rendering by triggering the onchange event of 
493        the layer select widget.
494        """
495        self.curing_layer_select.options = self.curing_mesh_data.keys()
496        self.curing_layer_select.value = list(self.curing_mesh_data.keys())[0]
497
498    def update_available_structure_layer_scalars(self, layer_id: int):
499        """Update scalar select with available scalars for currently displayed layer."""
500        options = []
501        for field in self.relevant_structure_cell_fields:
502            if not np.isnan(self.structure_mesh_data[layer_id]["input"][field]).all():
503                options.append(field)
504        if len(options) == 0:
505            options.append("No layers with valid values")
506        self.structure_scalar_select.options = options
507        self.structure_scalar_select.value = options[0]
508
509    def update_available_curing_layer_scalars(self, layer_id: int):
510        """Update scalar select with available scalars for currently displayed layer."""
511        options = []
512        for field in self.relevant_curing_cell_fields:
513            if not np.isnan(self.curing_mesh_data[layer_id]["input"][field]).all():
514                options.append(field)
515        if len(options) == 0:
516            options.append("No layers with valid values")
517        self.curing_scalar_select.options = options
518        self.curing_scalar_select.value = options[0]
519        
520    def on_structure_layer_changed(self, value: Bunch):
521        """Event to update UI after selected structure layer to display has changed."""
522        self.update_available_structure_layer_scalars(value["new"])
523        self.update_displayed_structure_scalar(self.structure_scalar_select.value)
524
525    def on_curing_layer_changed(self, value: Bunch):
526        """Event to update UI after selected curing layer to display has changed."""
527        self.update_available_curing_layer_scalars(value["new"])
528        self.update_displayed_curing_scalar(self.curing_scalar_select.value)
529
530    def on_structure_scalar_changed(self, value: Bunch):
531        """Event to update UI after selected structure scalar to display has changed."""
532        self.update_displayed_structure_scalar(value["new"])
533
534    def on_curing_scalar_changed(self, value: Bunch):
535        """Event to update UI after selected curing scalar to display has changed."""
536        self.update_displayed_curing_scalar(value["new"])
537
538    def on_structure_deformation_changed(self, value: Bunch):
539        """Event to update UI after value of deformation display has changed."""
540        self.update_displayed_structure_scalar(self.structure_scalar_select.value)
541
542    def on_curing_deformation_changed(self, value: Bunch):
543        """Event to update UI after value of deformation display has changed."""
544        self.update_displayed_curing_scalar(self.curing_scalar_select.value)
545
546    def update_displayed_structure_scalar(self, value: str):
547        """Update PyVista structure render.
548
549        Args:
550            value: Scalar name to display on 3D mesh
551        """
552        self.set_plotter_to_structure()
553        if self.structure_deformation_toggle.value == True:
554            mesh_name = "warped"
555        else:
556            mesh_name = "input"
557        for layer_id, structure_layer in self.structure_mesh_data.items():
558            if structure_layer["output"] != None:
559                self.plotter.remove_actor(structure_layer["output"])
560                # scalars bars with multiplots have unpredictable behaviour, do not this check
561                if structure_layer["scalar_bar_name"] in self.plotter.scalar_bars:
562                    self.plotter.remove_scalar_bar(structure_layer["scalar_bar_name"])
563                structure_layer["scalar_bar_name"] = None
564                structure_layer["output"] = None
565        if self.structure_limit_actor:
566            self.plotter.remove_actor(self.structure_limit_actor)
567            self.structure_limit_actor = None
568        self.structure_cell_info.value = ""
569
570        data = self.structure_mesh_data[self.structure_layer_select.value]
571        data[mesh_name].cell_data.set_scalars(data[mesh_name][value], "color")
572        if value.startswith("DFAT_"):
573            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["cmap"]
574            clim = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["clim"]
575            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["log_scale"]
576            title = "DFAT Layer {}".format(self.structure_layer_select.value)
577            self.structure_limit_actor = self.show_limit_value(data[mesh_name], value, limit="max", geometry_type="cell")
578        elif value.startswith("TFAT"):
579            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["cmap"]
580            clim = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["clim"]
581            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["log_scale"]
582            title = "TFAT Layer {}".format(self.structure_layer_select.value)
583            self.structure_limit_actor = self.show_limit_value(data[mesh_name], value, limit="min", geometry_type="cell")
584        elif value.startswith(self.LAYER_THICKNESS_KEY):
585            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["THICKNESS"]["cmap"]
586            clim = np.nanmin(data[mesh_name][self.LAYER_THICKNESS_KEY]), np.max(data[mesh_name][self.LAYER_THICKNESS_KEY])
587            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["THICKNESS"]["log_scale"]
588            title = "Thickness(mm) Layer {}".format(self.structure_layer_select.value)
589        else:
590            raise ValueError("No colorscheme for chosen scalar")
591
592        data["output"] = self.plotter.add_mesh(data[mesh_name], show_edges=False, lighting=False, show_scalar_bar=True, scalars="color", 
593                                               cmap=cmap, clim=clim, log_scale=log_scale, scalar_bar_args={'title': title})
594        data["scalar_bar_name"] = title
595        self.plotter.update()
596
597    def update_displayed_curing_scalar(self, value: str):
598        """Update PyVista curing render.
599
600        Args:
601            value: Scalar name to display on 3D mesh
602        """
603        self.set_plotter_to_curing()
604        if self.curing_deformation_toggle.value == True:
605            mesh_name = "warped"
606        else:
607            mesh_name = "input"
608        for layer_id, curing_layer in self.curing_mesh_data.items():
609            if curing_layer["output"] != None:
610                self.plotter.remove_actor(curing_layer["output"])
611                # scalars bars with multiplots have unpredictable behaviour, do not this check
612                if curing_layer["scalar_bar_name"] in self.plotter.scalar_bars:
613                    self.plotter.remove_scalar_bar(curing_layer["scalar_bar_name"])
614                curing_layer["scalar_bar_name"] = None
615                curing_layer["output"] = None
616        if self.curing_limit_actor:
617            self.plotter.remove_actor(self.curing_limit_actor)
618            self.curing_limit_actor = None
619        self.curing_cell_info.value = ""
620
621        data = self.curing_mesh_data[self.curing_layer_select.value]
622        if value.startswith(self.POINT_WARP_KEY):
623            cmap = 'jet'
624            title = value
625            self.curing_limit_actor = self.show_limit_value(data[mesh_name], value, limit="max", geometry_type="point")
626        else:
627            raise ValueError("No colorscheme for chosen scalar")
628        data["output"] = self.plotter.add_mesh(data[mesh_name], show_edges=False, lighting=False, show_scalar_bar=True, scalars=value, 
629                                               cmap=cmap, scalar_bar_args={'title': title})
630        data["scalar_bar_name"] = title
631        self.plotter.update()
632
633    def show_limit_value(self, mesh: pv.UnstructuredGrid, scalar: str, limit: str='max', geometry_type: str='cell') -> vtkActor2D:
634        """Render point with tooltip at place of maximum value in PyVista.
635        
636        Args:
637            mesh: 3D mesh with cells to check for values
638            scalar: Name of scalar to check for values
639            limit: 'max' or 'min'
640
641        Returns:
642            vtk actor of added max value point
643        """
644        if limit == 'max':
645            limit_value = np.nanmax(mesh[scalar])
646            fmt = "Max: %.5f"
647        else:
648            limit_value = np.nanmin(mesh[scalar])
649            fmt = "Min: %.5f"
650
651        if geometry_type == "cell":
652            limit_cell = mesh.remove_cells(np.argwhere(mesh[scalar] != limit_value))
653            point = limit_cell.points[0]
654            scalar_value = [limit_cell[scalar]]
655        elif geometry_type == "point":
656            limit_cells = mesh.extract_points(np.argwhere(mesh[scalar] == limit_value))
657            point = limit_cells.points[0]
658            scalar_value = [limit_cells[scalar][0]]
659
660        return self.plotter.add_point_scalar_labels(point, scalar_value, name="{}_limit_value_point".format(geometry_type), fmt=fmt, point_size=20, shape="rounded_rect", 
661                                point_color="red", render_points_as_spheres=True, fill_shape=True, shape_color="pink", text_color="red",shadow=True, tolerance=1)
662    
663    def selection_callback(self, cell: pv.Cell):
664        """Event to display detail information for selected cell."""
665        display_html = '<p class="global_headline"> Selected cell: </p>'
666        if cell["mesh_id"] == self.CURING_MESH_ID:
667            for prop in cell.point_data:
668                if not np.isnan(cell[prop].any()):
669                    display_html += "<b>{property_name}</b>: {property_value} \n".format(property_name=prop, property_value=cell[prop][0])
670            self.curing_cell_info.value = display_html
671        else:
672            for prop in cell.cell_data:
673                if not np.isnan(cell[prop]):
674                    display_html += "<b>{property_name}</b>: {property_value} \n".format(property_name=prop, property_value=cell[prop][0])
675            self.structure_cell_info.value = display_html
676    
677    def set_plotter_to_curing(self):
678        """Set active plot to curing simulation, redirecting call to plotter."""
679        self.plotter.subplot(0,0)
680
681    def set_plotter_to_structure(self):
682        """Set active plot to structure simulation, redirecting call to plotter."""
683        self.plotter.subplot(1,0)
684
685    def load_local_data(self, ui_element=None):
686        """Use locally stored data for UI.
687
688        Args:
689            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
690        """
691        self.conn = store_connection.LocalStoreConnection("sensotwin_world")
692        self.load_input_set_data()
693
694    def load_remote_data(self, ui_element=None):
695        """Use remotely stored data for UI.
696
697        Args:
698            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
699        """
700        self.conn = store_connection.FusekiConnection(self.remote_database_input.value)
701        self.load_input_set_data()
702
703    def load_input_set_data(self):
704        """Load data from previously set input source and populate UI."""
705        self.temperature_input_set_data = temperature_config.MaterialHardeningInputDataSet.get_all_entries_from_store(self.conn)
706        if self.temperature_input_set_data:
707            options = []
708            for key, input_set in self.temperature_input_set_data.items():
709                label = input_set.generate_input_set_display_label()
710                options.append((label, key))
711            self.temperature_input_selection.options = options
712            self.temperature_input_selection.value = list(self.temperature_input_set_data.keys())[0]
713
714        self.defect_input_set_data = defect_config.DefectInputDataSet.get_all_entries_from_store(self.conn)
715        if self.defect_input_set_data:
716            options = []
717            for key, input_set in self.defect_input_set_data.items():
718                label = input_set.generate_input_set_display_label()
719                options.append((label, key))
720            self.defect_input_selection.options = options
721            self.defect_input_selection.value = list(self.defect_input_set_data.keys())[0]
722
723        self.stress_input_set_data = stress_config.StressStrainInputDataSet.get_all_entries_from_store(self.conn)
724        if self.stress_input_set_data:
725            options = []
726            for key, input_set in self.stress_input_set_data.items():
727                label = input_set.generate_input_set_display_label()
728                options.append((label, key))
729            self.stress_input_selection.options = options
730            self.stress_input_selection.value = list(self.stress_input_set_data.keys())[0]
731
732    def apply_styling(self):
733        css = """
734            <style>
735            {}
736            {}
737            </style>
738        """.format(global_style.global_css, style.local_css)
739        return widgets.HTML(css)
def create_d_cmap():
21def create_d_cmap():
22    """Create matplotlib color map for d plot in result.
23    
24    Returns:
25        matplotlib Colormap
26    """
27    base_cmap = plt.get_cmap('jet')
28    damage_cmap = pltcolor.LinearSegmentedColormap.from_list(
29            'trunc({n},{a:.2f},{b:.2f})'.format(n=base_cmap.name, a=0.30, b=1.0),
30            base_cmap(np.linspace(0.30, 1.0, 100)))
31    return damage_cmap

Create matplotlib color map for d plot in result.

Returns: matplotlib Colormap

def create_t_cmap():
33def create_t_cmap():
34    """Create matplotlib color map for t plot in result.
35    
36    Returns:
37        matplotlib Colormap
38    """
39    base_cmap = plt.get_cmap('jet')
40    damage_cmap = pltcolor.LinearSegmentedColormap.from_list(
41            'trunc({n},{a:.2f},{b:.2f})'.format(n=base_cmap.name, a=0.30, b=1.0),
42            base_cmap(np.linspace(0.30, 1.0, 100)))
43    return "jet"

Create matplotlib color map for t plot in result.

Returns: matplotlib Colormap

class LifetimeExtrapolationUIElements:
 46class LifetimeExtrapolationUIElements:
 47    """Contains all UI functionality for executing simulation and viewing results (Step 4)."""
 48    LAYER_ID_KEY = "LAYID"
 49    LAYER_THICKNESS_KEY = 'THICKMM'
 50    POINT_WARP_KEY = "U_AEROCENT"
 51    CURING_MESH_ID = 0
 52    STRUCTURE_MESH_ID = 1
 53
 54    STRUCTURE_DISPLAY_DEFINITIONS = {
 55            "DFAT": {
 56                "clim": [0.000000001, 1],
 57                "cmap": create_d_cmap(),
 58                "log_scale": True
 59            },
 60            "TFAT": {
 61                "clim": [0.001, 100],
 62                "cmap": create_t_cmap(),
 63                "log_scale": True
 64            },
 65            "THICKNESS": {
 66                "cmap": "terrain",
 67                "log_scale": False
 68            }
 69        }
 70    
 71    structure_limit_actor = None
 72    curing_limit_actor = None
 73
 74    def __init__(self):
 75        """Initialize UI objects and associated UI functionality."""
 76        self.init_input_set_selection_UI()
 77        self.init_job_overview_UI()
 78        self.init_result_display_UI()
 79
 80    def init_input_set_selection_UI(self):
 81        """Initialize first UI of notebook, choosing data source and selecting input data sets."""
 82        temperature_input_selection_label = widgets.Label(value="Material Hardening Input:").add_class("global_headline")
 83        self.temperature_input_selection = widgets.Select(
 84            options=['No data in source'],
 85            value='No data in source',
 86            rows=10,
 87            description='Input sets:',
 88            disabled=False
 89        ).add_class("global_input_set_selection").add_class("global_basic_input")
 90        defect_input_selection_label = widgets.Label(value="Defect Placement Input Set:").add_class("global_headline")
 91        self.defect_input_selection = widgets.Select(
 92            options=['No data in source'],
 93            value='No data in source',
 94            rows=10,
 95            description='Input sets:',
 96            disabled=False
 97        ).add_class("global_input_set_selection").add_class("global_basic_input")
 98        stress_input_selection_label = widgets.Label(value="Stress/Strain Input Set:").add_class("global_headline")
 99        self.stress_input_selection = widgets.Select(
100            options=['No data in source'],
101            value='No data in source',
102            rows=10,
103            description='Input sets:',
104            disabled=False
105        ).add_class("global_input_set_selection").add_class("global_basic_input")
106        start_simulation_button = widgets.Button(
107            description='Start simulation with selected input data sets',
108            disabled=False,
109            tooltip='Start simulation with selected input data sets',
110            icon='play',
111        ).add_class("global_save_input_set_button").add_class("global_basic_button")
112        start_simulation_button.on_click(self.start_simulation)
113
114        simulation_temperature_selection = widgets.VBox([
115            temperature_input_selection_label,
116            self.temperature_input_selection
117        ]).add_class("life_extra_input_column_container")
118        simulation_defect_placement = widgets.VBox([
119            defect_input_selection_label,
120            self.defect_input_selection
121        ]).add_class("life_extra_input_column_container")
122        simulation_stress_selection = widgets.VBox([
123            stress_input_selection_label,
124            self.stress_input_selection
125        ]).add_class("life_extra_input_column_container")
126        simulation_box = widgets.VBox([
127            widgets.HBox([
128                simulation_temperature_selection,
129                simulation_defect_placement,
130                simulation_stress_selection
131            ]).add_class("life_extra_input_row_container"),
132            start_simulation_button
133        ])
134
135        local_database_label = widgets.Label(value="Use local owlready2 database:").add_class("global_headline")
136        use_local_data_button = widgets.Button(
137            description='Use local database',
138            disabled=False,
139            tooltip='Use local database',
140            icon='play',
141        ).add_class("global_load_data_button").add_class("global_basic_button")
142        use_local_data_button.on_click(self.load_local_data)
143        remote_database_label = widgets.Label(value="Use remote Apache Jena Fuseki database:").add_class("global_headline")
144        self.remote_database_input = widgets.Text(
145            value=None,
146            placeholder="insert SPARQL url here",
147            description="SPARQL Endpoint:",
148            disabled=False   
149        ).add_class("global_url_input").add_class("global_basic_input")
150        use_remote_data_button = widgets.Button(
151            description='Use remote database',
152            disabled=False,
153            tooltip='Use remote database',
154            icon='play',
155        ).add_class("global_load_data_button").add_class("global_basic_button")
156        use_remote_data_button.on_click(self.load_remote_data)
157        local_data_box = widgets.VBox([
158            local_database_label,
159            use_local_data_button
160        ])
161        remote_data_box = widgets.VBox([
162            remote_database_label,
163            self.remote_database_input,
164            use_remote_data_button
165        ])
166        data_source_box = widgets.VBox([
167            local_data_box,
168            remote_data_box
169        ]).add_class("global_data_tab_container")
170
171        self.input_dashboard = widgets.Tab().add_class("global_tab_container")
172        self.input_dashboard.children = [
173            data_source_box,
174            simulation_box
175        ]
176        tab_titles = ['Input', 'Simulation']
177        for i in range(len(tab_titles)):
178            self.input_dashboard.set_title(i, tab_titles[i])
179
180    def start_simulation(self, ui_element=None):
181        """Start simulation with the 3 currently selected input sets.
182
183        Args:
184            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
185        """
186        configuration.execute_job(
187            self.pyiron_project,
188            self.temperature_input_set_data[self.temperature_input_selection.value],
189            self.defect_input_set_data[self.defect_input_selection.value],
190            self.stress_input_set_data[self.stress_input_selection.value],
191            self.conn
192        )
193
194    def init_job_overview_UI(self):
195        """Initialize second UI of notebook, viewing the pyiron job table."""
196        self.pyiron_project = configuration.get_pyiron_project("jobs_test")
197        self.job_dashboard = widgets.Output(layout={'border': '1px solid black'})
198        self.update_job_list_display()
199
200    def update_job_list_display(self):
201        """Reload job table from pyiron project."""
202        if self.job_dashboard:
203            self.job_dashboard.clear_output()
204            columns_to_hide = ["projectpath","chemicalformula","parentid","masterid","hamversion"]
205            with self.job_dashboard:
206                display(configuration.get_job_table(self.pyiron_project).drop(columns=columns_to_hide))
207
208    def init_result_display_UI(self):
209        """Initialize third UI of notebook, displaying result of a simulation."""
210        result_selection_label = widgets.Label(value="Completed simulation:").add_class("global_headline")
211        self.result_selection = widgets.Select(
212            options=['Load data first'],
213            value='Load data first',
214            rows=10,
215            description='Input sets:',
216            disabled=False
217        ).add_class("global_input_set_selection").add_class("global_basic_input")
218        display_result_button = widgets.Button(
219            description='Display Result',
220            disabled=False,
221            tooltip='Display Result',
222            icon='play',
223        ).add_class("global_basic_button")
224        display_result_button.on_click(self.display_result)
225        result_display_selection_box = widgets.VBox([
226            result_selection_label,
227            self.result_selection,
228            display_result_button
229        ])
230
231        structure_render_label = widgets.Label(value="Structure simulation:").add_class("global_headline")
232        self.structure_deformation_toggle = widgets.ToggleButton(
233            value=False,
234            description='Toggle Deformation',
235            tooltip='Toggle Deformation',
236        ).add_class("global_basic_button")
237        self.structure_deformation_toggle.observe(self.on_structure_deformation_changed, names=['value'])
238        self.structure_layer_select = widgets.Dropdown(
239            options=['Load Result'],
240            value='Load Result',
241            description='Layer:',
242            disabled=False,
243        ).add_class("global_basic_input")
244        self.structure_layer_select.observe(self.on_structure_layer_changed, names=['value'])
245        self.structure_scalar_select = widgets.Dropdown(
246            options=['Select Layer'],
247            value='Select Layer',
248            description='Value:',
249            disabled=False,
250        ).add_class("global_basic_input")
251        self.structure_scalar_select.observe(self.on_structure_scalar_changed, names=['value'])
252        self.structure_cell_info = widgets.HTML().add_class("life_extra_cell_data_column_container")
253        
254        curing_render_label = widgets.Label(value="Curing simulation:").add_class("global_headline")
255        self.curing_deformation_toggle = widgets.ToggleButton(
256            value=False,
257            description='Toggle Deformation',
258            tooltip='Toggle Deformation',
259        ).add_class("global_basic_button")
260        self.curing_deformation_toggle.observe(self.on_curing_deformation_changed, names=['value'])
261        self.curing_layer_select = widgets.Dropdown(
262            options=['Load Result'],
263            value='Load Result',
264            description='Layer:',
265            disabled=False,
266        ).add_class("global_basic_input")
267        self.curing_layer_select.observe(self.on_curing_layer_changed, names=['value'])
268        self.curing_scalar_select = widgets.Dropdown(
269            options=['Select Layer'],
270            value='Select Layer',
271            description='Value:',
272            disabled=False,
273        ).add_class("global_basic_input")
274        self.curing_scalar_select.observe(self.on_curing_scalar_changed, names=['value'])
275        self.curing_cell_info = widgets.HTML().add_class("life_extra_cell_data_column_container")
276
277        result_display_controls = widgets.VBox([
278            widgets.VBox([
279                curing_render_label,
280                self.curing_deformation_toggle,
281                self.curing_layer_select,
282                self.curing_scalar_select
283            ]).add_class("life_extra_output_column_container"), 
284            widgets.VBox([
285                structure_render_label,
286                self.structure_deformation_toggle,
287                self.structure_layer_select,
288                self.structure_scalar_select
289            ]).add_class("life_extra_output_column_container")
290        ]).add_class("life_extra_output_column_container")
291
292        PYVISTA_OUTPUT_RENDER_HEIGHT = 1100
293        PYVISTA_OUTPUT_RENDER_WIDTH = 900
294        pyvista_render_widget = widgets.Output(layout={'height': '{}px'.format(PYVISTA_OUTPUT_RENDER_HEIGHT+15), 
295                                                           'width': '{}px'.format(PYVISTA_OUTPUT_RENDER_WIDTH+10)})
296
297        result_cell_info_box = widgets.VBox([
298            self.curing_cell_info,
299            self.structure_cell_info
300        ]).add_class("life_extra_output_column_container")
301        result_display_box = widgets.HBox([
302            widgets.VBox([
303                pyvista_render_widget
304            ]),
305            result_display_controls,
306            result_cell_info_box
307        ])
308
309        self.plotter = pv.Plotter(shape=(2,1))
310        self.plotter.window_size = [PYVISTA_OUTPUT_RENDER_WIDTH, PYVISTA_OUTPUT_RENDER_HEIGHT]
311        self.plotter.enable_element_picking(callback=self.selection_callback, show_message=False)
312        with pyvista_render_widget:
313            self.plotter.show(jupyter_backend='trame')
314
315        self.result_dashboard = widgets.Tab().add_class("global_tab_container")
316        self.result_dashboard.children = [
317            result_display_selection_box,
318            result_display_box
319        ]
320        tab_titles = ['Select Result', 'Result Display']
321        for i in range(len(tab_titles)):
322            self.result_dashboard.set_title(i, tab_titles[i])
323
324    def update_result_list_display(self):
325        """Update list of completed jobs in result display selection list."""
326        if self.result_dashboard:
327            options = []
328            for _, row in configuration.get_job_table(self.pyiron_project).iterrows():
329                if row["hamilton"] == "StructureSimulation" and row["status"] == "finished":
330                    options.append((row["job"], row["id"]))
331            if options:
332                self.result_selection.options = options
333                self.result_selection.value = options[0][1]
334            
335    def display_result(self, ui_element=None):
336        """Load currently selected result from list of finished jobs.
337
338        Args:
339            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
340        """
341        all_jobs = configuration.get_job_table(self.pyiron_project)
342        result = all_jobs.loc[all_jobs.id == self.result_selection.value].iloc[0]
343        result_job = self.pyiron_project.load(result["job"])
344        self.init_pyvista_render(result_job.output.curing_mesh,result_job.output.mesh)
345
346    def init_pyvista_render(self, curing_mesh, structure_mesh):
347        """Load result mesh from job into PyVista display.
348        
349        Args:
350            structure_mesh: Relative file path to result mesh
351        """
352        initial_camera_position = [(-30.765771120379302, -28.608772602676154, 39.46235706090557),
353             (0.9572034500000001, 0.0005481500000000805, -30.4),
354             (-0.16976192468426815, -0.8825527410211623, -0.43849919982085056)]
355
356        # render will be triggered by the scalar widget change event when setting new options
357        self.set_plotter_to_curing()
358        self.curing_mesh_data = self.create_curing_mesh_data(curing_mesh)
359        self.plotter.camera_position = initial_camera_position
360        self.relevant_curing_cell_fields = self.get_relevant_curing_point_fields()
361        self.update_available_curing_layers()
362        
363        # render will be triggered by the scalar widget change event when setting new options
364        self.set_plotter_to_structure()
365        self.structure_mesh_data = self.create_structure_mesh_data(structure_mesh)
366        self.plotter.camera_position = initial_camera_position
367        self.relevant_structure_cell_fields = self.get_relevant_structure_cell_fields()
368        self.update_available_structure_layers()
369
370        self.plotter.update()
371
372    def create_structure_mesh_data(self, mesh_path: str) -> dict:
373        """Read structure 3D mesh and split into information necessary for display purposes.
374        
375        Args:
376            mesh_path: Path to vtu file on file system
377
378        Returns:
379            dictionary containing parsed mesh data for given file with placeholders for display PyVista actors
380        """
381        mesh = self.reduce_cell_points(pv.read(mesh_path).cast_to_unstructured_grid(), self.STRUCTURE_MESH_ID)
382        layers = np.unique(mesh[self.LAYER_ID_KEY])
383        mesh_data = {}
384        for layer_id in layers:
385            input_mesh = mesh.remove_cells(np.argwhere(mesh[self.LAYER_ID_KEY] != layer_id))
386            input_mesh['color'] = [0 for x in range(len(input_mesh[self.LAYER_THICKNESS_KEY]))]
387            mesh_data[layer_id] = {
388                "input": input_mesh,
389                "warped": input_mesh.warp_by_vector(vectors=self.POINT_WARP_KEY),
390                "scalar_bar_name": None,
391                "output": None
392            }
393        return mesh_data
394
395    def create_curing_mesh_data(self, mesh_path: str) -> dict:
396        """Read structure 3D mesh and split into information necessary for display purposes.
397        
398        Args:
399            mesh_path: Path to vtu file on file system
400
401        Returns:
402            dictionary containing parsed mesh data for given file with placeholders for display PyVista actors
403        """
404        mesh = self.reduce_cell_points(pv.read(mesh_path).cast_to_unstructured_grid(), self.CURING_MESH_ID)
405        layers = np.unique(mesh[self.LAYER_ID_KEY])
406        mesh_data = {}
407        for layer_id in layers:
408            input_mesh = mesh.remove_cells(np.argwhere(mesh[self.LAYER_ID_KEY] != layer_id))
409            input_mesh['color'] = [0 for x in range(len(input_mesh[self.LAYER_THICKNESS_KEY]))]
410            mesh_data[layer_id] = {
411                "input": input_mesh,
412                "warped": input_mesh.warp_by_vector(vectors=self.POINT_WARP_KEY),
413                "scalar_bar_name": None,
414                "output": None
415            }
416        return mesh_data
417
418    def reduce_cell_points(self, mesh: pv.UnstructuredGrid, key: int) -> pv.UnstructuredGrid:
419        """Reduces 3D cells of structure simulation result to 2D polygons for display purposes.
420        
421        Args:
422            mesh: PyVista mesh read directly from simulation result file
423            key: key to add to cells for later identification of meshes
424
425        Returns:
426            3D mesh with same cell layout and cell data content as input, but reduced in dimension (20 to 4 points per cell)
427        """
428        full_cell_data = mesh.cell_data
429        full_point_data = mesh.point_data
430        cell_ids = mesh.cells
431        cell_ids = np.split(cell_ids, len(cell_ids) / 21)
432        
433        new_points = []
434        new_ids = []
435        point_scalar_slice = []
436        for cell_id in range(mesh.n_cells):
437            cell = mesh.get_cell(cell_id)
438            new_ids.append(4)
439            for i, point in enumerate(cell.points[:4]):
440                new_points.append(point)
441                point_scalar_slice.append(cell_ids[cell_id][i+1])
442                new_ids.append(len(new_points) - 1)
443        
444        point_scalars = full_point_data[self.POINT_WARP_KEY][point_scalar_slice]
445        
446        reduced_mesh = pv.UnstructuredGrid(new_ids, [pv.CellType.POLYGON for _ in range(mesh.n_cells)], new_points)
447        for prop in full_cell_data:
448            reduced_mesh[prop] = full_cell_data[prop]
449        reduced_mesh["mesh_id"] = [key] * len(cell_ids)
450        reduced_mesh.point_data[self.POINT_WARP_KEY] = point_scalars
451        reduced_mesh.point_data["{}_total".format(self.POINT_WARP_KEY)] = [math.sqrt(x*x+y*y+z*z) for [x,y,z] in point_scalars]
452        for i in range(len(point_scalars[0])):
453            reduced_mesh.point_data["{}_{}".format(self.POINT_WARP_KEY, i)] = [x[i] for x in point_scalars]
454
455        return reduced_mesh
456
457    def get_relevant_structure_cell_fields(self) -> list:
458        """Get all fields that should ne filter-able for structure mesh.
459        
460        Returns:
461            list of string names of scalar fields that should be toggleable for display
462        """
463        relevant_cells = ["THICKMM"]
464        for x in self.structure_mesh_data[list(self.structure_mesh_data.keys())[0]]["input"].cell_data:
465            if x.startswith("DFAT_") or x.startswith("TFAT_"):
466                relevant_cells.append(x)
467        return relevant_cells
468
469    def get_relevant_curing_point_fields(self) -> list:
470        """Get all fields that should ne filter-able for curing mesh.
471        
472        Returns:
473            list of string names of scalar fields that should be toggleable for display
474        """
475        relevant_cells = []
476        for x in self.curing_mesh_data[list(self.curing_mesh_data.keys())[0]]["input"].point_data:
477            if x.startswith("{}_".format(self.POINT_WARP_KEY)):
478                relevant_cells.append(x)
479        return relevant_cells
480
481    def update_available_structure_layers(self):
482        """Update available layers for display in structure dropdown.
483        
484        This causes an update in PyVista rendering by triggering the onchange event of 
485        the layer select widget.
486        """
487        self.structure_layer_select.options = self.structure_mesh_data.keys()
488        self.structure_layer_select.value = list(self.structure_mesh_data.keys())[0]
489
490    def update_available_curing_layers(self):
491        """Update available layers for display in curing dropdown.
492        
493        This causes an update in PyVista rendering by triggering the onchange event of 
494        the layer select widget.
495        """
496        self.curing_layer_select.options = self.curing_mesh_data.keys()
497        self.curing_layer_select.value = list(self.curing_mesh_data.keys())[0]
498
499    def update_available_structure_layer_scalars(self, layer_id: int):
500        """Update scalar select with available scalars for currently displayed layer."""
501        options = []
502        for field in self.relevant_structure_cell_fields:
503            if not np.isnan(self.structure_mesh_data[layer_id]["input"][field]).all():
504                options.append(field)
505        if len(options) == 0:
506            options.append("No layers with valid values")
507        self.structure_scalar_select.options = options
508        self.structure_scalar_select.value = options[0]
509
510    def update_available_curing_layer_scalars(self, layer_id: int):
511        """Update scalar select with available scalars for currently displayed layer."""
512        options = []
513        for field in self.relevant_curing_cell_fields:
514            if not np.isnan(self.curing_mesh_data[layer_id]["input"][field]).all():
515                options.append(field)
516        if len(options) == 0:
517            options.append("No layers with valid values")
518        self.curing_scalar_select.options = options
519        self.curing_scalar_select.value = options[0]
520        
521    def on_structure_layer_changed(self, value: Bunch):
522        """Event to update UI after selected structure layer to display has changed."""
523        self.update_available_structure_layer_scalars(value["new"])
524        self.update_displayed_structure_scalar(self.structure_scalar_select.value)
525
526    def on_curing_layer_changed(self, value: Bunch):
527        """Event to update UI after selected curing layer to display has changed."""
528        self.update_available_curing_layer_scalars(value["new"])
529        self.update_displayed_curing_scalar(self.curing_scalar_select.value)
530
531    def on_structure_scalar_changed(self, value: Bunch):
532        """Event to update UI after selected structure scalar to display has changed."""
533        self.update_displayed_structure_scalar(value["new"])
534
535    def on_curing_scalar_changed(self, value: Bunch):
536        """Event to update UI after selected curing scalar to display has changed."""
537        self.update_displayed_curing_scalar(value["new"])
538
539    def on_structure_deformation_changed(self, value: Bunch):
540        """Event to update UI after value of deformation display has changed."""
541        self.update_displayed_structure_scalar(self.structure_scalar_select.value)
542
543    def on_curing_deformation_changed(self, value: Bunch):
544        """Event to update UI after value of deformation display has changed."""
545        self.update_displayed_curing_scalar(self.curing_scalar_select.value)
546
547    def update_displayed_structure_scalar(self, value: str):
548        """Update PyVista structure render.
549
550        Args:
551            value: Scalar name to display on 3D mesh
552        """
553        self.set_plotter_to_structure()
554        if self.structure_deformation_toggle.value == True:
555            mesh_name = "warped"
556        else:
557            mesh_name = "input"
558        for layer_id, structure_layer in self.structure_mesh_data.items():
559            if structure_layer["output"] != None:
560                self.plotter.remove_actor(structure_layer["output"])
561                # scalars bars with multiplots have unpredictable behaviour, do not this check
562                if structure_layer["scalar_bar_name"] in self.plotter.scalar_bars:
563                    self.plotter.remove_scalar_bar(structure_layer["scalar_bar_name"])
564                structure_layer["scalar_bar_name"] = None
565                structure_layer["output"] = None
566        if self.structure_limit_actor:
567            self.plotter.remove_actor(self.structure_limit_actor)
568            self.structure_limit_actor = None
569        self.structure_cell_info.value = ""
570
571        data = self.structure_mesh_data[self.structure_layer_select.value]
572        data[mesh_name].cell_data.set_scalars(data[mesh_name][value], "color")
573        if value.startswith("DFAT_"):
574            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["cmap"]
575            clim = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["clim"]
576            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["log_scale"]
577            title = "DFAT Layer {}".format(self.structure_layer_select.value)
578            self.structure_limit_actor = self.show_limit_value(data[mesh_name], value, limit="max", geometry_type="cell")
579        elif value.startswith("TFAT"):
580            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["cmap"]
581            clim = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["clim"]
582            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["log_scale"]
583            title = "TFAT Layer {}".format(self.structure_layer_select.value)
584            self.structure_limit_actor = self.show_limit_value(data[mesh_name], value, limit="min", geometry_type="cell")
585        elif value.startswith(self.LAYER_THICKNESS_KEY):
586            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["THICKNESS"]["cmap"]
587            clim = np.nanmin(data[mesh_name][self.LAYER_THICKNESS_KEY]), np.max(data[mesh_name][self.LAYER_THICKNESS_KEY])
588            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["THICKNESS"]["log_scale"]
589            title = "Thickness(mm) Layer {}".format(self.structure_layer_select.value)
590        else:
591            raise ValueError("No colorscheme for chosen scalar")
592
593        data["output"] = self.plotter.add_mesh(data[mesh_name], show_edges=False, lighting=False, show_scalar_bar=True, scalars="color", 
594                                               cmap=cmap, clim=clim, log_scale=log_scale, scalar_bar_args={'title': title})
595        data["scalar_bar_name"] = title
596        self.plotter.update()
597
598    def update_displayed_curing_scalar(self, value: str):
599        """Update PyVista curing render.
600
601        Args:
602            value: Scalar name to display on 3D mesh
603        """
604        self.set_plotter_to_curing()
605        if self.curing_deformation_toggle.value == True:
606            mesh_name = "warped"
607        else:
608            mesh_name = "input"
609        for layer_id, curing_layer in self.curing_mesh_data.items():
610            if curing_layer["output"] != None:
611                self.plotter.remove_actor(curing_layer["output"])
612                # scalars bars with multiplots have unpredictable behaviour, do not this check
613                if curing_layer["scalar_bar_name"] in self.plotter.scalar_bars:
614                    self.plotter.remove_scalar_bar(curing_layer["scalar_bar_name"])
615                curing_layer["scalar_bar_name"] = None
616                curing_layer["output"] = None
617        if self.curing_limit_actor:
618            self.plotter.remove_actor(self.curing_limit_actor)
619            self.curing_limit_actor = None
620        self.curing_cell_info.value = ""
621
622        data = self.curing_mesh_data[self.curing_layer_select.value]
623        if value.startswith(self.POINT_WARP_KEY):
624            cmap = 'jet'
625            title = value
626            self.curing_limit_actor = self.show_limit_value(data[mesh_name], value, limit="max", geometry_type="point")
627        else:
628            raise ValueError("No colorscheme for chosen scalar")
629        data["output"] = self.plotter.add_mesh(data[mesh_name], show_edges=False, lighting=False, show_scalar_bar=True, scalars=value, 
630                                               cmap=cmap, scalar_bar_args={'title': title})
631        data["scalar_bar_name"] = title
632        self.plotter.update()
633
634    def show_limit_value(self, mesh: pv.UnstructuredGrid, scalar: str, limit: str='max', geometry_type: str='cell') -> vtkActor2D:
635        """Render point with tooltip at place of maximum value in PyVista.
636        
637        Args:
638            mesh: 3D mesh with cells to check for values
639            scalar: Name of scalar to check for values
640            limit: 'max' or 'min'
641
642        Returns:
643            vtk actor of added max value point
644        """
645        if limit == 'max':
646            limit_value = np.nanmax(mesh[scalar])
647            fmt = "Max: %.5f"
648        else:
649            limit_value = np.nanmin(mesh[scalar])
650            fmt = "Min: %.5f"
651
652        if geometry_type == "cell":
653            limit_cell = mesh.remove_cells(np.argwhere(mesh[scalar] != limit_value))
654            point = limit_cell.points[0]
655            scalar_value = [limit_cell[scalar]]
656        elif geometry_type == "point":
657            limit_cells = mesh.extract_points(np.argwhere(mesh[scalar] == limit_value))
658            point = limit_cells.points[0]
659            scalar_value = [limit_cells[scalar][0]]
660
661        return self.plotter.add_point_scalar_labels(point, scalar_value, name="{}_limit_value_point".format(geometry_type), fmt=fmt, point_size=20, shape="rounded_rect", 
662                                point_color="red", render_points_as_spheres=True, fill_shape=True, shape_color="pink", text_color="red",shadow=True, tolerance=1)
663    
664    def selection_callback(self, cell: pv.Cell):
665        """Event to display detail information for selected cell."""
666        display_html = '<p class="global_headline"> Selected cell: </p>'
667        if cell["mesh_id"] == self.CURING_MESH_ID:
668            for prop in cell.point_data:
669                if not np.isnan(cell[prop].any()):
670                    display_html += "<b>{property_name}</b>: {property_value} \n".format(property_name=prop, property_value=cell[prop][0])
671            self.curing_cell_info.value = display_html
672        else:
673            for prop in cell.cell_data:
674                if not np.isnan(cell[prop]):
675                    display_html += "<b>{property_name}</b>: {property_value} \n".format(property_name=prop, property_value=cell[prop][0])
676            self.structure_cell_info.value = display_html
677    
678    def set_plotter_to_curing(self):
679        """Set active plot to curing simulation, redirecting call to plotter."""
680        self.plotter.subplot(0,0)
681
682    def set_plotter_to_structure(self):
683        """Set active plot to structure simulation, redirecting call to plotter."""
684        self.plotter.subplot(1,0)
685
686    def load_local_data(self, ui_element=None):
687        """Use locally stored data for UI.
688
689        Args:
690            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
691        """
692        self.conn = store_connection.LocalStoreConnection("sensotwin_world")
693        self.load_input_set_data()
694
695    def load_remote_data(self, ui_element=None):
696        """Use remotely stored data for UI.
697
698        Args:
699            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
700        """
701        self.conn = store_connection.FusekiConnection(self.remote_database_input.value)
702        self.load_input_set_data()
703
704    def load_input_set_data(self):
705        """Load data from previously set input source and populate UI."""
706        self.temperature_input_set_data = temperature_config.MaterialHardeningInputDataSet.get_all_entries_from_store(self.conn)
707        if self.temperature_input_set_data:
708            options = []
709            for key, input_set in self.temperature_input_set_data.items():
710                label = input_set.generate_input_set_display_label()
711                options.append((label, key))
712            self.temperature_input_selection.options = options
713            self.temperature_input_selection.value = list(self.temperature_input_set_data.keys())[0]
714
715        self.defect_input_set_data = defect_config.DefectInputDataSet.get_all_entries_from_store(self.conn)
716        if self.defect_input_set_data:
717            options = []
718            for key, input_set in self.defect_input_set_data.items():
719                label = input_set.generate_input_set_display_label()
720                options.append((label, key))
721            self.defect_input_selection.options = options
722            self.defect_input_selection.value = list(self.defect_input_set_data.keys())[0]
723
724        self.stress_input_set_data = stress_config.StressStrainInputDataSet.get_all_entries_from_store(self.conn)
725        if self.stress_input_set_data:
726            options = []
727            for key, input_set in self.stress_input_set_data.items():
728                label = input_set.generate_input_set_display_label()
729                options.append((label, key))
730            self.stress_input_selection.options = options
731            self.stress_input_selection.value = list(self.stress_input_set_data.keys())[0]
732
733    def apply_styling(self):
734        css = """
735            <style>
736            {}
737            {}
738            </style>
739        """.format(global_style.global_css, style.local_css)
740        return widgets.HTML(css)

Contains all UI functionality for executing simulation and viewing results (Step 4).

LifetimeExtrapolationUIElements()
74    def __init__(self):
75        """Initialize UI objects and associated UI functionality."""
76        self.init_input_set_selection_UI()
77        self.init_job_overview_UI()
78        self.init_result_display_UI()

Initialize UI objects and associated UI functionality.

LAYER_ID_KEY = 'LAYID'
LAYER_THICKNESS_KEY = 'THICKMM'
POINT_WARP_KEY = 'U_AEROCENT'
CURING_MESH_ID = 0
STRUCTURE_MESH_ID = 1
STRUCTURE_DISPLAY_DEFINITIONS = {'DFAT': {'clim': [1e-09, 1], 'cmap': <matplotlib.colors.LinearSegmentedColormap object>, 'log_scale': True}, 'TFAT': {'clim': [0.001, 100], 'cmap': 'jet', 'log_scale': True}, 'THICKNESS': {'cmap': 'terrain', 'log_scale': False}}
structure_limit_actor = None
curing_limit_actor = None
def init_input_set_selection_UI(self):
 80    def init_input_set_selection_UI(self):
 81        """Initialize first UI of notebook, choosing data source and selecting input data sets."""
 82        temperature_input_selection_label = widgets.Label(value="Material Hardening Input:").add_class("global_headline")
 83        self.temperature_input_selection = widgets.Select(
 84            options=['No data in source'],
 85            value='No data in source',
 86            rows=10,
 87            description='Input sets:',
 88            disabled=False
 89        ).add_class("global_input_set_selection").add_class("global_basic_input")
 90        defect_input_selection_label = widgets.Label(value="Defect Placement Input Set:").add_class("global_headline")
 91        self.defect_input_selection = widgets.Select(
 92            options=['No data in source'],
 93            value='No data in source',
 94            rows=10,
 95            description='Input sets:',
 96            disabled=False
 97        ).add_class("global_input_set_selection").add_class("global_basic_input")
 98        stress_input_selection_label = widgets.Label(value="Stress/Strain Input Set:").add_class("global_headline")
 99        self.stress_input_selection = widgets.Select(
100            options=['No data in source'],
101            value='No data in source',
102            rows=10,
103            description='Input sets:',
104            disabled=False
105        ).add_class("global_input_set_selection").add_class("global_basic_input")
106        start_simulation_button = widgets.Button(
107            description='Start simulation with selected input data sets',
108            disabled=False,
109            tooltip='Start simulation with selected input data sets',
110            icon='play',
111        ).add_class("global_save_input_set_button").add_class("global_basic_button")
112        start_simulation_button.on_click(self.start_simulation)
113
114        simulation_temperature_selection = widgets.VBox([
115            temperature_input_selection_label,
116            self.temperature_input_selection
117        ]).add_class("life_extra_input_column_container")
118        simulation_defect_placement = widgets.VBox([
119            defect_input_selection_label,
120            self.defect_input_selection
121        ]).add_class("life_extra_input_column_container")
122        simulation_stress_selection = widgets.VBox([
123            stress_input_selection_label,
124            self.stress_input_selection
125        ]).add_class("life_extra_input_column_container")
126        simulation_box = widgets.VBox([
127            widgets.HBox([
128                simulation_temperature_selection,
129                simulation_defect_placement,
130                simulation_stress_selection
131            ]).add_class("life_extra_input_row_container"),
132            start_simulation_button
133        ])
134
135        local_database_label = widgets.Label(value="Use local owlready2 database:").add_class("global_headline")
136        use_local_data_button = widgets.Button(
137            description='Use local database',
138            disabled=False,
139            tooltip='Use local database',
140            icon='play',
141        ).add_class("global_load_data_button").add_class("global_basic_button")
142        use_local_data_button.on_click(self.load_local_data)
143        remote_database_label = widgets.Label(value="Use remote Apache Jena Fuseki database:").add_class("global_headline")
144        self.remote_database_input = widgets.Text(
145            value=None,
146            placeholder="insert SPARQL url here",
147            description="SPARQL Endpoint:",
148            disabled=False   
149        ).add_class("global_url_input").add_class("global_basic_input")
150        use_remote_data_button = widgets.Button(
151            description='Use remote database',
152            disabled=False,
153            tooltip='Use remote database',
154            icon='play',
155        ).add_class("global_load_data_button").add_class("global_basic_button")
156        use_remote_data_button.on_click(self.load_remote_data)
157        local_data_box = widgets.VBox([
158            local_database_label,
159            use_local_data_button
160        ])
161        remote_data_box = widgets.VBox([
162            remote_database_label,
163            self.remote_database_input,
164            use_remote_data_button
165        ])
166        data_source_box = widgets.VBox([
167            local_data_box,
168            remote_data_box
169        ]).add_class("global_data_tab_container")
170
171        self.input_dashboard = widgets.Tab().add_class("global_tab_container")
172        self.input_dashboard.children = [
173            data_source_box,
174            simulation_box
175        ]
176        tab_titles = ['Input', 'Simulation']
177        for i in range(len(tab_titles)):
178            self.input_dashboard.set_title(i, tab_titles[i])

Initialize first UI of notebook, choosing data source and selecting input data sets.

def start_simulation(self, ui_element=None):
180    def start_simulation(self, ui_element=None):
181        """Start simulation with the 3 currently selected input sets.
182
183        Args:
184            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
185        """
186        configuration.execute_job(
187            self.pyiron_project,
188            self.temperature_input_set_data[self.temperature_input_selection.value],
189            self.defect_input_set_data[self.defect_input_selection.value],
190            self.stress_input_set_data[self.stress_input_selection.value],
191            self.conn
192        )

Start simulation with the 3 currently selected input sets.

Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event

def init_job_overview_UI(self):
194    def init_job_overview_UI(self):
195        """Initialize second UI of notebook, viewing the pyiron job table."""
196        self.pyiron_project = configuration.get_pyiron_project("jobs_test")
197        self.job_dashboard = widgets.Output(layout={'border': '1px solid black'})
198        self.update_job_list_display()

Initialize second UI of notebook, viewing the pyiron job table.

def update_job_list_display(self):
200    def update_job_list_display(self):
201        """Reload job table from pyiron project."""
202        if self.job_dashboard:
203            self.job_dashboard.clear_output()
204            columns_to_hide = ["projectpath","chemicalformula","parentid","masterid","hamversion"]
205            with self.job_dashboard:
206                display(configuration.get_job_table(self.pyiron_project).drop(columns=columns_to_hide))

Reload job table from pyiron project.

def init_result_display_UI(self):
208    def init_result_display_UI(self):
209        """Initialize third UI of notebook, displaying result of a simulation."""
210        result_selection_label = widgets.Label(value="Completed simulation:").add_class("global_headline")
211        self.result_selection = widgets.Select(
212            options=['Load data first'],
213            value='Load data first',
214            rows=10,
215            description='Input sets:',
216            disabled=False
217        ).add_class("global_input_set_selection").add_class("global_basic_input")
218        display_result_button = widgets.Button(
219            description='Display Result',
220            disabled=False,
221            tooltip='Display Result',
222            icon='play',
223        ).add_class("global_basic_button")
224        display_result_button.on_click(self.display_result)
225        result_display_selection_box = widgets.VBox([
226            result_selection_label,
227            self.result_selection,
228            display_result_button
229        ])
230
231        structure_render_label = widgets.Label(value="Structure simulation:").add_class("global_headline")
232        self.structure_deformation_toggle = widgets.ToggleButton(
233            value=False,
234            description='Toggle Deformation',
235            tooltip='Toggle Deformation',
236        ).add_class("global_basic_button")
237        self.structure_deformation_toggle.observe(self.on_structure_deformation_changed, names=['value'])
238        self.structure_layer_select = widgets.Dropdown(
239            options=['Load Result'],
240            value='Load Result',
241            description='Layer:',
242            disabled=False,
243        ).add_class("global_basic_input")
244        self.structure_layer_select.observe(self.on_structure_layer_changed, names=['value'])
245        self.structure_scalar_select = widgets.Dropdown(
246            options=['Select Layer'],
247            value='Select Layer',
248            description='Value:',
249            disabled=False,
250        ).add_class("global_basic_input")
251        self.structure_scalar_select.observe(self.on_structure_scalar_changed, names=['value'])
252        self.structure_cell_info = widgets.HTML().add_class("life_extra_cell_data_column_container")
253        
254        curing_render_label = widgets.Label(value="Curing simulation:").add_class("global_headline")
255        self.curing_deformation_toggle = widgets.ToggleButton(
256            value=False,
257            description='Toggle Deformation',
258            tooltip='Toggle Deformation',
259        ).add_class("global_basic_button")
260        self.curing_deformation_toggle.observe(self.on_curing_deformation_changed, names=['value'])
261        self.curing_layer_select = widgets.Dropdown(
262            options=['Load Result'],
263            value='Load Result',
264            description='Layer:',
265            disabled=False,
266        ).add_class("global_basic_input")
267        self.curing_layer_select.observe(self.on_curing_layer_changed, names=['value'])
268        self.curing_scalar_select = widgets.Dropdown(
269            options=['Select Layer'],
270            value='Select Layer',
271            description='Value:',
272            disabled=False,
273        ).add_class("global_basic_input")
274        self.curing_scalar_select.observe(self.on_curing_scalar_changed, names=['value'])
275        self.curing_cell_info = widgets.HTML().add_class("life_extra_cell_data_column_container")
276
277        result_display_controls = widgets.VBox([
278            widgets.VBox([
279                curing_render_label,
280                self.curing_deformation_toggle,
281                self.curing_layer_select,
282                self.curing_scalar_select
283            ]).add_class("life_extra_output_column_container"), 
284            widgets.VBox([
285                structure_render_label,
286                self.structure_deformation_toggle,
287                self.structure_layer_select,
288                self.structure_scalar_select
289            ]).add_class("life_extra_output_column_container")
290        ]).add_class("life_extra_output_column_container")
291
292        PYVISTA_OUTPUT_RENDER_HEIGHT = 1100
293        PYVISTA_OUTPUT_RENDER_WIDTH = 900
294        pyvista_render_widget = widgets.Output(layout={'height': '{}px'.format(PYVISTA_OUTPUT_RENDER_HEIGHT+15), 
295                                                           'width': '{}px'.format(PYVISTA_OUTPUT_RENDER_WIDTH+10)})
296
297        result_cell_info_box = widgets.VBox([
298            self.curing_cell_info,
299            self.structure_cell_info
300        ]).add_class("life_extra_output_column_container")
301        result_display_box = widgets.HBox([
302            widgets.VBox([
303                pyvista_render_widget
304            ]),
305            result_display_controls,
306            result_cell_info_box
307        ])
308
309        self.plotter = pv.Plotter(shape=(2,1))
310        self.plotter.window_size = [PYVISTA_OUTPUT_RENDER_WIDTH, PYVISTA_OUTPUT_RENDER_HEIGHT]
311        self.plotter.enable_element_picking(callback=self.selection_callback, show_message=False)
312        with pyvista_render_widget:
313            self.plotter.show(jupyter_backend='trame')
314
315        self.result_dashboard = widgets.Tab().add_class("global_tab_container")
316        self.result_dashboard.children = [
317            result_display_selection_box,
318            result_display_box
319        ]
320        tab_titles = ['Select Result', 'Result Display']
321        for i in range(len(tab_titles)):
322            self.result_dashboard.set_title(i, tab_titles[i])

Initialize third UI of notebook, displaying result of a simulation.

def update_result_list_display(self):
324    def update_result_list_display(self):
325        """Update list of completed jobs in result display selection list."""
326        if self.result_dashboard:
327            options = []
328            for _, row in configuration.get_job_table(self.pyiron_project).iterrows():
329                if row["hamilton"] == "StructureSimulation" and row["status"] == "finished":
330                    options.append((row["job"], row["id"]))
331            if options:
332                self.result_selection.options = options
333                self.result_selection.value = options[0][1]

Update list of completed jobs in result display selection list.

def display_result(self, ui_element=None):
335    def display_result(self, ui_element=None):
336        """Load currently selected result from list of finished jobs.
337
338        Args:
339            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
340        """
341        all_jobs = configuration.get_job_table(self.pyiron_project)
342        result = all_jobs.loc[all_jobs.id == self.result_selection.value].iloc[0]
343        result_job = self.pyiron_project.load(result["job"])
344        self.init_pyvista_render(result_job.output.curing_mesh,result_job.output.mesh)

Load currently selected result from list of finished jobs.

Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event

def init_pyvista_render(self, curing_mesh, structure_mesh):
346    def init_pyvista_render(self, curing_mesh, structure_mesh):
347        """Load result mesh from job into PyVista display.
348        
349        Args:
350            structure_mesh: Relative file path to result mesh
351        """
352        initial_camera_position = [(-30.765771120379302, -28.608772602676154, 39.46235706090557),
353             (0.9572034500000001, 0.0005481500000000805, -30.4),
354             (-0.16976192468426815, -0.8825527410211623, -0.43849919982085056)]
355
356        # render will be triggered by the scalar widget change event when setting new options
357        self.set_plotter_to_curing()
358        self.curing_mesh_data = self.create_curing_mesh_data(curing_mesh)
359        self.plotter.camera_position = initial_camera_position
360        self.relevant_curing_cell_fields = self.get_relevant_curing_point_fields()
361        self.update_available_curing_layers()
362        
363        # render will be triggered by the scalar widget change event when setting new options
364        self.set_plotter_to_structure()
365        self.structure_mesh_data = self.create_structure_mesh_data(structure_mesh)
366        self.plotter.camera_position = initial_camera_position
367        self.relevant_structure_cell_fields = self.get_relevant_structure_cell_fields()
368        self.update_available_structure_layers()
369
370        self.plotter.update()

Load result mesh from job into PyVista display.

Args: structure_mesh: Relative file path to result mesh

def create_structure_mesh_data(self, mesh_path: str) -> dict:
372    def create_structure_mesh_data(self, mesh_path: str) -> dict:
373        """Read structure 3D mesh and split into information necessary for display purposes.
374        
375        Args:
376            mesh_path: Path to vtu file on file system
377
378        Returns:
379            dictionary containing parsed mesh data for given file with placeholders for display PyVista actors
380        """
381        mesh = self.reduce_cell_points(pv.read(mesh_path).cast_to_unstructured_grid(), self.STRUCTURE_MESH_ID)
382        layers = np.unique(mesh[self.LAYER_ID_KEY])
383        mesh_data = {}
384        for layer_id in layers:
385            input_mesh = mesh.remove_cells(np.argwhere(mesh[self.LAYER_ID_KEY] != layer_id))
386            input_mesh['color'] = [0 for x in range(len(input_mesh[self.LAYER_THICKNESS_KEY]))]
387            mesh_data[layer_id] = {
388                "input": input_mesh,
389                "warped": input_mesh.warp_by_vector(vectors=self.POINT_WARP_KEY),
390                "scalar_bar_name": None,
391                "output": None
392            }
393        return mesh_data

Read structure 3D mesh and split into information necessary for display purposes.

Args: mesh_path: Path to vtu file on file system

Returns: dictionary containing parsed mesh data for given file with placeholders for display PyVista actors

def create_curing_mesh_data(self, mesh_path: str) -> dict:
395    def create_curing_mesh_data(self, mesh_path: str) -> dict:
396        """Read structure 3D mesh and split into information necessary for display purposes.
397        
398        Args:
399            mesh_path: Path to vtu file on file system
400
401        Returns:
402            dictionary containing parsed mesh data for given file with placeholders for display PyVista actors
403        """
404        mesh = self.reduce_cell_points(pv.read(mesh_path).cast_to_unstructured_grid(), self.CURING_MESH_ID)
405        layers = np.unique(mesh[self.LAYER_ID_KEY])
406        mesh_data = {}
407        for layer_id in layers:
408            input_mesh = mesh.remove_cells(np.argwhere(mesh[self.LAYER_ID_KEY] != layer_id))
409            input_mesh['color'] = [0 for x in range(len(input_mesh[self.LAYER_THICKNESS_KEY]))]
410            mesh_data[layer_id] = {
411                "input": input_mesh,
412                "warped": input_mesh.warp_by_vector(vectors=self.POINT_WARP_KEY),
413                "scalar_bar_name": None,
414                "output": None
415            }
416        return mesh_data

Read structure 3D mesh and split into information necessary for display purposes.

Args: mesh_path: Path to vtu file on file system

Returns: dictionary containing parsed mesh data for given file with placeholders for display PyVista actors

def reduce_cell_points( self, mesh: pyvista.core.pointset.UnstructuredGrid, key: int) -> pyvista.core.pointset.UnstructuredGrid:
418    def reduce_cell_points(self, mesh: pv.UnstructuredGrid, key: int) -> pv.UnstructuredGrid:
419        """Reduces 3D cells of structure simulation result to 2D polygons for display purposes.
420        
421        Args:
422            mesh: PyVista mesh read directly from simulation result file
423            key: key to add to cells for later identification of meshes
424
425        Returns:
426            3D mesh with same cell layout and cell data content as input, but reduced in dimension (20 to 4 points per cell)
427        """
428        full_cell_data = mesh.cell_data
429        full_point_data = mesh.point_data
430        cell_ids = mesh.cells
431        cell_ids = np.split(cell_ids, len(cell_ids) / 21)
432        
433        new_points = []
434        new_ids = []
435        point_scalar_slice = []
436        for cell_id in range(mesh.n_cells):
437            cell = mesh.get_cell(cell_id)
438            new_ids.append(4)
439            for i, point in enumerate(cell.points[:4]):
440                new_points.append(point)
441                point_scalar_slice.append(cell_ids[cell_id][i+1])
442                new_ids.append(len(new_points) - 1)
443        
444        point_scalars = full_point_data[self.POINT_WARP_KEY][point_scalar_slice]
445        
446        reduced_mesh = pv.UnstructuredGrid(new_ids, [pv.CellType.POLYGON for _ in range(mesh.n_cells)], new_points)
447        for prop in full_cell_data:
448            reduced_mesh[prop] = full_cell_data[prop]
449        reduced_mesh["mesh_id"] = [key] * len(cell_ids)
450        reduced_mesh.point_data[self.POINT_WARP_KEY] = point_scalars
451        reduced_mesh.point_data["{}_total".format(self.POINT_WARP_KEY)] = [math.sqrt(x*x+y*y+z*z) for [x,y,z] in point_scalars]
452        for i in range(len(point_scalars[0])):
453            reduced_mesh.point_data["{}_{}".format(self.POINT_WARP_KEY, i)] = [x[i] for x in point_scalars]
454
455        return reduced_mesh

Reduces 3D cells of structure simulation result to 2D polygons for display purposes.

Args: mesh: PyVista mesh read directly from simulation result file key: key to add to cells for later identification of meshes

Returns: 3D mesh with same cell layout and cell data content as input, but reduced in dimension (20 to 4 points per cell)

def get_relevant_structure_cell_fields(self) -> list:
457    def get_relevant_structure_cell_fields(self) -> list:
458        """Get all fields that should ne filter-able for structure mesh.
459        
460        Returns:
461            list of string names of scalar fields that should be toggleable for display
462        """
463        relevant_cells = ["THICKMM"]
464        for x in self.structure_mesh_data[list(self.structure_mesh_data.keys())[0]]["input"].cell_data:
465            if x.startswith("DFAT_") or x.startswith("TFAT_"):
466                relevant_cells.append(x)
467        return relevant_cells

Get all fields that should ne filter-able for structure mesh.

Returns: list of string names of scalar fields that should be toggleable for display

def get_relevant_curing_point_fields(self) -> list:
469    def get_relevant_curing_point_fields(self) -> list:
470        """Get all fields that should ne filter-able for curing mesh.
471        
472        Returns:
473            list of string names of scalar fields that should be toggleable for display
474        """
475        relevant_cells = []
476        for x in self.curing_mesh_data[list(self.curing_mesh_data.keys())[0]]["input"].point_data:
477            if x.startswith("{}_".format(self.POINT_WARP_KEY)):
478                relevant_cells.append(x)
479        return relevant_cells

Get all fields that should ne filter-able for curing mesh.

Returns: list of string names of scalar fields that should be toggleable for display

def update_available_structure_layers(self):
481    def update_available_structure_layers(self):
482        """Update available layers for display in structure dropdown.
483        
484        This causes an update in PyVista rendering by triggering the onchange event of 
485        the layer select widget.
486        """
487        self.structure_layer_select.options = self.structure_mesh_data.keys()
488        self.structure_layer_select.value = list(self.structure_mesh_data.keys())[0]

Update available layers for display in structure dropdown.

This causes an update in PyVista rendering by triggering the onchange event of the layer select widget.

def update_available_curing_layers(self):
490    def update_available_curing_layers(self):
491        """Update available layers for display in curing dropdown.
492        
493        This causes an update in PyVista rendering by triggering the onchange event of 
494        the layer select widget.
495        """
496        self.curing_layer_select.options = self.curing_mesh_data.keys()
497        self.curing_layer_select.value = list(self.curing_mesh_data.keys())[0]

Update available layers for display in curing dropdown.

This causes an update in PyVista rendering by triggering the onchange event of the layer select widget.

def update_available_structure_layer_scalars(self, layer_id: int):
499    def update_available_structure_layer_scalars(self, layer_id: int):
500        """Update scalar select with available scalars for currently displayed layer."""
501        options = []
502        for field in self.relevant_structure_cell_fields:
503            if not np.isnan(self.structure_mesh_data[layer_id]["input"][field]).all():
504                options.append(field)
505        if len(options) == 0:
506            options.append("No layers with valid values")
507        self.structure_scalar_select.options = options
508        self.structure_scalar_select.value = options[0]

Update scalar select with available scalars for currently displayed layer.

def update_available_curing_layer_scalars(self, layer_id: int):
510    def update_available_curing_layer_scalars(self, layer_id: int):
511        """Update scalar select with available scalars for currently displayed layer."""
512        options = []
513        for field in self.relevant_curing_cell_fields:
514            if not np.isnan(self.curing_mesh_data[layer_id]["input"][field]).all():
515                options.append(field)
516        if len(options) == 0:
517            options.append("No layers with valid values")
518        self.curing_scalar_select.options = options
519        self.curing_scalar_select.value = options[0]

Update scalar select with available scalars for currently displayed layer.

def on_structure_layer_changed(self, value: traitlets.utils.bunch.Bunch):
521    def on_structure_layer_changed(self, value: Bunch):
522        """Event to update UI after selected structure layer to display has changed."""
523        self.update_available_structure_layer_scalars(value["new"])
524        self.update_displayed_structure_scalar(self.structure_scalar_select.value)

Event to update UI after selected structure layer to display has changed.

def on_curing_layer_changed(self, value: traitlets.utils.bunch.Bunch):
526    def on_curing_layer_changed(self, value: Bunch):
527        """Event to update UI after selected curing layer to display has changed."""
528        self.update_available_curing_layer_scalars(value["new"])
529        self.update_displayed_curing_scalar(self.curing_scalar_select.value)

Event to update UI after selected curing layer to display has changed.

def on_structure_scalar_changed(self, value: traitlets.utils.bunch.Bunch):
531    def on_structure_scalar_changed(self, value: Bunch):
532        """Event to update UI after selected structure scalar to display has changed."""
533        self.update_displayed_structure_scalar(value["new"])

Event to update UI after selected structure scalar to display has changed.

def on_curing_scalar_changed(self, value: traitlets.utils.bunch.Bunch):
535    def on_curing_scalar_changed(self, value: Bunch):
536        """Event to update UI after selected curing scalar to display has changed."""
537        self.update_displayed_curing_scalar(value["new"])

Event to update UI after selected curing scalar to display has changed.

def on_structure_deformation_changed(self, value: traitlets.utils.bunch.Bunch):
539    def on_structure_deformation_changed(self, value: Bunch):
540        """Event to update UI after value of deformation display has changed."""
541        self.update_displayed_structure_scalar(self.structure_scalar_select.value)

Event to update UI after value of deformation display has changed.

def on_curing_deformation_changed(self, value: traitlets.utils.bunch.Bunch):
543    def on_curing_deformation_changed(self, value: Bunch):
544        """Event to update UI after value of deformation display has changed."""
545        self.update_displayed_curing_scalar(self.curing_scalar_select.value)

Event to update UI after value of deformation display has changed.

def update_displayed_structure_scalar(self, value: str):
547    def update_displayed_structure_scalar(self, value: str):
548        """Update PyVista structure render.
549
550        Args:
551            value: Scalar name to display on 3D mesh
552        """
553        self.set_plotter_to_structure()
554        if self.structure_deformation_toggle.value == True:
555            mesh_name = "warped"
556        else:
557            mesh_name = "input"
558        for layer_id, structure_layer in self.structure_mesh_data.items():
559            if structure_layer["output"] != None:
560                self.plotter.remove_actor(structure_layer["output"])
561                # scalars bars with multiplots have unpredictable behaviour, do not this check
562                if structure_layer["scalar_bar_name"] in self.plotter.scalar_bars:
563                    self.plotter.remove_scalar_bar(structure_layer["scalar_bar_name"])
564                structure_layer["scalar_bar_name"] = None
565                structure_layer["output"] = None
566        if self.structure_limit_actor:
567            self.plotter.remove_actor(self.structure_limit_actor)
568            self.structure_limit_actor = None
569        self.structure_cell_info.value = ""
570
571        data = self.structure_mesh_data[self.structure_layer_select.value]
572        data[mesh_name].cell_data.set_scalars(data[mesh_name][value], "color")
573        if value.startswith("DFAT_"):
574            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["cmap"]
575            clim = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["clim"]
576            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["DFAT"]["log_scale"]
577            title = "DFAT Layer {}".format(self.structure_layer_select.value)
578            self.structure_limit_actor = self.show_limit_value(data[mesh_name], value, limit="max", geometry_type="cell")
579        elif value.startswith("TFAT"):
580            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["cmap"]
581            clim = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["clim"]
582            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["TFAT"]["log_scale"]
583            title = "TFAT Layer {}".format(self.structure_layer_select.value)
584            self.structure_limit_actor = self.show_limit_value(data[mesh_name], value, limit="min", geometry_type="cell")
585        elif value.startswith(self.LAYER_THICKNESS_KEY):
586            cmap = self.STRUCTURE_DISPLAY_DEFINITIONS["THICKNESS"]["cmap"]
587            clim = np.nanmin(data[mesh_name][self.LAYER_THICKNESS_KEY]), np.max(data[mesh_name][self.LAYER_THICKNESS_KEY])
588            log_scale = self.STRUCTURE_DISPLAY_DEFINITIONS["THICKNESS"]["log_scale"]
589            title = "Thickness(mm) Layer {}".format(self.structure_layer_select.value)
590        else:
591            raise ValueError("No colorscheme for chosen scalar")
592
593        data["output"] = self.plotter.add_mesh(data[mesh_name], show_edges=False, lighting=False, show_scalar_bar=True, scalars="color", 
594                                               cmap=cmap, clim=clim, log_scale=log_scale, scalar_bar_args={'title': title})
595        data["scalar_bar_name"] = title
596        self.plotter.update()

Update PyVista structure render.

Args: value: Scalar name to display on 3D mesh

def update_displayed_curing_scalar(self, value: str):
598    def update_displayed_curing_scalar(self, value: str):
599        """Update PyVista curing render.
600
601        Args:
602            value: Scalar name to display on 3D mesh
603        """
604        self.set_plotter_to_curing()
605        if self.curing_deformation_toggle.value == True:
606            mesh_name = "warped"
607        else:
608            mesh_name = "input"
609        for layer_id, curing_layer in self.curing_mesh_data.items():
610            if curing_layer["output"] != None:
611                self.plotter.remove_actor(curing_layer["output"])
612                # scalars bars with multiplots have unpredictable behaviour, do not this check
613                if curing_layer["scalar_bar_name"] in self.plotter.scalar_bars:
614                    self.plotter.remove_scalar_bar(curing_layer["scalar_bar_name"])
615                curing_layer["scalar_bar_name"] = None
616                curing_layer["output"] = None
617        if self.curing_limit_actor:
618            self.plotter.remove_actor(self.curing_limit_actor)
619            self.curing_limit_actor = None
620        self.curing_cell_info.value = ""
621
622        data = self.curing_mesh_data[self.curing_layer_select.value]
623        if value.startswith(self.POINT_WARP_KEY):
624            cmap = 'jet'
625            title = value
626            self.curing_limit_actor = self.show_limit_value(data[mesh_name], value, limit="max", geometry_type="point")
627        else:
628            raise ValueError("No colorscheme for chosen scalar")
629        data["output"] = self.plotter.add_mesh(data[mesh_name], show_edges=False, lighting=False, show_scalar_bar=True, scalars=value, 
630                                               cmap=cmap, scalar_bar_args={'title': title})
631        data["scalar_bar_name"] = title
632        self.plotter.update()

Update PyVista curing render.

Args: value: Scalar name to display on 3D mesh

def show_limit_value( self, mesh: pyvista.core.pointset.UnstructuredGrid, scalar: str, limit: str = 'max', geometry_type: str = 'cell') -> vtkmodules.vtkRenderingCore.vtkActor2D:
634    def show_limit_value(self, mesh: pv.UnstructuredGrid, scalar: str, limit: str='max', geometry_type: str='cell') -> vtkActor2D:
635        """Render point with tooltip at place of maximum value in PyVista.
636        
637        Args:
638            mesh: 3D mesh with cells to check for values
639            scalar: Name of scalar to check for values
640            limit: 'max' or 'min'
641
642        Returns:
643            vtk actor of added max value point
644        """
645        if limit == 'max':
646            limit_value = np.nanmax(mesh[scalar])
647            fmt = "Max: %.5f"
648        else:
649            limit_value = np.nanmin(mesh[scalar])
650            fmt = "Min: %.5f"
651
652        if geometry_type == "cell":
653            limit_cell = mesh.remove_cells(np.argwhere(mesh[scalar] != limit_value))
654            point = limit_cell.points[0]
655            scalar_value = [limit_cell[scalar]]
656        elif geometry_type == "point":
657            limit_cells = mesh.extract_points(np.argwhere(mesh[scalar] == limit_value))
658            point = limit_cells.points[0]
659            scalar_value = [limit_cells[scalar][0]]
660
661        return self.plotter.add_point_scalar_labels(point, scalar_value, name="{}_limit_value_point".format(geometry_type), fmt=fmt, point_size=20, shape="rounded_rect", 
662                                point_color="red", render_points_as_spheres=True, fill_shape=True, shape_color="pink", text_color="red",shadow=True, tolerance=1)

Render point with tooltip at place of maximum value in PyVista.

Args: mesh: 3D mesh with cells to check for values scalar: Name of scalar to check for values limit: 'max' or 'min'

Returns: vtk actor of added max value point

def selection_callback(self, cell: pyvista.core.cell.Cell):
664    def selection_callback(self, cell: pv.Cell):
665        """Event to display detail information for selected cell."""
666        display_html = '<p class="global_headline"> Selected cell: </p>'
667        if cell["mesh_id"] == self.CURING_MESH_ID:
668            for prop in cell.point_data:
669                if not np.isnan(cell[prop].any()):
670                    display_html += "<b>{property_name}</b>: {property_value} \n".format(property_name=prop, property_value=cell[prop][0])
671            self.curing_cell_info.value = display_html
672        else:
673            for prop in cell.cell_data:
674                if not np.isnan(cell[prop]):
675                    display_html += "<b>{property_name}</b>: {property_value} \n".format(property_name=prop, property_value=cell[prop][0])
676            self.structure_cell_info.value = display_html

Event to display detail information for selected cell.

def set_plotter_to_curing(self):
678    def set_plotter_to_curing(self):
679        """Set active plot to curing simulation, redirecting call to plotter."""
680        self.plotter.subplot(0,0)

Set active plot to curing simulation, redirecting call to plotter.

def set_plotter_to_structure(self):
682    def set_plotter_to_structure(self):
683        """Set active plot to structure simulation, redirecting call to plotter."""
684        self.plotter.subplot(1,0)

Set active plot to structure simulation, redirecting call to plotter.

def load_local_data(self, ui_element=None):
686    def load_local_data(self, ui_element=None):
687        """Use locally stored data for UI.
688
689        Args:
690            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
691        """
692        self.conn = store_connection.LocalStoreConnection("sensotwin_world")
693        self.load_input_set_data()

Use locally stored data for UI.

Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event

def load_remote_data(self, ui_element=None):
695    def load_remote_data(self, ui_element=None):
696        """Use remotely stored data for UI.
697
698        Args:
699            ui_element: Override for ipywidgets to not pass the UI element that triggered the event
700        """
701        self.conn = store_connection.FusekiConnection(self.remote_database_input.value)
702        self.load_input_set_data()

Use remotely stored data for UI.

Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event

def load_input_set_data(self):
704    def load_input_set_data(self):
705        """Load data from previously set input source and populate UI."""
706        self.temperature_input_set_data = temperature_config.MaterialHardeningInputDataSet.get_all_entries_from_store(self.conn)
707        if self.temperature_input_set_data:
708            options = []
709            for key, input_set in self.temperature_input_set_data.items():
710                label = input_set.generate_input_set_display_label()
711                options.append((label, key))
712            self.temperature_input_selection.options = options
713            self.temperature_input_selection.value = list(self.temperature_input_set_data.keys())[0]
714
715        self.defect_input_set_data = defect_config.DefectInputDataSet.get_all_entries_from_store(self.conn)
716        if self.defect_input_set_data:
717            options = []
718            for key, input_set in self.defect_input_set_data.items():
719                label = input_set.generate_input_set_display_label()
720                options.append((label, key))
721            self.defect_input_selection.options = options
722            self.defect_input_selection.value = list(self.defect_input_set_data.keys())[0]
723
724        self.stress_input_set_data = stress_config.StressStrainInputDataSet.get_all_entries_from_store(self.conn)
725        if self.stress_input_set_data:
726            options = []
727            for key, input_set in self.stress_input_set_data.items():
728                label = input_set.generate_input_set_display_label()
729                options.append((label, key))
730            self.stress_input_selection.options = options
731            self.stress_input_selection.value = list(self.stress_input_set_data.keys())[0]

Load data from previously set input source and populate UI.

def apply_styling(self):
733    def apply_styling(self):
734        css = """
735            <style>
736            {}
737            {}
738            </style>
739        """.format(global_style.global_css, style.local_css)
740        return widgets.HTML(css)