From 15389348be1ea43abc0a9d4507ce4b582f86c615 Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Wed, 20 May 2026 07:56:01 +0000 Subject: [PATCH 01/10] Added a try except for related displays without files --- src/techui_builder/generate.py | 14 ++++++++++---- src/techui_builder/models.py | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/techui_builder/generate.py b/src/techui_builder/generate.py index 7ea36f1..2666c60 100644 --- a/src/techui_builder/generate.py +++ b/src/techui_builder/generate.py @@ -194,11 +194,15 @@ def _allocate_widget( ) -> EmbeddedDisplay | ActionButton | None | list[EmbeddedDisplay | ActionButton]: name, suffix, suffix_label = self._initialise_name_suffix(component) # Get relative path to screen - scrn_path = self.support_path.joinpath(f"bob/{scrn_mapping['file']}") - logger_.debug(f"Screen path: {scrn_path}") + try: + scrn_path = self.support_path.joinpath(f"bob/{scrn_mapping['file']}") + logger_.debug(f"Screen path: {scrn_path}") - # Path of screen relative to data/ so it knows where to open the file from - data_scrn_path = scrn_path.relative_to(self.synoptic_dir, walk_up=True) + # Path of screen relative to data/ so it knows where to open the file from + data_scrn_path = scrn_path.relative_to(self.synoptic_dir, walk_up=True) + except KeyError: + scrn_path = None + data_scrn_path = None # For Gui Components with multiple components embedded, we add a suffix field # to the components, and adjust the name and suffix accordingly @@ -255,6 +259,7 @@ def _allocate_widget( target="tab", macros={ "P": component.P, + "IOC": f"{self.beamline_url}/{component.service_name}", f"{suffix_label}": suffix, }, ) @@ -264,6 +269,7 @@ def _allocate_widget( target="tab", macros={ "P": component.P, + "IOC": f"{self.beamline_url}/{component.service_name}", }, ) diff --git a/src/techui_builder/models.py b/src/techui_builder/models.py index 61e6440..398e1da 100644 --- a/src/techui_builder/models.py +++ b/src/techui_builder/models.py @@ -247,7 +247,7 @@ class TechUi(BaseModel): class GuiComponentEntry(BaseModel): - file: BobPath + file: BobPath | None = None prefix: MacroString suffix: MacroString | None = None type: ScreenType From 8005d05da94e2578584248b313b7394fffae27cf Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Wed, 20 May 2026 10:19:56 +0000 Subject: [PATCH 02/10] Modified to use techui-support remote screens --- src/techui_builder/generate.py | 3 +-- src/techui_builder/models.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/techui_builder/generate.py b/src/techui_builder/generate.py index 2666c60..47d8648 100644 --- a/src/techui_builder/generate.py +++ b/src/techui_builder/generate.py @@ -201,8 +201,7 @@ def _allocate_widget( # Path of screen relative to data/ so it knows where to open the file from data_scrn_path = scrn_path.relative_to(self.synoptic_dir, walk_up=True) except KeyError: - scrn_path = None - data_scrn_path = None + scrn_path = data_scrn_path = f"$(IOC)/{scrn_mapping['remote_screen']}" # For Gui Components with multiple components embedded, we add a suffix field # to the components, and adjust the name and suffix accordingly diff --git a/src/techui_builder/models.py b/src/techui_builder/models.py index 398e1da..ce5e9b6 100644 --- a/src/techui_builder/models.py +++ b/src/techui_builder/models.py @@ -248,6 +248,7 @@ class TechUi(BaseModel): class GuiComponentEntry(BaseModel): file: BobPath | None = None + remote_screen: BobPath | None = None prefix: MacroString suffix: MacroString | None = None type: ScreenType From 23eff1adb5f4eda86f9f735deab90ba0e16624bc Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Wed, 20 May 2026 12:18:32 +0000 Subject: [PATCH 03/10] Added IOC macro to test files --- tests/test_files/widget_related.xml | 1 + tests/test_files/widget_related_no_suffix.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/test_files/widget_related.xml b/tests/test_files/widget_related.xml index 8903a99..b4e980a 100644 --- a/tests/test_files/widget_related.xml +++ b/tests/test_files/widget_related.xml @@ -12,6 +12,7 @@ Open Display

BL01T-MO-IOC-01

+ test_url/bl01t-mo-ioc-01 :M
techui-support/bob/pmac/pmacController.bob diff --git a/tests/test_files/widget_related_no_suffix.xml b/tests/test_files/widget_related_no_suffix.xml index 53e29e4..21f8717 100644 --- a/tests/test_files/widget_related_no_suffix.xml +++ b/tests/test_files/widget_related_no_suffix.xml @@ -12,6 +12,7 @@ Open Display

BL01T-MO-IOC-01

+ test_url/bl01t-mo-ioc-01
techui-support/bob/pmac/pmacController.bob tab From 1a876b086b6b7b3974a1c4ae7e99fa90ea9d5e12 Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Thu, 21 May 2026 09:08:25 +0000 Subject: [PATCH 04/10] reverted remote_screen to file --- src/techui_builder/generate.py | 19 +++++++++----- src/techui_builder/models.py | 3 +-- tests/test_builder.py | 4 ++- tests/test_files/widget_related.xml | 1 - tests/test_files/widget_related_no_suffix.xml | 1 - tests/test_generate.py | 25 +++++++++++++++++++ 6 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/techui_builder/generate.py b/src/techui_builder/generate.py index 47d8648..80c986d 100644 --- a/src/techui_builder/generate.py +++ b/src/techui_builder/generate.py @@ -193,15 +193,24 @@ def _allocate_widget( self, scrn_mapping: Mapping, component: Entity ) -> EmbeddedDisplay | ActionButton | None | list[EmbeddedDisplay | ActionButton]: name, suffix, suffix_label = self._initialise_name_suffix(component) + # Get relative path to screen - try: - scrn_path = self.support_path.joinpath(f"bob/{scrn_mapping['file']}") + file = scrn_mapping["file"] + if file.startswith("$(IOC)"): + scrn_path = data_scrn_path = file.replace( + "$(IOC)", f"{self.beamline_url}/{component.service_name}" + ) # Only works with related displays as + # embedded displays need to access the file to get dimensions + + assert scrn_mapping["type"] == "related", ( + "Only related displays can have remote screens" + ) + else: + scrn_path = self.support_path.joinpath(f"bob/{file}") logger_.debug(f"Screen path: {scrn_path}") # Path of screen relative to data/ so it knows where to open the file from data_scrn_path = scrn_path.relative_to(self.synoptic_dir, walk_up=True) - except KeyError: - scrn_path = data_scrn_path = f"$(IOC)/{scrn_mapping['remote_screen']}" # For Gui Components with multiple components embedded, we add a suffix field # to the components, and adjust the name and suffix accordingly @@ -258,7 +267,6 @@ def _allocate_widget( target="tab", macros={ "P": component.P, - "IOC": f"{self.beamline_url}/{component.service_name}", f"{suffix_label}": suffix, }, ) @@ -268,7 +276,6 @@ def _allocate_widget( target="tab", macros={ "P": component.P, - "IOC": f"{self.beamline_url}/{component.service_name}", }, ) diff --git a/src/techui_builder/models.py b/src/techui_builder/models.py index ce5e9b6..61e6440 100644 --- a/src/techui_builder/models.py +++ b/src/techui_builder/models.py @@ -247,8 +247,7 @@ class TechUi(BaseModel): class GuiComponentEntry(BaseModel): - file: BobPath | None = None - remote_screen: BobPath | None = None + file: BobPath prefix: MacroString suffix: MacroString | None = None type: ScreenType diff --git a/tests/test_builder.py b/tests/test_builder.py index 24375b0..ee59a32 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -337,7 +337,9 @@ def test_generate_json_map_embedded_screen( test_json_map = builder_with_test_files._generate_json_map( screen_path, dest_path, components ) - + print(test_json_map) + print("--------------") + print(example_json_map) assert test_json_map == example_json_map diff --git a/tests/test_files/widget_related.xml b/tests/test_files/widget_related.xml index b4e980a..8903a99 100644 --- a/tests/test_files/widget_related.xml +++ b/tests/test_files/widget_related.xml @@ -12,7 +12,6 @@ Open Display

BL01T-MO-IOC-01

- test_url/bl01t-mo-ioc-01 :M
techui-support/bob/pmac/pmacController.bob diff --git a/tests/test_files/widget_related_no_suffix.xml b/tests/test_files/widget_related_no_suffix.xml index 21f8717..53e29e4 100644 --- a/tests/test_files/widget_related_no_suffix.xml +++ b/tests/test_files/widget_related_no_suffix.xml @@ -12,7 +12,6 @@ Open Display

BL01T-MO-IOC-01

- test_url/bl01t-mo-ioc-01
techui-support/bob/pmac/pmacController.bob tab diff --git a/tests/test_generate.py b/tests/test_generate.py index 18f742a..7a4bc8f 100644 --- a/tests/test_generate.py +++ b/tests/test_generate.py @@ -265,6 +265,31 @@ def test_generator_allocate_widget(generator): assert str(widget) == xml_content +def test_generator_allocate_widget_with_remote_screens(generator): + generator._initilise_name_suffix = Mock(return_value=("CAM:", "CAM:", "R")) + + scrn_mapping = { + "file": "$(IOC)/ADAravis_summary.bob", + "prefix": "$(P)$(R)", + "type": "related", + } + component = Entity( + service_name="bl01t-di-ioc-01", + type="ADAravis.aravisCamera", + P="BL01T-DI-IOC-01", + desc=None, + M=None, + R=":CAM:", + ) + widget = generator._allocate_widget(scrn_mapping, component) + control_widget = Path("tests/test_files/widget_url_screen.xml") + + with open(control_widget) as f: + xml_content = f.read() + print(str(widget)) + assert str(widget) == xml_content + + def test_generator_allocate_widget_with_suffix(generator): generator._initialise_name_suffix = Mock(return_value=(":CAM:", ":CAM:", "R")) From 78758a500f8122c8e963c4447fa982ecd4a55891 Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Thu, 21 May 2026 09:16:49 +0000 Subject: [PATCH 05/10] Adding new test widget --- tests/test_files/widget_url_screen.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/test_files/widget_url_screen.xml diff --git a/tests/test_files/widget_url_screen.xml b/tests/test_files/widget_url_screen.xml new file mode 100644 index 0000000..c20d93d --- /dev/null +++ b/tests/test_files/widget_url_screen.xml @@ -0,0 +1,21 @@ + + + CAM + 0 + 0 + 100 + 40 + + CAM + + + Open Display + +

BL01T-DI-IOC-01

+ :CAM: +
+ test_url/bl01t-di-ioc-01/ADAravis_summary.bob + tab +
+
+
From d2a747af66e0621f63f8e25eb8b4220873e18ea4 Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Thu, 21 May 2026 09:29:09 +0000 Subject: [PATCH 06/10] Added a condition to handle embedded screens --- src/techui_builder/builder.py | 21 +++++++++++++-------- tests/test_builder.py | 17 ++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/techui_builder/builder.py b/src/techui_builder/builder.py index fdfbdac..0664ca1 100644 --- a/src/techui_builder/builder.py +++ b/src/techui_builder/builder.py @@ -318,9 +318,7 @@ def _generate_json_map( macro_dict = self._get_macros(open_display) case "embedded": - file_elem = self._extract_action_button_file_from_embedded( - widget_elem.file, dest_path - ) + file_elem = widget_elem.file name_elem = widget_elem.name.text macro_dict = self._get_macros(widget_elem) @@ -362,11 +360,18 @@ def _generate_json_map( str(file_path), display_name, exists=("IOC" in macro_dict) ) - child_node.macros = macro_dict - # TODO: make this work for only list[JsonMap] - assert isinstance(current_node.children, list) - # TODO: fix typing - current_node.children.append(child_node) + if widget_type == "embedded": + for embedded_child in child_node.children: + embedded_child.macros = {**embedded_child.macros, **macro_dict} + embedded_child.display_name = display_name + current_node.children.append(embedded_child) + + else: + child_node.macros = macro_dict + # TODO: make this work for only list[JsonMap] + assert isinstance(current_node.children, list) + # TODO: fix typing + current_node.children.append(child_node) except etree.ParseError as e: current_node.error = f"XML parse error: {e}" diff --git a/tests/test_builder.py b/tests/test_builder.py index ee59a32..0f30dc3 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -321,25 +321,20 @@ def test_generate_json_map_embedded_screen( builder_with_test_files, example_json_map, components ): builder_with_test_files._get_component_label = Mock( - side_effect=["Display", "Detector", "Embedded Display"] + side_effect=["Display", "Detector"] ) - screen_path = Path("tests/test_files/test_bob_embedded.bob").absolute() - dest_path = Path("tests/test_files/") + screen_path = Path( + "/workspaces/techui-builder/tests/test_files/test_bob_embedded.bob" + ).absolute() + dest_path = Path("/workspaces/techui-builder/tests/test_files/") example_json_map.file = "test_bob_embedded.bob" - example_json_map.children.append( - JsonMap( - "$(IOC)/pmacAxis.pvi.bob", display_name="Embedded Display", exists=False - ) - ) test_json_map = builder_with_test_files._generate_json_map( screen_path, dest_path, components ) - print(test_json_map) - print("--------------") - print(example_json_map) + assert test_json_map == example_json_map From 03d9209bcd9f7c4de0f3776df46943055c0d9142 Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Thu, 21 May 2026 09:44:39 +0000 Subject: [PATCH 07/10] added a modified the exists flag got https screens --- src/techui_builder/builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/techui_builder/builder.py b/src/techui_builder/builder.py index 0664ca1..3d39441 100644 --- a/src/techui_builder/builder.py +++ b/src/techui_builder/builder.py @@ -357,7 +357,9 @@ def _generate_json_map( ) else: child_node = JsonMap( - str(file_path), display_name, exists=("IOC" in macro_dict) + str(file_path), + display_name, + exists=("IOC" in macro_dict or ("https:/" in str(file_path))), ) if widget_type == "embedded": From 6154512e91877378e47da81f5704665c6ccd3522 Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Thu, 21 May 2026 10:03:40 +0000 Subject: [PATCH 08/10] Fixed test --- tests/test_builder.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_builder.py b/tests/test_builder.py index 0f30dc3..288339c 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -324,10 +324,8 @@ def test_generate_json_map_embedded_screen( side_effect=["Display", "Detector"] ) - screen_path = Path( - "/workspaces/techui-builder/tests/test_files/test_bob_embedded.bob" - ).absolute() - dest_path = Path("/workspaces/techui-builder/tests/test_files/") + screen_path = Path("tests/test_files/test_bob_embedded.bob").absolute() + dest_path = Path("tests/test_files/") example_json_map.file = "test_bob_embedded.bob" From f887227b2c92c0278ccdd7a3efdbb66263c8e5dd Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Thu, 21 May 2026 10:09:42 +0000 Subject: [PATCH 09/10] Removed unused function --- src/techui_builder/builder.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/techui_builder/builder.py b/src/techui_builder/builder.py index 3d39441..c4a1072 100644 --- a/src/techui_builder/builder.py +++ b/src/techui_builder/builder.py @@ -417,34 +417,6 @@ def _get_component_label( display_name = child_labels[name_elem] return display_name - def _extract_action_button_file_from_embedded( - self, file_elem: ObjectifiedElement, dest_path: Path - ) -> ObjectifiedElement: - file_path = Path(file_elem.text.strip() if file_elem.text else "") - file_path = dest_path.joinpath(file_path) - if not file_path.exists(): - rel_file_path = Path(str(file_elem.base)).relative_to( - dest_path.absolute(), walk_up=True - ) - file_path = dest_path.joinpath(rel_file_path) - tree = objectify.parse(file_path.absolute()) - root: ObjectifiedElement = tree.getroot() - - # Find all elements - widgets = [ - w - for w in root.findall(".//widget") - if w.get("type", default=None) == "action_button" - ] - - for widget_elem in widgets: - open_display = _get_action_group(widget_elem) - if open_display is None: - continue - file_elem = open_display.file - return file_elem - return file_elem - def _get_macros(self, element: ObjectifiedElement): if hasattr(element, "macros"): macros = element.macros.getchildren() From 2b1add1a3bcfec0da5144e93cffb0c72b542c132 Mon Sep 17 00:00:00 2001 From: "Sode, Adedamola (DLSLtd,RAL,LSCI)" Date: Thu, 21 May 2026 13:09:58 +0000 Subject: [PATCH 10/10] Changed current_node, relative to paths to absolute by resolving --- src/techui_builder/builder.py | 2 +- tests/test_builder.py | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/techui_builder/builder.py b/src/techui_builder/builder.py index c4a1072..05f8c07 100644 --- a/src/techui_builder/builder.py +++ b/src/techui_builder/builder.py @@ -268,7 +268,7 @@ def _generate_json_map( # Create initial node at top of .bob file current_node = JsonMap( - str(screen_path.relative_to(self._write_directory)), + str(screen_path.resolve().relative_to(self._write_directory.resolve())), display_name=None, ) diff --git a/tests/test_builder.py b/tests/test_builder.py index 288339c..10a8535 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -317,22 +317,31 @@ def test_generate_json_map( # TODO: write this test -def test_generate_json_map_embedded_screen( - builder_with_test_files, example_json_map, components -): +def test_generate_json_map_embedded_screen(builder_with_test_files, example_json_map): builder_with_test_files._get_component_label = Mock( - side_effect=["Display", "Detector"] + side_effect=[ + "Display", + "Detector", + "Embedded Display", + "Embedded Display", + "Embedded Display", + ] ) screen_path = Path("tests/test_files/test_bob_embedded.bob").absolute() dest_path = Path("tests/test_files/") example_json_map.file = "test_bob_embedded.bob" - - test_json_map = builder_with_test_files._generate_json_map( - screen_path, dest_path, components + example_json_map.children.append( + JsonMap( + "$(IOC)/pmacAxis.pvi.bob", + display_name="Embedded Display", + exists=False, + macros={"M": "$(M)", "P": "$(P)"}, + ) ) + test_json_map = builder_with_test_files._generate_json_map(screen_path, dest_path) assert test_json_map == example_json_map