diff --git a/docs/arguments.md b/docs/arguments.md index b6bcf96..19c9adb 100644 --- a/docs/arguments.md +++ b/docs/arguments.md @@ -15,6 +15,26 @@ This argument allows users to override the output file if it already exists. Mos This argument adds scroll bars to converted screens. It will ad a horizontal and vertical scroll bar. If this argument is not included, the screen will not contain a scroll bar. +## Site + +`--site ` + +This argument applies site-specific conversion rules during the conversion process. Some facilities have EDM widgets or patterns that have no equivalent in PyDM and should be skipped or handled differently. The `--site` flag lets you opt into these rules without affecting the default conversion behavior. + +**Available sites:** + +| Site | Rules Applied | +|--------|---------------| +| `slac` | Skips `activeExitButtonClass` (exit buttons present on every SLAC EDM screen that have no PyDM equivalent) | + +**Usage:** +``` bash +pydmconverter /path/to/file.edl output.ui --site slac +``` + +**Adding a new site:** +To add conversion rules for a new site, create a new module in `pydmconverter/sites/` (e.g. `mysite.py`) that defines a `SKIP_WIDGETS` set, then register it in `pydmconverter/sites/__init__.py`. + ## Help `-h` or `--help` diff --git a/docs/user_guide.md b/docs/user_guide.md index 30e1853..58ee750 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -69,5 +69,10 @@ pydmconverter /afs/slac/g/lcls/edm/file.edl file.ui` ``` To convert EDM files in a folder called "edm" to PYDM file in the current folder : ``` bash -pydmconverter /afs/slac/g/lcls/edm . .edl` +pydmconverter /afs/slac/g/lcls/edm . .edl +``` + +To convert with SLAC-specific rules (e.g. skipping exit buttons): +``` bash +pydmconverter /afs/slac/g/lcls/edm/file.edl file.ui --site slac ``` diff --git a/pydmconverter/__main__.py b/pydmconverter/__main__.py index a5e0162..e626153 100644 --- a/pydmconverter/__main__.py +++ b/pydmconverter/__main__.py @@ -29,7 +29,7 @@ def run_gui() -> None: subprocess.run(["bash", str(launch_script)], check=True) -def run(input_file, output_file, input_file_type=".edl", override=False, scrollable=False): +def run(input_file, output_file, input_file_type=".edl", override=False, scrollable=False, site=None): input_path: Path = Path(input_file) output_path: Path = Path(output_file) @@ -39,7 +39,7 @@ def run(input_file, output_file, input_file_type=".edl", override=False, scrolla if output_path.is_file() and not override: raise FileExistsError(f"Output file '{output_path}' already exists. Use --override or -o to overwrite it.") copy_img_files(input_path.parent, output_path.parent) - convert(str(input_path), str(output_path), scrollable) + convert(str(input_path), str(output_path), scrollable, site=site) else: if input_file_type[0] != ".": # prepending . so it will not pick up other file types with same suffix input_file_type = "." + input_file_type @@ -47,7 +47,7 @@ def run(input_file, output_file, input_file_type=".edl", override=False, scrolla files_found: int files_failed: list[str] files_found, files_failed = convert_files_in_folder( - input_path, output_path, input_file_type, override, scrollable + input_path, output_path, input_file_type, override, scrollable, site=site ) if files_found == 0: @@ -76,7 +76,8 @@ def run_cli(args: argparse.Namespace) -> None: input_file_type: str = args.output_type override: bool = args.override scrollable: bool = args.scrollable - run(input_file, output_file, input_file_type, override, scrollable) + site: str = args.site + run(input_file, output_file, input_file_type, override, scrollable, site=site) """ @@ -98,7 +99,7 @@ def run_cli(args: argparse.Namespace) -> None: files_found: int files_failed: list[str] files_found, files_failed = convert_files_in_folder( - input_path, output_path, input_file_type, override, scrollable + input_path, output_path, input_file_type, override, scrollable, site=site ) if files_found == 0: @@ -125,7 +126,7 @@ def copy_img_files(input_path: Path, output_path: Path) -> None: def convert_files_in_folder( - input_path: Path, output_path: Path, input_file_type: str, override: bool, scrollable: bool + input_path: Path, output_path: Path, input_file_type: str, override: bool, scrollable: bool, site=None ) -> tuple[int, list[str]]: """Recursively runs convert on files in directory and subdirectories @@ -161,7 +162,7 @@ def convert_files_in_folder( logging.warning(f"Skipped: {output_file_path} already exists. Use --override or -o to overwrite it.") else: try: - convert(file, output_file_path, scrollable) + convert(file, output_file_path, scrollable, site=site) except Exception as e: files_failed.append(str(file)) logging.warning(f"Failed to convert {file}: {e}") @@ -171,7 +172,7 @@ def convert_files_in_folder( subdirectories = [item for item in input_path.iterdir() if item.is_dir()] for subdir in subdirectories: sub_found, sub_failed = convert_files_in_folder( - subdir, output_path / subdir.name, input_file_type, override, scrollable + subdir, output_path / subdir.name, input_file_type, override, scrollable, site=site ) files_found += sub_found files_failed += sub_failed @@ -227,6 +228,12 @@ def main() -> None: action="store_true", help="create scrollable pydm windows that replicate edm windows (may cause spacing issues for embedded displays)", ) + parser.add_argument( + "--site", + type=str, + default=None, + help="Apply site-specific conversion rules (e.g., slac)", + ) args: argparse.Namespace = parser.parse_args() if args.input_file: diff --git a/pydmconverter/edm/converter.py b/pydmconverter/edm/converter.py index 4afe427..1e40ea3 100644 --- a/pydmconverter/edm/converter.py +++ b/pydmconverter/edm/converter.py @@ -50,7 +50,7 @@ } -def convert(input_path, output_path, scrollable=False): +def convert(input_path, output_path, scrollable=False, site=None): try: edm_parser = EDMFileParser(input_path, output_path) pprint(edm_parser.ui, indent=2) @@ -61,7 +61,7 @@ def convert(input_path, output_path, scrollable=False): # edm_parser.ui, _, _ = replace_calc_and_loc_in_edm_content(edm_parser.ui, input_path) - pydm_widgets, used_classes = convert_edm_to_pydm_widgets(edm_parser) + pydm_widgets, used_classes = convert_edm_to_pydm_widgets(edm_parser, site=site) logger.info(f"Converted EDM objects to {len(pydm_widgets)} PyDM widgets.") page_header = PageHeader() diff --git a/pydmconverter/edm/converter_helpers.py b/pydmconverter/edm/converter_helpers.py index 7e4e2a1..2a8fbd3 100644 --- a/pydmconverter/edm/converter_helpers.py +++ b/pydmconverter/edm/converter_helpers.py @@ -423,7 +423,7 @@ def handle_button_polygon_overlaps(pydm_widgets): return pydm_widgets -def convert_edm_to_pydm_widgets(parser: EDMFileParser): +def convert_edm_to_pydm_widgets(parser: EDMFileParser, site=None): """ Converts an EDMFileParser object into a collection of PyDM widget instances. @@ -437,6 +437,10 @@ def convert_edm_to_pydm_widgets(parser: EDMFileParser): List[Union[widgets.PyDMWidgetBase, widgets.PyDMGroup]] A list of PyDM widget instances representing the EDM UI. """ + from pydmconverter.sites import get_skip_widgets + + skip_widgets = get_skip_widgets(site) + pydm_widgets = [] used_classes = set() color_list_filepath = search_color_list() @@ -529,6 +533,9 @@ def traverse_group( ) elif isinstance(obj, EDMObject): + if obj.name.lower() in skip_widgets: + logger.info(f"Skipping {obj.name} (site rule: {site})") + continue if obj.name.lower() == "activelineclass": widget_type = get_polyline_widget_type(obj) elif obj.name.lower() == "activearcclass": diff --git a/pydmconverter/sites/__init__.py b/pydmconverter/sites/__init__.py new file mode 100644 index 0000000..3a70d70 --- /dev/null +++ b/pydmconverter/sites/__init__.py @@ -0,0 +1,12 @@ +from typing import Optional, Set + + +def get_skip_widgets(site: Optional[str]) -> Set[str]: + """Return set of lowercase EDM widget class names to skip for the given site.""" + if site is None: + return set() + if site == "slac": + from pydmconverter.sites.slac import SKIP_WIDGETS + + return SKIP_WIDGETS + raise ValueError(f"Unknown site: {site}") diff --git a/pydmconverter/sites/slac.py b/pydmconverter/sites/slac.py new file mode 100644 index 0000000..3f34731 --- /dev/null +++ b/pydmconverter/sites/slac.py @@ -0,0 +1,2 @@ +# EDM widget class names (lowercase) to skip during conversion at SLAC +SKIP_WIDGETS = {"activeexitbuttonclass"} diff --git a/tests/test_sites.py b/tests/test_sites.py new file mode 100644 index 0000000..702b5d3 --- /dev/null +++ b/tests/test_sites.py @@ -0,0 +1,16 @@ +import pytest +from pydmconverter.sites import get_skip_widgets + + +def test_get_skip_widgets_none(): + assert get_skip_widgets(None) == set() + + +def test_get_skip_widgets_slac(): + result = get_skip_widgets("slac") + assert "activeexitbuttonclass" in result + + +def test_get_skip_widgets_unknown(): + with pytest.raises(ValueError, match="Unknown site"): + get_skip_widgets("unknown")