diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 957b1a9..0000000 --- a/.flake8 +++ /dev/null @@ -1,23 +0,0 @@ -[flake8] -exclude = .git,__pycache__,build,dist,pcdswidgets/_version.py -max-line-length = 88 -select = C,E,F,W,B,B950 -extend-ignore = E203, E501, E226, W503, W504 - -# Explanation section: -# B950 -# This takes into account max-line-length but only triggers when the value -# has been exceeded by more than 10% (96 characters). -# E203: Whitespace before ':' -# This is recommended by black in relation to slice formatting. -# E501: Line too long (82 > 79 characters) -# Our line length limit is 88 (above 79 defined in E501). Ignore it. -# E226: Missing whitespace around arithmetic operator -# This is a stylistic choice which you'll find everywhere in pcdsdevices, for -# example. Formulas can be easier to read when operators and operands -# have no whitespace between them. -# -# W503: Line break occurred before a binary operator -# W504: Line break occurred after a binary operator -# flake8 wants us to choose one of the above two. Our choice -# is to make no decision. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index b7ee0ca..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,26 +0,0 @@ - -## Expected Behavior - - - -## Current Behavior - - - -## Possible Solution - - - -## Steps to Reproduce (for bugs) - - -1. -2. -3. - -## Context - - - -## Your Environment - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 657111f..cd79aca 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,6 +15,15 @@ - +## Screenshots + + +## Pre-merge Checklist +- [ ] Screenshots of these widgets in designer are included above (`try_in_designer.sh`) +- [ ] Screenshots of these widgets working in PyDM are included above (`try_in_pydm.sh`) +- [ ] Code works interactively +- [ ] Code contains descriptive docstrings, including context and API +- [ ] New/changed functions and methods are covered in the test suite where possible +- [ ] New/changed widgets are part of the test suite (semi-automatic) +- [ ] Test suite passes locally +- [ ] Test suite passes on GitHub Actions diff --git a/.github/workflows/standard.yml b/.github/workflows/standard.yml index 588518d..dd00043 100644 --- a/.github/workflows/standard.yml +++ b/.github/workflows/standard.yml @@ -20,6 +20,8 @@ jobs: # Extras to be installed only for conda-based testing: conda-testing-extras: "" # Extras to be installed only for pip-based testing: - pip-testing-extras: "" + pip-testing-extras: "PyQt5" + # Extras to be installed only for docs builds (pip) + docs-build-extras: "PyQt5" # Set if using setuptools-scm for the conda-build workflow use-setuptools-scm: true diff --git a/.gitignore b/.gitignore index 9a1abc8..c6ba6a7 100644 --- a/.gitignore +++ b/.gitignore @@ -76,9 +76,6 @@ target/ # Jupyter Notebook .ipynb_checkpoints -# pyenv -.python-version - # celery beat schedule file celerybeat-schedule @@ -106,3 +103,13 @@ venv.bak/ # mypy .mypy_cache/ + +# setuptools_scm +*/_version.py + +# pyuic5 +**/ui/*.py +!**/ui/__init__.py + +# vscode +.vscode/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 20c760d..330fb13 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,28 +6,25 @@ exclude: | )$ repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: no-commit-to-branch - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-ast - - id: check-case-conflict - - id: check-json - - id: check-merge-conflict - - id: check-symlinks - - id: check-xml - - id: check-yaml - exclude: '^(conda-recipe/meta.yaml)$' - - id: debug-statements +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: no-commit-to-branch + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-ast + - id: check-case-conflict + - id: check-json + - id: check-merge-conflict + - id: check-symlinks + - id: check-xml + - id: check-yaml + exclude: '^(conda-recipe/meta.yaml)$' + - id: debug-statements -- repo: https://github.com/pycqa/flake8.git - rev: 7.2.0 - hooks: - - id: flake8 - -- repo: https://github.com/timothycrosley/isort - rev: 6.0.1 - hooks: - - id: isort +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.6 + hooks: + - id: ruff-check # run the linter + args: [ --fix ] # and the safe fixes + - id: ruff-format # run the formatter diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/LICENSE.md b/LICENSE.md index e14da2c..88bab14 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2023, The Board of Trustees of the Leland Stanford Junior +Copyright (c) 2026, The Board of Trustees of the Leland Stanford Junior University, through SLAC National Accelerator Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/MANIFEST.in b/MANIFEST.in index 17eb881..6d30b08 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,11 +1,5 @@ -include pcdswidgets/_version.py - include AUTHORS.rst include CONTRIBUTING.rst include LICENSE.md include MANIFEST.in include README.md - -include dev-requirements.txt -include docs-requirements.txt -include requirements.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c4d5217 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +.PHONY: all build inits venv + +UI_SOURCE := $(wildcard pcdswidgets/ui/*/*/*.ui) +PY_SOURCE := $(filter-out pcdswidgets/builder/ui/%.py, $(filter-out pcdswidgets/_version.py, $(shell find pcdswidgets -name "*.py"))) + +PY_FORM := $(UI_SOURCE:pcdswidgets/ui/%.ui=pcdswidgets/generated/%_form.py) +PY_BASE := $(UI_SOURCE:pcdswidgets/ui/%.ui=pcdswidgets/generated/%_base.py) +PY_MAIN := $(UI_SOURCE:pcdswidgets/ui/%.ui=pcdswidgets/%.py) + +BIN := .venv/bin +BUILD_CMD := $(BIN)/python -m pcdswidgets.builder.build +CHECK_FIX := $(BIN)/ruff check --exit-zero --fix --quiet +FORMAT := $(BIN)/ruff format --quiet + +# We need to update the venv before doing any step and after doing all of them +# The order matters here, except the py files in the build target could be done in any order +all: + $(MAKE) venv + $(MAKE) build + $(MAKE) inits + $(MAKE) pyproject.toml + $(MAKE) venv + +build: $(PY_FORM) $(PY_BASE) $(PY_MAIN) + +# Need to re-run form and base if the ui file is updated +$(PY_FORM): pcdswidgets/generated/%_form.py: pcdswidgets/ui/%.ui + $(BUILD_CMD) uic $^ + $(CHECK_FIX) $@ + $(FORMAT) $@ + +$(PY_BASE): pcdswidgets/generated/%_base.py: pcdswidgets/ui/%.ui + $(BUILD_CMD) base $^ + $(CHECK_FIX) $@ + $(FORMAT) $@ + +# Only run if the target is missing: user can edit these +$(PY_MAIN): + $(BUILD_CMD) main $(@:pcdswidgets/%.py=pcdswidgets/ui/%.ui) + $(CHECK_FIX) $@ + $(FORMAT) $@ + +inits: + $(BIN)/python -m pcdswidgets.builder.inits + +# Rerun if any python file is updated +pyproject.toml: $(PY_SOURCE) + $(BIN)/python -m pcdswidgets.builder.entrypoint_finder + +venv: + ./build_local_venv.sh diff --git a/README.md b/README.md index 82960de..784fbab 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,334 @@ - [![Build Status](https://travis-ci.org/pcdshub/pcdswidgets.svg?branch=master)](https://travis-ci.org/pcdshub/pcdswidgets) # pcdswidgets -LCLS PyDM Widget Library +## Usage +This is a widget library that uses the `pydm` framework to add additional widgets to the `pydm` ecosystem. + +When `pcdswidgets` is installed in a `python` environment, it will provide: + +- Additional widgets in designer via `pydm`'s widget entrypoint. +- The same additional widgets at runtime for use in `pydm` and `PyQt` displays. + +At `LCLS`, this is currently distributed as part of the `pcds_conda` environments: + +``` +source pcds_conda +designer +``` + +Note that for the designer integration to work properly, the python designer plugin must be built and installed correctly. + + +## Installation +### Production Environments +`pcdswidgets` is packaged using standard tools and can be installed with standard tools. We maintain both `PyPI` and `conda-forge` builds. + +Pick your favorite: + +- `pip install pcdswidgets` +- `conda install pcdswidgets` + +You can also install `pcdswidgets` using other standard tools (such as `uv`) or directly from source in `GitHub`. + + +### Development Environments +A helper script is included here: `build_local_venv.sh` (or, `make venv`) (or, just `make`). + +This will create a virtual environment under the `.venv` folder that will be ready to go +to help you run designer and test your custom widgets. +To work, this requires a suitable base environment to already exist on +your system: one with `PyQt` and designer python plugin support, +which is tricky to set up properly. + +These base environments are stored centrally at LCLS and are +specified in `base_env_vars.sh`. +If you are not at LCLS, you will need to edit this file to use these scripts. + +You can run the `build_local_venv.sh` again (or, `make venv`) +to update the environment with any new widgets you've added since the last run. + +Once this environment is created, you can use `try_in_designer.sh` to +make sure your widgets are exporting cleanly in an editable way in designer. + +You can also use `try_in_pydm.sh` to launch a version of `pydm` that includes +your new widgets. + +You can alternatively build your own environment: + +- `pip install -e .` + +or + +- `uv sync` + +or whatever your favorite method is. + +Note that we can currently only run designer with custom widgets on our Rocky 9 OS machines at LCLS. +This is due to complications in the build process. + + +## Adding Widgets Tutorial +There are two kinds of widgets in `pcdswidgets` +1. Composite widgets, which start their lifecycles as standard `pydm` screens and are composed entirely of smaller standard widgets. +2. Symbol-based widgets, which start their lifecycles here in `pcdswidgets` and feature fully custom symbol components. + +This tutorial will first go through how to add composite widgets, and then how to add symbol-based widgets. It is expected that most contributors will be adding composite widgets. + +Along the way, we'll introduce concepts like naming and sizing rules as they become relevant. + + +### Why would I add a widget? +Before starting, consider why you might add a widget to `pcdswidgets`. +Some good reasons may be: + +- Making a particularly useful or ubiquitous widget globally available and discoverable +- Making a high-usage widget easier to add to screens and control the settings of + +The alternative is to pass your widget around via filepath and macros using `PyDMEmbeddedDisplay`, +which works great and may be sufficient for many use cases. + + +### Provisioning a Composite Widget +Before cloning this repo, you should create your widget as a `pydm` screen and try it out. +It will be simpler and faster to iterate on your design this way +and you can get immediate feedback without doing any library work. + +If you don't know how to do this, refer to the `pydm` documentation: + +- [PyDM Macro Substitution](https://slaclab.github.io/pydm/tutorials/intro/macros.html) +- [Creating a small (widget) ui file with macros](https://slaclab.github.io/pydm/tutorials/action/designer_inline.html) +- [Creating a screen that uses embedded displays](https://slaclab.github.io/pydm/tutorials/action/designer_main.html) + +Before getting too deep, however, please consider widget sizing: + + +### Widget Sizing +We have some strict guidelines on widget sizing. These are established to give us some consistency in application of widgets, as well as to make it simpler to avoid resizing a widget between library releases. + +Device control widgets should fall into exactly one of three size classes. +(Note: we can add more size classes if necessary). + +To ensure sizing consistency, set the minimum and maximum sizes to values that look good throughout the range +and are permissible sizes as recorded below. +It's recommended to use fixed sizing when possible because dynamic sizing is hard to implement correctly. + +Widgets should always be maintained to work at the original designed size, because changing this can break existing screens. + +| Size Class | Width | Height | +| ---------- | ----- | -------| +| Full | 400 px | 125 px | +| Compact | 100 px | 75 px | +| Row | 800 px | 50 px | + +Note: +- All widgets are allowed to be smaller than the maximum of their size class by up to 20%. +- Rows are also allowed to be double-height, e.g. 100px height. +- Widgets that aren't control widgets (containers, etc.) should not have a maximum or a minimum size. These widgets should instead be usable at any size. There is a list in the test suite to add test exceptions for these. + + +### Environment Setup +If you've gotten this far, with a provisioned widget of a good size class, it's time to set up your dev environment. +Before we begin, please clone the source code and make sure you can establish a working `designer` build +using the commands below. + +Note that at LCLS this only works on Rocky 9 machines! + +``` +make +./try_in_designer.sh +``` + +If the `make` completes successfully, you will have a working `python` environment +and `try_in_designer.sh` will open a designer window with the existing `pcdswidgets` widgets in the sidebar. + + +### Adding Your Composite Widget: Part 1 +1. Decide on your widget category: this is the subsystem and the type of the widget. + - Example subsystems include "motion" and "vacuum". + - Example types include "common", "smaract", and "beckhoff". + +2. Copy your `.ui` file into `pcdswidgets` in the folder corresponding with your choices in step 1: `pcdswidgets/ui/${subsystem}/${type}` + - Example: `pcdswidgets/ui/motion/beckhoff` + - If this folder does not exist, consider if an existing folder is appropriate. + - If no existing folder is appropriate, feel free to create a new folder. + +3. Rename your `.ui` file to match the widget naming convention below. + - It's important to be intentional about widget naming because renaming a widget can break existing screens. + + +### Widget Naming +Widget names and ui filenames should have one to one correspondence and contain three parts: + +- Type of device controlled +- Descriptor word to differentiate this widget from other possible widgets with the same device type and size +- Size class signifier + +For casing: +- `.ui` filenames should be lowercase_with_underscores for ease of working with filenames. +- Class names should use CamelCase to match qt and python naming conventions. + - The class name will be generated automatically from the ui filename. + +Examples: +- `motor_classic_full.ui` (`MotorClassicFull`) + - Controls generic EPICS motor record + - Is inspired by the classic EDM style + - Is sized to be the "full" size +- `motor_tc_classic_row.ui` (`MotorTcClassicRow`) + - Controls generic EPICS motor record with a thermocouple added + - Is inspired by the classic EDM style + - Is sized to be the "row" size + +Other guidelines: + +- The name should not be unnecessarily long, but avoid abbreviations. +- If multiple devices are controlled, include them in order of importance, e.g. `MotorTcClassicRow`. +- There is no need to end a widget name or filename with "Widget". Please avoid this. +- Widgets should never be renamed between tags, this will break existing screens. +- Widgets named before 2026 may break some of these rules because we don't want to rename them. + + +### Adding Your Composite Widget: Part 2 +4. Run `make` to generate the code and update the project metadata. + - This will generate at least three `.py` files and add a row to `pyproject.toml`. + - Do not edit the files in `generated`. +5. Try it out! + - Run `./try_in_designer.sh` and make a test screen. (Which, reminder: only works on rocky9 at LCLS). + - After you've made a test screen, then do `./try_in_pydm.sh my_screen.ui` for further testing. + - Make sure to take screenshots to include in your pull request. + +At this point, if you like what you see, you're actually done. +You can commit, push, and make a pull request if you'd like. +The next few sections are optional. + +Some notes: + +- If you edit the ui file, you should `make` again, or your changes will not take effect. +- If you change your mind about which subsystem and type directory you'd like to use, you must manually delete the generated files from the old locations. + + +### Optional: Edit the Designer Settings +One of the built files is in `pcdswidgets/ui/${subsystem}/${type}`. + +Unlike the files in `generated`, this file is free to edit, +and, among other things, contains a `DesignerOptions` specification for the widget. + +This looks something like: + +``` +class MyClassFull(MyClassFullBase): + designer_options = DesignerOptions( + group="ECS Subsystem Type", + is_container=False, + icon=IconOptions.NONE, + ) +``` + +The editable options are: +- `group`, which determines which category the widget sorts into in the designer sidebar. +- `is_container`, which tells designer if we should be able to drag other widgets into this one in designer. +- `icon`, which tells designer which icon to use in the designer sidebar (see next section). + + +### Optional: Choose a Designer Icon +The designer icon is the symbol that appears to the left of the widget name in the left-hand widget box. +The default designer icon is simply the `Qt` logo. If you'd like to change it, you have a few options. + +1. Use `IconOptions` (recommended) + - `pydm` provides the free subset of fontawesome as icons. + - You can select one of these by changing `IconOptions.NONE` to any of the other enum options. + - If you're using an IDE, the options should autocomplete. + - To see all of the options, run `show_icon_options.sh`. This will open up a grid with all of the options and names. + +Here's an example: + +``` +class MyClassFull(MyClassFullBase): + designer_options = DesignerOptions( + group="ECS Subsystem Type", + is_container=False, + icon=IconOptions.expand_arrows_alt, + ) +``` + +2. Create your own `QIcon` + - You can use the `Qt` APIs to create your own icon object. + - For example: you can create an icon from a `.png`. + - Please refer to the `Qt`/`PyQt` docs for how to do this. + - You can set `icon=your_qicon_object` in your `DesignerOptions` to include your custom icon. + + +### Optional: Add Logic to a Composite Widget +The widget class here that includes the `designer_options` object is exactly the class that will be used +when your widget is included in a screen. +This means you can add code to the widget to override and extend any built-in behavior. + +There are a few things to keep in mind when you do this: +1. If you override `__init__`, you must call `super().__init__(parent)` before doing any other `Qt`-related operations. +2. There is no way to pass custom arguments to `__init__` in `designer`. + - Any parameterization should be done via `Qt` properties, which will show up in the sidebar. + - If you do this, do not assume that the properties will be set in any particular order. + - Make your code work regardless of which order the properties are set in. +3. Be wary of backwards compatibility. + - Removing properties from a widget will break existing screens. + +Here is an example where we add a single configuration parameter that does nothing. In practice, you would also change something meaningful about the widget during the setter. + +``` +try: + from qtpy.QtCore import pyqtProperty +except ImportError: + from qtpy.QtCore import Property as pyqtProperty # type: ignore + + +class MyClassFull(MyClassFullBase): + designer_options = DesignerOptions( + group="ECS Subsystem Type", + is_container=False, + icon=IconOptions.NONE, + ) + + def __init__(self, parent: QWidget | None = None): + super().__init__(parent) + self._my_value = 0 + + def get_my_value(self) -> int: + return self._my_value + + def set_my_value(self, value: int) -> None: + self._my_value = value + + my_value = pyqtProperty(int, get_my_value, set_my_value) +``` + + +### Composite Widget Limitations +- Widgets that contain `PyDMEmbeddedDisplay` are not supported: bootstrap these by turning the contents into widgets themselves. +- The automatic type hinting runs into issues when the qt object names are the same as the classnames. If you want to extend the composite widget class in python, giving your child widgets more unique names will result in more useful type hints, automatically. +- Only direct `QString` and `QStringList` properties are supported. We still need to implement support for item-based `QString` widgets such as `QListWidget`. +- The ordering of the designer widget categories is chaotic. This will require an update to `pydm` to resolve. +- In `pydm`, you can edit a ui file by hand and add a macro anywhere. This is not supported for composite widgets. + + +### Adding a Symbol-based Widget +This is how you would add e.g. a pump or valve widget with a custom drawing symbol and some color awareness. + +This will require at least some familiarity with `Python`, `Qt`, `PyQt`, `pydm`, and with the structure of this module. + +Largely: refer back to the existing widgets. + +The steps are: + +1. Create a new subclass of `BaseSymbolIcon` in the icons subfolder. + - Define a path + - Implement draw_icon +2. Create a new subclass of `PCDSSymbolBase`. + - Include your icon as self.icon + - Add relevant properties as needed, or inherit them from the existing mixins + - include the `_qt_designer_` class attribute +3. `make`, to update `pyproject.toml` and the venv with new widget locations. + +If the widget has been added and is included in the `pyproject.toml` file, it will appear in designer after installing `pcdswidgets`. + +Note: +- At time of writing, all symbol-based widgets are vacuum widgets, and as such all the symbol-related code is in the vacuum folder. + - If you would like to make a non-vacuum widget in this style, you should first refactor to pull out the base icon and symbol code, then edit the readme here to remove this note. +- The colors of all the existing vacuum symbol widgets is based on stylesheet rules. We keep the latest version of the stylesheet in use at LCLS in another module: see [lcls-twincat-vacuum](https://github.com/pcdshub/lcls-pydm-vacuum/blob/master/styleSheet/masterStyleSheet.qss). + - You are not required to continue the stylesheet pattern if you add new symbol widgets. diff --git a/base_env_vars.sh b/base_env_vars.sh new file mode 100644 index 0000000..fbc3583 --- /dev/null +++ b/base_env_vars.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# +# Local environment settings for building/testing at LCLS +# + +# Disable GL because it crashes on our servers +QT_XCB_GL_INTEGRATION=none +# Pick a base env that has designer configured appropriately and built to match python + pyqt versions +BASE_ENV="/cds/group/pcds/pyps/conda/dev/zlentz/miniforge3/envs/ecs-base-0.0.3" + +export QT_XCB_GL_INTEGRATION +export BASE_ENV diff --git a/build_local_venv.sh b/build_local_venv.sh new file mode 100755 index 0000000..5b87db4 --- /dev/null +++ b/build_local_venv.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# shellcheck disable=SC1091 +# +# This is the script we run on "make venv" +# +# Builds a .venv with a local install of pcdswidgets and working designer plugin. +# This can be re-run to update the pcdswidgets install, e.g. if you added a new widget. +# If you change the base environment, you'll have to remove and rebuild the .venv +# +set -e + +THIS_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" +cd "${THIS_DIR}" + +source base_env_vars.sh +PYTHON_EXE="${BASE_ENV}/bin/python" + +# Two paths: can specify uv or venv as an input arg +# Or, I will default to uv if it's on your path, venv otherwise +if [[ "$1" == "uv" ]]; then + MODE="uv" +elif [[ "$1" == "venv" ]]; then + MODE="venv" +elif command -v "uv"; then + MODE="uv" +else + MODE="venv" +fi + +if [[ "$MODE" == "uv" ]]; then + if [[ ! -d ".venv" ]]; then + echo "Building new .venv using uv" + uv venv --system-site-packages --python "$PYTHON_EXE" .venv + fi + echo "Updating .venv using uv" + uv sync --extra dev --extra doc --extra test +elif [[ "$MODE" == "venv" ]]; then + if [[ ! -d ".venv" ]]; then + echo "Building new .venv using venv module" + "$PYTHON_EXE" -m venv --system-site-packages .venv + PIP_VER="$("${PYTHON_EXE}" -c "import pip; print(pip.__version__)")" + source .venv/bin/activate + echo "Updating pip to match base env" + pip install pip=="${PIP_VER}" + fi + source .venv/bin/activate + echo "Updating .venv using pip" + PKG_CUTOFF="$(date --date "7 days ago" --iso-8601)" + pip install -e '.[dev,doc,test]' --uploaded-prior-to "${PKG_CUTOFF}" +else + echo "Unhandled mode ${MODE}" +fi diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 64fc919..a87aa5f 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -19,6 +19,9 @@ requirements: - python >=3.9 - setuptools_scm - pip + - jinja2 >=3 + - qtpy + - pyqt >=5 run: - python >=3.9 - pydm >=1.9.0 @@ -29,9 +32,11 @@ test: imports: - pcdswidgets requires: + - jinja2 >=3 - pytest - pytest-qt - pytest-timeout + - tomlkit >=0.14.0 about: home: https://github.com/pcdshub/pcdswidgets diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index c9d6678..0000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -pytest -pytest-qt -pytest-timeout diff --git a/docs-requirements.txt b/docs-requirements.txt deleted file mode 100644 index 7df0a57..0000000 --- a/docs-requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -docs-versions-menu -sphinx -sphinx_rtd_theme -sphinxcontrib-jquery diff --git a/examples/basic_table.py b/examples/basic_table.py index b1416ec..ffc0c75 100644 --- a/examples/basic_table.py +++ b/examples/basic_table.py @@ -7,29 +7,29 @@ class BasicTable(Display): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ui.example_table.add_filter( - 'Hide Negative Values', + "Hide Negative Values", self.neg_filter, active=True, ) self.ui.example_table.add_filter( - 'Hide Even Values', + "Hide Even Values", self.even_filter, active=False, ) self.ui.example_table.add_filter( - 'Hide Rows with 4 Character Names', + "Hide Rows with 4 Character Names", self.four_filter, active=False, ) def neg_filter(self, value_dict): - return value_dict['readback'] >= 0 + return value_dict["readback"] >= 0 def even_filter(self, value_dict): - return value_dict['readback'] % 2 + return value_dict["readback"] % 2 def four_filter(self, value_dict): - return len(value_dict['row_name']) != 4 + return len(value_dict["row_name"]) != 4 def ui_filename(self): - return os.path.join(os.path.dirname(__file__), 'basic_table.ui') + return os.path.join(os.path.dirname(__file__), "basic_table.ui") diff --git a/examples/show_icon.py b/examples/show_icon.py index bf51561..697bcd4 100644 --- a/examples/show_icon.py +++ b/examples/show_icon.py @@ -12,7 +12,7 @@ def screenshot(widget, filename): s = str(filename) + ".png" print("Filename: ", s) p = QWidget.grab(widget) - p.save(s, 'png') + p.save(s, "png") app = QApplication([]) @@ -22,8 +22,6 @@ def screenshot(widget, filename): tp = ic() tp.setFixedSize(64, 64) tp.show() - path = os.path.join(os.path.dirname(__file__), '..', 'docs', 'source', - '_static', "icons", - tp.__class__.__name__) + path = os.path.join(os.path.dirname(__file__), "..", "docs", "source", "_static", "icons", tp.__class__.__name__) screenshot(tp, path) diff --git a/pcdswidgets/builder/__init__.py b/pcdswidgets/builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/builder/build.py b/pcdswidgets/builder/build.py new file mode 100644 index 0000000..4c7d0b6 --- /dev/null +++ b/pcdswidgets/builder/build.py @@ -0,0 +1,303 @@ +import argparse +import dataclasses +import os +import re +import xml.etree.ElementTree as ET +from collections import defaultdict +from io import StringIO +from pathlib import Path + +from jinja2 import Environment, PackageLoader +from qtpy.uic import compileUi # type: ignore + + +def build_uic(designer_ui: str, output_dir: str = ""): + """ + Use the standard uic parser to create a .py file with a .ui file's widget layouts. + + The files are named systematically with patterns like: + some_name.ui -> some_name_form.py + """ + string_io = StringIO() + compileUi(designer_ui, string_io) + + comment_lines = [] + import_lines = [] + impl_lines = [] + + for line in string_io.getvalue().split("\n"): + if line.startswith("from"): + import_lines.append(line.replace("PyQt5", "qtpy")) + continue + if import_lines: + impl_lines.append(line) + elif line: + comment_lines.append(line) + + comment_lines.append("#") + comment_lines.append("# Augmented by pcdswidgets.builder.build") + comment_lines.append("# ruff: noqa: E501") + + output_dir_path = get_output_path(designer_ui=designer_ui, default_base="generated", output_dir=output_dir) + output_dir_path.mkdir(parents=True, exist_ok=True) + output_file = output_dir_path / os.path.basename(designer_ui).replace(".ui", "_form.py") + with open(output_file, "w") as fd: + fd.writelines(cl + "\n" for cl in comment_lines) + fd.writelines(il + "\n" for il in import_lines) + fd.writelines(impl + "\n" for impl in impl_lines) + + +def build_base_widget(designer_ui: str, output_dir: str = ""): + """ + Create a .py file with a suitable base widget for inclusion in designer. + + The files are named systematically with patterns like: + some_name.ui -> some_name_base.py + + The base widget will have the following properties: + - Imports from the uic output for its widget layouts + - Has appropriately-named properties for any expected macros in the pydm source file + - Has type hints to help the IDE keep track of which child widgets are available + + See ui_base_widget.j2, which is the jinja template for these output files. + """ + # Parse the file + ui_info = get_ui_info(designer_ui) + + # Bring the info into a good form for the jinja template + ui_name = os.path.basename(designer_ui) + base_cls = get_base_class_name(designer_ui=designer_ui) + info_for_jinja = process_widget_macros(ui_info) + + macro_names = sorted(info_for_jinja.macro_set) + widget_names = sorted(info_for_jinja.widget_set) + + # Fill the template + jinja_template = "ui_base_widget.j2" + env = Environment(trim_blocks=True, loader=PackageLoader("pcdswidgets", "builder")) + template = env.get_template(jinja_template) + jinja_output = template.render( + jinja_template=jinja_template, + ui_name=ui_name, + form_cls=ui_info.form_cls, + base_cls=base_cls, + macro_names=macro_names, + widget_names=widget_names, + widget_name_to_class=ui_info.widget_name_to_class, + macro_to_widget=info_for_jinja.macro_to_widget, + widget_to_macro=info_for_jinja.widget_to_macro, + widget_to_pre_templ_strs=info_for_jinja.widget_to_pre_templ_strs, + widget_to_pre_templ_lists=info_for_jinja.widget_to_pre_templ_lists, + ) + output_dir_path = get_output_path(designer_ui=designer_ui, default_base="generated", output_dir=output_dir) + output_dir_path.mkdir(parents=True, exist_ok=True) + output_file = output_dir_path / os.path.basename(designer_ui).replace(".ui", "_base.py") + with open(output_file, "w") as fd: + fd.write(jinja_output) + + +def build_main_widget(designer_ui: str, output_dir: str = ""): + """ + Create a .py file that will be included in designer as-is. + + The files are named systematically with patterns like: + some_name.ui -> some_name.py + + See ui_main_widget.j2, which is the jinja template for these output files. + """ + # Collect some info + designer_path = Path(designer_ui) + module_parts = ["pcdswidgets", "generated"] + seen_ui = False + for path_part in designer_path.parts[:-1]: + if path_part == "ui": + seen_ui = True + elif seen_ui: + module_parts.append(path_part) + module_parts.append(os.path.basename(designer_ui).replace(".ui", "_base")) + absolute_import_path = ".".join(module_parts) + default_group = f"ECS {module_parts[2].title()} {module_parts[3].title()}" + # Fill the template + jinja_template = "ui_main_widget.j2" + env = Environment(trim_blocks=True, loader=PackageLoader("pcdswidgets", "builder")) + template = env.get_template(jinja_template) + jinja_output = template.render( + absolute_import_path=absolute_import_path, + base_cls=get_base_class_name(designer_ui=designer_ui), + main_cls=get_main_class_name(designer_ui=designer_ui), + default_group=default_group, + ) + output_dir_path = get_output_path(designer_ui=designer_ui, default_base="", output_dir=output_dir) + output_dir_path.mkdir(parents=True, exist_ok=True) + output_file = output_dir_path / os.path.basename(designer_ui).replace(".ui", ".py") + with open(output_file, "w") as fd: + fd.write(jinja_output + "\n") + + +def get_output_path(designer_ui: str | Path, default_base: str, output_dir: str | Path = "") -> Path: + if output_dir: + return Path(output_dir) + else: + designer_ui_path = Path(designer_ui) + designer_parts = list(designer_ui_path.parts) + if default_base: + for idx, part in enumerate(designer_parts): + if part == "ui": + designer_parts[idx] = default_base + break + else: + designer_parts.remove("ui") + return designer_ui_path.with_segments(*designer_parts[:-1]) + + +def get_base_class_name(designer_ui: str) -> str: + return get_main_class_name(designer_ui=designer_ui) + "Base" + + +def get_main_class_name(designer_ui: str): + ui_name = os.path.basename(designer_ui) + ui_parts = ui_name.removesuffix(".ui").split("_") + return "".join(part.title() for part in ui_parts) + + +@dataclasses.dataclass +class UiInfo: + """Information parsed from a .ui file.""" + + widget_name_to_class: dict[str, str] + widget_macros: dict[str, dict[str, str | list[str]]] + form_cls: str + + +def get_ui_info(designer_ui: str) -> UiInfo: + """Parse a .ui file and collect information about each widget.""" + # Need a name to class mapping for the IDE type hints + widget_name_to_class: dict[str, str] = {} + # Need to keep track of which widget properties have macros + # widget_macros[widget_name][property_name] == "${MACRO} in context" + widget_macros: dict[str, dict[str, str | list[str]]] = defaultdict(dict) + + tree = ET.parse(designer_ui) + for widget in tree.iter("widget"): + name = widget.attrib["name"] + cls = widget.attrib["class"] + widget_name_to_class[name] = cls + for prop in widget.findall("property"): + add_prop_to_widget_macros(widget_macros, name, prop) + + # Need to get the name of the form class, which is "Ui_" and the name of the top-level widget + # Usually this ends up being "Ui_Form" with default naming but the user can change this + top_level_widget = tree.find("widget") + if top_level_widget is None: + raise RuntimeError("No top level widget in ui file") + form_cls = f"Ui_{top_level_widget.attrib['name']}" + + return UiInfo( + widget_name_to_class=widget_name_to_class, + widget_macros=widget_macros, + form_cls=form_cls, + ) + + +def add_prop_to_widget_macros(widget_macros: defaultdict[str, dict[str, str | list[str]]], name: str, prop: ET.Element): + """Incorporate a single property into the macros dict if there is a macro in it.""" + # Looking for string and stringlist only + str_node = prop.find("string") + if str_node is not None and str_node.text is not None: + # We have simple text! + if "${" in str_node.text: + widget_macros[name][prop.attrib["name"]] = str_node.text + return + strlist_node = prop.find("stringlist") + if strlist_node is not None: + # We have a list of strings! Some may have macros. + all_str_nodes = strlist_node.findall("string") + all_str_literals = [node.text for node in all_str_nodes if node.text is not None] + for text in all_str_literals: + if "${" in text: + widget_macros[name][prop.attrib["name"]] = all_str_literals + return + + +@dataclasses.dataclass +class InfoForJinja: + """Distilled widget and macro information for easily filling in the jinja template.""" + + macro_set: set[str] + widget_set: set[str] + macro_to_widget: dict[str, list[str]] + widget_to_macro: dict[str, list[str]] + widget_to_pre_templ_strs: dict[str, list[tuple[str, str]]] + widget_to_pre_templ_lists: dict[str, list[tuple[str, list[str]]]] + + +def process_widget_macros(ui_info: UiInfo) -> InfoForJinja: + """Convert the raw ui info into a more useful form for filling the jinja template.""" + ij = InfoForJinja( + macro_set=set(), + widget_set=set(), + macro_to_widget=defaultdict(list), + widget_to_macro={}, + widget_to_pre_templ_strs=defaultdict(list), + widget_to_pre_templ_lists=defaultdict(list), + ) + + for widget_name, prop_info in ui_info.widget_macros.items(): + macros_here = set() + str_opts: list[tuple[str, str]] = [] + list_opts: list[tuple[str, list[str]]] = [] + for prop_name, value_with_macro in prop_info.items(): + if isinstance(value_with_macro, str): + str_opts.append((prop_name, value_with_macro)) + macros_here.update(_get_macros(value_with_macro)) + elif isinstance(value_with_macro, list): + list_opts.append((prop_name, value_with_macro)) + for val in value_with_macro: + macros_here.update(_get_macros(val)) + else: + raise TypeError(f"Invalid macro type: {value_with_macro}") + ij.macro_set.update(macros_here) + ij.widget_set.add(widget_name) + for macro in macros_here: + ij.macro_to_widget[macro].append(widget_name) + ij.widget_to_macro[widget_name] = sorted(macros_here) + ij.widget_to_pre_templ_strs[widget_name].extend(str_opts) + ij.widget_to_pre_templ_lists[widget_name].extend(list_opts) + + return ij + + +macro_re = re.compile(r"\${(\S+?)}") + + +def _get_macros(text_with_macro_sub: str) -> list[str]: + """Helper for getting the name of each macro in use in a macro string.""" + return macro_re.findall(text_with_macro_sub) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + "pcdswidgets.builder.build", + description="Automatically build the form or base class files associated with a widget .ui file.", + ) + parser.add_argument( + "mode", + choices=["uic", "base", "main"], + help=( + "Choose 'uic' to build the pyuic form, " + "'base' to build the pcdswidgets base class, " + "or 'main' to build the pcdswidgets main (user editable) class." + ), + ) + parser.add_argument("designer_ui", help="Path to the designer .ui file to use as the source for the build.") + args = parser.parse_args() + + if args.mode == "uic": + build_uic(args.designer_ui) + elif args.mode == "base": + build_base_widget(args.designer_ui) + elif args.mode == "main": + build_main_widget(args.designer_ui) + else: + # Currently unreachable, probably + raise ValueError(f"Invalid mode {args.mode}, must be uic, base, or main") diff --git a/pcdswidgets/builder/designer_options.py b/pcdswidgets/builder/designer_options.py new file mode 100644 index 0000000..1e03139 --- /dev/null +++ b/pcdswidgets/builder/designer_options.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass + +from qtpy.QtGui import QIcon + +from .icon_options import IconOptions + + +@dataclass +class DesignerOptions: + """ + Options for designer. + + Parameters + ---------- + group : str + Widgets with the same group will be grouped together in designer. + is_container : bool + Set this to True if this widget can contain other widgets (via drag and drop), + or False otherwise. + icon : IconOptions, icon, or None + The icon to use to represent this widget in the qt sidebar. + If an IconOptions enum, we'll use PyDM's iconfont to pick the matching icon. + If a QIcon, we'll use it as-is. + If None, we'll use the default icon. + """ + + group: str + is_container: bool + icon: IconOptions | QIcon | None diff --git a/pcdswidgets/builder/designer_widget.py b/pcdswidgets/builder/designer_widget.py new file mode 100644 index 0000000..bfb27f3 --- /dev/null +++ b/pcdswidgets/builder/designer_widget.py @@ -0,0 +1,185 @@ +""" +Helper for using designer to layout widgets. +""" + +from string import Template +from typing import Any, ClassVar, Protocol + +from pydm.utilities.iconfont import IconFont +from pydm.widgets.base import PyDMPrimitiveWidget +from pydm.widgets.designer_settings import update_property_for_widget +from pydm.widgets.qtplugin_extensions import RulesExtension +from qtpy.QtWidgets import QAction, QDialog, QFormLayout, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout, QWidget + +from .designer_options import DesignerOptions +from .icon_options import IconOptions + +ifont = IconFont() + + +class _UiForm(Protocol): + def setupUi(self, Form): ... + + def retranslateUi(self, Form): ... + + +class DesignerWidget(QWidget, PyDMPrimitiveWidget): # type: ignore + """Helper class for converting pydm displays for embedding to standalone widgets.""" + + # Loaded from uic + ui_form: ClassVar[type[_UiForm]] + # Used to generate _qt_designer_ + designer_options: ClassVar[DesignerOptions] + # Tells PyDM to include in designer + _qt_designer_: ClassVar[dict[str, Any]] + # Macro name to widget names that include that macro + _macro_to_widget: ClassVar[dict[str, list[str]]] + # Widget name to required macros: all must be non-empty before updating + _widget_to_macro: ClassVar[dict[str, list[str]]] + # Widget name to per-property template to fill + _widget_to_pre_template: ClassVar[dict[str, list[tuple[str, str | list[str]]]]] + # Current values for each macro + _macro_values: dict[str, str] + + def __init_subclass__(cls): + super().__init_subclass__() + # Create _qt_designer_ for pydm if designer_options is present + if hasattr(cls, "designer_options"): + cls._qt_designer_ = { + "group": cls.designer_options.group, + "is_container": cls.designer_options.is_container, + } + icon_obj = cls.designer_options.icon + if icon_obj is not None: + cls._qt_designer_["icon"] = icon_obj + # Interpret enum as icons for ease of selection + try: + icon = cls._qt_designer_["icon"] + if isinstance(icon, IconOptions): + if str(icon): + cls._qt_designer_["icon"] = ifont.icon(str(icon)) + else: + del cls._qt_designer_["icon"] + except (AttributeError, KeyError): + ... + # Include a quick editor for macro vals + new_ext = [MacroEditExtension, RulesExtension] + try: + cls._qt_designer_["extensions"].extend(new_ext) + except (AttributeError, KeyError): + try: + cls._qt_designer_["extensions"] = new_ext + except AttributeError: + ... + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.ui_form.setupUi(self, self) # type: ignore + + def retranslateUi(self, *args, **kwargs): + """Required function for setupUi to work in __init__""" + self.ui_form.retranslateUi(self, *args, **kwargs) # type: ignore + + def _get_macro(self, macro_name: str) -> str: + return self._macro_values[macro_name] + + def _set_macro(self, macro_name: str, value: str): + self._macro_values[macro_name] = value + self._updates_for_macro(macro_name) + + def _updates_for_macro(self, macro_name: str): + for widget_name in self._macro_to_widget[macro_name]: + self._update_widget_for_macros(widget_name) + + def _update_widget_for_macros(self, widget_name: str): + needed_macros = self._widget_to_macro[widget_name] + if not all(self._macro_values[macro_name] for macro_name in needed_macros): + # Skip! Not ready! + return + widget = getattr(self, widget_name) + if not isinstance(widget, QWidget): + raise TypeError(f"{widget_name} is not a widget: {widget}") + for prop, templ in self._widget_to_pre_template[widget_name]: + if isinstance(templ, str): + value = Template(templ).substitute(self._macro_values) + elif isinstance(templ, list): + value = [Template(tp).substitute(self._macro_values) for tp in templ] + else: + raise TypeError(f"Unexpected template type, should be str or stringlist: {templ}") + widget.setProperty(prop, value) + + +class MacroEditExtension: + """ + Adds helpful macro editing options in designer on double or right click. + + See the BasicSettingsExtension from PyDM + """ + + def __init__(self, widget: DesignerWidget): + self.widget = widget + self.edit_macros_action = QAction("&Edit Core Properties", self.widget) + self.edit_macros_action.triggered.connect(self.open_dialog) + + def actions(self) -> list[QAction]: + """ + PyDM checks this to decide which actions to prepent in designer. The first action is mapped to double-click. + """ + return [self.edit_macros_action] + + def open_dialog(self): + dialog = MacroValueEditor(self.widget, parent=self.widget) + dialog.exec_() + + +class MacroValueEditor(QDialog): + """ + Dialog for MacroEditExtension + + See the BasicSettingsEditor from PyDM + """ + + def __init__(self, widget: DesignerWidget, parent: QWidget | None): + super().__init__(parent) + self.widget = widget + self.edit_widgets: dict[str, QLineEdit] = {} + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle("Widget Core Settings Editor") + outer_layout = QVBoxLayout() + outer_layout.setContentsMargins(5, 5, 5, 5) + outer_layout.setSpacing(5) + self.setLayout(outer_layout) + + edit_form_layout = QFormLayout() + outer_layout.addLayout(edit_form_layout) + + for macro_name, value in self.widget._macro_values.items(): + self.edit_widgets[macro_name] = QLineEdit() + self.edit_widgets[macro_name].setText(value) + edit_form_layout.addRow(macro_name.lower(), self.edit_widgets[macro_name]) + + button_layout = QHBoxLayout() + outer_layout.addLayout(button_layout) + + self.save_button = QPushButton("&Save") + self.save_button.setAutoDefault(True) + self.save_button.setDefault(True) + self.save_button.clicked.connect(self.save_changes) + update_button = QPushButton("&Update") + update_button.clicked.connect(self.save_changes) + cancel_button = QPushButton("&Cancel") + cancel_button.clicked.connect(self.cancel_changes) + button_layout.addWidget(cancel_button) + button_layout.addWidget(update_button) + button_layout.addWidget(self.save_button) + + def save_changes(self): + for macro_name, widget in self.edit_widgets.items(): + update_property_for_widget(self.widget, macro_name.lower(), widget.text()) + if self.sender() == self.save_button: + self.accept() + + def cancel_changes(self): + self.close() diff --git a/pcdswidgets/builder/entrypoint_finder.py b/pcdswidgets/builder/entrypoint_finder.py new file mode 100644 index 0000000..99d0030 --- /dev/null +++ b/pcdswidgets/builder/entrypoint_finder.py @@ -0,0 +1,110 @@ +""" +Helper module for creating the [project.entry-points."pydm.widget"] +section in pyproject.toml + +python -m pcdswidgets.builder.entrypoint_finder +""" + +import importlib +import inspect +import pkgutil +from pathlib import Path +from types import ModuleType +from typing import Iterator, cast + +import tomlkit as tk +import tomlkit.items as tki +from qtpy.QtWidgets import QWidget + +import pcdswidgets + +SKIP_WIDGETS = [] + +SKIP_MODULES = [ + ".tests", + ".demo", + ".ui", +] + + +def main(): + key_val = get_widget_entrypoint_data() + widget_table, toml_doc = get_current_widget_table() + update_widget_table(widget_table, key_val) + write_pyproj(toml_doc) + + +def get_widget_entrypoint_data() -> list[tuple[str, str]]: + key_val_set: set[tuple[str, str]] = set() + for name, WidgetCls in iter_all_widgets(): + key_val_set.add((name, f"{WidgetCls.__module__}:{name}")) + key_val = sorted(key_val_set) + return key_val + + +def iter_all_widgets() -> Iterator[tuple[str, type[QWidget]]]: + """ + Recursively yield all widgets to export from pcdswidgets. + + Yields + ------ + name, widget: str, QWidget + """ + seen: set[str] = set() + for module in iter_submodules(): + for name, obj in inspect.getmembers(module, inspect.isclass): + if name in SKIP_WIDGETS: + continue + if name in seen: + continue + if issubclass(obj, QWidget) and hasattr(obj, "_qt_designer_"): + seen.add(name) + yield (name, obj) + + +def iter_submodules(package: str = "pcdswidgets") -> Iterator[ModuleType]: + """Recursively yield all submodules of a package.""" + if any(mod in package for mod in SKIP_MODULES): + return + module = importlib.import_module(package) + yield module + try: + for _, modname, _ in pkgutil.walk_packages(module.__path__, module.__name__ + "."): + if "__main__" not in modname: + yield from iter_submodules(modname) + except AttributeError: + ... + + +def get_pyproj_path() -> Path: + return Path(pcdswidgets.__file__).parent.parent / "pyproject.toml" + + +def get_current_widget_table() -> tuple[tki.Table, tk.TOMLDocument]: + pyproj = get_pyproj_path() + if not pyproj.exists(): + raise RuntimeError(f"Project file {pyproj} missing?") + with open(pyproj, "r") as fd: + toml_doc = tk.parse(fd.read()) + + project_table = cast(tki.Table, toml_doc["project"]) + entrypoint_table = cast(tki.Table, project_table["entry-points"]) + widget_table = cast(tki.Table, entrypoint_table["pydm.widget"]) + + return widget_table, toml_doc + + +def update_widget_table(widget_table: tki.Table, key_val: list[tuple[str, str]]): + widget_table.clear() + for key, value in key_val: + widget_table[key] = value + + +def write_pyproj(toml_doc: tk.TOMLDocument): + pyproj = get_pyproj_path() + with open(pyproj, "w") as fd: + tk.dump(toml_doc, fd) + + +if __name__ == "__main__": + main() diff --git a/pcdswidgets/builder/get_icon_options.py b/pcdswidgets/builder/get_icon_options.py new file mode 100644 index 0000000..87ef06b --- /dev/null +++ b/pcdswidgets/builder/get_icon_options.py @@ -0,0 +1,110 @@ +""" +Generate a fresh version of icon_options.py + +This helps us figure out what options exist for designer icons as provided by pydm. +""" + +import argparse +import json +from pathlib import Path + +from jinja2 import Environment, PackageLoader +from pydm.utilities import iconfont +from qtpy.QtCore import QSize +from qtpy.QtWidgets import QApplication, QGridLayout, QLabel, QMainWindow, QScrollArea, QVBoxLayout, QWidget + + +def show_icon_options(): + """ + Show a simple qt window with a grid of valid rendered icons alongside their names. + + This is the full set of usable icons from pydm's iconfont. + """ + app = QApplication([]) + main_window = QMainWindow() + scroll_area = QScrollArea() + main_widget = QWidget() + main_layout = QGridLayout() + + main_window.setCentralWidget(scroll_area) + scroll_area.setWidgetResizable(True) + scroll_area.setWidget(main_widget) + main_widget.setLayout(main_layout) + + cols = 5 + curr = 0 + row = 0 + + ifont = iconfont.IconFont() + + for icon_name in get_icon_options(): + icon = ifont.icon(icon_name) + if icon is None: + continue + + icon_widget = QWidget() + icon_layout = QVBoxLayout() + icon_image = QLabel() + icon_text = QLabel(icon_name) + + icon_widget.setLayout(icon_layout) + icon_image.setPixmap(icon.pixmap(QSize(32, 32))) + icon_layout.addWidget(icon_image) + icon_layout.addWidget(icon_text) + + main_layout.addWidget(icon_widget, row, curr) + curr += 1 + if curr >= cols: + curr = 0 + row += 1 + + main_window.resize(1200, 600) + main_window.show() + app.exec_() + + +def generate_icon_options(): + """ + Generate icon_options.py, which contains a large enum with icon options. + """ + jinja_template = "icon_options.j2" + env = Environment(trim_blocks=True, loader=PackageLoader("pcdswidgets", "builder")) + template = env.get_template(jinja_template) + jinja_output = template.render( + options=get_icon_options(), + ) + output_file = Path(__file__).parent / "icon_options.py" + with open(output_file, "w") as fd: + fd.write(jinja_output) + fd.write("\n") + + +def get_icon_options() -> list[str]: + """ + Returns the names of all the icons present in pydm's iconfont with valid rendering. + """ + # The charmap file is everything that pydm recognizes as an icon, including things it has no image data for + with open(Path(iconfont.__file__).parent / "fontawesome-charmap.json", "r") as fd: + charmap: dict[str, str] = json.load(fd) + + # The glyph map is everything actually present in the otf file pydm uses + # I got this map by uploading the otf file to fontdrop.info and copying the glyphIndexMap in the cmap verbatim + # There are probably other ways to get this info, but this one avoided adding a dependency here + with open(Path(__file__).parent / "valid_glyph_map.json", "r") as fd: + glyph_map: dict[str, int] = json.load(fd) + valid_hex = {hex(int(key))[2:] for key in glyph_map} + + # If something is present in both, it's a valid icon! + return [name for name, hexval in charmap.items() if hexval in valid_hex] + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("mode", choices=("show", "build"), default="show") + args = parser.parse_args() + if args.mode == "show": + show_icon_options() + elif args.mode == "build": + generate_icon_options() + else: + raise RuntimeError(f"Invalid option {args.mode}") diff --git a/pcdswidgets/builder/icon_options.j2 b/pcdswidgets/builder/icon_options.j2 new file mode 100644 index 0000000..0999e1b --- /dev/null +++ b/pcdswidgets/builder/icon_options.j2 @@ -0,0 +1,14 @@ +""" +Define the IconOptions enum, which helps us keep track of which string icon names are valid. + +This is a file generated from icon_options.j2 +""" + +from enum import StrEnum + + +class IconOptions(StrEnum): + NONE = "" +{% for opt in options %} + {{ opt.replace("-", "_") }} = "{{ opt }}" +{% endfor %} diff --git a/pcdswidgets/builder/icon_options.py b/pcdswidgets/builder/icon_options.py new file mode 100644 index 0000000..336b8fc --- /dev/null +++ b/pcdswidgets/builder/icon_options.py @@ -0,0 +1,1013 @@ +""" +Define the IconOptions enum, which helps us keep track of which string icon names are valid. + +This is a file generated from icon_options.j2 +""" + +from enum import StrEnum + + +class IconOptions(StrEnum): + NONE = "" + ad = "ad" + address_book = "address-book" + address_card = "address-card" + adjust = "adjust" + air_freshener = "air-freshener" + align_center = "align-center" + align_justify = "align-justify" + align_left = "align-left" + align_right = "align-right" + allergies = "allergies" + ambulance = "ambulance" + american_sign_language_interpreting = "american-sign-language-interpreting" + anchor = "anchor" + angle_double_down = "angle-double-down" + angle_double_left = "angle-double-left" + angle_double_right = "angle-double-right" + angle_double_up = "angle-double-up" + angle_down = "angle-down" + angle_left = "angle-left" + angle_right = "angle-right" + angle_up = "angle-up" + angry = "angry" + ankh = "ankh" + apple_alt = "apple-alt" + archive = "archive" + archway = "archway" + arrow_alt_circle_down = "arrow-alt-circle-down" + arrow_alt_circle_left = "arrow-alt-circle-left" + arrow_alt_circle_right = "arrow-alt-circle-right" + arrow_alt_circle_up = "arrow-alt-circle-up" + arrow_circle_down = "arrow-circle-down" + arrow_circle_left = "arrow-circle-left" + arrow_circle_right = "arrow-circle-right" + arrow_circle_up = "arrow-circle-up" + arrow_down = "arrow-down" + arrow_left = "arrow-left" + arrow_right = "arrow-right" + arrow_up = "arrow-up" + arrows_alt = "arrows-alt" + arrows_alt_h = "arrows-alt-h" + arrows_alt_v = "arrows-alt-v" + assistive_listening_systems = "assistive-listening-systems" + asterisk = "asterisk" + at = "at" + atlas = "atlas" + atom = "atom" + audio_description = "audio-description" + award = "award" + baby = "baby" + baby_carriage = "baby-carriage" + backspace = "backspace" + backward = "backward" + bacon = "bacon" + bacteria = "bacteria" + bacterium = "bacterium" + bahai = "bahai" + balance_scale = "balance-scale" + balance_scale_left = "balance-scale-left" + balance_scale_right = "balance-scale-right" + ban = "ban" + band_aid = "band-aid" + barcode = "barcode" + bars = "bars" + baseball_ball = "baseball-ball" + basketball_ball = "basketball-ball" + bath = "bath" + battery_empty = "battery-empty" + battery_full = "battery-full" + battery_half = "battery-half" + battery_quarter = "battery-quarter" + battery_three_quarters = "battery-three-quarters" + bed = "bed" + beer = "beer" + bell = "bell" + bell_slash = "bell-slash" + bezier_curve = "bezier-curve" + bible = "bible" + bicycle = "bicycle" + biking = "biking" + binoculars = "binoculars" + biohazard = "biohazard" + birthday_cake = "birthday-cake" + blender = "blender" + blender_phone = "blender-phone" + blind = "blind" + blog = "blog" + bold = "bold" + bolt = "bolt" + bomb = "bomb" + bone = "bone" + bong = "bong" + book = "book" + book_dead = "book-dead" + book_medical = "book-medical" + book_open = "book-open" + book_reader = "book-reader" + bookmark = "bookmark" + border_all = "border-all" + border_none = "border-none" + border_style = "border-style" + bowling_ball = "bowling-ball" + box = "box" + box_open = "box-open" + box_tissue = "box-tissue" + boxes = "boxes" + braille = "braille" + brain = "brain" + bread_slice = "bread-slice" + briefcase = "briefcase" + briefcase_medical = "briefcase-medical" + broadcast_tower = "broadcast-tower" + broom = "broom" + brush = "brush" + bug = "bug" + building = "building" + bullhorn = "bullhorn" + bullseye = "bullseye" + burn = "burn" + bus = "bus" + bus_alt = "bus-alt" + business_time = "business-time" + calculator = "calculator" + calendar = "calendar" + calendar_alt = "calendar-alt" + calendar_check = "calendar-check" + calendar_day = "calendar-day" + calendar_minus = "calendar-minus" + calendar_plus = "calendar-plus" + calendar_times = "calendar-times" + calendar_week = "calendar-week" + camera = "camera" + camera_retro = "camera-retro" + campground = "campground" + candy_cane = "candy-cane" + cannabis = "cannabis" + capsules = "capsules" + car = "car" + car_alt = "car-alt" + car_battery = "car-battery" + car_crash = "car-crash" + car_side = "car-side" + caravan = "caravan" + caret_down = "caret-down" + caret_left = "caret-left" + caret_right = "caret-right" + caret_square_down = "caret-square-down" + caret_square_left = "caret-square-left" + caret_square_right = "caret-square-right" + caret_square_up = "caret-square-up" + caret_up = "caret-up" + carrot = "carrot" + cart_arrow_down = "cart-arrow-down" + cart_plus = "cart-plus" + cash_register = "cash-register" + cat = "cat" + certificate = "certificate" + chair = "chair" + chalkboard = "chalkboard" + chalkboard_teacher = "chalkboard-teacher" + charging_station = "charging-station" + chart_area = "chart-area" + chart_bar = "chart-bar" + chart_line = "chart-line" + chart_pie = "chart-pie" + check = "check" + check_circle = "check-circle" + check_double = "check-double" + check_square = "check-square" + cheese = "cheese" + chess = "chess" + chess_bishop = "chess-bishop" + chess_board = "chess-board" + chess_king = "chess-king" + chess_knight = "chess-knight" + chess_pawn = "chess-pawn" + chess_queen = "chess-queen" + chess_rook = "chess-rook" + chevron_circle_down = "chevron-circle-down" + chevron_circle_left = "chevron-circle-left" + chevron_circle_right = "chevron-circle-right" + chevron_circle_up = "chevron-circle-up" + chevron_down = "chevron-down" + chevron_left = "chevron-left" + chevron_right = "chevron-right" + chevron_up = "chevron-up" + child = "child" + church = "church" + circle = "circle" + circle_notch = "circle-notch" + city = "city" + clinic_medical = "clinic-medical" + clipboard = "clipboard" + clipboard_check = "clipboard-check" + clipboard_list = "clipboard-list" + clock = "clock" + clone = "clone" + closed_captioning = "closed-captioning" + cloud = "cloud" + cloud_download_alt = "cloud-download-alt" + cloud_meatball = "cloud-meatball" + cloud_moon = "cloud-moon" + cloud_moon_rain = "cloud-moon-rain" + cloud_rain = "cloud-rain" + cloud_showers_heavy = "cloud-showers-heavy" + cloud_sun = "cloud-sun" + cloud_sun_rain = "cloud-sun-rain" + cloud_upload_alt = "cloud-upload-alt" + cocktail = "cocktail" + code = "code" + code_branch = "code-branch" + coffee = "coffee" + cog = "cog" + cogs = "cogs" + coins = "coins" + columns = "columns" + comment = "comment" + comment_alt = "comment-alt" + comment_dollar = "comment-dollar" + comment_dots = "comment-dots" + comment_medical = "comment-medical" + comment_slash = "comment-slash" + comments = "comments" + comments_dollar = "comments-dollar" + compact_disc = "compact-disc" + compass = "compass" + compress = "compress" + compress_alt = "compress-alt" + compress_arrows_alt = "compress-arrows-alt" + concierge_bell = "concierge-bell" + cookie = "cookie" + cookie_bite = "cookie-bite" + copy = "copy" + copyright = "copyright" + couch = "couch" + credit_card = "credit-card" + crop = "crop" + crop_alt = "crop-alt" + cross = "cross" + crosshairs = "crosshairs" + crow = "crow" + crown = "crown" + crutch = "crutch" + cube = "cube" + cubes = "cubes" + cut = "cut" + database = "database" + deaf = "deaf" + democrat = "democrat" + desktop = "desktop" + dharmachakra = "dharmachakra" + diagnoses = "diagnoses" + dice = "dice" + dice_d20 = "dice-d20" + dice_d6 = "dice-d6" + dice_five = "dice-five" + dice_four = "dice-four" + dice_one = "dice-one" + dice_six = "dice-six" + dice_three = "dice-three" + dice_two = "dice-two" + digital_tachograph = "digital-tachograph" + directions = "directions" + disease = "disease" + divide = "divide" + dizzy = "dizzy" + dna = "dna" + dog = "dog" + dollar_sign = "dollar-sign" + dolly = "dolly" + dolly_flatbed = "dolly-flatbed" + donate = "donate" + door_closed = "door-closed" + door_open = "door-open" + dot_circle = "dot-circle" + dove = "dove" + download = "download" + drafting_compass = "drafting-compass" + dragon = "dragon" + draw_polygon = "draw-polygon" + drum = "drum" + drum_steelpan = "drum-steelpan" + drumstick_bite = "drumstick-bite" + dumbbell = "dumbbell" + dumpster = "dumpster" + dumpster_fire = "dumpster-fire" + dungeon = "dungeon" + edit = "edit" + egg = "egg" + eject = "eject" + ellipsis_h = "ellipsis-h" + ellipsis_v = "ellipsis-v" + envelope = "envelope" + envelope_open = "envelope-open" + envelope_open_text = "envelope-open-text" + envelope_square = "envelope-square" + equals = "equals" + eraser = "eraser" + ethernet = "ethernet" + euro_sign = "euro-sign" + exchange_alt = "exchange-alt" + exclamation = "exclamation" + exclamation_circle = "exclamation-circle" + exclamation_triangle = "exclamation-triangle" + expand = "expand" + expand_alt = "expand-alt" + expand_arrows_alt = "expand-arrows-alt" + external_link_alt = "external-link-alt" + external_link_square_alt = "external-link-square-alt" + eye = "eye" + eye_dropper = "eye-dropper" + eye_slash = "eye-slash" + fan = "fan" + fast_backward = "fast-backward" + fast_forward = "fast-forward" + faucet = "faucet" + fax = "fax" + feather = "feather" + feather_alt = "feather-alt" + female = "female" + fighter_jet = "fighter-jet" + file = "file" + file_alt = "file-alt" + file_archive = "file-archive" + file_audio = "file-audio" + file_code = "file-code" + file_contract = "file-contract" + file_csv = "file-csv" + file_download = "file-download" + file_excel = "file-excel" + file_export = "file-export" + file_image = "file-image" + file_import = "file-import" + file_invoice = "file-invoice" + file_invoice_dollar = "file-invoice-dollar" + file_medical = "file-medical" + file_medical_alt = "file-medical-alt" + file_pdf = "file-pdf" + file_powerpoint = "file-powerpoint" + file_prescription = "file-prescription" + file_signature = "file-signature" + file_upload = "file-upload" + file_video = "file-video" + file_word = "file-word" + fill = "fill" + fill_drip = "fill-drip" + film = "film" + filter = "filter" + fingerprint = "fingerprint" + fire = "fire" + fire_alt = "fire-alt" + fire_extinguisher = "fire-extinguisher" + first_aid = "first-aid" + fish = "fish" + fist_raised = "fist-raised" + flag = "flag" + flag_checkered = "flag-checkered" + flag_usa = "flag-usa" + flask = "flask" + flushed = "flushed" + folder = "folder" + folder_minus = "folder-minus" + folder_open = "folder-open" + folder_plus = "folder-plus" + font = "font" + font_awesome_logo_full = "font-awesome-logo-full" + football_ball = "football-ball" + forward = "forward" + frog = "frog" + frown = "frown" + frown_open = "frown-open" + funnel_dollar = "funnel-dollar" + futbol = "futbol" + gamepad = "gamepad" + gas_pump = "gas-pump" + gavel = "gavel" + gem = "gem" + genderless = "genderless" + ghost = "ghost" + gift = "gift" + gifts = "gifts" + glass_cheers = "glass-cheers" + glass_martini = "glass-martini" + glass_martini_alt = "glass-martini-alt" + glass_whiskey = "glass-whiskey" + glasses = "glasses" + globe = "globe" + globe_africa = "globe-africa" + globe_americas = "globe-americas" + globe_asia = "globe-asia" + globe_europe = "globe-europe" + golf_ball = "golf-ball" + gopuram = "gopuram" + graduation_cap = "graduation-cap" + greater_than = "greater-than" + greater_than_equal = "greater-than-equal" + grimace = "grimace" + grin = "grin" + grin_alt = "grin-alt" + grin_beam = "grin-beam" + grin_beam_sweat = "grin-beam-sweat" + grin_hearts = "grin-hearts" + grin_squint = "grin-squint" + grin_squint_tears = "grin-squint-tears" + grin_stars = "grin-stars" + grin_tears = "grin-tears" + grin_tongue = "grin-tongue" + grin_tongue_squint = "grin-tongue-squint" + grin_tongue_wink = "grin-tongue-wink" + grin_wink = "grin-wink" + grip_horizontal = "grip-horizontal" + grip_lines = "grip-lines" + grip_lines_vertical = "grip-lines-vertical" + grip_vertical = "grip-vertical" + guitar = "guitar" + h_square = "h-square" + hamburger = "hamburger" + hammer = "hammer" + hamsa = "hamsa" + hand_holding = "hand-holding" + hand_holding_heart = "hand-holding-heart" + hand_holding_medical = "hand-holding-medical" + hand_holding_usd = "hand-holding-usd" + hand_holding_water = "hand-holding-water" + hand_lizard = "hand-lizard" + hand_middle_finger = "hand-middle-finger" + hand_paper = "hand-paper" + hand_peace = "hand-peace" + hand_point_down = "hand-point-down" + hand_point_left = "hand-point-left" + hand_point_right = "hand-point-right" + hand_point_up = "hand-point-up" + hand_pointer = "hand-pointer" + hand_rock = "hand-rock" + hand_scissors = "hand-scissors" + hand_sparkles = "hand-sparkles" + hand_spock = "hand-spock" + hands = "hands" + hands_helping = "hands-helping" + hands_wash = "hands-wash" + handshake = "handshake" + handshake_alt_slash = "handshake-alt-slash" + handshake_slash = "handshake-slash" + hanukiah = "hanukiah" + hard_hat = "hard-hat" + hashtag = "hashtag" + hat_cowboy = "hat-cowboy" + hat_cowboy_side = "hat-cowboy-side" + hat_wizard = "hat-wizard" + hdd = "hdd" + head_side_cough = "head-side-cough" + head_side_cough_slash = "head-side-cough-slash" + head_side_mask = "head-side-mask" + head_side_virus = "head-side-virus" + heading = "heading" + headphones = "headphones" + headphones_alt = "headphones-alt" + headset = "headset" + heart = "heart" + heart_broken = "heart-broken" + heartbeat = "heartbeat" + helicopter = "helicopter" + highlighter = "highlighter" + hiking = "hiking" + hippo = "hippo" + history = "history" + hockey_puck = "hockey-puck" + holly_berry = "holly-berry" + home = "home" + horse = "horse" + horse_head = "horse-head" + hospital = "hospital" + hospital_alt = "hospital-alt" + hospital_symbol = "hospital-symbol" + hospital_user = "hospital-user" + hot_tub = "hot-tub" + hotdog = "hotdog" + hotel = "hotel" + hourglass = "hourglass" + hourglass_end = "hourglass-end" + hourglass_half = "hourglass-half" + hourglass_start = "hourglass-start" + house_damage = "house-damage" + house_user = "house-user" + hryvnia = "hryvnia" + i_cursor = "i-cursor" + ice_cream = "ice-cream" + icicles = "icicles" + icons = "icons" + id_badge = "id-badge" + id_card = "id-card" + id_card_alt = "id-card-alt" + igloo = "igloo" + image = "image" + images = "images" + inbox = "inbox" + indent = "indent" + industry = "industry" + infinity = "infinity" + info = "info" + info_circle = "info-circle" + italic = "italic" + jedi = "jedi" + joint = "joint" + journal_whills = "journal-whills" + kaaba = "kaaba" + key = "key" + keyboard = "keyboard" + khanda = "khanda" + kiss = "kiss" + kiss_beam = "kiss-beam" + kiss_wink_heart = "kiss-wink-heart" + kiwi_bird = "kiwi-bird" + landmark = "landmark" + language = "language" + laptop = "laptop" + laptop_code = "laptop-code" + laptop_house = "laptop-house" + laptop_medical = "laptop-medical" + laugh = "laugh" + laugh_beam = "laugh-beam" + laugh_squint = "laugh-squint" + laugh_wink = "laugh-wink" + layer_group = "layer-group" + leaf = "leaf" + lemon = "lemon" + less_than = "less-than" + less_than_equal = "less-than-equal" + level_down_alt = "level-down-alt" + level_up_alt = "level-up-alt" + life_ring = "life-ring" + lightbulb = "lightbulb" + link = "link" + lira_sign = "lira-sign" + list = "list" + list_alt = "list-alt" + list_ol = "list-ol" + list_ul = "list-ul" + location_arrow = "location-arrow" + lock = "lock" + lock_open = "lock-open" + long_arrow_alt_down = "long-arrow-alt-down" + long_arrow_alt_left = "long-arrow-alt-left" + long_arrow_alt_right = "long-arrow-alt-right" + long_arrow_alt_up = "long-arrow-alt-up" + low_vision = "low-vision" + luggage_cart = "luggage-cart" + lungs = "lungs" + lungs_virus = "lungs-virus" + magic = "magic" + magnet = "magnet" + mail_bulk = "mail-bulk" + male = "male" + map = "map" + map_marked = "map-marked" + map_marked_alt = "map-marked-alt" + map_marker = "map-marker" + map_marker_alt = "map-marker-alt" + map_pin = "map-pin" + map_signs = "map-signs" + marker = "marker" + mars = "mars" + mars_double = "mars-double" + mars_stroke = "mars-stroke" + mars_stroke_h = "mars-stroke-h" + mars_stroke_v = "mars-stroke-v" + mask = "mask" + medal = "medal" + medkit = "medkit" + meh = "meh" + meh_blank = "meh-blank" + meh_rolling_eyes = "meh-rolling-eyes" + memory = "memory" + menorah = "menorah" + mercury = "mercury" + meteor = "meteor" + microchip = "microchip" + microphone = "microphone" + microphone_alt = "microphone-alt" + microphone_alt_slash = "microphone-alt-slash" + microphone_slash = "microphone-slash" + microscope = "microscope" + minus = "minus" + minus_circle = "minus-circle" + minus_square = "minus-square" + mitten = "mitten" + mobile = "mobile" + mobile_alt = "mobile-alt" + money_bill = "money-bill" + money_bill_alt = "money-bill-alt" + money_bill_wave = "money-bill-wave" + money_bill_wave_alt = "money-bill-wave-alt" + money_check = "money-check" + money_check_alt = "money-check-alt" + monument = "monument" + moon = "moon" + mortar_pestle = "mortar-pestle" + mosque = "mosque" + motorcycle = "motorcycle" + mountain = "mountain" + mouse = "mouse" + mouse_pointer = "mouse-pointer" + mug_hot = "mug-hot" + music = "music" + network_wired = "network-wired" + neuter = "neuter" + newspaper = "newspaper" + not_equal = "not-equal" + notes_medical = "notes-medical" + object_group = "object-group" + object_ungroup = "object-ungroup" + oil_can = "oil-can" + om = "om" + otter = "otter" + outdent = "outdent" + pager = "pager" + paint_brush = "paint-brush" + paint_roller = "paint-roller" + palette = "palette" + pallet = "pallet" + paper_plane = "paper-plane" + paperclip = "paperclip" + parachute_box = "parachute-box" + paragraph = "paragraph" + parking = "parking" + passport = "passport" + pastafarianism = "pastafarianism" + paste = "paste" + pause = "pause" + pause_circle = "pause-circle" + paw = "paw" + peace = "peace" + pen = "pen" + pen_alt = "pen-alt" + pen_fancy = "pen-fancy" + pen_nib = "pen-nib" + pen_square = "pen-square" + pencil_alt = "pencil-alt" + pencil_ruler = "pencil-ruler" + people_arrows = "people-arrows" + people_carry = "people-carry" + pepper_hot = "pepper-hot" + percent = "percent" + percentage = "percentage" + person_booth = "person-booth" + phone = "phone" + phone_alt = "phone-alt" + phone_slash = "phone-slash" + phone_square = "phone-square" + phone_square_alt = "phone-square-alt" + phone_volume = "phone-volume" + photo_video = "photo-video" + piggy_bank = "piggy-bank" + pills = "pills" + pizza_slice = "pizza-slice" + place_of_worship = "place-of-worship" + plane = "plane" + plane_arrival = "plane-arrival" + plane_departure = "plane-departure" + plane_slash = "plane-slash" + play = "play" + play_circle = "play-circle" + plug = "plug" + plus = "plus" + plus_circle = "plus-circle" + plus_square = "plus-square" + podcast = "podcast" + poll = "poll" + poll_h = "poll-h" + poo = "poo" + poo_storm = "poo-storm" + poop = "poop" + portrait = "portrait" + pound_sign = "pound-sign" + power_off = "power-off" + pray = "pray" + praying_hands = "praying-hands" + prescription = "prescription" + prescription_bottle = "prescription-bottle" + prescription_bottle_alt = "prescription-bottle-alt" + print = "print" + procedures = "procedures" + project_diagram = "project-diagram" + pump_medical = "pump-medical" + pump_soap = "pump-soap" + puzzle_piece = "puzzle-piece" + qrcode = "qrcode" + question = "question" + question_circle = "question-circle" + quidditch = "quidditch" + quote_left = "quote-left" + quote_right = "quote-right" + quran = "quran" + radiation = "radiation" + radiation_alt = "radiation-alt" + rainbow = "rainbow" + random = "random" + receipt = "receipt" + record_vinyl = "record-vinyl" + recycle = "recycle" + redo = "redo" + redo_alt = "redo-alt" + registered = "registered" + remove_format = "remove-format" + reply = "reply" + reply_all = "reply-all" + republican = "republican" + restroom = "restroom" + retweet = "retweet" + ribbon = "ribbon" + ring = "ring" + road = "road" + robot = "robot" + rocket = "rocket" + route = "route" + rss = "rss" + rss_square = "rss-square" + ruble_sign = "ruble-sign" + ruler = "ruler" + ruler_combined = "ruler-combined" + ruler_horizontal = "ruler-horizontal" + ruler_vertical = "ruler-vertical" + running = "running" + rupee_sign = "rupee-sign" + sad_cry = "sad-cry" + sad_tear = "sad-tear" + satellite = "satellite" + satellite_dish = "satellite-dish" + save = "save" + school = "school" + screwdriver = "screwdriver" + scroll = "scroll" + sd_card = "sd-card" + search = "search" + search_dollar = "search-dollar" + search_location = "search-location" + search_minus = "search-minus" + search_plus = "search-plus" + seedling = "seedling" + server = "server" + shapes = "shapes" + share = "share" + share_alt = "share-alt" + share_alt_square = "share-alt-square" + share_square = "share-square" + shekel_sign = "shekel-sign" + shield_alt = "shield-alt" + shield_virus = "shield-virus" + ship = "ship" + shipping_fast = "shipping-fast" + shoe_prints = "shoe-prints" + shopping_bag = "shopping-bag" + shopping_basket = "shopping-basket" + shopping_cart = "shopping-cart" + shower = "shower" + shuttle_van = "shuttle-van" + sign = "sign" + sign_in_alt = "sign-in-alt" + sign_language = "sign-language" + sign_out_alt = "sign-out-alt" + signal = "signal" + signature = "signature" + sim_card = "sim-card" + sink = "sink" + sitemap = "sitemap" + skating = "skating" + skiing = "skiing" + skiing_nordic = "skiing-nordic" + skull = "skull" + skull_crossbones = "skull-crossbones" + slash = "slash" + sleigh = "sleigh" + sliders_h = "sliders-h" + smile = "smile" + smile_beam = "smile-beam" + smile_wink = "smile-wink" + smog = "smog" + smoking = "smoking" + smoking_ban = "smoking-ban" + sms = "sms" + snowboarding = "snowboarding" + snowflake = "snowflake" + snowman = "snowman" + snowplow = "snowplow" + soap = "soap" + socks = "socks" + solar_panel = "solar-panel" + sort = "sort" + sort_alpha_down = "sort-alpha-down" + sort_alpha_down_alt = "sort-alpha-down-alt" + sort_alpha_up = "sort-alpha-up" + sort_alpha_up_alt = "sort-alpha-up-alt" + sort_amount_down = "sort-amount-down" + sort_amount_down_alt = "sort-amount-down-alt" + sort_amount_up = "sort-amount-up" + sort_amount_up_alt = "sort-amount-up-alt" + sort_down = "sort-down" + sort_numeric_down = "sort-numeric-down" + sort_numeric_down_alt = "sort-numeric-down-alt" + sort_numeric_up = "sort-numeric-up" + sort_numeric_up_alt = "sort-numeric-up-alt" + sort_up = "sort-up" + spa = "spa" + space_shuttle = "space-shuttle" + spell_check = "spell-check" + spider = "spider" + spinner = "spinner" + splotch = "splotch" + spray_can = "spray-can" + square = "square" + square_full = "square-full" + square_root_alt = "square-root-alt" + stamp = "stamp" + star = "star" + star_and_crescent = "star-and-crescent" + star_half = "star-half" + star_half_alt = "star-half-alt" + star_of_david = "star-of-david" + star_of_life = "star-of-life" + step_backward = "step-backward" + step_forward = "step-forward" + stethoscope = "stethoscope" + sticky_note = "sticky-note" + stop = "stop" + stop_circle = "stop-circle" + stopwatch = "stopwatch" + stopwatch_20 = "stopwatch-20" + store = "store" + store_alt = "store-alt" + store_alt_slash = "store-alt-slash" + store_slash = "store-slash" + stream = "stream" + street_view = "street-view" + strikethrough = "strikethrough" + stroopwafel = "stroopwafel" + subscript = "subscript" + subway = "subway" + suitcase = "suitcase" + suitcase_rolling = "suitcase-rolling" + sun = "sun" + superscript = "superscript" + surprise = "surprise" + swatchbook = "swatchbook" + swimmer = "swimmer" + swimming_pool = "swimming-pool" + synagogue = "synagogue" + sync = "sync" + sync_alt = "sync-alt" + syringe = "syringe" + table = "table" + table_tennis = "table-tennis" + tablet = "tablet" + tablet_alt = "tablet-alt" + tablets = "tablets" + tachometer_alt = "tachometer-alt" + tag = "tag" + tags = "tags" + tape = "tape" + tasks = "tasks" + taxi = "taxi" + teeth = "teeth" + teeth_open = "teeth-open" + temperature_high = "temperature-high" + temperature_low = "temperature-low" + tenge = "tenge" + terminal = "terminal" + text_height = "text-height" + text_width = "text-width" + th = "th" + th_large = "th-large" + th_list = "th-list" + theater_masks = "theater-masks" + thermometer = "thermometer" + thermometer_empty = "thermometer-empty" + thermometer_full = "thermometer-full" + thermometer_half = "thermometer-half" + thermometer_quarter = "thermometer-quarter" + thermometer_three_quarters = "thermometer-three-quarters" + thumbs_down = "thumbs-down" + thumbs_up = "thumbs-up" + thumbtack = "thumbtack" + ticket_alt = "ticket-alt" + times = "times" + times_circle = "times-circle" + tint = "tint" + tint_slash = "tint-slash" + tired = "tired" + toggle_off = "toggle-off" + toggle_on = "toggle-on" + toilet = "toilet" + toilet_paper = "toilet-paper" + toilet_paper_slash = "toilet-paper-slash" + toolbox = "toolbox" + tools = "tools" + tooth = "tooth" + torah = "torah" + torii_gate = "torii-gate" + tractor = "tractor" + trademark = "trademark" + traffic_light = "traffic-light" + trailer = "trailer" + train = "train" + tram = "tram" + transgender = "transgender" + transgender_alt = "transgender-alt" + trash = "trash" + trash_alt = "trash-alt" + trash_restore = "trash-restore" + trash_restore_alt = "trash-restore-alt" + tree = "tree" + trophy = "trophy" + truck = "truck" + truck_loading = "truck-loading" + truck_monster = "truck-monster" + truck_moving = "truck-moving" + truck_pickup = "truck-pickup" + tshirt = "tshirt" + tty = "tty" + tv = "tv" + umbrella = "umbrella" + umbrella_beach = "umbrella-beach" + underline = "underline" + undo = "undo" + undo_alt = "undo-alt" + universal_access = "universal-access" + university = "university" + unlink = "unlink" + unlock = "unlock" + unlock_alt = "unlock-alt" + upload = "upload" + user = "user" + user_alt = "user-alt" + user_alt_slash = "user-alt-slash" + user_astronaut = "user-astronaut" + user_check = "user-check" + user_circle = "user-circle" + user_clock = "user-clock" + user_cog = "user-cog" + user_edit = "user-edit" + user_friends = "user-friends" + user_graduate = "user-graduate" + user_injured = "user-injured" + user_lock = "user-lock" + user_md = "user-md" + user_minus = "user-minus" + user_ninja = "user-ninja" + user_nurse = "user-nurse" + user_plus = "user-plus" + user_secret = "user-secret" + user_shield = "user-shield" + user_slash = "user-slash" + user_tag = "user-tag" + user_tie = "user-tie" + user_times = "user-times" + users = "users" + users_cog = "users-cog" + users_slash = "users-slash" + utensil_spoon = "utensil-spoon" + utensils = "utensils" + vector_square = "vector-square" + venus = "venus" + venus_double = "venus-double" + venus_mars = "venus-mars" + vest = "vest" + vest_patches = "vest-patches" + vial = "vial" + vials = "vials" + video = "video" + video_slash = "video-slash" + vihara = "vihara" + virus = "virus" + virus_slash = "virus-slash" + viruses = "viruses" + voicemail = "voicemail" + volleyball_ball = "volleyball-ball" + volume_down = "volume-down" + volume_mute = "volume-mute" + volume_off = "volume-off" + volume_up = "volume-up" + vote_yea = "vote-yea" + vr_cardboard = "vr-cardboard" + walking = "walking" + wallet = "wallet" + warehouse = "warehouse" + water = "water" + wave_square = "wave-square" + weight = "weight" + weight_hanging = "weight-hanging" + wheelchair = "wheelchair" + wifi = "wifi" + wind = "wind" + window_close = "window-close" + window_maximize = "window-maximize" + window_minimize = "window-minimize" + window_restore = "window-restore" + wine_bottle = "wine-bottle" + wine_glass = "wine-glass" + wine_glass_alt = "wine-glass-alt" + won_sign = "won-sign" + wrench = "wrench" + x_ray = "x-ray" + yen_sign = "yen-sign" + yin_yang = "yin-yang" diff --git a/pcdswidgets/builder/inits.py b/pcdswidgets/builder/inits.py new file mode 100644 index 0000000..38e89c7 --- /dev/null +++ b/pcdswidgets/builder/inits.py @@ -0,0 +1,50 @@ +from pathlib import Path + +import pcdswidgets + + +def main(): + """ + Backfill generated directories with __init__.py to make them python modules. + """ + for base_dir in get_generated_top_dirs(): + build_inits(base_dir=base_dir) + + +def get_generated_top_dirs() -> list[Path]: + """ + Returns the top-level directories that were originally generated. + """ + module_dir = Path(pcdswidgets.__file__).parent + top_dirs = [module_dir / "generated"] + # Other generated directories are those that mirror the ui folder filetree + for path in (module_dir / "ui").glob("*"): + if path.is_dir(): + # There is a generated directory at the top-level of the same name, without "ui" + new_dir_parts = [part for part in path.parts if part != "ui"] + top_dirs.append(path.with_segments(*new_dir_parts)) + return top_dirs + + +def build_inits(base_dir: Path): + """ + Creates blank __init__.py files wherever they are needed in generated directories. + + This makes Python treat these directories as Python modules. + + Parameters + ---------- + base_dir : Path + The directory path that contains generated python files in a nested filetree. + """ + candidates: set[Path] = set() + for path in base_dir.rglob("*"): + if "__pycache__" not in path.parts: + candidates.add(path.with_name("__init__.py")) + for cand_path in candidates: + if not cand_path.exists(): + cand_path.touch() + + +if __name__ == "__main__": + main() diff --git a/pcdswidgets/builder/ui_base_widget.j2 b/pcdswidgets/builder/ui_base_widget.j2 new file mode 100644 index 0000000..1d0a0cb --- /dev/null +++ b/pcdswidgets/builder/ui_base_widget.j2 @@ -0,0 +1,89 @@ +""" +Generated by jinja from {{ jinja_template }} with: +ui_name = {{ ui_name }} +form_cls = {{ form_cls }} +base_cls = {{ base_cls }} +macro_names = {{ macro_names }} + +Other long required variables: +widget_names: list[str] +widget_name_to_class: dict[str, str] +macro_to_widget: dict[str, str] +widget_to_macro: dict[str, str] +widget_to_pre_templ_strs: dict[str, list[tuple[str, str]]] +widget_to_pre_templ_lists: dict[str, list[tuple[str, list[str]]]] +""" +# ruff: noqa: E501 +# ruff: noqa: F403 +# ruff: noqa: F405 + +from pcdswidgets.builder.designer_widget import DesignerWidget +from .{{ ui_name.removesuffix(".ui") }}_form import * + +try: + from qtpy.QtCore import pyqtProperty +except ImportError: + from qtpy.QtCore import Property as pyqtProperty # type: ignore + + +class {{ base_cls }}(DesignerWidget): +{% for widget in widget_names %} + {{ widget }}: "{{ widget_name_to_class[widget] }}" +{% endfor %} + + ui_form = {{ form_cls }} + _macro_to_widget = { +{% for macro in macro_names %} + "{{ macro }}": [ +{% for widget in macro_to_widget[macro] %} + "{{ widget }}", +{% endfor %} + ], +{% endfor %} + } + _widget_to_macro = { +{% for widget in widget_names %} + "{{ widget }}": [ +{% for macro in widget_to_macro[widget] %} + "{{ macro }}", +{% endfor %} + ], +{% endfor %} + } + _widget_to_pre_template = { +{% for widget in widget_names %} + "{{ widget }}": [ +{% for prop, value in widget_to_pre_templ_strs[widget] %} + ("{{ prop }}", "{{ value }}"), +{% endfor %} +{% for prop, value_list in widget_to_pre_templ_lists[widget] %} + ( + "{{ prop }}", + [ +{% for value in value_list %} + "{{ value }}", +{% endfor %} + ] + ), +{% endfor %} + ], +{% endfor %} + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._macro_values = { +{% for macro in macro_names %} + "{{ macro }}": "", +{% endfor %} + } +{% for macro in macro_names %} + + def get_{{ macro.lower() }}(self) -> str: + return self._get_macro("{{ macro }}") + + def set_{{ macro.lower() }}(self, value: str) -> None: + self._set_macro("{{ macro }}", value) + + {{ macro.lower() }} = pyqtProperty(str, get_{{ macro.lower() }}, set_{{ macro.lower() }}) +{% endfor %} diff --git a/pcdswidgets/builder/ui_main_widget.j2 b/pcdswidgets/builder/ui_main_widget.j2 new file mode 100644 index 0000000..b4b1aa5 --- /dev/null +++ b/pcdswidgets/builder/ui_main_widget.j2 @@ -0,0 +1,17 @@ +""" +Originally generated from jinja template ui_main_widget.j2 + +This file can be safely edited to change the runtime behavior of the widget. +""" + +from pcdswidgets.builder.designer_options import DesignerOptions +from pcdswidgets.builder.icon_options import IconOptions +from {{ absolute_import_path }} import {{ base_cls }} + + +class {{ main_cls }}({{ base_cls }}): + designer_options = DesignerOptions( + group="{{ default_group }}", + is_container=False, + icon=IconOptions.NONE, + ) diff --git a/pcdswidgets/builder/valid_glyph_map.json b/pcdswidgets/builder/valid_glyph_map.json new file mode 100644 index 0000000..a4053fc --- /dev/null +++ b/pcdswidgets/builder/valid_glyph_map.json @@ -0,0 +1,2 @@ + +{"32":1,"45":2,"46":3,"48":4,"49":5,"50":6,"51":7,"52":8,"53":9,"54":10,"55":11,"56":12,"57":13,"65":14,"66":15,"67":16,"68":17,"69":18,"70":19,"71":20,"72":21,"73":22,"74":23,"75":24,"76":25,"77":26,"78":27,"79":28,"80":29,"81":30,"82":31,"83":32,"84":33,"85":34,"86":35,"87":36,"88":37,"89":38,"90":39,"97":40,"98":41,"99":42,"100":43,"101":44,"102":45,"103":46,"104":47,"105":48,"106":49,"107":50,"108":51,"109":52,"110":53,"111":54,"112":55,"113":56,"114":57,"115":58,"116":59,"117":60,"118":61,"119":62,"120":63,"121":64,"122":65,"57349":66,"57409":67,"57433":68,"57434":69,"57435":70,"57436":71,"57437":72,"57438":73,"57439":74,"57440":75,"57441":76,"57442":77,"57443":78,"57444":79,"57445":80,"57446":81,"57447":82,"57448":83,"57449":84,"57450":85,"57451":86,"57452":87,"57453":88,"57454":89,"57455":90,"57456":91,"57457":92,"57458":93,"57459":94,"57460":95,"57461":96,"57462":97,"57477":98,"57478":99,"61440":100,"61441":101,"61442":102,"61444":103,"61445":104,"61447":105,"61448":106,"61449":107,"61450":108,"61451":109,"61452":110,"61453":111,"61454":112,"61456":113,"61457":114,"61458":115,"61459":116,"61461":117,"61463":118,"61464":119,"61465":120,"61468":121,"61470":122,"61473":123,"61474":124,"61475":125,"61476":126,"61477":127,"61478":128,"61479":129,"61480":130,"61481":131,"61482":132,"61483":133,"61484":134,"61485":135,"61486":136,"61487":137,"61488":138,"61489":139,"61490":140,"61491":141,"61492":142,"61493":143,"61494":144,"61495":145,"61496":146,"61497":147,"61498":148,"61499":149,"61500":150,"61501":151,"61502":152,"61505":153,"61506":154,"61507":155,"61508":156,"61512":157,"61513":158,"61514":159,"61515":160,"61516":161,"61517":162,"61518":163,"61520":164,"61521":165,"61522":166,"61523":167,"61524":168,"61525":169,"61526":170,"61527":171,"61528":172,"61529":173,"61530":174,"61531":175,"61534":176,"61536":177,"61537":178,"61538":179,"61539":180,"61540":181,"61541":182,"61542":183,"61543":184,"61544":185,"61545":186,"61546":187,"61547":188,"61548":189,"61549":190,"61550":191,"61552":192,"61553":193,"61554":194,"61555":195,"61556":196,"61557":197,"61558":198,"61559":199,"61560":200,"61561":201,"61562":202,"61563":203,"61564":204,"61568":205,"61571":206,"61572":207,"61573":208,"61574":209,"61577":210,"61581":211,"61585":212,"61587":213,"61588":214,"61589":215,"61592":216,"61596":217,"61597":218,"61598":219,"61600":220,"61601":221,"61603":222,"61604":223,"61605":224,"61606":225,"61607":226,"61608":227,"61609":228,"61610":229,"61611":230,"61612":231,"61613":232,"61614":233,"61616":234,"61617":235,"61618":236,"61632":237,"61633":238,"61634":239,"61635":240,"61636":241,"61637":242,"61638":243,"61639":244,"61640":245,"61641":246,"61642":247,"61643":248,"61644":249,"61645":250,"61646":251,"61648":252,"61649":253,"61654":254,"61655":255,"61656":256,"61657":257,"61658":258,"61659":259,"61660":260,"61661":261,"61662":262,"61664":263,"61666":264,"61667":265,"61671":266,"61672":267,"61673":268,"61674":269,"61675":270,"61680":271,"61681":272,"61682":273,"61683":274,"61684":275,"61688":276,"61689":277,"61690":278,"61691":279,"61692":280,"61693":281,"61694":282,"61696":283,"61697":284,"61698":285,"61699":286,"61700":287,"61701":288,"61702":289,"61703":290,"61704":291,"61705":292,"61706":293,"61707":294,"61709":295,"61710":296,"61712":297,"61713":298,"61720":299,"61721":300,"61722":301,"61723":302,"61724":303,"61726":304,"61728":305,"61729":306,"61730":307,"61732":308,"61733":309,"61734":310,"61735":311,"61736":312,"61737":313,"61738":314,"61739":315,"61740":316,"61741":317,"61742":318,"61744":319,"61745":320,"61747":321,"61748":322,"61749":323,"61751":324,"61752":325,"61753":326,"61754":327,"61757":328,"61758":329,"61760":330,"61761":331,"61762":332,"61763":333,"61764":334,"61766":335,"61770":336,"61771":337,"61773":338,"61774":339,"61776":340,"61777":341,"61778":342,"61779":343,"61780":344,"61781":345,"61782":346,"61783":347,"61784":348,"61785":349,"61787":350,"61788":351,"61789":352,"61790":353,"61792":354,"61793":355,"61794":356,"61795":357,"61796":358,"61797":359,"61826":360,"61827":361,"61829":362,"61830":363,"61831":364,"61832":365,"61841":366,"61842":367,"61843":368,"61845":369,"61847":370,"61849":371,"61852":372,"61853":373,"61867":374,"61868":375,"61869":376,"61870":377,"61872":378,"61874":379,"61875":380,"61880":381,"61881":382,"61882":383,"61883":384,"61888":385,"61889":386,"61890":387,"61891":388,"61892":389,"61893":390,"61894":391,"61895":392,"61896":393,"61897":394,"61901":395,"61902":396,"61912":397,"61914":398,"61916":399,"61917":400,"61918":401,"61920":402,"61921":403,"61922":404,"61923":405,"61924":406,"61925":407,"61926":408,"61930":409,"61931":410,"61932":411,"61942":412,"61944":413,"61945":414,"61946":415,"61947":416,"61948":417,"61949":418,"61950":419,"61952":420,"61953":421,"61956":422,"61957":423,"61958":424,"61959":425,"61962":426,"61963":427,"61975":428,"61976":429,"61978":430,"61979":431,"61980":432,"61981":433,"61982":434,"61985":435,"61986":436,"61987":437,"61988":438,"61989":439,"61990":440,"61991":441,"61992":442,"61993":443,"61994":444,"61995":445,"61996":446,"61997":447,"62003":448,"62004":449,"62005":450,"62006":451,"62008":452,"62009":453,"62016":454,"62017":455,"62018":456,"62019":457,"62020":458,"62021":459,"62022":460,"62023":461,"62024":462,"62025":463,"62029":464,"62030":465,"62033":466,"62034":467,"62035":468,"62036":469,"62037":470,"62038":471,"62039":472,"62040":473,"62041":474,"62042":475,"62043":476,"62044":477,"62045":478,"62060":479,"62065":480,"62066":481,"62067":482,"62068":483,"62069":484,"62070":485,"62071":486,"62073":487,"62074":488,"62091":489,"62093":490,"62096":491,"62097":492,"62098":493,"62101":494,"62106":495,"62109":496,"62110":497,"62112":498,"62113":499,"62114":500,"62115":501,"62116":502,"62119":503,"62120":504,"62133":505,"62134":506,"62137":507,"62139":508,"62141":509,"62145":510,"62146":511,"62151":512,"62152":513,"62153":514,"62154":515,"62155":516,"62156":517,"62157":518,"62158":519,"62160":520,"62161":521,"62162":522,"62171":523,"62172":524,"62181":525,"62183":526,"62186":527,"62189":528,"62193":529,"62194":530,"62197":531,"62198":532,"62201":533,"62206":534,"62210":535,"62211":536,"62212":537,"62213":538,"62217":539,"62218":540,"62219":541,"62220":542,"62238":543,"62248":544,"62263":545,"62264":546,"62296":547,"62297":548,"62298":549,"62299":550,"62301":551,"62304":552,"62306":553,"62337":554,"62338":555,"62373":556,"62398":557,"62399":558,"62401":559,"62405":560,"62409":561,"62413":562,"62417":563,"62429":564,"62432":565,"62437":566,"62445":567,"62458":568,"62461":569,"62463":570,"62470":571,"62480":572,"62498":573,"62500":574,"62515":575,"62516":576,"62518":577,"62521":578,"62522":579,"62524":580,"62527":581,"62529":582,"62531":583,"62533":584,"62535":585,"62539":586,"62542":587,"62544":588,"62547":589,"62552":590,"62556":591,"62557":592,"62559":593,"62561":594,"62562":595,"62566":596,"62568":597,"62569":598,"62570":599,"62571":600,"62572":601,"62573":602,"62576":603,"62577":604,"62578":605,"62580":606,"62583":607,"62584":608,"62585":609,"62589":610,"62590":611,"62591":612,"62593":613,"62594":614,"62596":615,"62597":616,"62598":617,"62599":618,"62603":619,"62605":620,"62606":621,"62608":622,"62609":623,"62610":624,"62611":625,"62612":626,"62614":627,"62615":628,"62622":629,"62637":630,"62643":631,"62648":632,"62649":633,"62650":634,"62653":635,"62654":636,"62656":637,"62657":638,"62658":639,"62660":640,"62669":641,"62670":642,"62675":643,"62678":644,"62679":645,"62680":646,"62681":647,"62682":648,"62683":649,"62686":650,"62687":651,"62690":652,"62691":653,"62694":654,"62714":655,"62715":656,"62716":657,"62717":658,"62718":659,"62719":660,"62720":661,"62721":662,"62722":663,"62723":664,"62724":665,"62725":666,"62726":667,"62727":668,"62728":669,"62729":670,"62741":671,"62742":672,"62743":673,"62744":674,"62745":675,"62746":676,"62747":677,"62748":678,"62749":679,"62750":680,"62751":681,"62752":682,"62753":683,"62754":684,"62755":685,"62756":686,"62757":687,"62758":688,"62759":689,"62760":690,"62761":691,"62762":692,"62763":693,"62764":694,"62765":695,"62766":696,"62767":697,"62768":698,"62769":699,"62770":700,"62771":701,"62772":702,"62773":703,"62774":704,"62775":705,"62776":706,"62777":707,"62778":708,"62779":709,"62780":710,"62781":711,"62782":712,"62783":713,"62784":714,"62785":715,"62786":716,"62787":717,"62788":718,"62789":719,"62790":720,"62791":721,"62792":722,"62793":723,"62794":724,"62795":725,"62796":726,"62797":727,"62798":728,"62799":729,"62800":730,"62801":731,"62802":732,"62803":733,"62804":734,"62805":735,"62806":736,"62807":737,"62808":738,"62809":739,"62810":740,"62811":741,"62812":742,"62813":743,"62814":744,"62815":745,"62816":746,"62817":747,"62818":748,"62819":749,"62820":750,"62821":751,"62822":752,"62823":753,"62824":754,"62825":755,"62826":756,"62827":757,"62828":758,"62829":759,"62830":760,"62831":761,"62832":762,"62833":763,"62834":764,"62835":765,"62836":766,"62837":767,"62838":768,"62839":769,"62840":770,"62841":771,"62842":772,"62843":773,"62844":774,"62845":775,"62846":776,"62847":777,"62848":778,"62849":779,"62850":780,"62851":781,"62852":782,"62853":783,"62854":784,"62855":785,"62856":786,"62857":787,"62858":788,"62859":789,"62860":790,"62861":791,"62862":792,"62863":793,"62864":794,"62865":795,"62867":796,"62868":797,"62869":798,"62870":799,"62871":800,"62872":801,"62873":802,"62874":803,"62875":804,"62876":805,"62877":806,"62879":807,"62880":808,"62881":809,"62882":810,"62884":811,"62885":812,"62886":813,"62887":814,"62890":815,"62891":816,"62892":817,"62893":818,"62894":819,"62895":820,"62896":821,"62897":822,"62899":823,"62900":824,"62902":825,"62903":826,"62904":827,"62906":828,"62907":829,"62908":830,"62909":831,"62911":832,"62912":833,"62913":834,"62914":835,"62915":836,"62916":837,"62917":838,"62919":839,"62920":840,"62921":841,"62922":842,"62923":843,"62925":844,"62926":845,"62928":846,"62929":847,"62930":848,"62935":849,"62938":850,"62940":851,"62942":852,"62943":853,"62945":854,"62948":855,"62951":856,"62955":857,"62958":858,"62972":859,"62973":860,"62980":861,"62992":862,"62995":863,"63001":864,"63007":865,"63009":866,"63022":867,"63023":868,"63024":869,"63031":870,"63035":871,"63036":872,"63041":873,"63044":874,"63047":875,"63050":876,"63055":877,"63057":878,"63059":879,"63060":880,"63061":881,"63064":882,"63069":883,"63070":884,"63074":885,"63076":886,"63077":887,"63078":888,"63081":889,"63082":890,"63083":891,"63085":892,"63087":893,"63092":894,"63094":895,"63096":896,"63097":897,"63099":898,"63100":899,"63103":900,"63105":901,"63106":902,"63107":903,"63108":904,"63111":905,"63112":906,"63113":907,"63126":908,"63128":909,"63129":910,"63130":911,"63131":912,"63136":913,"63137":914,"63143":915,"63145":916,"63149":917,"63158":918,"63159":919,"63163":920,"63166":921,"63168":922,"63171":923,"63172":924,"63183":925,"63185":926,"63187":927,"63189":928,"63191":929,"63193":930,"63197":931,"63198":932,"63202":933,"63203":934,"63206":935,"63208":936,"63212":937,"63213":938,"63216":939,"63217":940,"63218":941,"63226":942,"63228":943,"63231":944,"63232":945,"63243":946,"63244":947,"63246":948,"63252":949,"63253":950,"63255":951,"63262":952,"63266":953,"63272":954,"63273":955,"63278":956,"63279":957,"63291":958,"63292":959,"63293":960,"63296":961,"63299":962,"63303":963,"63309":964,"63315":965,"63318":966,"63322":967,"63323":968,"63326":969,"63327":970,"63337":971,"63339":972,"63346":973,"63347":974,"63356":975,"63357":976,"63360":977,"63361":978,"63363":979,"63364":980,"63366":981,"63367":982,"63368":983,"63372":984,"63379":985,"63380":986,"63382":987,"63388":988,"63391":989,"63392":990,"63394":991,"63396":992,"63397":993,"63398":994,"63401":995,"63402":996,"63403":997,"63405":998,"63406":999,"63413":1000,"63414":1001,"63417":1002,"63418":1003,"63421":1004,"63423":1005,"63424":1006,"63426":1007,"63428":1008,"63429":1009,"63433":1010,"63434":1011,"63436":1012,"63437":1013,"63438":1014,"63440":1015,"63442":1016,"63447":1017,"63448":1018,"63449":1019,"63450":1020,"63460":1021,"63461":1022,"63462":1023,"63468":1024,"63471":1025,"63474":1026,"63477":1027,"63479":1028,"63482":1029,"63483":1030,"63493":1031,"63494":1032,"63495":1033,"63501":1034,"63503":1035,"63504":1036,"63506":1037,"63509":1038,"63510":1039,"63512":1040,"63529":1041,"63530":1042,"63535":1043,"63550":1044,"63562":1045,"63564":1046,"63568":1047,"63571":1048,"63587":1049,"63597":1050,"63609":1051,"63611":1052,"63612":1053,"63613":1054,"63617":1055,"63618":1056,"63620":1057,"63621":1058,"63622":1059,"63623":1060,"63633":1061,"63639":1062,"63680":1063,"63681":1064,"63692":1065,"63705":1066,"63743":1067} diff --git a/pcdswidgets/eps_byteindicator.py b/pcdswidgets/eps_byteindicator.py index ac18f89..1cf5c19 100644 --- a/pcdswidgets/eps_byteindicator.py +++ b/pcdswidgets/eps_byteindicator.py @@ -19,9 +19,8 @@ def update_indicators(self): else: value = int(self.value) >> self._shift - bits = [(value >> i) & 1 - for i in range(self._num_bits)] - for bit, indicator in zip(bits, self._indicators): + bits = [(value >> i) & 1 for i in range(self._num_bits)] + for bit, indicator in zip(bits, self._indicators, strict=False): if self._connected: if self._alarm_state == 3: c = self._invalid_color @@ -38,7 +37,7 @@ class EPSByteIndicator(QWidget): """ _qt_designer_ = { - "group": "PCDS Utilities", + "group": "ECS EPS", "is_container": False, } @@ -70,10 +69,9 @@ def label_change(self, new_labels): Callback function when the lables change """ - labels = parse_value_for_display(value=new_labels, precision=0, - display_format_type=DisplayFormat.String) + labels = parse_value_for_display(value=new_labels, precision=0, display_format_type=DisplayFormat.String) - labels = labels.split(';') + labels = labels.split(";") self.template_widget.numBits = len(labels) self.template_widget.labels = labels self.template_widget.update_indicators() @@ -97,12 +95,11 @@ def channel(self, ch): self._value_pv = ch + ":nFlags_RBV" self._label_pv = ch + ":sFlagDesc_RBV" - _value_channel = PyDMChannel(address=self._value_pv, - connection_slot=self.value_channel, - value_slot=self.value_change) + _value_channel = PyDMChannel( + address=self._value_pv, connection_slot=self.value_channel, value_slot=self.value_change + ) - _label_channel = PyDMChannel(address=self._label_pv, - value_slot=self.label_change) + _label_channel = PyDMChannel(address=self._label_pv, value_slot=self.label_change) _value_channel.connect() _label_channel.connect() diff --git a/pcdswidgets/generated/__init__.py b/pcdswidgets/generated/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/generated/motion/__init__.py b/pcdswidgets/generated/motion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/generated/motion/common/__init__.py b/pcdswidgets/generated/motion/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/generated/motion/common/motor_classic_full_base.py b/pcdswidgets/generated/motion/common/motor_classic_full_base.py new file mode 100644 index 0000000..07eda0d --- /dev/null +++ b/pcdswidgets/generated/motion/common/motor_classic_full_base.py @@ -0,0 +1,155 @@ +""" +Generated by jinja from ui_base_widget.j2 with: +ui_name = motor_classic_full.ui +form_cls = Ui_Form +base_cls = MotorClassicFullBase +macro_names = ['MOTOR'] + +Other long required variables: +widget_names: list[str] +widget_name_to_class: dict[str, str] +macro_to_widget: dict[str, str] +widget_to_macro: dict[str, str] +widget_to_pre_templ_strs: dict[str, list[tuple[str, str]]] +widget_to_pre_templ_lists: dict[str, list[tuple[str, list[str]]]] +""" +# ruff: noqa: E501 +# ruff: noqa: F403 +# ruff: noqa: F405 + +from pcdswidgets.builder.designer_widget import DesignerWidget + +from .motor_classic_full_form import * + +try: + from qtpy.QtCore import pyqtProperty +except ImportError: + from qtpy.QtCore import Property as pyqtProperty # type: ignore + + +class MotorClassicFullBase(DesignerWidget): + PyDMByteIndicator_hls: "PyDMByteIndicator" + PyDMByteIndicator_lls: "PyDMByteIndicator" + PyDMByteIndicator_mvn: "PyDMByteIndicator" + PyDMLabel_egu: "PyDMLabel" + PyDMLabel_name: "PyDMLabel" + PyDMLabel_rbv: "PyDMLabel" + PyDMLineEdit_setpoint: "PyDMLineEdit" + PyDMLineEdit_twVal: "PyDMLineEdit" + PyDMPushButton_stop: "PyDMPushButton" + PyDMPushButton_twkL: "PyDMPushButton" + PyDMPushButton_twkR: "PyDMPushButton" + PyDMShellCommand_expert: "PyDMShellCommand" + + ui_form = Ui_Form + _macro_to_widget = { + "MOTOR": [ + "PyDMLabel_name", + "PyDMByteIndicator_mvn", + "PyDMByteIndicator_lls", + "PyDMLabel_rbv", + "PyDMByteIndicator_hls", + "PyDMPushButton_stop", + "PyDMLineEdit_setpoint", + "PyDMLabel_egu", + "PyDMPushButton_twkL", + "PyDMLineEdit_twVal", + "PyDMPushButton_twkR", + "PyDMShellCommand_expert", + ], + } + _widget_to_macro = { + "PyDMByteIndicator_hls": [ + "MOTOR", + ], + "PyDMByteIndicator_lls": [ + "MOTOR", + ], + "PyDMByteIndicator_mvn": [ + "MOTOR", + ], + "PyDMLabel_egu": [ + "MOTOR", + ], + "PyDMLabel_name": [ + "MOTOR", + ], + "PyDMLabel_rbv": [ + "MOTOR", + ], + "PyDMLineEdit_setpoint": [ + "MOTOR", + ], + "PyDMLineEdit_twVal": [ + "MOTOR", + ], + "PyDMPushButton_stop": [ + "MOTOR", + ], + "PyDMPushButton_twkL": [ + "MOTOR", + ], + "PyDMPushButton_twkR": [ + "MOTOR", + ], + "PyDMShellCommand_expert": [ + "MOTOR", + ], + } + _widget_to_pre_template = { + "PyDMByteIndicator_hls": [ + ("channel", "ca://${MOTOR}.HLS"), + ], + "PyDMByteIndicator_lls": [ + ("channel", "ca://${MOTOR}.LLS"), + ], + "PyDMByteIndicator_mvn": [ + ("channel", "ca://${MOTOR}.MOVN"), + ], + "PyDMLabel_egu": [ + ("channel", "ca://${MOTOR}.EGU"), + ], + "PyDMLabel_name": [ + ("channel", "ca://${MOTOR}.DESC"), + ], + "PyDMLabel_rbv": [ + ("channel", "ca://${MOTOR}.RBV"), + ], + "PyDMLineEdit_setpoint": [ + ("channel", "ca://${MOTOR}.VAL"), + ], + "PyDMLineEdit_twVal": [ + ("channel", "ca://${MOTOR}.TWV"), + ], + "PyDMPushButton_stop": [ + ("channel", "ca://${MOTOR}.STOP"), + ], + "PyDMPushButton_twkL": [ + ("channel", "ca://${MOTOR}.TWR"), + ], + "PyDMPushButton_twkR": [ + ("channel", "ca://${MOTOR}.TWF"), + ], + "PyDMShellCommand_expert": [ + ( + "commands", + [ + "motor-expert-screen ${MOTOR}", + ], + ), + ], + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._macro_values = { + "MOTOR": "", + } + + def get_motor(self) -> str: + return self._get_macro("MOTOR") + + def set_motor(self, value: str) -> None: + self._set_macro("MOTOR", value) + + motor = pyqtProperty(str, get_motor, set_motor) diff --git a/pcdswidgets/generated/motion/common/motor_classic_full_form.py b/pcdswidgets/generated/motion/common/motor_classic_full_form.py new file mode 100644 index 0000000..d1a672b --- /dev/null +++ b/pcdswidgets/generated/motion/common/motor_classic_full_form.py @@ -0,0 +1,304 @@ +# -*- coding: utf-8 -*- +# Form implementation generated from reading ui file 'pcdswidgets/ui/motion/common/motor_classic_full.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. +# +# Augmented by pcdswidgets.builder.build +# ruff: noqa: E501 +from pydm.widgets.byte import PyDMByteIndicator +from pydm.widgets.label import PyDMLabel +from pydm.widgets.line_edit import PyDMLineEdit +from pydm.widgets.pushbutton import PyDMPushButton +from pydm.widgets.shell_command import PyDMShellCommand +from qtpy import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 125) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + Form.setMinimumSize(QtCore.QSize(400, 125)) + Form.setMaximumSize(QtCore.QSize(400, 125)) + self.horizontalLayout = QtWidgets.QHBoxLayout(Form) + self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) + self.horizontalLayout.setContentsMargins(1, 1, 0, 1) + self.horizontalLayout.setSpacing(1) + self.horizontalLayout.setObjectName("horizontalLayout") + self.gridFrame = QtWidgets.QFrame(Form) + self.gridFrame.setMinimumSize(QtCore.QSize(0, 0)) + self.gridFrame.setFrameShape(QtWidgets.QFrame.Box) + self.gridFrame.setFrameShadow(QtWidgets.QFrame.Sunken) + self.gridFrame.setObjectName("gridFrame") + self.gridLayout_2 = QtWidgets.QGridLayout(self.gridFrame) + self.gridLayout_2.setContentsMargins(1, -1, -1, -1) + self.gridLayout_2.setSpacing(1) + self.gridLayout_2.setObjectName("gridLayout_2") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setContentsMargins(0, 0, -1, -1) + self.horizontalLayout_2.setSpacing(0) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.PyDMLabel_name = PyDMLabel(self.gridFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLabel_name.sizePolicy().hasHeightForWidth()) + self.PyDMLabel_name.setSizePolicy(sizePolicy) + self.PyDMLabel_name.setMinimumSize(QtCore.QSize(75, 0)) + self.PyDMLabel_name.setMaximumSize(QtCore.QSize(300, 16777215)) + font = QtGui.QFont() + font.setPointSize(12) + font.setBold(True) + font.setWeight(75) + self.PyDMLabel_name.setFont(font) + self.PyDMLabel_name.setToolTip("") + self.PyDMLabel_name.setAlarmSensitiveBorder(False) + self.PyDMLabel_name.setDisplayFormat(PyDMLabel.String) + self.PyDMLabel_name.setObjectName("PyDMLabel_name") + self.horizontalLayout_2.addWidget(self.PyDMLabel_name) + self.gridLayout_2.addLayout(self.horizontalLayout_2, 0, 0, 1, 1) + self.horizontalLayout_6 = QtWidgets.QHBoxLayout() + self.horizontalLayout_6.setContentsMargins(0, -1, -1, -1) + self.horizontalLayout_6.setSpacing(0) + self.horizontalLayout_6.setObjectName("horizontalLayout_6") + self.PyDMByteIndicator_mvn = PyDMByteIndicator(self.gridFrame) + self.PyDMByteIndicator_mvn.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMByteIndicator_mvn.sizePolicy().hasHeightForWidth()) + self.PyDMByteIndicator_mvn.setSizePolicy(sizePolicy) + self.PyDMByteIndicator_mvn.setMinimumSize(QtCore.QSize(25, 0)) + self.PyDMByteIndicator_mvn.setMaximumSize(QtCore.QSize(25, 16777215)) + self.PyDMByteIndicator_mvn.setToolTip("") + self.PyDMByteIndicator_mvn.setAlarmSensitiveBorder(False) + self.PyDMByteIndicator_mvn.setShowLabels(False) + self.PyDMByteIndicator_mvn.setCircles(True) + self.PyDMByteIndicator_mvn.setObjectName("PyDMByteIndicator_mvn") + self.horizontalLayout_6.addWidget(self.PyDMByteIndicator_mvn) + self.gridLayout_2.addLayout(self.horizontalLayout_6, 0, 2, 1, 1) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setContentsMargins(7, 0, 7, -1) + self.horizontalLayout_4.setSpacing(7) + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.PyDMByteIndicator_lls = PyDMByteIndicator(self.gridFrame) + self.PyDMByteIndicator_lls.setMaximumSize(QtCore.QSize(15, 15)) + self.PyDMByteIndicator_lls.setToolTip("") + self.PyDMByteIndicator_lls.setAlarmSensitiveContent(False) + self.PyDMByteIndicator_lls.setAlarmSensitiveBorder(True) + self.PyDMByteIndicator_lls.setPyDMToolTip("") + self.PyDMByteIndicator_lls.setBlinkOnChange(False) + self.PyDMByteIndicator_lls.setToggleMode(False) + self.PyDMByteIndicator_lls.setBlinkInterval(99) + self.PyDMByteIndicator_lls.setShowLabels(False) + self.PyDMByteIndicator_lls.setBigEndian(False) + self.PyDMByteIndicator_lls.setCircles(False) + self.PyDMByteIndicator_lls.setNumBits(1) + self.PyDMByteIndicator_lls.setShift(0) + self.PyDMByteIndicator_lls.setLabels(["Bit 0"]) + self.PyDMByteIndicator_lls.setObjectName("PyDMByteIndicator_lls") + self.horizontalLayout_4.addWidget(self.PyDMByteIndicator_lls) + self.PyDMLabel_rbv = PyDMLabel(self.gridFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLabel_rbv.sizePolicy().hasHeightForWidth()) + self.PyDMLabel_rbv.setSizePolicy(sizePolicy) + self.PyDMLabel_rbv.setMinimumSize(QtCore.QSize(150, 0)) + self.PyDMLabel_rbv.setMaximumSize(QtCore.QSize(300, 16777215)) + font = QtGui.QFont() + font.setPointSize(14) + font.setBold(False) + font.setWeight(50) + self.PyDMLabel_rbv.setFont(font) + self.PyDMLabel_rbv.setToolTip("") + self.PyDMLabel_rbv.setAlignment(QtCore.Qt.AlignCenter) + self.PyDMLabel_rbv.setDisplayFormat(PyDMLabel.Decimal) + self.PyDMLabel_rbv.setObjectName("PyDMLabel_rbv") + self.horizontalLayout_4.addWidget(self.PyDMLabel_rbv) + self.PyDMByteIndicator_hls = PyDMByteIndicator(self.gridFrame) + self.PyDMByteIndicator_hls.setMaximumSize(QtCore.QSize(15, 15)) + self.PyDMByteIndicator_hls.setToolTip("") + self.PyDMByteIndicator_hls.setAlarmSensitiveContent(False) + self.PyDMByteIndicator_hls.setAlarmSensitiveBorder(True) + self.PyDMByteIndicator_hls.setPyDMToolTip("") + self.PyDMByteIndicator_hls.setBlinkOnChange(False) + self.PyDMByteIndicator_hls.setToggleMode(False) + self.PyDMByteIndicator_hls.setBlinkInterval(99) + self.PyDMByteIndicator_hls.setShowLabels(False) + self.PyDMByteIndicator_hls.setBigEndian(False) + self.PyDMByteIndicator_hls.setCircles(False) + self.PyDMByteIndicator_hls.setNumBits(1) + self.PyDMByteIndicator_hls.setShift(0) + self.PyDMByteIndicator_hls.setLabels(["Bit 0"]) + self.PyDMByteIndicator_hls.setObjectName("PyDMByteIndicator_hls") + self.horizontalLayout_4.addWidget(self.PyDMByteIndicator_hls) + self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 1, 1, 1) + self.horizontalLayout_7 = QtWidgets.QHBoxLayout() + self.horizontalLayout_7.setObjectName("horizontalLayout_7") + self.PyDMPushButton_stop = PyDMPushButton(self.gridFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMPushButton_stop.sizePolicy().hasHeightForWidth()) + self.PyDMPushButton_stop.setSizePolicy(sizePolicy) + self.PyDMPushButton_stop.setMinimumSize(QtCore.QSize(50, 0)) + self.PyDMPushButton_stop.setMaximumSize(QtCore.QSize(60, 16777215)) + self.PyDMPushButton_stop.setToolTip("") + self.PyDMPushButton_stop.setStyleSheet("background-color: rgb(170, 0, 0);") + self.PyDMPushButton_stop.setObjectName("PyDMPushButton_stop") + self.horizontalLayout_7.addWidget(self.PyDMPushButton_stop) + self.gridLayout_2.addLayout(self.horizontalLayout_7, 1, 2, 1, 1) + self.horizontalLayout_5 = QtWidgets.QHBoxLayout() + self.horizontalLayout_5.setContentsMargins(30, -1, 30, -1) + self.horizontalLayout_5.setObjectName("horizontalLayout_5") + self.PyDMLineEdit_setpoint = PyDMLineEdit(self.gridFrame) + self.PyDMLineEdit_setpoint.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLineEdit_setpoint.sizePolicy().hasHeightForWidth()) + self.PyDMLineEdit_setpoint.setSizePolicy(sizePolicy) + self.PyDMLineEdit_setpoint.setMinimumSize(QtCore.QSize(150, 0)) + self.PyDMLineEdit_setpoint.setMaximumSize(QtCore.QSize(300, 16777215)) + font = QtGui.QFont() + font.setPointSize(12) + self.PyDMLineEdit_setpoint.setFont(font) + self.PyDMLineEdit_setpoint.setToolTip("") + self.PyDMLineEdit_setpoint.setMaxLength(12) + self.PyDMLineEdit_setpoint.setAlignment(QtCore.Qt.AlignCenter) + self.PyDMLineEdit_setpoint.setPrecision(0) + self.PyDMLineEdit_setpoint.setPrecisionFromPV(True) + self.PyDMLineEdit_setpoint.setAlarmSensitiveBorder(False) + self.PyDMLineEdit_setpoint.setDisplayFormat(PyDMLineEdit.Decimal) + self.PyDMLineEdit_setpoint.setObjectName("PyDMLineEdit_setpoint") + self.horizontalLayout_5.addWidget(self.PyDMLineEdit_setpoint) + self.gridLayout_2.addLayout(self.horizontalLayout_5, 1, 1, 1, 1) + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setContentsMargins(0, 0, -1, -1) + self.horizontalLayout_3.setSpacing(7) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.PyDMLabel_egu = PyDMLabel(self.gridFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLabel_egu.sizePolicy().hasHeightForWidth()) + self.PyDMLabel_egu.setSizePolicy(sizePolicy) + self.PyDMLabel_egu.setMinimumSize(QtCore.QSize(60, 0)) + self.PyDMLabel_egu.setMaximumSize(QtCore.QSize(150, 16777215)) + font = QtGui.QFont() + font.setPointSize(10) + self.PyDMLabel_egu.setFont(font) + self.PyDMLabel_egu.setToolTip("") + self.PyDMLabel_egu.setPrecision(0) + self.PyDMLabel_egu.setShowUnits(False) + self.PyDMLabel_egu.setPrecisionFromPV(True) + self.PyDMLabel_egu.setAlarmSensitiveContent(False) + self.PyDMLabel_egu.setAlarmSensitiveBorder(False) + self.PyDMLabel_egu.setPyDMToolTip("") + self.PyDMLabel_egu.setObjectName("PyDMLabel_egu") + self.horizontalLayout_3.addWidget(self.PyDMLabel_egu) + self.gridLayout_2.addLayout(self.horizontalLayout_3, 1, 0, 1, 1) + self.horizontalLayout_8 = QtWidgets.QHBoxLayout() + self.horizontalLayout_8.setObjectName("horizontalLayout_8") + self.gridLayout_2.addLayout(self.horizontalLayout_8, 2, 0, 1, 1) + self.horizontalLayout_9 = QtWidgets.QHBoxLayout() + self.horizontalLayout_9.setContentsMargins(5, -1, 5, -1) + self.horizontalLayout_9.setSpacing(3) + self.horizontalLayout_9.setObjectName("horizontalLayout_9") + self.PyDMPushButton_twkL = PyDMPushButton(self.gridFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMPushButton_twkL.sizePolicy().hasHeightForWidth()) + self.PyDMPushButton_twkL.setSizePolicy(sizePolicy) + self.PyDMPushButton_twkL.setMinimumSize(QtCore.QSize(35, 0)) + self.PyDMPushButton_twkL.setMaximumSize(QtCore.QSize(35, 16777215)) + self.PyDMPushButton_twkL.setToolTip("") + self.PyDMPushButton_twkL.setRelativeChange(True) + self.PyDMPushButton_twkL.setObjectName("PyDMPushButton_twkL") + self.horizontalLayout_9.addWidget(self.PyDMPushButton_twkL) + self.PyDMLineEdit_twVal = PyDMLineEdit(self.gridFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLineEdit_twVal.sizePolicy().hasHeightForWidth()) + self.PyDMLineEdit_twVal.setSizePolicy(sizePolicy) + self.PyDMLineEdit_twVal.setMinimumSize(QtCore.QSize(70, 0)) + self.PyDMLineEdit_twVal.setMaximumSize(QtCore.QSize(200, 16777215)) + font = QtGui.QFont() + font.setPointSize(12) + self.PyDMLineEdit_twVal.setFont(font) + self.PyDMLineEdit_twVal.setToolTip("") + self.PyDMLineEdit_twVal.setAlignment(QtCore.Qt.AlignCenter) + self.PyDMLineEdit_twVal.setPrecision(0) + self.PyDMLineEdit_twVal.setPrecisionFromPV(True) + self.PyDMLineEdit_twVal.setAlarmSensitiveBorder(False) + self.PyDMLineEdit_twVal.setObjectName("PyDMLineEdit_twVal") + self.horizontalLayout_9.addWidget(self.PyDMLineEdit_twVal) + self.PyDMPushButton_twkR = PyDMPushButton(self.gridFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMPushButton_twkR.sizePolicy().hasHeightForWidth()) + self.PyDMPushButton_twkR.setSizePolicy(sizePolicy) + self.PyDMPushButton_twkR.setMinimumSize(QtCore.QSize(35, 0)) + self.PyDMPushButton_twkR.setMaximumSize(QtCore.QSize(35, 16777215)) + self.PyDMPushButton_twkR.setToolTip("") + self.PyDMPushButton_twkR.setRelativeChange(True) + self.PyDMPushButton_twkR.setObjectName("PyDMPushButton_twkR") + self.horizontalLayout_9.addWidget(self.PyDMPushButton_twkR) + self.gridLayout_2.addLayout(self.horizontalLayout_9, 2, 1, 1, 1) + self.horizontalLayout_10 = QtWidgets.QHBoxLayout() + self.horizontalLayout_10.setObjectName("horizontalLayout_10") + self.PyDMShellCommand_expert = PyDMShellCommand(self.gridFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMShellCommand_expert.sizePolicy().hasHeightForWidth()) + self.PyDMShellCommand_expert.setSizePolicy(sizePolicy) + self.PyDMShellCommand_expert.setMinimumSize(QtCore.QSize(30, 0)) + self.PyDMShellCommand_expert.setMaximumSize(QtCore.QSize(60, 16777215)) + self.PyDMShellCommand_expert.setToolTip("") + self.PyDMShellCommand_expert.setCommands(["motor-expert-screen ${MOTOR}"]) + self.PyDMShellCommand_expert.setObjectName("PyDMShellCommand_expert") + self.horizontalLayout_10.addWidget(self.PyDMShellCommand_expert) + self.gridLayout_2.addLayout(self.horizontalLayout_10, 2, 2, 1, 1) + self.gridLayout_2.setColumnStretch(0, 4) + self.gridLayout_2.setColumnStretch(1, 6) + self.gridLayout_2.setColumnStretch(2, 1) + self.gridLayout_2.setRowStretch(0, 3) + self.gridLayout_2.setRowStretch(1, 2) + self.gridLayout_2.setRowStretch(2, 2) + self.horizontalLayout.addWidget(self.gridFrame) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.PyDMLabel_name.setChannel(_translate("Form", "ca://${MOTOR}.DESC")) + self.PyDMByteIndicator_mvn.setChannel(_translate("Form", "ca://${MOTOR}.MOVN")) + self.PyDMByteIndicator_lls.setChannel(_translate("Form", "ca://${MOTOR}.LLS")) + self.PyDMLabel_rbv.setChannel(_translate("Form", "ca://${MOTOR}.RBV")) + self.PyDMByteIndicator_hls.setChannel(_translate("Form", "ca://${MOTOR}.HLS")) + self.PyDMPushButton_stop.setText(_translate("Form", "Stop")) + self.PyDMPushButton_stop.setChannel(_translate("Form", "ca://${MOTOR}.STOP")) + self.PyDMPushButton_stop.setPressValue(_translate("Form", "1")) + self.PyDMLineEdit_setpoint.setChannel(_translate("Form", "ca://${MOTOR}.VAL")) + self.PyDMLabel_egu.setChannel(_translate("Form", "ca://${MOTOR}.EGU")) + self.PyDMPushButton_twkL.setText(_translate("Form", "<<")) + self.PyDMPushButton_twkL.setChannel(_translate("Form", "ca://${MOTOR}.TWR")) + self.PyDMPushButton_twkL.setPressValue(_translate("Form", "1")) + self.PyDMLineEdit_twVal.setChannel(_translate("Form", "ca://${MOTOR}.TWV")) + self.PyDMPushButton_twkR.setText(_translate("Form", ">>")) + self.PyDMPushButton_twkR.setChannel(_translate("Form", "ca://${MOTOR}.TWF")) + self.PyDMPushButton_twkR.setPressValue(_translate("Form", "1")) diff --git a/pcdswidgets/generated/motion/common/motor_classic_row_base.py b/pcdswidgets/generated/motion/common/motor_classic_row_base.py new file mode 100644 index 0000000..dec1115 --- /dev/null +++ b/pcdswidgets/generated/motion/common/motor_classic_row_base.py @@ -0,0 +1,155 @@ +""" +Generated by jinja from ui_base_widget.j2 with: +ui_name = motor_classic_row.ui +form_cls = Ui_Form +base_cls = MotorClassicRowBase +macro_names = ['MOTOR'] + +Other long required variables: +widget_names: list[str] +widget_name_to_class: dict[str, str] +macro_to_widget: dict[str, str] +widget_to_macro: dict[str, str] +widget_to_pre_templ_strs: dict[str, list[tuple[str, str]]] +widget_to_pre_templ_lists: dict[str, list[tuple[str, list[str]]]] +""" +# ruff: noqa: E501 +# ruff: noqa: F403 +# ruff: noqa: F405 + +from pcdswidgets.builder.designer_widget import DesignerWidget + +from .motor_classic_row_form import * + +try: + from qtpy.QtCore import pyqtProperty +except ImportError: + from qtpy.QtCore import Property as pyqtProperty # type: ignore + + +class MotorClassicRowBase(DesignerWidget): + PyDMByteIndicator: "PyDMByteIndicator" + PyDMByteIndicator_2: "PyDMByteIndicator" + PyDMByteIndicator_mvn: "PyDMByteIndicator" + PyDMLabel: "PyDMLabel" + PyDMLabel_name: "PyDMLabel" + PyDMLabel_rbv: "PyDMLabel" + PyDMLineEdit: "PyDMLineEdit" + PyDMLineEdit_setpoint: "PyDMLineEdit" + PyDMPushButton_stop: "PyDMPushButton" + PyDMPushButton_twkL: "PyDMPushButton" + PyDMPushButton_twkR: "PyDMPushButton" + PyDMShellCommand: "PyDMShellCommand" + + ui_form = Ui_Form + _macro_to_widget = { + "MOTOR": [ + "PyDMPushButton_twkR", + "PyDMByteIndicator_2", + "PyDMLabel_name", + "PyDMLabel", + "PyDMShellCommand", + "PyDMLineEdit", + "PyDMByteIndicator", + "PyDMLabel_rbv", + "PyDMByteIndicator_mvn", + "PyDMPushButton_twkL", + "PyDMPushButton_stop", + "PyDMLineEdit_setpoint", + ], + } + _widget_to_macro = { + "PyDMByteIndicator": [ + "MOTOR", + ], + "PyDMByteIndicator_2": [ + "MOTOR", + ], + "PyDMByteIndicator_mvn": [ + "MOTOR", + ], + "PyDMLabel": [ + "MOTOR", + ], + "PyDMLabel_name": [ + "MOTOR", + ], + "PyDMLabel_rbv": [ + "MOTOR", + ], + "PyDMLineEdit": [ + "MOTOR", + ], + "PyDMLineEdit_setpoint": [ + "MOTOR", + ], + "PyDMPushButton_stop": [ + "MOTOR", + ], + "PyDMPushButton_twkL": [ + "MOTOR", + ], + "PyDMPushButton_twkR": [ + "MOTOR", + ], + "PyDMShellCommand": [ + "MOTOR", + ], + } + _widget_to_pre_template = { + "PyDMByteIndicator": [ + ("channel", "ca://${MOTOR}.LLS"), + ], + "PyDMByteIndicator_2": [ + ("channel", "ca://${MOTOR}.HLS"), + ], + "PyDMByteIndicator_mvn": [ + ("channel", "ca://${MOTOR}.MOVN"), + ], + "PyDMLabel": [ + ("channel", "ca://${MOTOR}.EGU"), + ], + "PyDMLabel_name": [ + ("channel", "ca://${MOTOR}.DESC"), + ], + "PyDMLabel_rbv": [ + ("channel", "ca://${MOTOR}.RBV"), + ], + "PyDMLineEdit": [ + ("channel", "ca://${MOTOR}.TWV"), + ], + "PyDMLineEdit_setpoint": [ + ("channel", "ca://${MOTOR}.VAL"), + ], + "PyDMPushButton_stop": [ + ("channel", "ca://${MOTOR}.STOP"), + ], + "PyDMPushButton_twkL": [ + ("channel", "ca://${MOTOR}.TWR"), + ], + "PyDMPushButton_twkR": [ + ("channel", "ca://${MOTOR}.TWF"), + ], + "PyDMShellCommand": [ + ( + "commands", + [ + "motor-expert-screen ${MOTOR}", + ], + ), + ], + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._macro_values = { + "MOTOR": "", + } + + def get_motor(self) -> str: + return self._get_macro("MOTOR") + + def set_motor(self, value: str) -> None: + self._set_macro("MOTOR", value) + + motor = pyqtProperty(str, get_motor, set_motor) diff --git a/pcdswidgets/generated/motion/common/motor_classic_row_form.py b/pcdswidgets/generated/motion/common/motor_classic_row_form.py new file mode 100644 index 0000000..ee7128e --- /dev/null +++ b/pcdswidgets/generated/motion/common/motor_classic_row_form.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +# Form implementation generated from reading ui file 'pcdswidgets/ui/motion/common/motor_classic_row.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. +# +# Augmented by pcdswidgets.builder.build +# ruff: noqa: E501 +from pydm.widgets.byte import PyDMByteIndicator +from pydm.widgets.label import PyDMLabel +from pydm.widgets.line_edit import PyDMLineEdit +from pydm.widgets.pushbutton import PyDMPushButton +from pydm.widgets.shell_command import PyDMShellCommand +from qtpy import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(753, 45) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + Form.setMinimumSize(QtCore.QSize(700, 45)) + Form.setMaximumSize(QtCore.QSize(800, 50)) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.main_frame = QtWidgets.QFrame(Form) + font = QtGui.QFont() + font.setPointSize(11) + self.main_frame.setFont(font) + self.main_frame.setFrameShape(QtWidgets.QFrame.Box) + self.main_frame.setFrameShadow(QtWidgets.QFrame.Sunken) + self.main_frame.setObjectName("main_frame") + self.gridLayout = QtWidgets.QGridLayout(self.main_frame) + self.gridLayout.setContentsMargins(1, 5, 5, 5) + self.gridLayout.setSpacing(5) + self.gridLayout.setObjectName("gridLayout") + self.PyDMPushButton_twkR = PyDMPushButton(self.main_frame) + self.PyDMPushButton_twkR.setMinimumSize(QtCore.QSize(35, 0)) + self.PyDMPushButton_twkR.setToolTip("") + self.PyDMPushButton_twkR.setAlarmSensitiveContent(False) + self.PyDMPushButton_twkR.setAlarmSensitiveBorder(False) + self.PyDMPushButton_twkR.setPyDMToolTip("") + self.PyDMPushButton_twkR.setPasswordProtected(False) + self.PyDMPushButton_twkR.setPassword("") + self.PyDMPushButton_twkR.setProtectedPassword("") + self.PyDMPushButton_twkR.setShowConfirmDialog(False) + self.PyDMPushButton_twkR.setRelativeChange(False) + self.PyDMPushButton_twkR.setWriteWhenRelease(False) + self.PyDMPushButton_twkR.setObjectName("PyDMPushButton_twkR") + self.gridLayout.addWidget(self.PyDMPushButton_twkR, 0, 10, 1, 1) + self.PyDMByteIndicator_2 = PyDMByteIndicator(self.main_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMByteIndicator_2.sizePolicy().hasHeightForWidth()) + self.PyDMByteIndicator_2.setSizePolicy(sizePolicy) + self.PyDMByteIndicator_2.setMaximumSize(QtCore.QSize(15, 15)) + self.PyDMByteIndicator_2.setToolTip("") + self.PyDMByteIndicator_2.setAlarmSensitiveContent(False) + self.PyDMByteIndicator_2.setAlarmSensitiveBorder(True) + self.PyDMByteIndicator_2.setPyDMToolTip("") + self.PyDMByteIndicator_2.setOnColor(QtGui.QColor(255, 165, 0)) + self.PyDMByteIndicator_2.setShowLabels(False) + self.PyDMByteIndicator_2.setBigEndian(False) + self.PyDMByteIndicator_2.setCircles(False) + self.PyDMByteIndicator_2.setNumBits(1) + self.PyDMByteIndicator_2.setLabels(["Bit 0"]) + self.PyDMByteIndicator_2.setObjectName("PyDMByteIndicator_2") + self.gridLayout.addWidget(self.PyDMByteIndicator_2, 0, 5, 1, 1) + self.PyDMLabel_name = PyDMLabel(self.main_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLabel_name.sizePolicy().hasHeightForWidth()) + self.PyDMLabel_name.setSizePolicy(sizePolicy) + self.PyDMLabel_name.setMinimumSize(QtCore.QSize(125, 0)) + self.PyDMLabel_name.setMaximumSize(QtCore.QSize(125, 16777215)) + font = QtGui.QFont() + font.setPointSize(10) + font.setBold(True) + font.setWeight(75) + self.PyDMLabel_name.setFont(font) + self.PyDMLabel_name.setToolTip("") + self.PyDMLabel_name.setAlarmSensitiveBorder(False) + self.PyDMLabel_name.setDisplayFormat(PyDMLabel.String) + self.PyDMLabel_name.setObjectName("PyDMLabel_name") + self.gridLayout.addWidget(self.PyDMLabel_name, 0, 0, 1, 1) + self.PyDMLabel = PyDMLabel(self.main_frame) + self.PyDMLabel.setMinimumSize(QtCore.QSize(75, 0)) + self.PyDMLabel.setMaximumSize(QtCore.QSize(75, 16777215)) + font = QtGui.QFont() + font.setPointSize(10) + font.setBold(False) + font.setWeight(50) + self.PyDMLabel.setFont(font) + self.PyDMLabel.setToolTip("") + self.PyDMLabel.setPrecision(0) + self.PyDMLabel.setShowUnits(False) + self.PyDMLabel.setPrecisionFromPV(True) + self.PyDMLabel.setAlarmSensitiveContent(False) + self.PyDMLabel.setAlarmSensitiveBorder(False) + self.PyDMLabel.setObjectName("PyDMLabel") + self.gridLayout.addWidget(self.PyDMLabel, 0, 1, 1, 1) + self.PyDMShellCommand = PyDMShellCommand(self.main_frame) + self.PyDMShellCommand.setToolTip("") + self.PyDMShellCommand.setAlarmSensitiveBorder(False) + self.PyDMShellCommand.setCommands(["motor-expert-screen ${MOTOR}"]) + self.PyDMShellCommand.setObjectName("PyDMShellCommand") + self.gridLayout.addWidget(self.PyDMShellCommand, 0, 13, 1, 1) + self.PyDMLineEdit = PyDMLineEdit(self.main_frame) + self.PyDMLineEdit.setMinimumSize(QtCore.QSize(60, 0)) + self.PyDMLineEdit.setMaximumSize(QtCore.QSize(150, 16777215)) + font = QtGui.QFont() + font.setPointSize(11) + self.PyDMLineEdit.setFont(font) + self.PyDMLineEdit.setToolTip("") + self.PyDMLineEdit.setPrecision(0) + self.PyDMLineEdit.setShowUnits(False) + self.PyDMLineEdit.setPrecisionFromPV(True) + self.PyDMLineEdit.setAlarmSensitiveContent(False) + self.PyDMLineEdit.setAlarmSensitiveBorder(False) + self.PyDMLineEdit.setObjectName("PyDMLineEdit") + self.gridLayout.addWidget(self.PyDMLineEdit, 0, 9, 1, 1) + self.PyDMByteIndicator = PyDMByteIndicator(self.main_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMByteIndicator.sizePolicy().hasHeightForWidth()) + self.PyDMByteIndicator.setSizePolicy(sizePolicy) + self.PyDMByteIndicator.setMaximumSize(QtCore.QSize(15, 15)) + self.PyDMByteIndicator.setToolTip("") + self.PyDMByteIndicator.setAlarmSensitiveContent(False) + self.PyDMByteIndicator.setAlarmSensitiveBorder(True) + self.PyDMByteIndicator.setPyDMToolTip("") + self.PyDMByteIndicator.setOnColor(QtGui.QColor(255, 165, 0)) + self.PyDMByteIndicator.setShowLabels(False) + self.PyDMByteIndicator.setBigEndian(False) + self.PyDMByteIndicator.setCircles(False) + self.PyDMByteIndicator.setNumBits(1) + self.PyDMByteIndicator.setShift(0) + self.PyDMByteIndicator.setLabels(["Bit 0"]) + self.PyDMByteIndicator.setObjectName("PyDMByteIndicator") + self.gridLayout.addWidget(self.PyDMByteIndicator, 0, 3, 1, 1) + self.PyDMLabel_rbv = PyDMLabel(self.main_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLabel_rbv.sizePolicy().hasHeightForWidth()) + self.PyDMLabel_rbv.setSizePolicy(sizePolicy) + self.PyDMLabel_rbv.setMinimumSize(QtCore.QSize(125, 0)) + font = QtGui.QFont() + font.setPointSize(14) + font.setBold(False) + font.setWeight(50) + self.PyDMLabel_rbv.setFont(font) + self.PyDMLabel_rbv.setToolTip("") + self.PyDMLabel_rbv.setAlignment(QtCore.Qt.AlignCenter) + self.PyDMLabel_rbv.setPrecision(0) + self.PyDMLabel_rbv.setShowUnits(False) + self.PyDMLabel_rbv.setPrecisionFromPV(True) + self.PyDMLabel_rbv.setAlarmSensitiveContent(False) + self.PyDMLabel_rbv.setAlarmSensitiveBorder(True) + self.PyDMLabel_rbv.setDisplayFormat(PyDMLabel.Decimal) + self.PyDMLabel_rbv.setObjectName("PyDMLabel_rbv") + self.gridLayout.addWidget(self.PyDMLabel_rbv, 0, 4, 1, 1) + self.PyDMByteIndicator_mvn = PyDMByteIndicator(self.main_frame) + self.PyDMByteIndicator_mvn.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMByteIndicator_mvn.sizePolicy().hasHeightForWidth()) + self.PyDMByteIndicator_mvn.setSizePolicy(sizePolicy) + self.PyDMByteIndicator_mvn.setMinimumSize(QtCore.QSize(15, 15)) + self.PyDMByteIndicator_mvn.setMaximumSize(QtCore.QSize(15, 15)) + self.PyDMByteIndicator_mvn.setToolTip("") + self.PyDMByteIndicator_mvn.setAlarmSensitiveContent(False) + self.PyDMByteIndicator_mvn.setAlarmSensitiveBorder(True) + self.PyDMByteIndicator_mvn.setShowLabels(False) + self.PyDMByteIndicator_mvn.setBigEndian(False) + self.PyDMByteIndicator_mvn.setCircles(True) + self.PyDMByteIndicator_mvn.setNumBits(1) + self.PyDMByteIndicator_mvn.setShift(0) + self.PyDMByteIndicator_mvn.setLabels(["Bit 0"]) + self.PyDMByteIndicator_mvn.setObjectName("PyDMByteIndicator_mvn") + self.gridLayout.addWidget(self.PyDMByteIndicator_mvn, 0, 7, 1, 1) + self.PyDMPushButton_twkL = PyDMPushButton(self.main_frame) + self.PyDMPushButton_twkL.setMinimumSize(QtCore.QSize(35, 0)) + self.PyDMPushButton_twkL.setToolTip("") + self.PyDMPushButton_twkL.setAlarmSensitiveContent(False) + self.PyDMPushButton_twkL.setAlarmSensitiveBorder(False) + self.PyDMPushButton_twkL.setPyDMToolTip("") + self.PyDMPushButton_twkL.setPasswordProtected(False) + self.PyDMPushButton_twkL.setPassword("") + self.PyDMPushButton_twkL.setProtectedPassword("") + self.PyDMPushButton_twkL.setShowConfirmDialog(False) + self.PyDMPushButton_twkL.setRelativeChange(False) + self.PyDMPushButton_twkL.setWriteWhenRelease(False) + self.PyDMPushButton_twkL.setObjectName("PyDMPushButton_twkL") + self.gridLayout.addWidget(self.PyDMPushButton_twkL, 0, 8, 1, 1) + self.PyDMPushButton_stop = PyDMPushButton(self.main_frame) + self.PyDMPushButton_stop.setMinimumSize(QtCore.QSize(40, 0)) + self.PyDMPushButton_stop.setMaximumSize(QtCore.QSize(40, 16777215)) + self.PyDMPushButton_stop.setToolTip("") + self.PyDMPushButton_stop.setStyleSheet("background-color: rgb(170, 0, 0);") + self.PyDMPushButton_stop.setObjectName("PyDMPushButton_stop") + self.gridLayout.addWidget(self.PyDMPushButton_stop, 0, 12, 1, 1) + self.PyDMLineEdit_setpoint = PyDMLineEdit(self.main_frame) + self.PyDMLineEdit_setpoint.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLineEdit_setpoint.sizePolicy().hasHeightForWidth()) + self.PyDMLineEdit_setpoint.setSizePolicy(sizePolicy) + self.PyDMLineEdit_setpoint.setMinimumSize(QtCore.QSize(75, 0)) + font = QtGui.QFont() + font.setPointSize(11) + self.PyDMLineEdit_setpoint.setFont(font) + self.PyDMLineEdit_setpoint.setToolTip("") + self.PyDMLineEdit_setpoint.setPrecision(0) + self.PyDMLineEdit_setpoint.setShowUnits(False) + self.PyDMLineEdit_setpoint.setPrecisionFromPV(True) + self.PyDMLineEdit_setpoint.setAlarmSensitiveContent(False) + self.PyDMLineEdit_setpoint.setAlarmSensitiveBorder(False) + self.PyDMLineEdit_setpoint.setDisplayFormat(PyDMLineEdit.Default) + self.PyDMLineEdit_setpoint.setObjectName("PyDMLineEdit_setpoint") + self.gridLayout.addWidget(self.PyDMLineEdit_setpoint, 0, 6, 1, 1) + self.gridLayout.setColumnStretch(0, 4) + self.gridLayout.setColumnStretch(1, 1) + self.gridLayout.setColumnStretch(3, 1) + self.gridLayout.setColumnStretch(4, 5) + self.gridLayout.setColumnStretch(6, 3) + self.gridLayout.setColumnStretch(8, 1) + self.gridLayout.setColumnStretch(9, 2) + self.gridLayout.setColumnStretch(10, 1) + self.gridLayout.setColumnStretch(12, 1) + self.gridLayout.setColumnStretch(13, 1) + self.verticalLayout.addWidget(self.main_frame) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.PyDMPushButton_twkR.setText(_translate("Form", ">>")) + self.PyDMPushButton_twkR.setChannel(_translate("Form", "ca://${MOTOR}.TWF")) + self.PyDMPushButton_twkR.setConfirmMessage(_translate("Form", "Are you sure you want to proceed?")) + self.PyDMPushButton_twkR.setPressValue(_translate("Form", "1")) + self.PyDMPushButton_twkR.setReleaseValue(_translate("Form", "None")) + self.PyDMByteIndicator_2.setChannel(_translate("Form", "ca://${MOTOR}.HLS")) + self.PyDMLabel_name.setChannel(_translate("Form", "ca://${MOTOR}.DESC")) + self.PyDMLabel.setChannel(_translate("Form", "ca://${MOTOR}.EGU")) + self.PyDMLineEdit.setChannel(_translate("Form", "ca://${MOTOR}.TWV")) + self.PyDMByteIndicator.setChannel(_translate("Form", "ca://${MOTOR}.LLS")) + self.PyDMLabel_rbv.setChannel(_translate("Form", "ca://${MOTOR}.RBV")) + self.PyDMByteIndicator_mvn.setChannel(_translate("Form", "ca://${MOTOR}.MOVN")) + self.PyDMPushButton_twkL.setText(_translate("Form", "<<")) + self.PyDMPushButton_twkL.setChannel(_translate("Form", "ca://${MOTOR}.TWR")) + self.PyDMPushButton_twkL.setConfirmMessage(_translate("Form", "Are you sure you want to proceed?")) + self.PyDMPushButton_twkL.setPressValue(_translate("Form", "1")) + self.PyDMPushButton_twkL.setReleaseValue(_translate("Form", "None")) + self.PyDMPushButton_stop.setText(_translate("Form", "Stop")) + self.PyDMPushButton_stop.setChannel(_translate("Form", "ca://${MOTOR}.STOP")) + self.PyDMPushButton_stop.setPressValue(_translate("Form", "1")) + self.PyDMLineEdit_setpoint.setChannel(_translate("Form", "ca://${MOTOR}.VAL")) diff --git a/pcdswidgets/generated/motion/common/motor_tc_classic_row_base.py b/pcdswidgets/generated/motion/common/motor_tc_classic_row_base.py new file mode 100644 index 0000000..30193a8 --- /dev/null +++ b/pcdswidgets/generated/motion/common/motor_tc_classic_row_base.py @@ -0,0 +1,78 @@ +""" +Generated by jinja from ui_base_widget.j2 with: +ui_name = motor_tc_classic_row.ui +form_cls = Ui_Form +base_cls = MotorTcClassicRowBase +macro_names = ['MOTOR'] + +Other long required variables: +widget_names: list[str] +widget_name_to_class: dict[str, str] +macro_to_widget: dict[str, str] +widget_to_macro: dict[str, str] +widget_to_pre_templ_strs: dict[str, list[tuple[str, str]]] +widget_to_pre_templ_lists: dict[str, list[tuple[str, list[str]]]] +""" +# ruff: noqa: E501 +# ruff: noqa: F403 +# ruff: noqa: F405 + +from pcdswidgets.builder.designer_widget import DesignerWidget + +from .motor_tc_classic_row_form import * + +try: + from qtpy.QtCore import pyqtProperty +except ImportError: + from qtpy.QtCore import Property as pyqtProperty # type: ignore + + +class MotorTcClassicRowBase(DesignerWidget): + MotorClassicRow: "MotorClassicRow" + interlock_indicator: "PyDMByteIndicator" + temperature_label: "PyDMLabel" + + ui_form = Ui_Form + _macro_to_widget = { + "MOTOR": [ + "MotorClassicRow", + "temperature_label", + "interlock_indicator", + ], + } + _widget_to_macro = { + "MotorClassicRow": [ + "MOTOR", + ], + "interlock_indicator": [ + "MOTOR", + ], + "temperature_label": [ + "MOTOR", + ], + } + _widget_to_pre_template = { + "MotorClassicRow": [ + ("motor", "${MOTOR}"), + ], + "interlock_indicator": [ + ("channel", "ca://${MOTOR}:ILOCK:ACTIVE_RBV"), + ], + "temperature_label": [ + ("channel", "ca://${MOTOR}:ILOCK:TC_TEMP_RBV"), + ], + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._macro_values = { + "MOTOR": "", + } + + def get_motor(self) -> str: + return self._get_macro("MOTOR") + + def set_motor(self, value: str) -> None: + self._set_macro("MOTOR", value) + + motor = pyqtProperty(str, get_motor, set_motor) diff --git a/pcdswidgets/generated/motion/common/motor_tc_classic_row_form.py b/pcdswidgets/generated/motion/common/motor_tc_classic_row_form.py new file mode 100644 index 0000000..a7411a1 --- /dev/null +++ b/pcdswidgets/generated/motion/common/motor_tc_classic_row_form.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# Form implementation generated from reading ui file 'pcdswidgets/ui/motion/common/motor_tc_classic_row.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. +# +# Augmented by pcdswidgets.builder.build +# ruff: noqa: E501 +from pydm.widgets.byte import PyDMByteIndicator +from pydm.widgets.label import PyDMLabel +from qtpy import QtCore, QtGui, QtWidgets + +from pcdswidgets.motion.common.motor_classic_row import MotorClassicRow + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(725, 100) + Form.setMinimumSize(QtCore.QSize(725, 100)) + Form.setMaximumSize(QtCore.QSize(800, 100)) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.frame = QtWidgets.QFrame(Form) + self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame.setObjectName("frame") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame) + self.verticalLayout_2.setContentsMargins(5, 5, 5, 5) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.MotorClassicRow = MotorClassicRow(self.frame) + self.MotorClassicRow.setToolTip("") + self.MotorClassicRow.setObjectName("MotorClassicRow") + self.verticalLayout_2.addWidget(self.MotorClassicRow) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSpacing(6) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(self.frame) + font = QtGui.QFont() + font.setPointSize(10) + self.label.setFont(font) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.temperature_label = PyDMLabel(self.frame) + self.temperature_label.setMaximumSize(QtCore.QSize(100, 16777215)) + font = QtGui.QFont() + font.setPointSize(10) + self.temperature_label.setFont(font) + self.temperature_label.setToolTip("") + self.temperature_label.setPrecision(1) + self.temperature_label.setShowUnits(False) + self.temperature_label.setPrecisionFromPV(False) + self.temperature_label.setAlarmSensitiveContent(False) + self.temperature_label.setAlarmSensitiveBorder(True) + self.temperature_label.setPyDMToolTip("") + self.temperature_label.setEnableRichText(False) + self.temperature_label.setObjectName("temperature_label") + self.horizontalLayout.addWidget(self.temperature_label) + self.label_2 = QtWidgets.QLabel(self.frame) + font = QtGui.QFont() + font.setPointSize(10) + self.label_2.setFont(font) + self.label_2.setObjectName("label_2") + self.horizontalLayout.addWidget(self.label_2) + self.interlock_indicator = PyDMByteIndicator(self.frame) + self.interlock_indicator.setMinimumSize(QtCore.QSize(15, 15)) + self.interlock_indicator.setMaximumSize(QtCore.QSize(15, 15)) + font = QtGui.QFont() + font.setPointSize(1) + self.interlock_indicator.setFont(font) + self.interlock_indicator.setToolTip("") + self.interlock_indicator.setAlarmSensitiveContent(False) + self.interlock_indicator.setAlarmSensitiveBorder(True) + self.interlock_indicator.setPyDMToolTip("") + self.interlock_indicator.setOnColor(QtGui.QColor(255, 0, 0)) + self.interlock_indicator.setOffColor(QtGui.QColor(0, 255, 0)) + self.interlock_indicator.setShowLabels(False) + self.interlock_indicator.setBigEndian(False) + self.interlock_indicator.setCircles(False) + self.interlock_indicator.setLabelPosition(QtWidgets.QTabWidget.West) + self.interlock_indicator.setNumBits(1) + self.interlock_indicator.setShift(0) + self.interlock_indicator.setLabels(["Bit 0"]) + self.interlock_indicator.setObjectName("interlock_indicator") + self.horizontalLayout.addWidget(self.interlock_indicator) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.verticalLayout_2.addLayout(self.horizontalLayout) + self.verticalLayout.addWidget(self.frame) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.MotorClassicRow.setProperty("motor", _translate("Form", "${MOTOR}")) + self.label.setText(_translate("Form", "Motor temperature: ")) + self.temperature_label.setChannel(_translate("Form", "ca://${MOTOR}:ILOCK:TC_TEMP_RBV")) + self.label_2.setText(_translate("Form", "Interlock:")) + self.interlock_indicator.setChannel(_translate("Form", "ca://${MOTOR}:ILOCK:ACTIVE_RBV")) diff --git a/pcdswidgets/generated/motion/smaract/__init__.py b/pcdswidgets/generated/motion/smaract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/generated/motion/smaract/smaract_open_loop_classic_row_base.py b/pcdswidgets/generated/motion/smaract/smaract_open_loop_classic_row_base.py new file mode 100644 index 0000000..19438cd --- /dev/null +++ b/pcdswidgets/generated/motion/smaract/smaract_open_loop_classic_row_base.py @@ -0,0 +1,115 @@ +""" +Generated by jinja from ui_base_widget.j2 with: +ui_name = smaract_open_loop_classic_row.ui +form_cls = Ui_Form +base_cls = SmaractOpenLoopClassicRowBase +macro_names = ['MOTOR'] + +Other long required variables: +widget_names: list[str] +widget_name_to_class: dict[str, str] +macro_to_widget: dict[str, str] +widget_to_macro: dict[str, str] +widget_to_pre_templ_strs: dict[str, list[tuple[str, str]]] +widget_to_pre_templ_lists: dict[str, list[tuple[str, list[str]]]] +""" +# ruff: noqa: E501 +# ruff: noqa: F403 +# ruff: noqa: F405 + +from pcdswidgets.builder.designer_widget import DesignerWidget + +from .smaract_open_loop_classic_row_form import * + +try: + from qtpy.QtCore import pyqtProperty +except ImportError: + from qtpy.QtCore import Property as pyqtProperty # type: ignore + + +class SmaractOpenLoopClassicRowBase(DesignerWidget): + PyDMLabel_name: "PyDMLabel" + PyDMLineEdit: "PyDMLineEdit" + PyDMLineEdit_setpoint: "PyDMLineEdit" + PyDMPushButton_stop: "PyDMPushButton" + PyDMPushButton_twkL: "PyDMPushButton" + PyDMPushButton_twkR: "PyDMPushButton" + PyDMShellCommand: "PyDMShellCommand" + + ui_form = Ui_Form + _macro_to_widget = { + "MOTOR": [ + "PyDMShellCommand", + "PyDMLabel_name", + "PyDMLineEdit_setpoint", + "PyDMPushButton_twkR", + "PyDMLineEdit", + "PyDMPushButton_stop", + "PyDMPushButton_twkL", + ], + } + _widget_to_macro = { + "PyDMLabel_name": [ + "MOTOR", + ], + "PyDMLineEdit": [ + "MOTOR", + ], + "PyDMLineEdit_setpoint": [ + "MOTOR", + ], + "PyDMPushButton_stop": [ + "MOTOR", + ], + "PyDMPushButton_twkL": [ + "MOTOR", + ], + "PyDMPushButton_twkR": [ + "MOTOR", + ], + "PyDMShellCommand": [ + "MOTOR", + ], + } + _widget_to_pre_template = { + "PyDMLabel_name": [ + ("channel", "ca://${MOTOR}.DESC"), + ], + "PyDMLineEdit": [ + ("channel", "ca://${MOTOR}:STEP_COUNT"), + ], + "PyDMLineEdit_setpoint": [ + ("channel", "ca://${MOTOR}:TOTAL_STEP_COUNT"), + ], + "PyDMPushButton_stop": [ + ("channel", "ca://${MOTOR}.STOP"), + ], + "PyDMPushButton_twkL": [ + ("channel", "ca://${MOTOR}:STEP_REVERSE.PROC"), + ], + "PyDMPushButton_twkR": [ + ("channel", "ca://${MOTOR}:STEP_FORWARD.PROC"), + ], + "PyDMShellCommand": [ + ( + "commands", + [ + "edm -eolc -x -m MOTOR=${MOTOR} /reg/g/pcds/epics/ioc/common/smaract/R1.0.8/motorScreens/mcs2_openloop.edl", + ], + ), + ], + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._macro_values = { + "MOTOR": "", + } + + def get_motor(self) -> str: + return self._get_macro("MOTOR") + + def set_motor(self, value: str) -> None: + self._set_macro("MOTOR", value) + + motor = pyqtProperty(str, get_motor, set_motor) diff --git a/pcdswidgets/generated/motion/smaract/smaract_open_loop_classic_row_form.py b/pcdswidgets/generated/motion/smaract/smaract_open_loop_classic_row_form.py new file mode 100644 index 0000000..fe9aca7 --- /dev/null +++ b/pcdswidgets/generated/motion/smaract/smaract_open_loop_classic_row_form.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# Form implementation generated from reading ui file 'pcdswidgets/ui/motion/smaract/smaract_open_loop_classic_row.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. +# +# Augmented by pcdswidgets.builder.build +# ruff: noqa: E501 +from pydm.widgets.label import PyDMLabel +from pydm.widgets.line_edit import PyDMLineEdit +from pydm.widgets.pushbutton import PyDMPushButton +from pydm.widgets.shell_command import PyDMShellCommand +from qtpy import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(640, 40) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + Form.setMinimumSize(QtCore.QSize(640, 40)) + Form.setMaximumSize(QtCore.QSize(800, 45)) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setContentsMargins(0, -1, 5, 5) + self.gridLayout.setSpacing(5) + self.gridLayout.setObjectName("gridLayout") + self.PyDMShellCommand = PyDMShellCommand(Form) + self.PyDMShellCommand.setToolTip("") + self.PyDMShellCommand.setShowIcon(True) + self.PyDMShellCommand.setRedirectCommandOutput(False) + self.PyDMShellCommand.setAllowMultipleExecutions(False) + self.PyDMShellCommand.setTitles([]) + self.PyDMShellCommand.setCommands( + [ + "edm -eolc -x -m MOTOR=${MOTOR} /reg/g/pcds/epics/ioc/common/smaract/R1.0.8/motorScreens/mcs2_openloop.edl" + ] + ) + self.PyDMShellCommand.setObjectName("PyDMShellCommand") + self.gridLayout.addWidget(self.PyDMShellCommand, 0, 8, 1, 1) + self.PyDMLabel_name = PyDMLabel(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLabel_name.sizePolicy().hasHeightForWidth()) + self.PyDMLabel_name.setSizePolicy(sizePolicy) + self.PyDMLabel_name.setMinimumSize(QtCore.QSize(200, 0)) + font = QtGui.QFont() + font.setPointSize(12) + font.setBold(True) + font.setWeight(75) + self.PyDMLabel_name.setFont(font) + self.PyDMLabel_name.setToolTip("") + self.PyDMLabel_name.setPrecision(0) + self.PyDMLabel_name.setShowUnits(False) + self.PyDMLabel_name.setPrecisionFromPV(True) + self.PyDMLabel_name.setAlarmSensitiveContent(False) + self.PyDMLabel_name.setAlarmSensitiveBorder(True) + self.PyDMLabel_name.setDisplayFormat(PyDMLabel.String) + self.PyDMLabel_name.setObjectName("PyDMLabel_name") + self.gridLayout.addWidget(self.PyDMLabel_name, 0, 0, 1, 1) + self.PyDMLineEdit_setpoint = PyDMLineEdit(Form) + self.PyDMLineEdit_setpoint.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PyDMLineEdit_setpoint.sizePolicy().hasHeightForWidth()) + self.PyDMLineEdit_setpoint.setSizePolicy(sizePolicy) + self.PyDMLineEdit_setpoint.setMinimumSize(QtCore.QSize(100, 0)) + self.PyDMLineEdit_setpoint.setMaximumSize(QtCore.QSize(16777215, 16777215)) + font = QtGui.QFont() + font.setPointSize(12) + self.PyDMLineEdit_setpoint.setFont(font) + self.PyDMLineEdit_setpoint.setToolTip("") + self.PyDMLineEdit_setpoint.setMaxLength(12) + self.PyDMLineEdit_setpoint.setPrecision(0) + self.PyDMLineEdit_setpoint.setShowUnits(False) + self.PyDMLineEdit_setpoint.setPrecisionFromPV(True) + self.PyDMLineEdit_setpoint.setAlarmSensitiveContent(False) + self.PyDMLineEdit_setpoint.setAlarmSensitiveBorder(True) + self.PyDMLineEdit_setpoint.setDisplayFormat(PyDMLineEdit.Decimal) + self.PyDMLineEdit_setpoint.setObjectName("PyDMLineEdit_setpoint") + self.gridLayout.addWidget(self.PyDMLineEdit_setpoint, 0, 2, 1, 1) + self.PyDMPushButton_twkR = PyDMPushButton(Form) + self.PyDMPushButton_twkR.setMinimumSize(QtCore.QSize(32, 0)) + self.PyDMPushButton_twkR.setToolTip("") + self.PyDMPushButton_twkR.setAlarmSensitiveContent(False) + self.PyDMPushButton_twkR.setAlarmSensitiveBorder(False) + self.PyDMPushButton_twkR.setPasswordProtected(False) + self.PyDMPushButton_twkR.setPassword("") + self.PyDMPushButton_twkR.setProtectedPassword("") + self.PyDMPushButton_twkR.setShowConfirmDialog(False) + self.PyDMPushButton_twkR.setRelativeChange(True) + self.PyDMPushButton_twkR.setWriteWhenRelease(False) + self.PyDMPushButton_twkR.setObjectName("PyDMPushButton_twkR") + self.gridLayout.addWidget(self.PyDMPushButton_twkR, 0, 6, 1, 1) + self.PyDMLineEdit = PyDMLineEdit(Form) + self.PyDMLineEdit.setMinimumSize(QtCore.QSize(50, 0)) + self.PyDMLineEdit.setMaximumSize(QtCore.QSize(75, 16777215)) + font = QtGui.QFont() + font.setPointSize(12) + self.PyDMLineEdit.setFont(font) + self.PyDMLineEdit.setToolTip("") + self.PyDMLineEdit.setPrecision(0) + self.PyDMLineEdit.setShowUnits(False) + self.PyDMLineEdit.setPrecisionFromPV(True) + self.PyDMLineEdit.setAlarmSensitiveContent(False) + self.PyDMLineEdit.setAlarmSensitiveBorder(False) + self.PyDMLineEdit.setObjectName("PyDMLineEdit") + self.gridLayout.addWidget(self.PyDMLineEdit, 0, 5, 1, 1) + self.PyDMPushButton_stop = PyDMPushButton(Form) + self.PyDMPushButton_stop.setMinimumSize(QtCore.QSize(40, 0)) + self.PyDMPushButton_stop.setMaximumSize(QtCore.QSize(40, 16777215)) + self.PyDMPushButton_stop.setToolTip("") + self.PyDMPushButton_stop.setStyleSheet("background-color: rgb(170, 0, 0);") + self.PyDMPushButton_stop.setAlarmSensitiveContent(False) + self.PyDMPushButton_stop.setAlarmSensitiveBorder(False) + self.PyDMPushButton_stop.setPasswordProtected(False) + self.PyDMPushButton_stop.setPassword("") + self.PyDMPushButton_stop.setProtectedPassword("") + self.PyDMPushButton_stop.setShowConfirmDialog(False) + self.PyDMPushButton_stop.setRelativeChange(False) + self.PyDMPushButton_stop.setWriteWhenRelease(False) + self.PyDMPushButton_stop.setObjectName("PyDMPushButton_stop") + self.gridLayout.addWidget(self.PyDMPushButton_stop, 0, 7, 1, 1) + self.PyDMPushButton_twkL = PyDMPushButton(Form) + self.PyDMPushButton_twkL.setMinimumSize(QtCore.QSize(32, 0)) + self.PyDMPushButton_twkL.setToolTip("") + self.PyDMPushButton_twkL.setAlarmSensitiveContent(False) + self.PyDMPushButton_twkL.setAlarmSensitiveBorder(False) + self.PyDMPushButton_twkL.setPasswordProtected(False) + self.PyDMPushButton_twkL.setPassword("") + self.PyDMPushButton_twkL.setProtectedPassword("") + self.PyDMPushButton_twkL.setShowConfirmDialog(False) + self.PyDMPushButton_twkL.setRelativeChange(True) + self.PyDMPushButton_twkL.setWriteWhenRelease(False) + self.PyDMPushButton_twkL.setObjectName("PyDMPushButton_twkL") + self.gridLayout.addWidget(self.PyDMPushButton_twkL, 0, 4, 1, 1) + self.label = QtWidgets.QLabel(Form) + self.label.setMaximumSize(QtCore.QSize(75, 16777215)) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 1, 1, 1) + self.gridLayout.setColumnStretch(0, 4) + self.gridLayout.setColumnStretch(2, 5) + self.verticalLayout.addLayout(self.gridLayout) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.PyDMLabel_name.setChannel(_translate("Form", "ca://${MOTOR}.DESC")) + self.PyDMLineEdit_setpoint.setChannel(_translate("Form", "ca://${MOTOR}:TOTAL_STEP_COUNT")) + self.PyDMPushButton_twkR.setText(_translate("Form", ">>")) + self.PyDMPushButton_twkR.setChannel(_translate("Form", "ca://${MOTOR}:STEP_FORWARD.PROC")) + self.PyDMPushButton_twkR.setConfirmMessage(_translate("Form", "Are you sure you want to proceed?")) + self.PyDMPushButton_twkR.setPressValue(_translate("Form", "1")) + self.PyDMPushButton_twkR.setReleaseValue(_translate("Form", "None")) + self.PyDMLineEdit.setChannel(_translate("Form", "ca://${MOTOR}:STEP_COUNT")) + self.PyDMPushButton_stop.setText(_translate("Form", "Stop")) + self.PyDMPushButton_stop.setChannel(_translate("Form", "ca://${MOTOR}.STOP")) + self.PyDMPushButton_stop.setConfirmMessage(_translate("Form", "Are you sure you want to proceed?")) + self.PyDMPushButton_stop.setPressValue(_translate("Form", "1")) + self.PyDMPushButton_stop.setReleaseValue(_translate("Form", "None")) + self.PyDMPushButton_twkL.setText(_translate("Form", "<<")) + self.PyDMPushButton_twkL.setChannel(_translate("Form", "ca://${MOTOR}:STEP_REVERSE.PROC")) + self.PyDMPushButton_twkL.setConfirmMessage(_translate("Form", "Are you sure you want to proceed?")) + self.PyDMPushButton_twkL.setPressValue(_translate("Form", "1")) + self.PyDMPushButton_twkL.setReleaseValue(_translate("Form", "None")) + self.label.setText(_translate("Form", "(steps)")) diff --git a/pcdswidgets/generated/tests/__init__.py b/pcdswidgets/generated/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/generated/tests/builder/__init__.py b/pcdswidgets/generated/tests/builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/generated/tests/builder/widget_for_builder_test_base.py b/pcdswidgets/generated/tests/builder/widget_for_builder_test_base.py new file mode 100644 index 0000000..5a642aa --- /dev/null +++ b/pcdswidgets/generated/tests/builder/widget_for_builder_test_base.py @@ -0,0 +1,131 @@ +""" +Generated by jinja from ui_base_widget.j2 with: +ui_name = widget_for_builder_test.ui +form_cls = Ui_Form +base_cls = WidgetForBuilderTestBase +macro_names = ['NAME', 'NUM', 'ONE', 'TWO'] + +Other long required variables: +widget_names: list[str] +widget_name_to_class: dict[str, str] +macro_to_widget: dict[str, str] +widget_to_macro: dict[str, str] +widget_to_pre_templ_strs: dict[str, list[tuple[str, str]]] +widget_to_pre_templ_lists: dict[str, list[tuple[str, list[str]]]] +""" +# ruff: noqa: E501 +# ruff: noqa: F403 +# ruff: noqa: F405 + +from pcdswidgets.builder.designer_widget import DesignerWidget + +from .widget_for_builder_test_form import * + +try: + from qtpy.QtCore import pyqtProperty +except ImportError: + from qtpy.QtCore import Property as pyqtProperty # type: ignore + + +class WidgetForBuilderTestBase(DesignerWidget): + name_label: "QLabel" + name_num_label: "QLabel" + num_label: "QLabel" + one_two_shell: "PyDMShellCommand" + + ui_form = Ui_Form + _macro_to_widget = { + "NAME": [ + "name_label", + "name_num_label", + ], + "NUM": [ + "num_label", + "name_num_label", + ], + "ONE": [ + "one_two_shell", + ], + "TWO": [ + "one_two_shell", + ], + } + _widget_to_macro = { + "name_label": [ + "NAME", + ], + "name_num_label": [ + "NAME", + "NUM", + ], + "num_label": [ + "NUM", + ], + "one_two_shell": [ + "ONE", + "TWO", + ], + } + _widget_to_pre_template = { + "name_label": [ + ("toolTip", "${NAME}"), + ("text", "Name: ${NAME}"), + ], + "name_num_label": [ + ("text", "${NAME}:${NUM}"), + ], + "num_label": [ + ("text", "Num: ${NUM}"), + ], + "one_two_shell": [ + ( + "commands", + [ + "echo ${ONE}", + "echo ${TWO}", + "echo ${ONE}:${TWO}", + ], + ), + ], + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._macro_values = { + "NAME": "", + "NUM": "", + "ONE": "", + "TWO": "", + } + + def get_name(self) -> str: + return self._get_macro("NAME") + + def set_name(self, value: str) -> None: + self._set_macro("NAME", value) + + name = pyqtProperty(str, get_name, set_name) + + def get_num(self) -> str: + return self._get_macro("NUM") + + def set_num(self, value: str) -> None: + self._set_macro("NUM", value) + + num = pyqtProperty(str, get_num, set_num) + + def get_one(self) -> str: + return self._get_macro("ONE") + + def set_one(self, value: str) -> None: + self._set_macro("ONE", value) + + one = pyqtProperty(str, get_one, set_one) + + def get_two(self) -> str: + return self._get_macro("TWO") + + def set_two(self, value: str) -> None: + self._set_macro("TWO", value) + + two = pyqtProperty(str, get_two, set_two) diff --git a/pcdswidgets/generated/tests/builder/widget_for_builder_test_form.py b/pcdswidgets/generated/tests/builder/widget_for_builder_test_form.py new file mode 100644 index 0000000..d374a4b --- /dev/null +++ b/pcdswidgets/generated/tests/builder/widget_for_builder_test_form.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Form implementation generated from reading ui file 'pcdswidgets/ui/tests/builder/widget_for_builder_test.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. +# +# Augmented by pcdswidgets.builder.build +# ruff: noqa: E501 +from pydm.widgets.shell_command import PyDMShellCommand +from qtpy import QtCore, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 191) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setObjectName("verticalLayout") + self.name_label = QtWidgets.QLabel(Form) + self.name_label.setObjectName("name_label") + self.verticalLayout.addWidget(self.name_label) + self.num_label = QtWidgets.QLabel(Form) + self.num_label.setObjectName("num_label") + self.verticalLayout.addWidget(self.num_label) + self.name_num_label = QtWidgets.QLabel(Form) + self.name_num_label.setObjectName("name_num_label") + self.verticalLayout.addWidget(self.name_num_label) + self.one_two_shell = PyDMShellCommand(Form) + self.one_two_shell.setToolTip("") + self.one_two_shell.setAlarmSensitiveContent(False) + self.one_two_shell.setAlarmSensitiveBorder(True) + self.one_two_shell.setPyDMToolTip("") + self.one_two_shell.setChannel("") + self.one_two_shell.setPyDMIcon("") + self.one_two_shell.setShowConfirmDialog(False) + self.one_two_shell.setRunCommandsInFullShell(False) + self.one_two_shell.setEnvironmentVariables("") + self.one_two_shell.setShowIcon(True) + self.one_two_shell.setAllowMultipleExecutions(False) + self.one_two_shell.setTitles([]) + self.one_two_shell.setCommands(["echo ${ONE}", "echo ${TWO}", "echo ${ONE}:${TWO}"]) + self.one_two_shell.setPasswordProtected(False) + self.one_two_shell.setPassword("") + self.one_two_shell.setProtectedPassword("") + self.one_two_shell.setShowCurrentlyRunningIndication(False) + self.one_two_shell.setObjectName("one_two_shell") + self.verticalLayout.addWidget(self.one_two_shell) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.name_label.setToolTip(_translate("Form", "${NAME}")) + self.name_label.setText(_translate("Form", "Name: ${NAME}")) + self.num_label.setText(_translate("Form", "Num: ${NUM}")) + self.name_num_label.setText(_translate("Form", "${NAME}:${NUM}")) + self.one_two_shell.setConfirmMessage(_translate("Form", "Are you sure you want to proceed?")) diff --git a/pcdswidgets/icons/__init__.py b/pcdswidgets/icons/__init__.py index 15c8415..895e11b 100644 --- a/pcdswidgets/icons/__init__.py +++ b/pcdswidgets/icons/__init__.py @@ -1,26 +1,48 @@ -from .gauges import (CapManometerGaugeSymbolIcon, CathodeGaugeSymbolIcon, - ColdCathodeComboGaugeSymbolIcon, - ColdCathodeGaugeSymbolIcon, - HotCathodeComboGaugeSymbolIcon, HotCathodeGaugeSymbolIcon, - RoughGaugeSymbolIcon) +from .gauges import ( + CapManometerGaugeSymbolIcon, + CathodeGaugeSymbolIcon, + ColdCathodeComboGaugeSymbolIcon, + ColdCathodeGaugeSymbolIcon, + HotCathodeComboGaugeSymbolIcon, + HotCathodeGaugeSymbolIcon, + RoughGaugeSymbolIcon, +) from .others import RGASymbolIcon -from .pumps import (GetterPumpSymbolIcon, IonPumpSymbolIcon, - ScrollPumpSymbolIcon, TurboPumpSymbolIcon) -from .valves import (ApertureValveSymbolIcon, ControlOnlyValveSymbolIcon, - ControlValveSymbolIcon, FastShutterSymbolIcon, - NeedleValveSymbolIcon, PneumaticValveDASymbolIcon, - PneumaticValveNOSymbolIcon, PneumaticValveSymbolIcon, - ProportionalValveSymbolIcon, - RightAngleManualValveSymbolIcon) +from .pumps import GetterPumpSymbolIcon, IonPumpSymbolIcon, ScrollPumpSymbolIcon, TurboPumpSymbolIcon +from .valves import ( + ApertureValveSymbolIcon, + ControlOnlyValveSymbolIcon, + ControlValveSymbolIcon, + FastShutterSymbolIcon, + NeedleValveSymbolIcon, + PneumaticValveDASymbolIcon, + PneumaticValveNOSymbolIcon, + PneumaticValveSymbolIcon, + ProportionalValveSymbolIcon, + RightAngleManualValveSymbolIcon, +) -__all__ = ['RoughGaugeSymbolIcon', 'CathodeGaugeSymbolIcon', - 'HotCathodeGaugeSymbolIcon', 'ColdCathodeGaugeSymbolIcon', - 'IonPumpSymbolIcon', 'TurboPumpSymbolIcon', 'ScrollPumpSymbolIcon', - 'GetterPumpSymbolIcon', - 'PneumaticValveSymbolIcon', 'FastShutterSymbolIcon', - 'ApertureValveSymbolIcon', 'RightAngleManualValveSymbolIcon', - 'NeedleValveSymbolIcon', 'ProportionalValveSymbolIcon', - 'RGASymbolIcon', 'ControlValveSymbolIcon', - 'ControlOnlyValveSymbolIcon', 'PneumaticValveNOSymbolIcon', - 'PneumaticValveDASymbolIcon', 'CapManometerGaugeSymbolIcon', - 'HotCathodeComboGaugeSymbolIcon', 'ColdCathodeComboGaugeSymbolIcon'] +__all__ = [ + "RoughGaugeSymbolIcon", + "CathodeGaugeSymbolIcon", + "HotCathodeGaugeSymbolIcon", + "ColdCathodeGaugeSymbolIcon", + "IonPumpSymbolIcon", + "TurboPumpSymbolIcon", + "ScrollPumpSymbolIcon", + "GetterPumpSymbolIcon", + "PneumaticValveSymbolIcon", + "FastShutterSymbolIcon", + "ApertureValveSymbolIcon", + "RightAngleManualValveSymbolIcon", + "NeedleValveSymbolIcon", + "ProportionalValveSymbolIcon", + "RGASymbolIcon", + "ControlValveSymbolIcon", + "ControlOnlyValveSymbolIcon", + "PneumaticValveNOSymbolIcon", + "PneumaticValveDASymbolIcon", + "CapManometerGaugeSymbolIcon", + "HotCathodeComboGaugeSymbolIcon", + "ColdCathodeComboGaugeSymbolIcon", +] diff --git a/pcdswidgets/icons/base.py b/pcdswidgets/icons/base.py index 0e5a84d..93d77f2 100644 --- a/pcdswidgets/icons/base.py +++ b/pcdswidgets/icons/base.py @@ -1,8 +1,7 @@ from pydm.utilities import is_qt_designer, remove_protocol from qtpy.QtCore import Property, QEvent, QSize, Qt, Signal from qtpy.QtGui import QBrush, QColor, QPainter, QPen -from qtpy.QtWidgets import (QApplication, QStyle, QStyleOption, QToolTip, - QWidget) +from qtpy.QtWidgets import QApplication, QStyle, QStyleOption, QToolTip, QWidget from ..utils import find_ancestor_for_widget @@ -18,6 +17,7 @@ class BaseSymbolIcon(QWidget): parent : QWidget The parent widget for this widget. """ + clicked = Signal() def __init__(self, parent=None): @@ -66,7 +66,7 @@ def show_state_channel(self, event): if not p: return - state_suffix = getattr(p, '_state_suffix', None) + state_suffix = getattr(p, "_state_suffix", None) if not state_suffix: return diff --git a/pcdswidgets/icons/demo/__main__.py b/pcdswidgets/icons/demo/__main__.py index b0da870..db3b963 100644 --- a/pcdswidgets/icons/demo/__main__.py +++ b/pcdswidgets/icons/demo/__main__.py @@ -3,13 +3,14 @@ Invoke as e.g. "python -m pcdswidgets.icons.demo ControlValve" """ + import sys from qtpy.QtWidgets import QApplication from .. import * # noqa -cls = sys.argv[1] + 'SymbolIcon' +cls = sys.argv[1] + "SymbolIcon" app = QApplication([]) icon = globals()[cls]() icon.show() diff --git a/pcdswidgets/icons/gauges.py b/pcdswidgets/icons/gauges.py index 96aa962..413d766 100644 --- a/pcdswidgets/icons/gauges.py +++ b/pcdswidgets/icons/gauges.py @@ -46,6 +46,7 @@ class HotCathodeGaugeSymbolIcon(CathodeGaugeSymbolIcon): parent : QWidget The parent widget for the icon """ + def draw_icon(self, painter): super().draw_icon(painter) painter.drawLine(QPointF(0.3, 0.1), QPointF(0.3, 0.9)) @@ -62,9 +63,10 @@ class ColdCathodeGaugeSymbolIcon(CathodeGaugeSymbolIcon): parent : QWidget The parent widget for the icon """ + def draw_icon(self, painter): super().draw_icon(painter) - painter.drawArc(QRectF(0.25, 0.25, 0.5, 0.5), 45*16, 270*16) + painter.drawArc(QRectF(0.25, 0.25, 0.5, 0.5), 45 * 16, 270 * 16) class ColdCathodeComboGaugeSymbolIcon(CathodeGaugeSymbolIcon): @@ -76,9 +78,10 @@ class ColdCathodeComboGaugeSymbolIcon(CathodeGaugeSymbolIcon): parent : QWidget The parent widget for the icon """ + path = QPainterPath(QPointF(0.5, 0)) - path.lineTo(.933, .75) - path.lineTo(.067, .75) + path.lineTo(0.933, 0.75) + path.lineTo(0.067, 0.75) path.closeSubpath() def draw_icon(self, painter): @@ -95,6 +98,7 @@ class HotCathodeComboGaugeSymbolIcon(ColdCathodeComboGaugeSymbolIcon): parent : QWidget The parent widget for the icon """ + def draw_icon(self, painter): super().draw_icon(painter) painter.drawLine(QPointF(0.4, 0.30), QPointF(0.4, 0.65)) diff --git a/pcdswidgets/icons/others.py b/pcdswidgets/icons/others.py index d1ba542..ed16c58 100644 --- a/pcdswidgets/icons/others.py +++ b/pcdswidgets/icons/others.py @@ -13,6 +13,7 @@ class RGASymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + path = QPainterPath(QPointF(0, 0)) path.lineTo(1, 0) path.lineTo(1, 0.33) diff --git a/pcdswidgets/icons/pumps.py b/pcdswidgets/icons/pumps.py index 4cba42d..1baa99a 100644 --- a/pcdswidgets/icons/pumps.py +++ b/pcdswidgets/icons/pumps.py @@ -15,6 +15,7 @@ class ScrollPumpSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._center_brush = QBrush(QColor("transparent")) @@ -47,8 +48,7 @@ def draw_icon(self, painter): painter.drawArc(QRectF(0.3, 0.3, 0.4, 0.4), 90 * 16, -270 * 16) - arrow = QPolygonF( - [QPointF(-0.025, 0.0), QPointF(0.025, 0.0), QPointF(0.0, -0.025)]) + arrow = QPolygonF([QPointF(-0.025, 0.0), QPointF(0.025, 0.0), QPointF(0.0, -0.025)]) painter.setBrush(QBrush(QColor(0, 0, 0))) painter.drawPolygon(arrow.translated(circle_arrow_point)) @@ -72,22 +72,21 @@ def draw_icon(self, painter): curve_start = QPointF(0.5, 0.7) bend_angle = 25 curve_end_l = QPointF( - 0.4 * math.cos(math.radians(90 + bend_angle)) + 0.5, - -0.4 * math.sin(math.radians(90 + bend_angle)) + 0.5) + 0.4 * math.cos(math.radians(90 + bend_angle)) + 0.5, -0.4 * math.sin(math.radians(90 + bend_angle)) + 0.5 + ) c1 = QPointF(0.5, 0.4) path = QPainterPath(curve_start) path.quadTo(c1, curve_end_l) painter.drawPath(path) curve_end_r = QPointF( - 0.4 * math.cos(math.radians(90 - bend_angle)) + 0.5, - -0.4 * math.sin(math.radians(90 - bend_angle)) + 0.5) + 0.4 * math.cos(math.radians(90 - bend_angle)) + 0.5, -0.4 * math.sin(math.radians(90 - bend_angle)) + 0.5 + ) path = QPainterPath(curve_start) path.quadTo(c1, curve_end_r) painter.drawPath(path) # Draw the arrow end-caps painter.setBrush(QBrush(QColor(0, 0, 0))) - arrow = QPolygonF( - [QPointF(-0.025, 0.0), QPointF(0.025, 0.0), QPointF(0.0, 0.025)]) + arrow = QPolygonF([QPointF(-0.025, 0.0), QPointF(0.025, 0.0), QPointF(0.0, 0.025)]) painter.drawPolygon(arrow.translated(bottom_arrow_point)) t = QTransform() t.rotate(180.0 - 25.0) @@ -110,6 +109,7 @@ class TurboPumpSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._center_brush = QBrush(QColor("transparent")) @@ -165,13 +165,15 @@ def draw_icon(self, painter): top_arrow_point = QPointF(0.35, 0.15) arrow = QPolygonF( - [QPointF(-0.08, 0.0), - QPointF(-0.005, 0.0), - QPointF(-0.005, 0.15), - QPointF(0.005, 0.15), - QPointF(0.005, 0.0), - QPointF(0.08, 0.0), - QPointF(0.00, -0.08)] + [ + QPointF(-0.08, 0.0), + QPointF(-0.005, 0.0), + QPointF(-0.005, 0.15), + QPointF(0.005, 0.15), + QPointF(0.005, 0.0), + QPointF(0.08, 0.0), + QPointF(0.00, -0.08), + ] ) t = QTransform() diff --git a/pcdswidgets/icons/valves.py b/pcdswidgets/icons/valves.py index b8dce04..eb1da41 100644 --- a/pcdswidgets/icons/valves.py +++ b/pcdswidgets/icons/valves.py @@ -1,8 +1,7 @@ import math from qtpy.QtCore import Property, QLineF, QPointF, QRectF, Qt -from qtpy.QtGui import (QBrush, QColor, QPainterPath, QPen, QPolygonF, - QTransform) +from qtpy.QtGui import QBrush, QColor, QPainterPath, QPen, QPolygonF, QTransform from .base import BaseSymbolIcon @@ -16,6 +15,7 @@ class PneumaticValveSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._interlock_brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern) @@ -51,6 +51,7 @@ class FastShutterSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._arrow_brush = QBrush(QColor("transparent"), Qt.SolidPattern) @@ -79,12 +80,7 @@ def draw_icon(self, painter): painter.setPen(Qt.NoPen) painter.setBrush(self._arrow_brush) arrow = QPolygonF( - [QPointF(0.2, 0), - QPointF(0.2, 0.20), - QPointF(0.5, 0.40), - QPointF(0.8, 0.20), - QPointF(0.8, 0) - ] + [QPointF(0.2, 0), QPointF(0.2, 0.20), QPointF(0.5, 0.40), QPointF(0.8, 0.20), QPointF(0.8, 0)] ) painter.drawPolygon(arrow) @@ -107,6 +103,7 @@ class RightAngleManualValveSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def draw_icon(self, painter): path = QPainterPath(QPointF(0, 0)) path.lineTo(1, 1) @@ -127,6 +124,7 @@ class ApertureValveSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._interlock_brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern) @@ -163,6 +161,7 @@ class NeedleValveSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._interlock_brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern) @@ -191,13 +190,15 @@ def draw_icon(self, painter): top_arrow_point = QPointF(0.65, 0.36) arrow = QPolygonF( - [QPointF(-0.09, 0.0), - QPointF(-0.005, 0.0), - QPointF(-0.005, 0.8), - QPointF(0.005, 0.8), - QPointF(0.005, 0.0), - QPointF(0.09, 0.0), - QPointF(0.00, -0.25)] + [ + QPointF(-0.09, 0.0), + QPointF(-0.005, 0.0), + QPointF(-0.005, 0.8), + QPointF(0.005, 0.8), + QPointF(0.005, 0.0), + QPointF(0.09, 0.0), + QPointF(0.00, -0.25), + ] ) t = QTransform() @@ -219,6 +220,7 @@ class ProportionalValveSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._interlock_brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern) @@ -250,13 +252,15 @@ def draw_icon(self, painter): top_arrow_point = QPointF(0.65, 0.42) arrow = QPolygonF( - [QPointF(-0.07, 0.0), - QPointF(-0.005, 0.0), - QPointF(-0.005, 0.8), - QPointF(0.005, 0.8), - QPointF(0.005, 0.0), - QPointF(0.07, 0.0), - QPointF(0.00, -0.25)] + [ + QPointF(-0.07, 0.0), + QPointF(-0.005, 0.0), + QPointF(-0.005, 0.8), + QPointF(0.005, 0.8), + QPointF(0.005, 0.0), + QPointF(0.07, 0.0), + QPointF(0.00, -0.25), + ] ) t = QTransform() @@ -267,45 +271,43 @@ def draw_icon(self, painter): t_x = 0.4 t_y = 0.05 - painter.drawLines([QLineF(0.0+t_x, 0.0+t_y, 0.0+t_x, 0.2+t_y), - QLineF(0.0+t_x, 0.0+t_y, 0.1+t_x, 0.2+t_y), - QLineF(0.1+t_x, 0.2+t_y, 0.2+t_x, 0.0+t_y), - QLineF(0.2+t_x, 0.0+t_y, 0.2+t_x, 0.2+t_y)]) + painter.drawLines( + [ + QLineF(0.0 + t_x, 0.0 + t_y, 0.0 + t_x, 0.2 + t_y), + QLineF(0.0 + t_x, 0.0 + t_y, 0.1 + t_x, 0.2 + t_y), + QLineF(0.1 + t_x, 0.2 + t_y, 0.2 + t_x, 0.0 + t_y), + QLineF(0.2 + t_x, 0.0 + t_y, 0.2 + t_x, 0.2 + t_y), + ] + ) class ControlValveSymbolIcon(PneumaticValveSymbolIcon): """Icon for a Control Valve with readback""" + def draw_icon(self, painter): pen = painter.pen() - pen.setWidthF(pen.width()*2) + pen.setWidthF(pen.width() * 2) pen.setCapStyle(Qt.FlatCap) painter.setPen(pen) # Circle parameters radius = 0.3 center = (0.5, 1 - radius) # Draw circle - painter.drawEllipse(QPointF(*center), - radius, radius) + painter.drawEllipse(QPointF(*center), radius, radius) # X pattern quad = math.cos(math.radians(45)) * radius - painter.drawLine(QLineF(center[0] + quad, - center[1] + quad, - center[0] - quad, - center[1] - quad)) - painter.drawLine(QLineF(center[0] + quad, - center[1] - quad, - center[0] - quad, - center[1] + quad)) + painter.drawLine(QLineF(center[0] + quad, center[1] + quad, center[0] - quad, center[1] - quad)) + painter.drawLine(QLineF(center[0] + quad, center[1] - quad, center[0] - quad, center[1] + quad)) # Interlock Icon square_dims = (0.4, 0.2) - painter.drawLine(QPointF(center[0], center[1] - radius), - QPointF(center[0], square_dims[1])) + painter.drawLine(QPointF(center[0], center[1] - radius), QPointF(center[0], square_dims[1])) painter.setBrush(self._interlock_brush) - painter.drawRect(QRectF((1 - square_dims[0])/2., 0, *square_dims)) + painter.drawRect(QRectF((1 - square_dims[0]) / 2.0, 0, *square_dims)) class ControlOnlyValveSymbolIcon(BaseSymbolIcon): """Icon for a Control Valve with no readback""" + def draw_icon(self, painter): path = QPainterPath(QPointF(0, 0.3)) path.lineTo(0, 0.9) @@ -324,6 +326,7 @@ class PneumaticValveNOSymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._interlock_brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern) @@ -368,6 +371,7 @@ class PneumaticValveDASymbolIcon(BaseSymbolIcon): parent : QWidget The parent widget for the icon """ + def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._interlock_brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern) @@ -405,13 +409,15 @@ def draw_icon(self, painter): length = 0.2 width = 0.02 rightward_arrow = QPolygonF( - [QPointF(tip_length, 0.0), - QPointF(0.0, -tip_width), - QPointF(0.0, -width), - QPointF(-length, -width), - QPointF(-length, width), - QPointF(0.0, width), - QPointF(0.0, tip_width)] + [ + QPointF(tip_length, 0.0), + QPointF(0.0, -tip_width), + QPointF(0.0, -width), + QPointF(-length, -width), + QPointF(-length, width), + QPointF(0.0, width), + QPointF(0.0, tip_width), + ] ) # Second arrow looks left point_left = QTransform() diff --git a/pcdswidgets/motion/__init__.py b/pcdswidgets/motion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/motion/common/__init__.py b/pcdswidgets/motion/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/motion/common/motor_classic_full.py b/pcdswidgets/motion/common/motor_classic_full.py new file mode 100644 index 0000000..8d15977 --- /dev/null +++ b/pcdswidgets/motion/common/motor_classic_full.py @@ -0,0 +1,17 @@ +""" +Originally generated from jinja template ui_main_widget.j2 + +This file can be safely edited to change the runtime behavior of the widget. +""" + +from pcdswidgets.builder.designer_options import DesignerOptions +from pcdswidgets.builder.icon_options import IconOptions +from pcdswidgets.generated.motion.common.motor_classic_full_base import MotorClassicFullBase + + +class MotorClassicFull(MotorClassicFullBase): + designer_options = DesignerOptions( + group="ECS Motion Common", + is_container=False, + icon=IconOptions.NONE, + ) diff --git a/pcdswidgets/motion/common/motor_classic_row.py b/pcdswidgets/motion/common/motor_classic_row.py new file mode 100644 index 0000000..247fb81 --- /dev/null +++ b/pcdswidgets/motion/common/motor_classic_row.py @@ -0,0 +1,17 @@ +""" +Originally generated from jinja template ui_main_widget.j2 + +This file can be safely edited to change the runtime behavior of the widget. +""" + +from pcdswidgets.builder.designer_options import DesignerOptions +from pcdswidgets.builder.icon_options import IconOptions +from pcdswidgets.generated.motion.common.motor_classic_row_base import MotorClassicRowBase + + +class MotorClassicRow(MotorClassicRowBase): + designer_options = DesignerOptions( + group="ECS Motion Common", + is_container=False, + icon=IconOptions.NONE, + ) diff --git a/pcdswidgets/motion/common/motor_tc_classic_row.py b/pcdswidgets/motion/common/motor_tc_classic_row.py new file mode 100644 index 0000000..2279d6b --- /dev/null +++ b/pcdswidgets/motion/common/motor_tc_classic_row.py @@ -0,0 +1,17 @@ +""" +Originally generated from jinja template ui_main_widget.j2 + +This file can be safely edited to change the runtime behavior of the widget. +""" + +from pcdswidgets.builder.designer_options import DesignerOptions +from pcdswidgets.builder.icon_options import IconOptions +from pcdswidgets.generated.motion.common.motor_tc_classic_row_base import MotorTcClassicRowBase + + +class MotorTcClassicRow(MotorTcClassicRowBase): + designer_options = DesignerOptions( + group="ECS Motion Common", + is_container=False, + icon=IconOptions.NONE, + ) diff --git a/pcdswidgets/motion/smaract/__init__.py b/pcdswidgets/motion/smaract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/motion/smaract/smaract_open_loop_classic_row.py b/pcdswidgets/motion/smaract/smaract_open_loop_classic_row.py new file mode 100644 index 0000000..3ba628f --- /dev/null +++ b/pcdswidgets/motion/smaract/smaract_open_loop_classic_row.py @@ -0,0 +1,17 @@ +""" +Originally generated from jinja template ui_main_widget.j2 + +This file can be safely edited to change the runtime behavior of the widget. +""" + +from pcdswidgets.builder.designer_options import DesignerOptions +from pcdswidgets.builder.icon_options import IconOptions +from pcdswidgets.generated.motion.smaract.smaract_open_loop_classic_row_base import SmaractOpenLoopClassicRowBase + + +class SmaractOpenLoopClassicRow(SmaractOpenLoopClassicRowBase): + designer_options = DesignerOptions( + group="ECS Motion Smaract", + is_container=False, + icon=IconOptions.NONE, + ) diff --git a/pcdswidgets/table.py b/pcdswidgets/table.py index 1f2388b..5e424c7 100644 --- a/pcdswidgets/table.py +++ b/pcdswidgets/table.py @@ -21,8 +21,9 @@ class FilterSortWidgetTable(QtWidgets.QTableWidget): This will allow you to sort or filter based on macros and based on the values in each pydm widget. """ + _qt_designer_ = { - "group": "PCDS Utilities", + "group": "ECS Containers", "is_container": False, } @@ -57,7 +58,7 @@ def __init__(self, *args, **kwargs): self._header_map = {} self._channels = [] self._filters = {} - self._initial_sort_header = 'index' + self._initial_sort_header = "index" self._initial_sort_ascend = True self._hide_headers = [] @@ -144,7 +145,7 @@ def reload_macros_file(self) -> None: macros = json.load(fd) self.set_macros(macros) except Exception: - logger.exception('') + logger.exception("") return def set_macros(self, macros_list: list[dict[str, str]]) -> None: @@ -161,11 +162,7 @@ def set_macros(self, macros_list: list[dict[str, str]]) -> None: have the same keys or this will not work properly. """ self._macros = macros_list - self._macro_headers = ( - list(self._macros[0].keys()) - if self._macros - else [] - ) + self._macro_headers = list(self._macros[0].keys()) if self._macros else [] self.reinit_table() def reinit_table(self) -> None: @@ -223,16 +220,16 @@ def add_row(self, macros: dict[str, str]) -> None: # Put the widget into the table self.setCellWidget(row_position, 0, widget) - self._header_map['widget'] = 0 + self._header_map["widget"] = 0 self.setRowHeight(row_position, widget.height()) # Put the index into the table item = ChannelTableWidgetItem( - header='index', + header="index", default=row_position, ) self.setItem(row_position, 1, item) - self._header_map['index'] = 1 + self._header_map["index"] = 1 # Put the macros into the table index = 2 for key, value in macros.items(): @@ -263,30 +260,30 @@ def add_context_menu_to_children(self, widget: QtWidgets.QWidget) -> None: This makes it so you can right click to configure the table from within any of the contained widgets. """ - for widget in widget.children(): - widget.contextMenuEvent = self.contextMenuEvent + for child_widget in widget.children(): + child_widget.contextMenuEvent = self.contextMenuEvent def contextMenuEvent(self, _event) -> None: """ On right click, create and open a settings menu. """ menu = QtWidgets.QMenu(parent=self) - configure_action = menu.addAction('Configure') + configure_action = menu.addAction("Configure") configure_action.setCheckable(True) configure_action.setChecked(self.configurable) configure_action.toggled.connect(self.request_configurable) - active_sort_action = menu.addAction('Active Re-sort') + active_sort_action = menu.addAction("Active Re-sort") active_sort_action.setCheckable(True) active_sort_action.setChecked(self.isSortingEnabled()) active_sort_action.toggled.connect(self.setSortingEnabled) - sort_menu = menu.addMenu('Sorting') + sort_menu = menu.addMenu("Sorting") for header_name in self._header_map.keys(): - if header_name == 'widget': + if header_name == "widget": continue if header_name in self.hide_headers_in_menu: continue inner_menu = sort_menu.addMenu(header_name.lower()) - asc = inner_menu.addAction('Ascending') + asc = inner_menu.addAction("Ascending") asc.triggered.connect( functools.partial( self.menu_sort, @@ -294,7 +291,7 @@ def contextMenuEvent(self, _event) -> None: ascending=True, ) ) - dec = inner_menu.addAction('Descending') + dec = inner_menu.addAction("Descending") dec.triggered.connect( functools.partial( self.menu_sort, @@ -302,7 +299,7 @@ def contextMenuEvent(self, _event) -> None: ascending=False, ) ) - filter_menu = menu.addMenu('Filters') + filter_menu = menu.addMenu("Filters") for filter_name, filter_info in self._filters.items(): inner_action = filter_menu.addAction(filter_name) inner_action.setCheckable(True) @@ -332,20 +329,15 @@ def get_row_values(self, row: int) -> dict[str, Any]: is the 'connected' str, which is True if all channels are connected. """ - values = {'connected': True} + values = {"connected": True} for col in range(1, self.columnCount()): item = self.item(row, col) values[item.header] = item.get_value() if not item.connected: - values['connected'] = False + values["connected"] = False return values - def add_filter( - self, - filter_name: str, - filter_func: Callable[[dict[str, Any]], bool], - active: bool = True - ) -> None: + def add_filter(self, filter_name: str, filter_func: Callable[[dict[str, Any]], bool], active: bool = True) -> None: """ Add a new visibility filter to the table. @@ -422,7 +414,7 @@ def update_filter(self, row: int) -> None: should_show = filt_info.filter_func(values) except Exception: logger.debug( - 'Error in filter function %s', + "Error in filter function %s", filt_info.name, exc_info=True, ) @@ -503,7 +495,7 @@ def initial_sort(self) -> None: """ self.sort_table(self.initial_sort_header, self.initial_sort_ascending) - @QtCore.Property('QStringList') + @QtCore.Property("QStringList") def hide_headers_in_menu(self) -> list[str]: """ A list of headers that we don't want to see in the sort menu. @@ -599,6 +591,7 @@ class ChannelTableWidgetItem(QtWidgets.QTableWidgetItem): Only update the table if the change is more than the deadband. This can help make large tables less resource-hungry. """ + header: str channel: str | None deadband: float @@ -610,7 +603,7 @@ def __init__( default: Any | None = None, channel: str | None = None, deadband: float = 0.0, - parent: QtWidgets.QWidget | None = None + parent: QtWidgets.QWidget | None = None, ): super().__init__(parent) self.header = header @@ -667,9 +660,9 @@ def __lt__(self, other: ChannelTableWidgetItem) -> bool: elif other.get_value() is None: return True # Make sure empty string sorts as next greatest - elif self.get_value() == '': + elif self.get_value() == "": return False - elif other.get_value() == '': + elif other.get_value() == "": return True return self.get_value() < other.get_value() diff --git a/pcdswidgets/tests/builder/__init__.py b/pcdswidgets/tests/builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pcdswidgets/tests/builder/test_builder.py b/pcdswidgets/tests/builder/test_builder.py new file mode 100644 index 0000000..4500d34 --- /dev/null +++ b/pcdswidgets/tests/builder/test_builder.py @@ -0,0 +1,96 @@ +import importlib +import inspect +from pathlib import Path + +import pytest + +import pcdswidgets +from pcdswidgets.builder.designer_widget import DesignerWidget + +MODULE_ROOT = Path(pcdswidgets.__file__).parent +UI_SOURCES = sorted((MODULE_ROOT / "ui").rglob("*.ui")) + +TEST_UI = str(Path(__file__).parent / "pytest.ui") + + +@pytest.mark.parametrize("ui_source", UI_SOURCES) +def test_it_was_built(ui_source: Path): + """ + Check if .py files have been built from all the source .ui files. + """ + subsystem, device = get_subsystem_and_device(ui_source=ui_source) + + gen_dir = MODULE_ROOT / "generated" / subsystem / device + base_path = gen_dir / (ui_source.stem + "_base.py") + form_path = gen_dir / (ui_source.stem + "_form.py") + + main_path = MODULE_ROOT / subsystem / device / (ui_source.stem + ".py") + + assert base_path.exists() + assert form_path.exists() + assert main_path.exists() + + +def get_subsystem_and_device(ui_source: Path) -> tuple[str, str]: + subsystem = None + device = None + + seen_ui = False + for part in ui_source.parts: + if part == "ui": + seen_ui = True + elif seen_ui: + if subsystem is None: + subsystem = part + else: + device = part + break + + assert subsystem is not None + assert device is not None + + return subsystem, device + + +@pytest.mark.parametrize("ui_source", UI_SOURCES) +def test_built_is_importable(ui_source: Path): + """ + Check if the .py files have somewhat proper importable classes. + """ + subsystem, device = get_subsystem_and_device(ui_source=ui_source) + + form_module_name = f"pcdswidgets.generated.{subsystem}.{device}.{ui_source.stem}_form" + base_module_name = f"pcdswidgets.generated.{subsystem}.{device}.{ui_source.stem}_base" + main_module_name = f"pcdswidgets.{subsystem}.{device}.{ui_source.stem}" + + form_module = importlib.import_module(form_module_name) + base_module = importlib.import_module(base_module_name) + main_module = importlib.import_module(main_module_name) + + form_classes = [] + for _, cls in inspect.getmembers(form_module, inspect.isclass): + if inspect.getmodule(cls) is form_module: + form_classes.append(cls) + + base_classes = [] + for _, cls in inspect.getmembers(base_module, inspect.isclass): + if inspect.getmodule(cls) is base_module: + base_classes.append(cls) + + main_classes = [] + for _, cls in inspect.getmembers(main_module, inspect.isclass): + if inspect.getmodule(cls) is main_module: + main_classes.append(cls) + + assert len(form_classes) == 1 + assert hasattr(form_classes[0], "setupUi") + assert hasattr(form_classes[0], "retranslateUi") + + assert len(base_classes) == 1 + assert issubclass(base_classes[0], DesignerWidget) + assert base_classes[0].ui_form is form_classes[0] + + assert len(main_classes) == 1 + assert hasattr(main_classes[0], "designer_options") + assert hasattr(main_classes[0], "_qt_designer_") + assert issubclass(main_classes[0], base_classes[0]) diff --git a/pcdswidgets/tests/builder/test_designer_widget.py b/pcdswidgets/tests/builder/test_designer_widget.py new file mode 100644 index 0000000..df0942c --- /dev/null +++ b/pcdswidgets/tests/builder/test_designer_widget.py @@ -0,0 +1,112 @@ +import inspect + +import pytest +from pydm.widgets.shell_command import PyDMShellCommand +from pytestqt.qtbot import QtBot +from qtpy.QtWidgets import QLabel + +from pcdswidgets.generated.tests.builder.widget_for_builder_test_base import WidgetForBuilderTestBase + +from .widget_for_builder_test import WidgetForBuilderTest + + +@pytest.fixture(scope="function") +def test_widget(qtbot: QtBot) -> WidgetForBuilderTest: + widget = WidgetForBuilderTest() + qtbot.add_widget(widget) + return widget + + +def test_has_expected_hints(): + hints = inspect.get_annotations(WidgetForBuilderTestBase) + + for label_name in ("name_label", "num_label", "name_num_label"): + assert hints[label_name] == "QLabel" + assert hints["one_two_shell"] == "PyDMShellCommand" + + +def test_has_expected_widgets(test_widget: WidgetForBuilderTest): + assert isinstance(test_widget.name_label, QLabel) + assert isinstance(test_widget.num_label, QLabel) + assert isinstance(test_widget.name_num_label, QLabel) + assert isinstance(test_widget.one_two_shell, PyDMShellCommand) + + +def test_has_expected_macro_to_widget(test_widget: WidgetForBuilderTest): + assert set(test_widget._macro_to_widget.keys()) == {"NAME", "NUM", "ONE", "TWO"} + assert set(test_widget._macro_to_widget["NAME"]) == {"name_label", "name_num_label"} + assert set(test_widget._macro_to_widget["NUM"]) == {"num_label", "name_num_label"} + assert test_widget._macro_to_widget["ONE"] == ["one_two_shell"] + assert test_widget._macro_to_widget["TWO"] == ["one_two_shell"] + + +def test_has_expected_widget_to_macro(test_widget: WidgetForBuilderTest): + assert set(test_widget._widget_to_macro.keys()) == {"name_label", "num_label", "name_num_label", "one_two_shell"} + assert test_widget._widget_to_macro["name_label"] == ["NAME"] + assert test_widget._widget_to_macro["num_label"] == ["NUM"] + assert set(test_widget._widget_to_macro["name_num_label"]) == {"NAME", "NUM"} + assert set(test_widget._widget_to_macro["one_two_shell"]) == {"ONE", "TWO"} + + +def test_has_expected_widget_to_pre_template(test_widget: WidgetForBuilderTest): + assert set(test_widget._widget_to_pre_template.keys()) == { + "name_label", + "num_label", + "name_num_label", + "one_two_shell", + } + assert set(test_widget._widget_to_pre_template["name_label"]) == {("text", "Name: ${NAME}"), ("toolTip", "${NAME}")} + assert test_widget._widget_to_pre_template["num_label"] == [("text", "Num: ${NUM}")] + assert test_widget._widget_to_pre_template["name_num_label"] == [("text", "${NAME}:${NUM}")] + assert test_widget._widget_to_pre_template["one_two_shell"] == [ + ("commands", ["echo ${ONE}", "echo ${TWO}", "echo ${ONE}:${TWO}"]) + ] + + +def test_has_expected_macro_values(test_widget: WidgetForBuilderTest): + assert test_widget._macro_values == { + "NAME": "", + "NUM": "", + "ONE": "", + "TWO": "", + } + + +def test_macro_substitution_labels(test_widget: WidgetForBuilderTest): + assert test_widget.name_label.text() == "Name: ${NAME}" + assert test_widget.num_label.text() == "Num: ${NUM}" + assert test_widget.name_num_label.text() == "${NAME}:${NUM}" + + test_widget.setProperty("name", "Jimmy") + + assert test_widget.name_label.text() == "Name: Jimmy" + assert test_widget.num_label.text() == "Num: ${NUM}" + assert test_widget.name_num_label.text() == "${NAME}:${NUM}" + + test_widget.setProperty("num", "02") + + assert test_widget.name_label.text() == "Name: Jimmy" + assert test_widget.num_label.text() == "Num: 02" + assert test_widget.name_num_label.text() == "Jimmy:02" + + test_widget.setProperty("name", "Steven") + + assert test_widget.name_label.text() == "Name: Steven" + assert test_widget.num_label.text() == "Num: 02" + assert test_widget.name_num_label.text() == "Steven:02" + + +def test_macro_substitution_list_widget(test_widget: WidgetForBuilderTest): + assert test_widget.one_two_shell.readCommands() == ["echo ${ONE}", "echo ${TWO}", "echo ${ONE}:${TWO}"] + + test_widget.setProperty("one", "UNO") + + assert test_widget.one_two_shell.readCommands() == ["echo ${ONE}", "echo ${TWO}", "echo ${ONE}:${TWO}"] + + test_widget.setProperty("two", "DOS") + + assert test_widget.one_two_shell.readCommands() == ["echo UNO", "echo DOS", "echo UNO:DOS"] + + test_widget.setProperty("one", "ICHI") + + assert test_widget.one_two_shell.readCommands() == ["echo ICHI", "echo DOS", "echo ICHI:DOS"] diff --git a/pcdswidgets/tests/builder/test_entrypoint_widgets.py b/pcdswidgets/tests/builder/test_entrypoint_widgets.py new file mode 100644 index 0000000..e83de9e --- /dev/null +++ b/pcdswidgets/tests/builder/test_entrypoint_widgets.py @@ -0,0 +1,107 @@ +from importlib.metadata import entry_points + +import pytest +from pydm.config import ENTRYPOINT_WIDGET +from qtpy.QtWidgets import QWidget + +from pcdswidgets.builder.entrypoint_finder import get_widget_entrypoint_data, iter_all_widgets + + +def test_entrypoint_has_all_widgets(): + """ + Ensure that all widgets are included in designer via the pydm designer plugin. + + If this fails, but test_toml_has_all_widgets is passing, it's likely because + pcdswidgets is not installed in dev mode. + """ + pydm_widgets = entry_points(group=ENTRYPOINT_WIDGET) + name_and_entrypoint = get_widget_entrypoint_data() + for name, entrypoint in name_and_entrypoint: + assert pydm_widgets.select(name=name)[name].value == entrypoint + + +container_widgets = [ + "FilterSortWidgetTable", +] + +# Don't check widgets from before we made sizing/naming standards +exempt_widgets = [ + "ApertureValve", + "CapacitanceManometerGauge", + "ColdCathodeComboGauge", + "ColdCathodeGauge", + "ControlOnlyValveNC", + "ControlOnlyValveNO", + "ControlValve", + "EPSByteIndicator", + "FastShutter", + "GetterPump", + "HotCathodeComboGauge", + "HotCathodeGauge", + "IonPump", + "NeedleValve", + "PneumaticValve", + "PneumaticValveDA", + "PneumaticValveNO", + "ProportionalValve", + "RGA", + "RightAngleManualValve", + "RoughGauge", + "ScrollPump", + "TurboPump", +] + container_widgets + + +@pytest.mark.parametrize( + "widget_name,WidgetCls", [elem for elem in iter_all_widgets() if elem[0] not in exempt_widgets] +) +def test_widget_sizing(widget_name: str, WidgetCls: type[QWidget], qtbot): + """ + Ensure that all widgets are named and sized appropriately as per our standards. + """ + widget = WidgetCls() + qtbot.addWidget(widget) + # 20% smaller is OK + ratio = 0.8 + + if widget_name.endswith("Full"): + max_w = 400 + max_h = 125 + min_w = ratio * max_w + min_h = ratio * max_h + elif widget_name.endswith("Compact"): + max_w = 100 + max_h = 75 + min_w = ratio * max_w + min_h = ratio * max_h + elif widget_name.endswith("Row"): + max_w = 800 + max_h = 50 + min_w = ratio * max_w + min_h = ratio * max_h + # Allow double rows + max_h = max_h * 2 + else: + raise ValueError( + f"Widget named {widget_name} does not follow naming convention: " + "must end with Full, Compact, or Row to signal size class." + ) + + assert widget.minimumWidth() >= min_w, f"{widget_name}'s minimum width is too small." + assert widget.maximumWidth() <= max_w, f"{widget_name}'s maximum width is too large." + assert widget.minimumHeight() >= min_h, f"{widget_name}'s minimum height is too small." + assert widget.maximumHeight() <= max_h, f"{widget_name}'s maximum height is too large." + + +@pytest.mark.parametrize("widget_name,WidgetCls", [elem for elem in iter_all_widgets() if elem[0] in container_widgets]) +def test_container_widget_sizing(widget_name: str, WidgetCls: type[QWidget], qtbot): + """ + Ensure that container widgets have no maximum size. + """ + widget = WidgetCls() + qtbot.addWidget(widget) + + # The max size is currently 16777215 + # Pick a smaller but still absurd number as the threshold + assert widget.maximumWidth() >= 100000, f"{widget_name}'s maximum width is too small." + assert widget.maximumHeight() >= 100000, f"{widget_name}'s maximum height is too small." diff --git a/pcdswidgets/tests/builder/widget_for_builder_test.py b/pcdswidgets/tests/builder/widget_for_builder_test.py new file mode 100644 index 0000000..414f005 --- /dev/null +++ b/pcdswidgets/tests/builder/widget_for_builder_test.py @@ -0,0 +1,16 @@ +""" +Originally generated from jinja template ui_main_widget.j2 + +This file can be safely edited to change the runtime behavior of the widget. +""" + +from pcdswidgets.builder.designer_options import DesignerOptions +from pcdswidgets.generated.tests.builder.widget_for_builder_test_base import WidgetForBuilderTestBase + + +class WidgetForBuilderTest(WidgetForBuilderTestBase): + designer_options = DesignerOptions( + group="ECS Tests Builder", + is_container=False, + icon=None, + ) diff --git a/pcdswidgets/tests/test_icons.py b/pcdswidgets/tests/test_icons.py index 780028f..8437be9 100644 --- a/pcdswidgets/tests/test_icons.py +++ b/pcdswidgets/tests/test_icons.py @@ -7,11 +7,10 @@ import pcdswidgets.icons from pcdswidgets.icons.base import BaseSymbolIcon -icons = [getattr(pcdswidgets.icons, icon) - for icon in pcdswidgets.icons.__all__] +icons = [getattr(pcdswidgets.icons, icon) for icon in pcdswidgets.icons.__all__] -@pytest.mark.parametrize('icon_class', icons, ids=pcdswidgets.icons.__all__) +@pytest.mark.parametrize("icon_class", icons, ids=pcdswidgets.icons.__all__) def test_icon_smoke(qtbot, icon_class): icon = icon_class() qtbot.addWidget(icon) @@ -20,19 +19,23 @@ def test_icon_smoke(qtbot, icon_class): icon.repaint() -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def icon(qtbot): icon = BaseSymbolIcon() qtbot.addWidget(icon) return icon -@pytest.mark.parametrize('prop,value', - [('brush', QBrush(QColor(0, 0, 0), Qt.SolidPattern)), - ('penStyle', Qt.DotLine), - ('penColor', QColor(0, 0, 0)), - ('penWidth', 2.0)], - ids=('brush', 'penStyle', 'penColor', 'penWidth')) +@pytest.mark.parametrize( + "prop,value", + [ + ("brush", QBrush(QColor(0, 0, 0), Qt.SolidPattern)), + ("penStyle", Qt.DotLine), + ("penColor", QColor(0, 0, 0)), + ("penWidth", 2.0), + ], + ids=("brush", "penStyle", "penColor", "penWidth"), +) def test_icon_properties(icon, prop, value): setattr(icon, prop, value) assert getattr(icon, prop) == value diff --git a/pcdswidgets/tests/vacuum/test_base.py b/pcdswidgets/tests/vacuum/test_base.py index 3fc8665..dbc40bb 100644 --- a/pcdswidgets/tests/vacuum/test_base.py +++ b/pcdswidgets/tests/vacuum/test_base.py @@ -7,12 +7,13 @@ class BaseSymbol(PCDSSymbolBase): """Test Symbol for base class tests""" + def __init__(self, parent=None): super().__init__(parent=parent) self.icon = RGASymbolIcon(parent=self) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def symbol(qtbot): symbol = BaseSymbol() qtbot.addWidget(symbol) @@ -35,12 +36,16 @@ def test_no_controls_content(symbol): assert widget == symbol.icon -@pytest.mark.parametrize('location,layout,position', - [(ContentLocation.Top, QVBoxLayout, 0), - (ContentLocation.Bottom, QVBoxLayout, 1), - (ContentLocation.Left, QHBoxLayout, 0), - (ContentLocation.Right, QHBoxLayout, 1)], - ids=['Top', 'Bottom', 'Left', 'Right']) +@pytest.mark.parametrize( + "location,layout,position", + [ + (ContentLocation.Top, QVBoxLayout, 0), + (ContentLocation.Bottom, QVBoxLayout, 1), + (ContentLocation.Left, QHBoxLayout, 0), + (ContentLocation.Right, QHBoxLayout, 1), + ], + ids=["Top", "Bottom", "Left", "Right"], +) def test_controls_content_location(symbol, location, layout, position): symbol.controlsLocation = location assert isinstance(symbol.interlock.layout(), layout) @@ -56,18 +61,22 @@ def test_icon_fixed_size(symbol): assert symbol.icon.height() == size -@pytest.mark.parametrize('rotate', (False, True), ids=('Standard', 'Rotated')) +@pytest.mark.parametrize("rotate", (False, True), ids=("Standard", "Rotated")) def test_icon_rotation(symbol, rotate): symbol.rotateIcon = rotate assert symbol.icon.rotation == 90 * int(rotate) -@pytest.mark.parametrize('location,layout,position', - [(ContentLocation.Top, QVBoxLayout, 0), - (ContentLocation.Bottom, QVBoxLayout, 1), - (ContentLocation.Left, QVBoxLayout, 0), - (ContentLocation.Right, QVBoxLayout, 1)], - ids=['Top', 'Bottom', 'Left', 'Right']) +@pytest.mark.parametrize( + "location,layout,position", + [ + (ContentLocation.Top, QVBoxLayout, 0), + (ContentLocation.Bottom, QVBoxLayout, 1), + (ContentLocation.Left, QVBoxLayout, 0), + (ContentLocation.Right, QVBoxLayout, 1), + ], + ids=["Top", "Bottom", "Left", "Right"], +) def test_text_location(symbol, location, layout, position): symbol.controlsLocation = ContentLocation.Bottom symbol.channelsPrefix = "ca://area:function:device:01" @@ -79,10 +88,11 @@ def test_text_location(symbol, location, layout, position): assert widget == symbol.name -@pytest.mark.parametrize('location,layout,position', - [(ContentLocation.Left, QHBoxLayout, 0), - (ContentLocation.Right, QHBoxLayout, 1)], - ids=['Left', 'Right']) +@pytest.mark.parametrize( + "location,layout,position", + [(ContentLocation.Left, QHBoxLayout, 0), (ContentLocation.Right, QHBoxLayout, 1)], + ids=["Left", "Right"], +) def test_text_and_controls_location(symbol, location, layout, position): symbol.controlsLocation = location symbol.channelsPrefix = "ca://area:function:device:01" diff --git a/pcdswidgets/tests/vacuum/test_mixins.py b/pcdswidgets/tests/vacuum/test_mixins.py index 102e053..a33449d 100644 --- a/pcdswidgets/tests/vacuum/test_mixins.py +++ b/pcdswidgets/tests/vacuum/test_mixins.py @@ -2,12 +2,12 @@ from qtpy.QtWidgets import QWidget from pcdswidgets.vacuum.base import PCDSSymbolBase -from pcdswidgets.vacuum.mixins import (ErrorMixin, InterlockMixin, - OpenCloseStateMixin, StateMixin) +from pcdswidgets.vacuum.mixins import ErrorMixin, InterlockMixin, OpenCloseStateMixin, StateMixin class PCDSSymbolWithIcon(PCDSSymbolBase): """Base mixable class""" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.icon = QWidget(parent=self) @@ -15,61 +15,63 @@ def __init__(self, *args, **kwargs): class Interlock(InterlockMixin, PCDSSymbolWithIcon): """Simplest Interlock Widget""" + pass class Error(ErrorMixin, PCDSSymbolWithIcon): """Simplest Error Widget""" + pass class State(StateMixin, PCDSSymbolWithIcon): """Simplest State Widget""" + pass class OpenClose(OpenCloseStateMixin, PCDSSymbolWithIcon): """Simplest OpenCloseState Widget""" + pass -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def interlock(qtbot): - inter = Interlock(':ILK') + inter = Interlock(":ILK") qtbot.addWidget(inter) inter.create_channels() return inter -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def error(qtbot): - error = Error(':ILK') + error = Error(":ILK") qtbot.addWidget(error) error.create_channels() - error.error_enum_changed(('Bad', 'Good')) + error.error_enum_changed(("Bad", "Good")) return error -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def state(qtbot): - state = State(':Status') + state = State(":Status") qtbot.addWidget(state) state.create_channels() - state.state_enum_changed(('Bad', 'Good')) + state.state_enum_changed(("Bad", "Good")) return state -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def openclose(qtbot): - openclose = OpenClose(':Open', 'Close') + openclose = OpenClose(":Open", "Close") qtbot.addWidget(openclose) openclose.create_channels() return openclose -@pytest.mark.parametrize('interlock_bit', - (0, 1), - ids=('low', 'high')) +@pytest.mark.parametrize("interlock_bit", (0, 1), ids=("low", "high")) def test_interlock_value_changed(interlock, interlock_bit): interlock.interlock_value_changed(not interlock_bit) orig_tooltip = interlock.status_tooltip() @@ -83,22 +85,23 @@ def test_interlock_value_changed(interlock, interlock_bit): def test_error_value_changed(error): orig_tooltip = error.status_tooltip() error.error_value_changed(1) - error.error == 'Good' + assert error.error == "Good" assert orig_tooltip != error.status_tooltip() def test_state_value_changed(state): orig_tooltip = state.status_tooltip() state.state_value_changed(1) - state.state == 'Good' + assert state.state == "Good" assert orig_tooltip != state.status_tooltip() -@pytest.mark.parametrize('open_switch,closed_switch,state', - [(1, 1, 'INVALID'), (1, 0, 'Open'), - (0, 1, 'Close'), (0, 0, 'INVALID')], - ids=['Fault', 'Open', 'Closed', 'Invalid']) +@pytest.mark.parametrize( + "open_switch,closed_switch,state", + [(1, 1, "INVALID"), (1, 0, "Open"), (0, 1, "Close"), (0, 0, "INVALID")], + ids=["Fault", "Open", "Closed", "Invalid"], +) def test_openclose_value_changed(openclose, open_switch, closed_switch, state): - openclose.state_value_changed('OPEN', open_switch) - openclose.state_value_changed('CLOSE', closed_switch) + openclose.state_value_changed("OPEN", open_switch) + openclose.state_value_changed("CLOSE", closed_switch) assert openclose.state == state diff --git a/pcdswidgets/tests/vacuum/test_symbols.py b/pcdswidgets/tests/vacuum/test_symbols.py index 15cb218..dbe10e2 100644 --- a/pcdswidgets/tests/vacuum/test_symbols.py +++ b/pcdswidgets/tests/vacuum/test_symbols.py @@ -2,11 +2,10 @@ import pcdswidgets.vacuum -symbols = [getattr(pcdswidgets.vacuum, symbol) - for symbol in pcdswidgets.vacuum.__all__] +symbols = [getattr(pcdswidgets.vacuum, symbol) for symbol in pcdswidgets.vacuum.__all__] -@pytest.mark.parametrize('symbol', symbols, ids=pcdswidgets.vacuum.__all__) +@pytest.mark.parametrize("symbol", symbols, ids=pcdswidgets.vacuum.__all__) def test_vacuum_widgets(qtbot, symbol): widget = symbol() qtbot.addWidget(widget) diff --git a/pcdswidgets/ui/motion/common/motor_classic_full.ui b/pcdswidgets/ui/motion/common/motor_classic_full.ui new file mode 100644 index 0000000..fbf0e03 --- /dev/null +++ b/pcdswidgets/ui/motion/common/motor_classic_full.ui @@ -0,0 +1,715 @@ + + + Form + + + + 0 + 0 + 400 + 125 + + + + + 0 + 0 + + + + + 400 + 125 + + + + + 400 + 125 + + + + Form + + + + 1 + + + QLayout::SetMinimumSize + + + 1 + + + 1 + + + 0 + + + 1 + + + + + + 0 + 0 + + + + QFrame::Box + + + QFrame::Sunken + + + + 1 + + + 1 + + + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 75 + 0 + + + + + 300 + 16777215 + + + + + 12 + 75 + true + + + + + + + false + + + ca://${MOTOR}.DESC + + + PyDMLabel::String + + + + + + + + + 0 + + + 0 + + + + + false + + + + 0 + 0 + + + + + 25 + 0 + + + + + 25 + 16777215 + + + + + + + false + + + ca://${MOTOR}.MOVN + + + false + + + true + + + + + + + + + 7 + + + 7 + + + 0 + + + 7 + + + + + + 15 + 15 + + + + + + + false + + + true + + + + + + ca://${MOTOR}.LLS + + + false + + + false + + + 99 + + + false + + + false + + + false + + + 1 + + + 0 + + + + Bit 0 + + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + 14 + 50 + false + + + + + + + Qt::AlignCenter + + + ca://${MOTOR}.RBV + + + PyDMLabel::Decimal + + + + + + + + 15 + 15 + + + + + + + false + + + true + + + + + + ca://${MOTOR}.HLS + + + false + + + false + + + 99 + + + false + + + false + + + false + + + 1 + + + 0 + + + + Bit 0 + + + + + + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 60 + 16777215 + + + + + + + background-color: rgb(170, 0, 0); + + + Stop + + + ca://${MOTOR}.STOP + + + 1 + + + + + + + + + 30 + + + 30 + + + + + false + + + + 0 + 0 + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + 12 + + + + + + + 12 + + + Qt::AlignCenter + + + 0 + + + true + + + false + + + ca://${MOTOR}.VAL + + + PyDMLineEdit::Decimal + + + + + + + + + 7 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 60 + 0 + + + + + 150 + 16777215 + + + + + 10 + + + + + + + 0 + + + false + + + true + + + false + + + false + + + + + + ca://${MOTOR}.EGU + + + + + + + + + + + + 3 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + + 35 + 0 + + + + + 35 + 16777215 + + + + + + + << + + + ca://${MOTOR}.TWR + + + 1 + + + true + + + + + + + + 0 + 0 + + + + + 70 + 0 + + + + + 200 + 16777215 + + + + + 12 + + + + + + + Qt::AlignCenter + + + 0 + + + true + + + false + + + ca://${MOTOR}.TWV + + + + + + + + 0 + 0 + + + + + 35 + 0 + + + + + 35 + 16777215 + + + + + + + >> + + + ca://${MOTOR}.TWF + + + 1 + + + true + + + + + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + + 60 + 16777215 + + + + + + + + motor-expert-screen ${MOTOR} + + + + + + + + + + + + + + PyDMLabel + QLabel +
pydm.widgets.label
+
+ + PyDMByteIndicator + QWidget +
pydm.widgets.byte
+
+ + PyDMLineEdit + QLineEdit +
pydm.widgets.line_edit
+
+ + PyDMPushButton + QPushButton +
pydm.widgets.pushbutton
+
+ + PyDMShellCommand + QPushButton +
pydm.widgets.shell_command
+
+
+ + +
diff --git a/pcdswidgets/ui/motion/common/motor_classic_row.ui b/pcdswidgets/ui/motion/common/motor_classic_row.ui new file mode 100644 index 0000000..67eb05e --- /dev/null +++ b/pcdswidgets/ui/motion/common/motor_classic_row.ui @@ -0,0 +1,664 @@ + + + Form + + + + 0 + 0 + 753 + 45 + + + + + 0 + 0 + + + + + 700 + 45 + + + + + 800 + 50 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 11 + + + + QFrame::Box + + + QFrame::Sunken + + + + 1 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 35 + 0 + + + + + + + >> + + + false + + + false + + + + + + ca://${MOTOR}.TWF + + + false + + + + + + + + + false + + + Are you sure you want to proceed? + + + 1 + + + None + + + false + + + false + + + + + + + + 0 + 0 + + + + + 15 + 15 + + + + + + + false + + + true + + + + + + ca://${MOTOR}.HLS + + + + 255 + 165 + 0 + + + + false + + + false + + + false + + + 1 + + + + Bit 0 + + + + + + + + + 0 + 0 + + + + + 125 + 0 + + + + + 125 + 16777215 + + + + + 10 + 75 + true + + + + + + + false + + + ca://${MOTOR}.DESC + + + PyDMLabel::String + + + + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + + 10 + 50 + false + + + + + + + 0 + + + false + + + true + + + false + + + false + + + ca://${MOTOR}.EGU + + + + + + + + + + false + + + + motor-expert-screen ${MOTOR} + + + + + + + + + 60 + 0 + + + + + 150 + 16777215 + + + + + 11 + + + + + + + 0 + + + false + + + true + + + false + + + false + + + ca://${MOTOR}.TWV + + + + + + + + 0 + 0 + + + + + 15 + 15 + + + + + + + false + + + true + + + + + + ca://${MOTOR}.LLS + + + + 255 + 165 + 0 + + + + false + + + false + + + false + + + 1 + + + 0 + + + + Bit 0 + + + + + + + + + 0 + 0 + + + + + 125 + 0 + + + + + 14 + 50 + false + + + + + + + Qt::AlignCenter + + + 0 + + + false + + + true + + + false + + + true + + + ca://${MOTOR}.RBV + + + PyDMLabel::Decimal + + + + + + + false + + + + 0 + 0 + + + + + 15 + 15 + + + + + 15 + 15 + + + + + + + false + + + true + + + ca://${MOTOR}.MOVN + + + false + + + false + + + true + + + 1 + + + 0 + + + + Bit 0 + + + + + + + + + 35 + 0 + + + + + + + << + + + false + + + false + + + + + + ca://${MOTOR}.TWR + + + false + + + + + + + + + false + + + Are you sure you want to proceed? + + + 1 + + + None + + + false + + + false + + + + + + + + 40 + 0 + + + + + 40 + 16777215 + + + + + + + background-color: rgb(170, 0, 0); + + + Stop + + + ca://${MOTOR}.STOP + + + 1 + + + + + + + false + + + + 0 + 0 + + + + + 75 + 0 + + + + + 11 + + + + + + + 0 + + + false + + + true + + + false + + + false + + + ca://${MOTOR}.VAL + + + PyDMLineEdit::Default + + + + + + + + + + + PyDMLabel + QLabel +
pydm.widgets.label
+
+ + PyDMByteIndicator + QWidget +
pydm.widgets.byte
+
+ + PyDMLineEdit + QLineEdit +
pydm.widgets.line_edit
+
+ + PyDMPushButton + QPushButton +
pydm.widgets.pushbutton
+
+ + PyDMShellCommand + QPushButton +
pydm.widgets.shell_command
+
+
+ + +
diff --git a/pcdswidgets/ui/motion/common/motor_tc_classic_row.ui b/pcdswidgets/ui/motion/common/motor_tc_classic_row.ui new file mode 100644 index 0000000..ecdc82b --- /dev/null +++ b/pcdswidgets/ui/motion/common/motor_tc_classic_row.ui @@ -0,0 +1,261 @@ + + + Form + + + + 0 + 0 + 725 + 100 + + + + + 725 + 100 + + + + + 800 + 100 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + ${MOTOR} + + + + + + + 6 + + + + + + 10 + + + + Motor temperature: + + + + + + + + 100 + 16777215 + + + + + 10 + + + + + + + 1 + + + false + + + false + + + false + + + true + + + + + + ca://${MOTOR}:ILOCK:TC_TEMP_RBV + + + false + + + + + + + + 10 + + + + Interlock: + + + + + + + + 15 + 15 + + + + + 15 + 15 + + + + + 1 + + + + + + + false + + + true + + + + + + ca://${MOTOR}:ILOCK:ACTIVE_RBV + + + + 255 + 0 + 0 + + + + + 0 + 255 + 0 + + + + false + + + false + + + false + + + QTabWidget::West + + + 1 + + + 0 + + + + Bit 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + PyDMLabel + QLabel +
pydm.widgets.label
+
+ + PyDMByteIndicator + QWidget +
pydm.widgets.byte
+
+ + MotorClassicRow + QWidget +
pcdswidgets.motion.common.motor_classic_row
+
+
+ + +
diff --git a/pcdswidgets/ui/motion/smaract/smaract_open_loop_classic_row.ui b/pcdswidgets/ui/motion/smaract/smaract_open_loop_classic_row.ui new file mode 100644 index 0000000..18e697e --- /dev/null +++ b/pcdswidgets/ui/motion/smaract/smaract_open_loop_classic_row.ui @@ -0,0 +1,440 @@ + + + Form + + + + 0 + 0 + 640 + 40 + + + + + 0 + 0 + + + + + 640 + 40 + + + + + 800 + 45 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 5 + + + 5 + + + 5 + + + + + + + + true + + + false + + + false + + + + + + + edm -eolc -x -m MOTOR=${MOTOR} /reg/g/pcds/epics/ioc/common/smaract/R1.0.8/motorScreens/mcs2_openloop.edl + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + + 12 + 75 + true + + + + + + + 0 + + + false + + + true + + + false + + + true + + + ca://${MOTOR}.DESC + + + PyDMLabel::String + + + + + + + false + + + + 0 + 0 + + + + + 100 + 0 + + + + + 16777215 + 16777215 + + + + + 12 + + + + + + + 12 + + + 0 + + + false + + + true + + + false + + + true + + + ca://${MOTOR}:TOTAL_STEP_COUNT + + + PyDMLineEdit::Decimal + + + + + + + + 32 + 0 + + + + + + + >> + + + false + + + false + + + ca://${MOTOR}:STEP_FORWARD.PROC + + + false + + + + + + + + + false + + + Are you sure you want to proceed? + + + 1 + + + None + + + true + + + false + + + + + + + + 50 + 0 + + + + + 75 + 16777215 + + + + + 12 + + + + + + + 0 + + + false + + + true + + + false + + + false + + + ca://${MOTOR}:STEP_COUNT + + + + + + + + 40 + 0 + + + + + 40 + 16777215 + + + + + + + background-color: rgb(170, 0, 0); + + + Stop + + + false + + + false + + + ca://${MOTOR}.STOP + + + false + + + + + + + + + false + + + Are you sure you want to proceed? + + + 1 + + + None + + + false + + + false + + + + + + + + 32 + 0 + + + + + + + << + + + false + + + false + + + ca://${MOTOR}:STEP_REVERSE.PROC + + + false + + + + + + + + + false + + + Are you sure you want to proceed? + + + 1 + + + None + + + true + + + false + + + + + + + + 75 + 16777215 + + + + (steps) + + + + + + + + + + PyDMLabel + QLabel +
pydm.widgets.label
+
+ + PyDMLineEdit + QLineEdit +
pydm.widgets.line_edit
+
+ + PyDMPushButton + QPushButton +
pydm.widgets.pushbutton
+
+ + PyDMShellCommand + QPushButton +
pydm.widgets.shell_command
+
+
+ + +
diff --git a/pcdswidgets/ui/tests/builder/widget_for_builder_test.ui b/pcdswidgets/ui/tests/builder/widget_for_builder_test.ui new file mode 100644 index 0000000..e45b9a5 --- /dev/null +++ b/pcdswidgets/ui/tests/builder/widget_for_builder_test.ui @@ -0,0 +1,114 @@ + + + Form + + + + 0 + 0 + 400 + 191 + + + + Form + + + + + + ${NAME} + + + Name: ${NAME} + + + + + + + Num: ${NUM} + + + + + + + ${NAME}:${NUM} + + + + + + + + + + false + + + true + + + + + + + + + + + + false + + + false + + + Are you sure you want to proceed? + + + + + + true + + + false + + + + + + + echo ${ONE} + echo ${TWO} + echo ${ONE}:${TWO} + + + + false + + + + + + + + + false + + + + + + + + PyDMShellCommand + QPushButton +
pydm.widgets.shell_command
+
+
+ + +
diff --git a/pcdswidgets/vacuum/__init__.py b/pcdswidgets/vacuum/__init__.py index 41b2f75..0a1abae 100644 --- a/pcdswidgets/vacuum/__init__.py +++ b/pcdswidgets/vacuum/__init__.py @@ -1,16 +1,48 @@ -__all__ = ['HotCathodeGauge', 'RoughGauge', 'ColdCathodeGauge', 'IonPump', - 'TurboPump', 'ScrollPump', 'GetterPump', 'RGA', 'PneumaticValve', - 'ApertureValve', 'FastShutter', 'NeedleValve', 'ProportionalValve', - 'RightAngleManualValve', 'ControlValve', 'ControlOnlyValveNC', - 'ControlOnlyValveNO', 'PneumaticValveNO', 'PneumaticValveDA', - 'CapacitanceManometerGauge', 'HotCathodeComboGauge', 'ColdCathodeComboGauge'] +__all__ = [ + "HotCathodeGauge", + "RoughGauge", + "ColdCathodeGauge", + "IonPump", + "TurboPump", + "ScrollPump", + "GetterPump", + "RGA", + "PneumaticValve", + "ApertureValve", + "FastShutter", + "NeedleValve", + "ProportionalValve", + "RightAngleManualValve", + "ControlValve", + "ControlOnlyValveNC", + "ControlOnlyValveNO", + "PneumaticValveNO", + "PneumaticValveDA", + "CapacitanceManometerGauge", + "HotCathodeComboGauge", + "ColdCathodeComboGauge", +] -from .gauges import (CapacitanceManometerGauge, ColdCathodeComboGauge, - ColdCathodeGauge, HotCathodeComboGauge, HotCathodeGauge, - RoughGauge) +from .gauges import ( + CapacitanceManometerGauge, + ColdCathodeComboGauge, + ColdCathodeGauge, + HotCathodeComboGauge, + HotCathodeGauge, + RoughGauge, +) from .others import RGA from .pumps import GetterPump, IonPump, ScrollPump, TurboPump -from .valves import (ApertureValve, ControlOnlyValveNC, ControlOnlyValveNO, - ControlValve, FastShutter, NeedleValve, PneumaticValve, - PneumaticValveDA, PneumaticValveNO, ProportionalValve, - RightAngleManualValve) +from .valves import ( + ApertureValve, + ControlOnlyValveNC, + ControlOnlyValveNO, + ControlValve, + FastShutter, + NeedleValve, + PneumaticValve, + PneumaticValveDA, + PneumaticValveNO, + ProportionalValve, + RightAngleManualValve, +) diff --git a/pcdswidgets/vacuum/base.py b/pcdswidgets/vacuum/base.py index 33f5497..2457562 100644 --- a/pcdswidgets/vacuum/base.py +++ b/pcdswidgets/vacuum/base.py @@ -8,8 +8,17 @@ from pydm.widgets.embedded_display import PyDMEmbeddedDisplay from qtpy.QtCore import Q_ENUMS, Property, QSize, Qt from qtpy.QtGui import QCursor, QPainter -from qtpy.QtWidgets import (QFrame, QHBoxLayout, QLabel, QSizePolicy, QStyle, - QStyleOption, QTabWidget, QVBoxLayout, QWidget) +from qtpy.QtWidgets import ( + QFrame, + QHBoxLayout, + QLabel, + QSizePolicy, + QStyle, + QStyleOption, + QTabWidget, + QVBoxLayout, + QWidget, +) from ..utils import refresh_style @@ -21,6 +30,7 @@ class ContentLocation: Enum Class to be used by the widgets to configure the Controls Content Location. """ + Hidden = 0 Top = 1 Bottom = 2 @@ -38,11 +48,6 @@ class PCDSSymbolBase(QWidget, PyDMPrimitiveWidget, ContentLocation): The parent widget for this symbol. """ - _qt_designer_ = { - "group": "PCDS Symbols", - "is_container": False, - } - EXPERT_OPHYD_CLASS = "" Q_ENUMS(ContentLocation) @@ -67,35 +72,30 @@ def __init__(self, parent=None, **kwargs): self.name = QLabel(self) self.name.setWordWrap(True) - self.name.setSizePolicy(QSizePolicy.Maximum, - QSizePolicy.Maximum) + self.name.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.name.setAlignment(Qt.AlignCenter) self.name.setStyleSheet(f"font-size: {self._font_size}px; background: transparent") self.name.setVisible(self._show_name) - self._icon_cursor = self.setCursor( - QCursor(IconFont().icon("file").pixmap(16, 16)) - ) + self._icon_cursor = self.setCursor(QCursor(IconFont().icon("file").pixmap(16, 16))) self._expert_ophyd_class = self.EXPERT_OPHYD_CLASS or "" self.interlock = QFrame(self) self.interlock.setObjectName("interlock") - self.interlock.setSizePolicy(QSizePolicy.Expanding, - QSizePolicy.Expanding) + self.interlock.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.controls_frame = QFrame(self) self.controls_frame.setObjectName("controls") - self.controls_frame.setSizePolicy(QSizePolicy.Maximum, - QSizePolicy.Maximum) + self.controls_frame.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.setLayout(QVBoxLayout()) self.layout().setSpacing(0) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.interlock) - if not hasattr(self, '_controls_location'): + if not hasattr(self, "_controls_location"): self._controls_location = ContentLocation.Bottom - if not hasattr(self, '_text_lcoation'): + if not hasattr(self, "_text_lcoation"): self._text_location = ContentLocation.Top self.setup_icon() @@ -107,7 +107,7 @@ def __init__(self, parent=None, **kwargs): self.ui_file_titles = [] self.tab_widget = None - self.embedded_displays = list() + self.embedded_displays = [] def sizeHint(self): """ @@ -398,18 +398,16 @@ def iconSize(self, size): return if size <= 0: - size = - 1 + size = -1 min_size = 1 max_size = 999999 - self.icon.setSizePolicy(QSizePolicy.Expanding, - QSizePolicy.Expanding) + self.icon.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.icon.setMinimumSize(min_size, min_size) self.icon.setMaximumSize(max_size, max_size) else: self.icon.setFixedSize(size, size) - self.icon.setSizePolicy(QSizePolicy.Fixed, - QSizePolicy.Fixed) + self.icon.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self._icon_size = size self.icon.update() @@ -504,7 +502,7 @@ def clear(self): # empty widget. QWidget().setLayout(self.interlock.layout()) - def assemble_layout(self): + def assemble_layout(self): # noqa: C901 """ Assembles the widget's inner layout depending on the ContentLocation and other configurations set. @@ -518,7 +516,10 @@ def assemble_layout(self): grouped_widgets = QVBoxLayout() # Default # Determine what widgets to group - if self._text_location in [ContentLocation.Left, ContentLocation.Right] and self._text_location == self._controls_location: + if ( + self._text_location in [ContentLocation.Left, ContentLocation.Right] + and self._text_location == self._controls_location + ): grouped_widgets = QVBoxLayout() if self.name is not None: grouped_widgets.addWidget(self.name, alignment=Qt.AlignCenter) @@ -534,10 +535,14 @@ def assemble_layout(self): # Group icon and name if self._text_location in [ContentLocation.Left, ContentLocation.Right]: grouped_widgets = QHBoxLayout() - icon_and_text = [self.name, self.icon] if self._text_location == ContentLocation.Left else [self.icon, self.name] + icon_and_text = ( + [self.name, self.icon] if self._text_location == ContentLocation.Left else [self.icon, self.name] + ) else: grouped_widgets = QVBoxLayout() - icon_and_text = [self.name, self.icon] if self._text_location == ContentLocation.Top else [self.icon, self.name] + icon_and_text = ( + [self.name, self.icon] if self._text_location == ContentLocation.Top else [self.icon, self.name] + ) for widget in icon_and_text: if widget is None: @@ -548,10 +553,18 @@ def assemble_layout(self): if self._controls_location in [ContentLocation.Left, ContentLocation.Right]: layout_cls = QHBoxLayout - widgets = [self.controls_frame, grouped_frame] if self._controls_location == ContentLocation.Left else [grouped_frame, self.controls_frame] + widgets = ( + [self.controls_frame, grouped_frame] + if self._controls_location == ContentLocation.Left + else [grouped_frame, self.controls_frame] + ) else: layout_cls = QVBoxLayout - widgets = [self.controls_frame, grouped_frame] if self._controls_location == ContentLocation.Top else [grouped_frame, self.controls_frame] + widgets = ( + [self.controls_frame, grouped_frame] + if self._controls_location == ContentLocation.Top + else [grouped_frame, self.controls_frame] + ) grouped_widgets.setContentsMargins(0, 0, 0, 0) grouped_widgets.setSpacing(0) @@ -579,29 +592,29 @@ def setup_icon(self): if not self.icon: return self.icon.setMinimumSize(16, 16) - self.icon.setSizePolicy(QSizePolicy.Expanding, - QSizePolicy.Expanding) + self.icon.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.icon.setVisible(self._show_icon) self.iconSize = 32 - if hasattr(self.icon, 'clicked'): + if hasattr(self.icon, "clicked"): self.icon.clicked.connect(self._handle_icon_click) if self._expert_display is not None: self.icon.setCursor(self._icon_cursor) def _handle_icon_click(self): if not self.channelsPrefix: - logger.error('No channel prefix specified.' - 'Cannot proceed with opening expert screen for %s.', - self.__class__.__name__) + logger.error( + "No channel prefix specified.Cannot proceed with opening expert screen for %s.", + self.__class__.__name__, + ) return if self.tab_widget is not None: - logger.debug('Bringing existing custom display to front.') + logger.debug("Bringing existing custom display to front.") self.tab_widget.show() self.tab_widget.raise_() return elif self._expert_display is not None: - logger.debug('Bringing existing display to front.') + logger.debug("Bringing existing display to front.") self._expert_display.show() self._expert_display.raise_() return @@ -609,15 +622,14 @@ def _handle_icon_click(self): prefix = remove_protocol(self.channelsPrefix) klass = self.expertOphydClass if not klass: - logger.error('No expertOphydClass specified for pcdswidgets %s', - self.__class__.__name__) + logger.error("No expertOphydClass specified for pcdswidgets %s", self.__class__.__name__) return - name = prefix.replace(':', '_') + name = prefix.replace(":", "_") try: import typhos except ImportError: - logger.error('Typhos not installed. Cannot create display.') + logger.error("Typhos not installed. Cannot create display.") return kwargs = {"name": name, "prefix": prefix} @@ -630,14 +642,10 @@ def _handle_icon_click(self): self.tab_widget.setTabPosition(QTabWidget.TabPosition.West) self.tab_widget.addTab(display, "Typhos") - for file_path, title, macros in zip_longest( - self.ui_file_paths, - self.ui_file_titles, - self.ui_file_macros - ): + for file_path, title, macros in zip_longest(self.ui_file_paths, self.ui_file_titles, self.ui_file_macros): embedded = PyDMEmbeddedDisplay() title = title or file_path - macros = macros or '' + macros = macros or "" embedded.set_macros_and_filename(file_path, macros) self.tab_widget.addTab(embedded, title) @@ -648,7 +656,7 @@ def _handle_icon_click(self): elif display: display.show() - @Property('QStringList') + @Property("QStringList") def ui_paths(self): return self.ui_file_paths @@ -657,7 +665,7 @@ def ui_paths(self, path): if path != self.ui_file_paths: self.ui_file_paths = path - @Property('QStringList') + @Property("QStringList") def ui_macros(self): return self.ui_file_macros @@ -666,7 +674,7 @@ def ui_macros(self, macros): if macros != self.ui_macros: self.ui_file_macros = macros - @Property('QStringList') + @Property("QStringList") def ui_titles(self): return self.ui_file_titles @@ -687,7 +695,7 @@ def status_tooltip(self): str """ status = "" - if hasattr(self, 'NAME'): + if hasattr(self, "NAME"): status = self.NAME if status: status += os.linesep diff --git a/pcdswidgets/vacuum/demo/__main__.py b/pcdswidgets/vacuum/demo/__main__.py index 1804615..0d451a7 100644 --- a/pcdswidgets/vacuum/demo/__main__.py +++ b/pcdswidgets/vacuum/demo/__main__.py @@ -4,6 +4,7 @@ Invoke as e.g. "python -m pcdswidgets.vacuum.demo PneumaticValveDA CRIX:VGC:11" """ + import sys import pydm @@ -26,6 +27,6 @@ ) widget = globals()[cls]() -widget.channelsPrefix = 'ca://' + sys.argv[2] +widget.channelsPrefix = "ca://" + sys.argv[2] app.main_window.set_display_widget(widget) app.exec() diff --git a/pcdswidgets/vacuum/gauges.py b/pcdswidgets/vacuum/gauges.py index 7c74c1f..ba46d87 100644 --- a/pcdswidgets/vacuum/gauges.py +++ b/pcdswidgets/vacuum/gauges.py @@ -1,14 +1,16 @@ from pydm.widgets.display_format import DisplayFormat from qtpy.QtCore import QSize -from ..icons.gauges import (CapManometerGaugeSymbolIcon, - ColdCathodeComboGaugeSymbolIcon, - ColdCathodeGaugeSymbolIcon, - HotCathodeComboGaugeSymbolIcon, - HotCathodeGaugeSymbolIcon, RoughGaugeSymbolIcon) +from ..icons.gauges import ( + CapManometerGaugeSymbolIcon, + ColdCathodeComboGaugeSymbolIcon, + ColdCathodeGaugeSymbolIcon, + HotCathodeComboGaugeSymbolIcon, + HotCathodeGaugeSymbolIcon, + RoughGaugeSymbolIcon, +) from .base import PCDSSymbolBase -from .mixins import (ButtonLabelControl, InterlockMixin, LabelControl, - StateMixin) +from .mixins import ButtonLabelControl, InterlockMixin, LabelControl, StateMixin class RoughGauge(StateMixin, LabelControl, PCDSSymbolBase): @@ -66,7 +68,7 @@ class RoughGauge(StateMixin, LabelControl, PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Gauges", + "group": "ECS Vacuum Gauges", "is_container": False, } _state_suffix = ":STATE_RBV" @@ -80,8 +82,9 @@ def __init__(self, parent=None, **kwargs): parent=parent, state_suffix=self._state_suffix, readback_suffix=self._readback_suffix, - readback_name='pressure', - **kwargs) + readback_name="pressure", + **kwargs, + ) self.icon = RoughGaugeSymbolIcon(parent=self) self.readback_label.displayFormat = DisplayFormat.Exponential @@ -150,7 +153,7 @@ class HotCathodeGauge(ButtonLabelControl, InterlockMixin, StateMixin, PCDSSymbol """ _qt_designer_ = { - "group": "PCDS Gauges", + "group": "ECS Vacuum Gauges", "is_container": False, } _interlock_suffix = ":ILK_OK_RBV" @@ -167,8 +170,9 @@ def __init__(self, parent=None, **kwargs): state_suffix=self._state_suffix, command_suffix=self._command_suffix, readback_suffix=self._readback_suffix, - readback_name='pressure', - **kwargs) + readback_name="pressure", + **kwargs, + ) self.icon = HotCathodeGaugeSymbolIcon(parent=self) self.readback_label.displayFormat = DisplayFormat.Exponential @@ -237,7 +241,7 @@ class ColdCathodeGauge(InterlockMixin, StateMixin, ButtonLabelControl, PCDSSymbo """ _qt_designer_ = { - "group": "PCDS Gauges", + "group": "ECS Vacuum Gauges", "is_container": False, } _interlock_suffix = ":ILK_OK_RBV" @@ -255,8 +259,9 @@ def __init__(self, parent=None, **kwargs): state_suffix=self._state_suffix, command_suffix=self._command_suffix, readback_suffix=self._readback_suffix, - readback_name='pressure', - **kwargs) + readback_name="pressure", + **kwargs, + ) self.icon = ColdCathodeGaugeSymbolIcon(parent=self) self.readback_label.displayFormat = DisplayFormat.Exponential @@ -319,7 +324,7 @@ class ColdCathodeComboGauge(StateMixin, LabelControl, PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Gauges", + "group": "ECS Vacuum Gauges", "is_container": False, } _state_suffix = ":STATE_RBV" @@ -333,8 +338,9 @@ def __init__(self, parent=None, **kwargs): parent=parent, state_suffix=self._state_suffix, readback_suffix=self._readback_suffix, - readback_name='pressure', - **kwargs) + readback_name="pressure", + **kwargs, + ) self.icon = ColdCathodeComboGaugeSymbolIcon(parent=self) self.readback_label.displayFormat = DisplayFormat.Exponential @@ -397,7 +403,7 @@ class HotCathodeComboGauge(StateMixin, LabelControl, PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Gauges", + "group": "ECS Vacuum Gauges", "is_container": False, } _state_suffix = ":STATE_RBV" @@ -411,8 +417,9 @@ def __init__(self, parent=None, **kwargs): parent=parent, state_suffix=self._state_suffix, readback_suffix=self._readback_suffix, - readback_name='pressure', - **kwargs) + readback_name="pressure", + **kwargs, + ) self.icon = HotCathodeComboGaugeSymbolIcon(parent=self) self.readback_label.displayFormat = DisplayFormat.Exponential @@ -475,7 +482,7 @@ class CapacitanceManometerGauge(StateMixin, LabelControl, PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Gauges", + "group": "ECS Vacuum Gauges", "is_container": False, } _state_suffix = ":STATE_RBV" @@ -489,8 +496,9 @@ def __init__(self, parent=None, **kwargs): parent=parent, state_suffix=self._state_suffix, readback_suffix=self._readback_suffix, - readback_name='pressure', - **kwargs) + readback_name="pressure", + **kwargs, + ) self.icon = CapManometerGaugeSymbolIcon(parent=self) self.readback_label.displayFormat = DisplayFormat.Exponential diff --git a/pcdswidgets/vacuum/mixins.py b/pcdswidgets/vacuum/mixins.py index e3706fc..01ed810 100644 --- a/pcdswidgets/vacuum/mixins.py +++ b/pcdswidgets/vacuum/mixins.py @@ -32,6 +32,7 @@ class InterlockMixin: The suffix to be used along with the channelPrefix from PCDSSymbolBase to compose the interlock channel address. """ + def __init__(self, interlock_suffix, **kwargs): self._interlock_suffix = interlock_suffix self._interlocked = False @@ -64,10 +65,9 @@ def create_channels(self): self._interlock_connected = False self.interlock_channel = PyDMChannel( - address="{}{}".format(self._channels_prefix, - self._interlock_suffix), + address="{}{}".format(self._channels_prefix, self._interlock_suffix), connection_slot=self.interlock_connection_changed, - value_slot=self.interlock_value_changed + value_slot=self.interlock_value_changed, ) self.interlock_channel.connect() @@ -133,6 +133,7 @@ class ErrorMixin: The suffix to be used along with the channelPrefix from PCDSSymbolBase to compose the error channel address. """ + def __init__(self, error_suffix, **kwargs): self._error_suffix = error_suffix self._error = "" @@ -170,7 +171,7 @@ def create_channels(self): address=f"{self._channels_prefix}{self._error_suffix}", connection_slot=self.error_connection_changed, value_slot=self.error_value_changed, - enum_strings_slot=self.error_enum_changed + enum_strings_slot=self.error_enum_changed, ) self.error_channel.connect() @@ -271,6 +272,7 @@ class StateMixin: The suffix to be used along with the channelPrefix from PCDSSymbolBase to compose the state channel address. """ + def __init__(self, state_suffix, **kwargs): self._state_suffix = state_suffix self._state = "" @@ -308,7 +310,7 @@ def create_channels(self): address=f"{self._channels_prefix}{self._state_suffix}", connection_slot=self.state_connection_changed, value_slot=self.state_value_changed, - enum_strings_slot=self.state_enum_changed + enum_strings_slot=self.state_enum_changed, ) self.state_channel.connect() @@ -414,6 +416,7 @@ class OpenCloseStateMixin: The suffix to be used along with the channelPrefix from PCDSSymbolBase to compose the close state channel address. """ + def __init__(self, open_suffix, close_suffix, **kwargs): self._open_suffix = open_suffix self._close_suffix = close_suffix @@ -465,14 +468,14 @@ def create_channels(self): self.state_open_channel = PyDMChannel( address=f"{self._channels_prefix}{self._open_suffix}", connection_slot=partial(self.state_connection_changed, "OPEN"), - value_slot=partial(self.state_value_changed, "OPEN") + value_slot=partial(self.state_value_changed, "OPEN"), ) self.state_open_channel.connect() self.state_close_channel = PyDMChannel( address=f"{self._channels_prefix}{self._close_suffix}", connection_slot=partial(self.state_connection_changed, "CLOSE"), - value_slot=partial(self.state_value_changed, "CLOSE") + value_slot=partial(self.state_value_changed, "CLOSE"), ) self.state_close_channel.connect() @@ -542,6 +545,7 @@ class ButtonControl: The suffix to be used along with the channelPrefix from PCDSSymbolBase to compose the command button channel address. """ + def __init__(self, command_suffix, **kwargs): self._command_suffix = command_suffix self._orientation = Qt.Horizontal @@ -578,8 +582,7 @@ def create_channels(self): """ super().create_channels() if self._channels_prefix: - self.control_btn.channel = "{}{}".format(self._channels_prefix, - self._command_suffix) + self.control_btn.channel = "{}{}".format(self._channels_prefix, self._command_suffix) def destroy_channels(self): """ @@ -604,8 +607,8 @@ class LabelControl: The name to be set to the PyDMLabel so one can refer to it by name with stylesheet """ - def __init__(self, readback_suffix, readback_name, - **kwargs): + + def __init__(self, readback_suffix, readback_name, **kwargs): self._readback_suffix = readback_suffix self.readback_label = PyDMLabel() if readback_name: @@ -626,8 +629,7 @@ def create_channels(self): """ super().create_channels() if self._channels_prefix: - self.readback_label.channel = "{}{}".format(self._channels_prefix, - self._readback_suffix) + self.readback_label.channel = "{}{}".format(self._channels_prefix, self._readback_suffix) def destroy_channels(self): """ @@ -656,8 +658,8 @@ class ButtonLabelControl(ButtonControl): The name to be set to the PyDMLabel so one can refer to it by name with stylesheet """ - def __init__(self, command_suffix, readback_suffix, readback_name, - **kwargs): + + def __init__(self, command_suffix, readback_suffix, readback_name, **kwargs): self._readback_suffix = readback_suffix self.readback_label = PyDMLabel() @@ -674,8 +676,7 @@ def create_channels(self): """ super().create_channels() if self._channels_prefix: - self.readback_label.channel = "{}{}".format(self._channels_prefix, - self._readback_suffix) + self.readback_label.channel = "{}{}".format(self._channels_prefix, self._readback_suffix) def destroy_channels(self): """ @@ -706,6 +707,7 @@ class MultipleButtonControl: - value the value to be written when the button is pressed """ + def __init__(self, *, commands, **kwargs): self._command_buttons_config = commands self._orientation = Qt.Horizontal @@ -755,12 +757,12 @@ def clear_control_layout(self): def create_buttons(self): for btn in self._command_buttons_config: try: - text = btn['text'] - value = btn['value'] + text = btn["text"] + value = btn["value"] btn = PyDMPushButton(label=text, pressValue=value) self.buttons.append(btn) except KeyError: - logger.exception('Invalid config for MultipleButtonControl.') + logger.exception("Invalid config for MultipleButtonControl.") def create_channels(self): """ @@ -771,7 +773,7 @@ def create_channels(self): super().create_channels() if self._channels_prefix: for idx, btn in enumerate(self.buttons): - suffix = self._command_buttons_config[idx]['suffix'] + suffix = self._command_buttons_config[idx]["suffix"] btn.channel = f"{self._channels_prefix}{suffix}" def destroy_channels(self): diff --git a/pcdswidgets/vacuum/others.py b/pcdswidgets/vacuum/others.py index a3e089c..86ca40d 100644 --- a/pcdswidgets/vacuum/others.py +++ b/pcdswidgets/vacuum/others.py @@ -36,7 +36,7 @@ class RGA(PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Others", + "group": "ECS Vacuum Others", "is_container": False, } NAME = "Residual Gas Analyzer" diff --git a/pcdswidgets/vacuum/pumps.py b/pcdswidgets/vacuum/pumps.py index 40b3008..ab3e979 100644 --- a/pcdswidgets/vacuum/pumps.py +++ b/pcdswidgets/vacuum/pumps.py @@ -1,16 +1,12 @@ from pydm.widgets.display_format import DisplayFormat from qtpy.QtCore import Property, QSize -from ..icons.pumps import (GetterPumpSymbolIcon, IonPumpSymbolIcon, - ScrollPumpSymbolIcon, TurboPumpSymbolIcon) +from ..icons.pumps import GetterPumpSymbolIcon, IonPumpSymbolIcon, ScrollPumpSymbolIcon, TurboPumpSymbolIcon from .base import ContentLocation, PCDSSymbolBase -from .mixins import (ButtonControl, ButtonLabelControl, ErrorMixin, - InterlockMixin, StateMixin) +from .mixins import ButtonControl, ButtonLabelControl, ErrorMixin, InterlockMixin, StateMixin -class IonPump( - InterlockMixin, ErrorMixin, StateMixin, ButtonLabelControl, PCDSSymbolBase -): +class IonPump(InterlockMixin, ErrorMixin, StateMixin, ButtonLabelControl, PCDSSymbolBase): """ A Symbol Widget representing an Ion Pump with the proper icon and controls. @@ -77,7 +73,7 @@ class IonPump( """ _qt_designer_ = { - "group": "PCDS Pumps", + "group": "ECS Vacuum Pumps", "is_container": False, } _interlock_suffix = ":ILK_OK_RBV" @@ -98,7 +94,7 @@ def __init__(self, parent=None, **kwargs): command_suffix=self._command_suffix, readback_suffix=self._readback_suffix, readback_name="pressure", - **kwargs + **kwargs, ) self.icon = IonPumpSymbolIcon(parent=self) self.readback_label.displayFormat = DisplayFormat.Exponential @@ -172,7 +168,7 @@ class TurboPump(InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbo """ _qt_designer_ = { - "group": "PCDS Pumps", + "group": "ECS Vacuum Pumps", "is_container": False, } _interlock_suffix = ":ILK_OK_RBV" @@ -190,7 +186,8 @@ def __init__(self, parent=None, **kwargs): error_suffix=self._error_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = TurboPumpSymbolIcon(parent=self) def sizeHint(self): @@ -262,7 +259,7 @@ class ScrollPump(InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymb """ _qt_designer_ = { - "group": "PCDS Pumps", + "group": "ECS Vacuum Pumps", "is_container": False, } _interlock_suffix = ":ILK_OK_RBV" @@ -280,7 +277,8 @@ def __init__(self, parent=None, **kwargs): error_suffix=self._error_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = ScrollPumpSymbolIcon(parent=self) def sizeHint(self): @@ -319,7 +317,7 @@ class GetterPump(PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Pumps", + "group": "ECS Vacuum Pumps", "is_container": False, } NAME = "Getter Pump" diff --git a/pcdswidgets/vacuum/valves.py b/pcdswidgets/vacuum/valves.py index 68607ef..b57d136 100644 --- a/pcdswidgets/vacuum/valves.py +++ b/pcdswidgets/vacuum/valves.py @@ -3,22 +3,23 @@ from qtpy.QtCore import Property, QSize, Qt from qtpy.QtWidgets import QGridLayout -from ..icons.valves import (ApertureValveSymbolIcon, - ControlOnlyValveSymbolIcon, ControlValveSymbolIcon, - FastShutterSymbolIcon, NeedleValveSymbolIcon, - PneumaticValveDASymbolIcon, - PneumaticValveNOSymbolIcon, - PneumaticValveSymbolIcon, - ProportionalValveSymbolIcon, - RightAngleManualValveSymbolIcon) +from ..icons.valves import ( + ApertureValveSymbolIcon, + ControlOnlyValveSymbolIcon, + ControlValveSymbolIcon, + FastShutterSymbolIcon, + NeedleValveSymbolIcon, + PneumaticValveDASymbolIcon, + PneumaticValveNOSymbolIcon, + PneumaticValveSymbolIcon, + ProportionalValveSymbolIcon, + RightAngleManualValveSymbolIcon, +) from .base import ContentLocation, PCDSSymbolBase -from .mixins import (ButtonControl, ErrorMixin, InterlockMixin, - MultipleButtonControl, StateMixin) +from .mixins import ButtonControl, ErrorMixin, InterlockMixin, MultipleButtonControl, StateMixin -class PneumaticValve( - InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbolBase -): +class PneumaticValve(InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbolBase): """ A Symbol Widget representing a Pneumatic Valve with the proper icon and controls. @@ -91,7 +92,7 @@ class PneumaticValve( """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } @@ -110,16 +111,15 @@ def __init__(self, parent=None, **kwargs): error_suffix=self._error_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = PneumaticValveSymbolIcon(parent=self) def sizeHint(self): return QSize(180, 70) -class ApertureValve( - InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbolBase -): +class ApertureValve(InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbolBase): """ A Symbol Widget representing an Aperture Valve with the proper icon and controls. @@ -192,7 +192,7 @@ class ApertureValve( """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } _interlock_suffix = ":OPN_OK_RBV" @@ -210,16 +210,15 @@ def __init__(self, parent=None, **kwargs): error_suffix=self._error_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = ApertureValveSymbolIcon(parent=self) def sizeHint(self): return QSize(180, 70) -class FastShutter( - InterlockMixin, ErrorMixin, StateMixin, MultipleButtonControl, PCDSSymbolBase -): +class FastShutter(InterlockMixin, ErrorMixin, StateMixin, MultipleButtonControl, PCDSSymbolBase): """ A Symbol Widget representing a Fast Shutter with the proper icon and controls. @@ -286,7 +285,7 @@ class FastShutter( """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } _interlock_suffix = ":OPN_OK_RBV" @@ -307,7 +306,8 @@ def __init__(self, parent=None, **kwargs): error_suffix=self._error_suffix, state_suffix=self._state_suffix, commands=self._command_buttons, - **kwargs) + **kwargs, + ) self.icon = FastShutterSymbolIcon(parent=self) def sizeHint(self): @@ -373,7 +373,7 @@ class NeedleValve(InterlockMixin, StateMixin, ButtonControl, PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } _interlock_suffix = ":ILK_OK_RBV" @@ -389,7 +389,8 @@ def __init__(self, parent=None, **kwargs): interlock_suffix=self._interlock_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = NeedleValveSymbolIcon(parent=self) def sizeHint(self): @@ -455,7 +456,7 @@ class ProportionalValve(InterlockMixin, StateMixin, ButtonControl, PCDSSymbolBas """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } _interlock_suffix = ":ILK_OK_RBV" @@ -471,7 +472,8 @@ def __init__(self, parent=None, **kwargs): interlock_suffix=self._interlock_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = ProportionalValveSymbolIcon(parent=self) def sizeHint(self): @@ -511,7 +513,7 @@ class RightAngleManualValve(PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } NAME = "Right Angle Manual Valve" @@ -544,9 +546,7 @@ def controlsLocation(self): return super().controlsLocation -class ControlValve( - InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbolBase -): +class ControlValve(InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbolBase): """ A Symbol Widget representing a Control Valve with the proper icon and controls. @@ -619,10 +619,10 @@ class ControlValve( """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } - NAME = 'Control Valve with Readback' + NAME = "Control Valve with Readback" EXPERT_OPHYD_CLASS = "pcdsdevices.valve.VVC" _interlock_suffix = ":OPN_OK_RBV" @@ -637,7 +637,8 @@ def __init__(self, parent=None, **kwargs): error_suffix=self._error_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = ControlValveSymbolIcon(parent=self) def sizeHint(self): @@ -714,14 +715,14 @@ class ControlOnlyValveNC(InterlockMixin, StateMixin, ButtonControl, PCDSSymbolBa """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } - NAME = 'Normally Closed Control Valve with No Readback' + NAME = "Normally Closed Control Valve with No Readback" EXPERT_OPHYD_CLASS = "pcdsdevices.valve.VVC" _interlock_suffix = ":OPN_OK_RBV" - _state_suffix = ':OPN_DO_RBV' + _state_suffix = ":OPN_DO_RBV" _command_suffix = ":OPN_SW" def __init__(self, parent=None, **kwargs): @@ -730,7 +731,8 @@ def __init__(self, parent=None, **kwargs): interlock_suffix=self._interlock_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = ControlOnlyValveSymbolIcon(parent=self) def sizeHint(self): @@ -807,14 +809,14 @@ class ControlOnlyValveNO(InterlockMixin, StateMixin, ButtonControl, PCDSSymbolBa """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } - NAME = 'Normally Open Control Valve with No Readback' + NAME = "Normally Open Control Valve with No Readback" EXPERT_OPHYD_CLASS = "pcdsdevices.valve.VVCNO" _interlock_suffix = ":CLS_OK_RBV" - _state_suffix = ':CLS_DO_RBV' + _state_suffix = ":CLS_DO_RBV" _command_suffix = ":CLS_SW" def __init__(self, parent=None, **kwargs): @@ -823,13 +825,12 @@ def __init__(self, parent=None, **kwargs): interlock_suffix=self._interlock_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = ControlOnlyValveSymbolIcon(parent=self) -class PneumaticValveNO( - InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbolBase -): +class PneumaticValveNO(InterlockMixin, ErrorMixin, StateMixin, ButtonControl, PCDSSymbolBase): """ A Symbol Widget representing a Normally Open Pneumatic Valve with the proper icon and controls. @@ -902,7 +903,7 @@ class PneumaticValveNO( """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } _interlock_suffix = ":CLS_OK_RBV" @@ -920,7 +921,8 @@ def __init__(self, parent=None, **kwargs): error_suffix=self._error_suffix, state_suffix=self._state_suffix, command_suffix=self._command_suffix, - **kwargs) + **kwargs, + ) self.icon = PneumaticValveNOSymbolIcon(parent=self) def sizeHint(self): @@ -1007,7 +1009,7 @@ class PneumaticValveDA(InterlockMixin, ErrorMixin, StateMixin, PCDSSymbolBase): """ _qt_designer_ = { - "group": "PCDS Valves", + "group": "ECS Vacuum Valves", "is_container": False, } _interlock_suffix = ":OPN_OK_RBV" @@ -1030,14 +1032,15 @@ def __init__(self, parent=None, **kwargs): interlock_suffix=self._interlock_suffix, error_suffix=self._error_suffix, state_suffix=self._state_suffix, - **kwargs) + **kwargs, + ) self.icon = PneumaticValveDASymbolIcon(parent=self) self.open_btn = PyDMPushButton( - label='OPEN', + label="OPEN", pressValue=1, ) self.cls_btn = PyDMPushButton( - label='CLOSE', + label="CLOSE", pressValue=1, ) self.open_btn.setFixedSize(55, 25) @@ -1095,10 +1098,9 @@ def create_channels(self): self._cls_interlock_connected = False self.cls_interlock_channel = PyDMChannel( - address="{}{}".format(self._channels_prefix, - self._cls_interlock_suffix), + address="{}{}".format(self._channels_prefix, self._cls_interlock_suffix), connection_slot=self.cls_interlock_connection_changed, - value_slot=self.cls_interlock_value_changed + value_slot=self.cls_interlock_value_changed, ) self.cls_interlock_channel.connect() diff --git a/pcdswidgets/version.py b/pcdswidgets/version.py index da8a5d5..fa1e59b 100644 --- a/pcdswidgets/version.py +++ b/pcdswidgets/version.py @@ -22,6 +22,7 @@ class VersionProxy(UserString): 4. A fallback in case none of the above match - resulting in a version of 0.0.unknown """ + def __init__(self): self._version = None @@ -32,6 +33,7 @@ def _get_version(self) -> Optional[str]: try: # Git checkout from setuptools_scm import get_version + return get_version(root="..", relative_to=__file__) except (ImportError, LookupError): ... @@ -40,6 +42,7 @@ def _get_version(self) -> Optional[str]: # done a build at least once. try: from ._version import version # noqa: F401 + return version except ImportError: ... @@ -51,7 +54,7 @@ def data(self) -> str: # This is accessed by UserString to allow us to lazily fill in the # information if self._version is None: - self._version = self._get_version() or '0.0.unknown' + self._version = self._get_version() or "0.0.unknown" return self._version diff --git a/pyproject.toml b/pyproject.toml index cc04395..9560dcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,18 @@ [build-system] build-backend = "setuptools.build_meta" -requires = [ "setuptools>=45", "setuptools_scm[toml]>=6.2",] +requires = [ "setuptools>=45", "setuptools_scm[toml]>=6.2" ] [project] -classifiers = [ "Development Status :: 2 - Pre-Alpha", "Natural Language :: English", "Programming Language :: Python :: 3",] +classifiers = [ "Development Status :: 5 - Production/Stable", "Natural Language :: English", "Programming Language :: Python :: 3",] description = "LCLS PyDM Widget Library" -dynamic = [ "version", "readme", "dependencies", "optional-dependencies",] +dynamic = [ "version", "readme" ] keywords = [] name = "pcdswidgets" -requires-python = ">=3.9" +requires-python = ">=3.12" +dependencies = [ + "pydm>=1.9.0", + "qtpy>=2.4.3", +] [[project.authors]] name = "SLAC National Accelerator Laboratory" @@ -24,31 +28,54 @@ file = "LICENSE.md" write_to = "pcdswidgets/_version.py" [project.entry-points."pydm.widget"] -SymbolBase = "pcdswidgets.vacuum.base:PCDSSymbolBase" -FilterSortWidgetTable = "pcdswidgets.table:FilterSortWidgetTable" -PneumaticValve = "pcdswidgets.vacuum.valves:PneumaticValve" -PneumaticValveNO = "pcdswidgets.vacuum.valves:PneumaticValveNO" -PneumaticValveDA = "pcdswidgets.vacuum.valves:PneumaticValveDA" ApertureValve = "pcdswidgets.vacuum.valves:ApertureValve" -FastShutter = "pcdswidgets.vacuum.valves:FastShutter" -NeedleValve = "pcdswidgets.vacuum.valves:NeedleValve" -ProportionalValve = "pcdswidgets.vacuum.valves:ProportionalValve" -RightAngleManualValve = "pcdswidgets.vacuum.valves:RightAngleManualValve" -ControlValve = "pcdswidgets.vacuum.valves:ControlValve" +CapacitanceManometerGauge = "pcdswidgets.vacuum.gauges:CapacitanceManometerGauge" +ColdCathodeComboGauge = "pcdswidgets.vacuum.gauges:ColdCathodeComboGauge" +ColdCathodeGauge = "pcdswidgets.vacuum.gauges:ColdCathodeGauge" ControlOnlyValveNC = "pcdswidgets.vacuum.valves:ControlOnlyValveNC" ControlOnlyValveNO = "pcdswidgets.vacuum.valves:ControlOnlyValveNO" -IonPump = "pcdswidgets.vacuum.pumps:IonPump" -TurboPump = "pcdswidgets.vacuum.pumps:TurboPump" -ScrollPump = "pcdswidgets.vacuum.pumps:ScrollPump" +ControlValve = "pcdswidgets.vacuum.valves:ControlValve" +EPSByteIndicator = "pcdswidgets.eps_byteindicator:EPSByteIndicator" +FastShutter = "pcdswidgets.vacuum.valves:FastShutter" +FilterSortWidgetTable = "pcdswidgets.table:FilterSortWidgetTable" GetterPump = "pcdswidgets.vacuum.pumps:GetterPump" -RoughGauge = "pcdswidgets.vacuum.gauges:RoughGauge" +HotCathodeComboGauge = "pcdswidgets.vacuum.gauges:HotCathodeComboGauge" HotCathodeGauge = "pcdswidgets.vacuum.gauges:HotCathodeGauge" -ColdCathodeGauge = "pcdswidgets.vacuum.gauges:ColdCathodeGauge" +IonPump = "pcdswidgets.vacuum.pumps:IonPump" +MotorClassicFull = "pcdswidgets.motion.common.motor_classic_full:MotorClassicFull" +MotorClassicRow = "pcdswidgets.motion.common.motor_classic_row:MotorClassicRow" +MotorTcClassicRow = "pcdswidgets.motion.common.motor_tc_classic_row:MotorTcClassicRow" +NeedleValve = "pcdswidgets.vacuum.valves:NeedleValve" +PneumaticValve = "pcdswidgets.vacuum.valves:PneumaticValve" +PneumaticValveDA = "pcdswidgets.vacuum.valves:PneumaticValveDA" +PneumaticValveNO = "pcdswidgets.vacuum.valves:PneumaticValveNO" +ProportionalValve = "pcdswidgets.vacuum.valves:ProportionalValve" RGA = "pcdswidgets.vacuum.others:RGA" -EPSByteIndicator = "pcdswidgets.eps_byteindicator:EPSByteIndicator" -ColdCathodeComboGauge = "pcdswidgets.vacuum.gauges:ColdCathodeComboGauge" -HotCathodeComboGauge = "pcdswidgets.vacuum.gauges:HotCathodeComboGauge" -CapacitanceManometerGauge = "pcdswidgets.vacuum.gauges:CapacitanceManometerGauge" +RightAngleManualValve = "pcdswidgets.vacuum.valves:RightAngleManualValve" +RoughGauge = "pcdswidgets.vacuum.gauges:RoughGauge" +ScrollPump = "pcdswidgets.vacuum.pumps:ScrollPump" +SmaractOpenLoopClassicRow = "pcdswidgets.motion.smaract.smaract_open_loop_classic_row:SmaractOpenLoopClassicRow" +TurboPump = "pcdswidgets.vacuum.pumps:TurboPump" + +[project.optional-dependencies] +dev = [ + "jinja2>=3", + "ruff>=0.15.8", + "tomlkit>=0.14.0", +] +doc = [ + "docs-versions-menu>=0.5.2", + "sphinx>=9.1.0", + "sphinx-rtd-theme>=3.1.0", + "sphinxcontrib-jquery>=4.1", +] +test = [ + "jinja2>=3", + "pytest>=9.0.2", + "pytest-qt>=4.5.0", + "pytest-timeout>=2.4.0", + "tomlkit>=0.14.0", +] [tool.setuptools.packages.find] where = [ ".",] @@ -59,11 +86,21 @@ namespaces = false file = "README.md" content-type = "text/markdown" -[tool.setuptools.dynamic.dependencies] -file = [ "requirements.txt",] +[tool.ruff] +line-length = 120 +exclude = [".git", "__pycache__", "build", "dist", "*/_version.py"] + +[tool.ruff.lint] +select = ["C", "E", "F", "W", "B", "I"] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" -[tool.setuptools.dynamic.optional-dependencies.test] -file = "dev-requirements.txt" +[tool.pyright.defineConstant] +PYQT5 = true +PYSIDE2 = false +PYQT6 = false +PYSIDE6 = false -[tool.setuptools.dynamic.optional-dependencies.doc] -file = "docs-requirements.txt" +[tool.uv] +exclude-newer = "7 days" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e4e865c..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -pydm>=1.9.0 -qtpy -PyQt5 diff --git a/show_icon_options.sh b/show_icon_options.sh new file mode 100755 index 0000000..e757a06 --- /dev/null +++ b/show_icon_options.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# shellcheck disable=SC1091 +# +# Open a window with all built-in designer icon options. +# +set -e + +THIS_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" +cd "${THIS_DIR}" + +unset PYTHONPATH +source base_env_vars.sh +source .venv/bin/activate + +python -m pcdswidgets.builder.get_icon_options show diff --git a/try_in_designer.sh b/try_in_designer.sh new file mode 100755 index 0000000..354cfc1 --- /dev/null +++ b/try_in_designer.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# shellcheck disable=SC1091 +# +# Runs designer using widgets from the local .venv +# +set -e + +THIS_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" +cd "${THIS_DIR}" + +unset PYTHONPATH +source base_env_vars.sh +source .venv/bin/activate +PYVER="$(cat .python-version)" +export PYQTDESIGNERPATH=".venv/lib/python${PYVER}/site-packages/pydm" +export PYDM_DESIGNER_ONLINE=1 + +"${BASE_ENV}/bin/designer" "$@" diff --git a/try_in_pydm.sh b/try_in_pydm.sh new file mode 100755 index 0000000..5841470 --- /dev/null +++ b/try_in_pydm.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# shellcheck disable=SC1091 +# +# Runs pydm using widgets from the local .venv +# +set -e + +THIS_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" +cd "${THIS_DIR}" + +unset PYTHONPATH +source base_env_vars.sh +source .venv/bin/activate + +pydm "$@" diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..27ef2c3 --- /dev/null +++ b/uv.lock @@ -0,0 +1,700 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[options] +exclude-newer = "2026-04-03T02:16:30.640878921Z" +exclude-newer-span = "P7D" + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "docs-versions-menu" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pyparsing" }, + { name = "setuptools" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/b7/33bb6d00593ce0a7df5d4b13871b798d1cc8fd798933f036b058568dbe2d/docs_versions_menu-0.5.2.tar.gz", hash = "sha256:8c6eae5836fb63e4f9700387385c5074dac8187609538422dab5fa39de110a73", size = 782903, upload-time = "2023-04-20T03:41:47.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/23/89993b2e9895add5e76a3f38ceb3c06dc2f1d0733937568085a36a56b21b/docs_versions_menu-0.5.2-py3-none-any.whl", hash = "sha256:8e331e2e9b2c9d3a7b2a7c8325a7e7ed7070b144cd3ef0f701172468af7e3cdf", size = 533159, upload-time = "2023-04-20T03:41:45.019Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "entrypoints" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/8d/a7121ffe5f402dc015277d2d31eb82d2187334503a011c18f2e78ecbb9b2/entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4", size = 13974, upload-time = "2022-02-02T21:30:28.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/a8/365059bbcd4572cbc41de17fd5b682be5868b218c3c5479071865cab9078/entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f", size = 5294, upload-time = "2022-02-02T21:30:26.024Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pcdswidgets" +source = { editable = "." } +dependencies = [ + { name = "pydm" }, + { name = "qtpy" }, +] + +[package.optional-dependencies] +dev = [ + { name = "jinja2" }, + { name = "ruff" }, + { name = "tomlkit" }, +] +doc = [ + { name = "docs-versions-menu" }, + { name = "sphinx" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-jquery" }, +] +test = [ + { name = "jinja2" }, + { name = "pytest" }, + { name = "pytest-qt" }, + { name = "pytest-timeout" }, + { name = "tomlkit" }, +] + +[package.metadata] +requires-dist = [ + { name = "docs-versions-menu", marker = "extra == 'doc'", specifier = ">=0.5.2" }, + { name = "jinja2", marker = "extra == 'dev'", specifier = ">=3" }, + { name = "jinja2", marker = "extra == 'test'", specifier = ">=3" }, + { name = "pydm", specifier = ">=1.9.0" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=9.0.2" }, + { name = "pytest-qt", marker = "extra == 'test'", specifier = ">=4.5.0" }, + { name = "pytest-timeout", marker = "extra == 'test'", specifier = ">=2.4.0" }, + { name = "qtpy", specifier = ">=2.4.3" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.8" }, + { name = "sphinx", marker = "extra == 'doc'", specifier = ">=9.1.0" }, + { name = "sphinx-rtd-theme", marker = "extra == 'doc'", specifier = ">=3.1.0" }, + { name = "sphinxcontrib-jquery", marker = "extra == 'doc'", specifier = ">=4.1" }, + { name = "tomlkit", marker = "extra == 'dev'", specifier = ">=0.14.0" }, + { name = "tomlkit", marker = "extra == 'test'", specifier = ">=0.14.0" }, +] +provides-extras = ["dev", "doc", "test"] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pydm" +version = "1.28.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "entrypoints" }, + { name = "numpy" }, + { name = "pyepics" }, + { name = "pyqtgraph" }, + { name = "qtpy" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/f7/f561e3bc7341f688855d78aaa007e7d96853ad2de42c644a0bfc7a078152/pydm-1.28.2.tar.gz", hash = "sha256:1768a06997686bd4d3d899e52047054452646a2381e2c495719bf710fe45ab92", size = 18208518, upload-time = "2026-02-10T20:41:46.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/7a/4fdbf3ebc35f045c564141ca69b648c83c053fc38dac300c668b6e7b3939/pydm-1.28.2-py3-none-any.whl", hash = "sha256:e5d9c48adf50c423c32b05e7fc480b66b0996fea04bb1d80c793cda793583e7e", size = 769506, upload-time = "2026-02-10T20:41:44.149Z" }, +] + +[[package]] +name = "pyepics" +version = "3.5.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/56/b7edf871ec2d81ecc600a7687cf9c536759f31ea482e8aec453c6dd12d21/pyepics-3.5.9.tar.gz", hash = "sha256:78222c1a8aff55bc7a93bdcb6eea9cb544fa8b9122daed1e7ea5b5e87269d45c", size = 6149589, upload-time = "2025-12-17T17:16:33.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/83/7dafb09fbc3efe9d00c4667d22b32b53d08e8a676fa164c6dd8f5debe85e/pyepics-3.5.9-py3-none-any.whl", hash = "sha256:b9863cc55a58542f0a28ad04621d4471f649e9cacfa4ccf346a58d6ba158640c", size = 5332286, upload-time = "2025-12-17T17:16:31.93Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pyqtgraph" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "numpy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/36/4c242f81fdcbfa4fb62a5645f6af79191f4097a0577bd5460c24f19cc4ef/pyqtgraph-0.14.0-py3-none-any.whl", hash = "sha256:7abb7c3e17362add64f8711b474dffac5e7b0e9245abdf992e9a44119b7aa4f5", size = 1924755, upload-time = "2025-11-16T19:43:22.251Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-qt" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pluggy" }, + { name = "pytest" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/61/8bdec02663c18bf5016709b909411dce04a868710477dc9b9844ffcf8dd2/pytest_qt-4.5.0.tar.gz", hash = "sha256:51620e01c488f065d2036425cbc1cbcf8a6972295105fd285321eb47e66a319f", size = 128702, upload-time = "2025-07-01T17:24:39.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/d0/8339b888ad64a3d4e508fed8245a402b503846e1972c10ad60955883dcbb/pytest_qt-4.5.0-py3-none-any.whl", hash = "sha256:ed21ea9b861247f7d18090a26bfbda8fb51d7a8a7b6f776157426ff2ccf26eff", size = 37214, upload-time = "2025-07-01T17:24:38.226Z" }, +] + +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, +] + +[[package]] +name = "qtpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045, upload-time = "2025-02-11T15:09:24.162Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, + { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/68/a1bfbf38c0f7bccc9b10bbf76b94606f64acb1552ae394f0b8285bfaea25/sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c", size = 7620915, upload-time = "2026-01-12T16:03:31.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89", size = 7655617, upload-time = "2026-01-12T16:03:28.101Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +]