diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a19790 --- /dev/null +++ b/.gitignore @@ -0,0 +1,174 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc diff --git a/README.md b/README.md index 152e0a4..6e2551a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # pyqtprotractor -Multiplatform protractor overlay tool for measuring angles on screen. +Multiplatform protractor overlay tool for measuring angles on screen. -Just install the required PyQt 5 (for example `pip install PyQt5`, maybe with `--user`) and then run `./protractor.py`. +Probably easiest to run with `uv`. + +```sh +$ uv run pyqtprotractor +``` *Tested only on Linux so far, but since it is written in Python and Qt if should be able run on Windows and Mac as well.* @@ -11,7 +15,16 @@ Run `./protractor.py`. An overlay should appear in the top left corner of your s * You can move the individual points around by dragging them with the mouse. * You can move the whole protractor around without changing the angle by dragging the lines or the angle text box. -* You can work "around" the protractor, for example click between the arms to draw something in the window below. +* You can work "around" the protractor, for example click between the arms to draw something in the window below (unless you're using Gnome Wayland on Linux). + +### Keyboard shortcuts + +Probably broken on non-Linux platforms, but worth a try. + +* `X` = toggle inverting angle around 180 degrees +* `N` = toggle allow negative values +* `Ctrl+C` = copy value +* `Ctrl+Shift+C` = copy value, rounded to nearest integer ## Example diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..272339f --- /dev/null +++ b/flake.lock @@ -0,0 +1,170 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1763622513, + "narHash": "sha256-1jQnuyu82FpiSxowrF/iFK6Toh9BYprfDqfs4BB+19M=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c58bc7f5459328e4afac201c5c4feb7c818d604b", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1725103162, + "narHash": "sha256-Ym04C5+qovuQDYL/rKWSR+WESseQBbNAe5DsXNx5trY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "12228ff1752d7b7624a54e9c1af4b222b3c1073b", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pyproject-build-systems": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "pyproject-nix": [ + "pyproject-nix" + ], + "uv2nix": [ + "uv2nix" + ] + }, + "locked": { + "lastModified": 1763662255, + "narHash": "sha256-4bocaOyLa3AfiS8KrWjZQYu+IAta05u3gYZzZ6zXbT0=", + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "rev": "042904167604c681a090c07eb6967b4dd4dae88c", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "build-system-pkgs", + "type": "github" + } + }, + "pyproject-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1763716960, + "narHash": "sha256-PUlomle4klGbnZr0wOn8z61Mbt7tXh6Yp3hZ9/CQkq0=", + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "rev": "d6c61dbe0be75e2f4cf0efcdc62428175be4cfb5", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "pyproject.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "pyproject-build-systems": "pyproject-build-systems", + "pyproject-nix": "pyproject-nix", + "uv2nix": "uv2nix", + "uv2nix_hammer_overrides": "uv2nix_hammer_overrides" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "id": "systems", + "type": "indirect" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1725271838, + "narHash": "sha256-VcqxWT0O/gMaeWTTjf1r4MOyG49NaNxW4GHTO3xuThE=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "9fb342d14b69aefdf46187f6bb80a4a0d97007cd", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "uv2nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "pyproject-nix": [ + "pyproject-nix" + ] + }, + "locked": { + "lastModified": 1763962610, + "narHash": "sha256-UqEV7q+Op+jBysp5RLLApVkJB63V9quNx6mO6++XnqE=", + "owner": "pyproject-nix", + "repo": "uv2nix", + "rev": "fa9fba1c36ef1acbd9291c7df281a6d71cfc47c0", + "type": "github" + }, + "original": { + "owner": "pyproject-nix", + "repo": "uv2nix", + "type": "github" + } + }, + "uv2nix_hammer_overrides": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1752080735, + "narHash": "sha256-jrxqssrqRK6I6V6wBt7rp4B/jIXIgQNsXXeV+yguuJs=", + "owner": "TyberiusPrime", + "repo": "uv2nix_hammer_overrides", + "rev": "68d84fd911afa2383a1c1cb28712ca1d090a8c32", + "type": "github" + }, + "original": { + "owner": "TyberiusPrime", + "repo": "uv2nix_hammer_overrides", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..afa0f5e --- /dev/null +++ b/flake.nix @@ -0,0 +1,104 @@ +{ + description = "pyqtprotractor"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; + + pyproject-nix = { + url = "github:pyproject-nix/pyproject.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + uv2nix = { + url = "github:pyproject-nix/uv2nix"; + inputs.pyproject-nix.follows = "pyproject-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + pyproject-build-systems = { + url = "github:pyproject-nix/build-system-pkgs"; + inputs.pyproject-nix.follows = "pyproject-nix"; + inputs.uv2nix.follows = "uv2nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + # Override packages to make them build properly + uv2nix_hammer_overrides.url = "github:TyberiusPrime/uv2nix_hammer_overrides"; + uv2nix_hammer_overrides.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = + { + nixpkgs, + pyproject-nix, + uv2nix, + pyproject-build-systems, + uv2nix_hammer_overrides, + ... + }: + let + inherit (nixpkgs) lib; + forAllSystems = lib.genAttrs lib.systems.flakeExposed; + + workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; + + overlay = workspace.mkPyprojectOverlay { + sourcePreference = "wheel"; + }; + + editableOverlay = workspace.mkEditablePyprojectOverlay { + root = "$REPO_ROOT"; + }; + + pythonSets = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + python = pkgs.python3; + in + (pkgs.callPackage pyproject-nix.build.packages { + inherit python; + }).overrideScope + ( + lib.composeManyExtensions [ + (uv2nix_hammer_overrides.overrides pkgs) + pyproject-build-systems.overlays.wheel + overlay + ] + ) + ); + + in + { + devShells = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + pythonSet = pythonSets.${system}.overrideScope editableOverlay; + virtualenv = pythonSet.mkVirtualEnv "pyqtprotractor-dev-env" workspace.deps.all; + in + { + default = pkgs.mkShell { + packages = [ + virtualenv + pkgs.uv + pkgs.qt5.qtbase + ]; + env = { + UV_NO_SYNC = "1"; + UV_PYTHON = pythonSet.python.interpreter; + UV_PYTHON_DOWNLOADS = "never"; + }; + shellHook = '' + unset PYTHONPATH + export REPO_ROOT=$(git rev-parse --show-toplevel) + ''; + }; + } + ); + + packages = forAllSystems (system: { + default = pythonSets.${system}.mkVirtualEnv "pyqtprotractor" workspace.deps.default; + }); + }; +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..33fd722 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "pyqtprotractor" +version = "0.1.0" +description = "Multiplatform protractor overlay tool for measuring angles on screen" +readme = "README.md" +authors = [ + { name = "Fritz Webering", email = "fritz@webering.eu" } +] +requires-python = ">=3.9" +dependencies = [ + "pyqt5>=5.15.11", +] + +[project.scripts] +pyqtprotractor = "pyqtprotractor:main" + +[build-system] +requires = ["uv_build>=0.7.20,<0.10.0"] +build-backend = "uv_build" diff --git a/protractor.py b/src/pyqtprotractor/__init__.py old mode 100755 new mode 100644 similarity index 71% rename from protractor.py rename to src/pyqtprotractor/__init__.py index 319051c..2678886 --- a/protractor.py +++ b/src/pyqtprotractor/__init__.py @@ -22,10 +22,9 @@ import sys import math -from PyQt5.Qt import Qt, QApplication, QGuiApplication, QObject, QWidget, QRegion -from PyQt5.QtCore import QPoint, QPointF, QRect, QRectF, QSize, pyqtSignal +from PyQt5.Qt import Qt, QApplication, QWidget +from PyQt5.QtCore import QPoint, QPointF, QRectF, pyqtSignal from PyQt5.QtGui import QPainter, QPen -from PyQt5.QtGui import QImage, QPixmap, QColor, QBitmap from PyQt5.QtWidgets import QLabel @@ -75,7 +74,7 @@ def __init__(self, parent): def r(self): return self.width() / 4 - def paintEvent(self, event): + def paintEvent(self, a0): dark = QPen(Qt.black, 1, Qt.SolidLine) light = QPen(Qt.white, 2, Qt.SolidLine) # light = QPen(QColor(200, 220, 255), 2, Qt.SolidLine) @@ -100,15 +99,18 @@ def paintEvent(self, event): qp.drawPoint(self.width()//2, self.height()//2) qp.end() - def mousePressEvent(self, event): - self.offset = event.pos() - - def mouseMoveEvent(self, event): - self.move(self.mapToParent(event.pos()) - self.offset) + def mousePressEvent(self, ev): + assert ev is not None + self.offset = ev.pos() + + def mouseMoveEvent(self, ev): + assert ev is not None + self.move(self.mapToParent(ev.pos()) - self.offset) self.moved.emit() class Protractor(QLabel): angleInvert = False + allowNegative = True def __init__(self, parent): super().__init__(parent, Qt.WindowType.FramelessWindowHint) @@ -131,35 +133,48 @@ def __init__(self, parent): self.label.setAutoFillBackground(True) self.updateDisplay() - def mousePressEvent(self, event): + def mousePressEvent(self, ev): + assert ev is not None self.setCursor(Qt.ClosedHandCursor) - self.offset = event.pos() - event.accept() + self.offset = ev.pos() + ev.accept() - def mouseMoveEvent(self, event): - diff = event.pos() - self.offset + def mouseMoveEvent(self, ev): + assert ev is not None + diff = ev.pos() - self.offset for widget in self.children(): widget.move(widget.pos() + diff) - self.offset = event.pos() + self.offset = ev.pos() self.updateDisplay() - event.accept() + ev.accept() - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, ev): self.setCursor(Qt.OpenHandCursor) - def mouseDoubleClickEvent(self, event): + def mouseDoubleClickEvent(self, a0): + # For some reason, I couldn't make this function get called, at least + # on my system (Gnome 47 on Fedora Linux) self.angleInvert = not self.angleInvert self.updateDisplay() - def updateDisplay(self): - self.placeLabel() - self.adjustSize() + def getAngle(self) -> float: + """ + Calculates and returns angle in degrees + """ angle = threePointAngle(self.handle1.pos(), self.handleC.pos(), self.handle2.pos()) angleDeg = 180 * angle / math.pi if self.angleInvert: angleDeg = 180 - angleDeg if angleDeg > 180: - angleDeg = angleDeg - 360 + angleDeg -= 360 + if not self.allowNegative and angleDeg < 0: + angleDeg += 360 + return angleDeg + + def updateDisplay(self): + self.placeLabel() + self.adjustSize() + angleDeg = self.getAngle() self.label.setText("%0.2f °" % angleDeg) self.label.adjustSize() self.updateMask() @@ -186,7 +201,7 @@ def updateMask(self): mask = mask.united(r.toRect()) self.setMask(mask) - def paintEvent(self, event): + def paintEvent(self, a0): dark = QPen(Qt.black, 1, Qt.SolidLine) light = QPen(Qt.white, 2, Qt.SolidLine) # light = QPen(QColor(200, 220, 255), 2, Qt.SolidLine) @@ -195,28 +210,61 @@ def paintEvent(self, event): qp.begin(self) qp.setRenderHint(QPainter.Antialiasing) qp.setRenderHint(QPainter.HighQualityAntialiasing) - for pen in light, dark: + for pen in light, dark: qp.setPen(pen) drawShortenedLine(qp, centerPoint(self.handleC), centerPoint(self.handle1), self.handleC.r() + 1, self.handle1.r() + 1) drawShortenedLine(qp, centerPoint(self.handleC), centerPoint(self.handle2), self.handleC.r() + 1, self.handle2.r() + 1) qp.end() - def keyPressEvent(self, event): - if event.key() == Qt.Key_Escape: + def keyPressEvent(self, ev): + assert ev is not None + # This probably isn't the correct way to handle modifier keys, but I + # couldn't figure out how to get the modifier() method working + modifiers = (ev.nativeModifiers() - 16) + + # Esc = Quit + if ev.key() == Qt.Key_Escape: QApplication.quit() + # X = Toggle invert angle around 180 + elif ev.key() == Qt.Key_X: + self.angleInvert = not self.angleInvert + self.updateDisplay() + # N = Toggle negative angles + elif ev.key() == Qt.Key_N: + self.allowNegative = not self.allowNegative + self.updateDisplay() + # Ctrl+C + elif ev.key() == Qt.Key_C and modifiers & 4: + # https://stackoverflow.com/a/23119741/6335363 + cb = QApplication.clipboard() + angleDeg = self.getAngle() + if modifiers & 1: + # Shift = round to 0 decimal places + angleDeg = round(angleDeg) + else: + # Otherwise, 2 decimal places + angleDeg = round(angleDeg, 2) + cb.clear(mode=cb.Clipboard) + toCopy = str(angleDeg) + cb.setText(toCopy, mode=cb.Clipboard) else: - QWidget.keyPressEvent(self, event) + QWidget.keyPressEvent(self, ev) + +def main(): + a = QApplication(sys.argv) + + widget = Protractor(None) + widget.show() -a = QApplication(sys.argv) + # Negative coordinates are necessary because with (0,0) xfwm will place + # the window on the current desktop and below the task bar. + taskBarSize = 32 + allScreens = QApplication.desktop().geometry() + widgetSize = allScreens.adjusted(-10, -taskBarSize, 10, taskBarSize) + widget.setGeometry(widgetSize) -widget = Protractor(None) -widget.show() + a.exec() -# Negative coordinates are necessary because with (0,0) xfwm will place -# the window on the current desktop and below the task bar. -taskBarSize = 32 -allScreens = QApplication.desktop().geometry() -widgetSize = allScreens.adjusted(-10, -taskBarSize, 10, taskBarSize) -widget.setGeometry(widgetSize) -a.exec() +if __name__ == '__main__': + main() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..a36c87c --- /dev/null +++ b/uv.lock @@ -0,0 +1,70 @@ +version = 1 +revision = 1 +requires-python = ">=3.9" + +[[package]] +name = "pyqt5" +version = "5.15.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyqt5-qt5" }, + { name = "pyqt5-sip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/07/c9ed0bd428df6f87183fca565a79fee19fa7c88c7f00a7f011ab4379e77a/PyQt5-5.15.11.tar.gz", hash = "sha256:fda45743ebb4a27b4b1a51c6d8ef455c4c1b5d610c90d2934c7802b5c1557c52", size = 3216775 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/64/42ec1b0bd72d87f87bde6ceb6869f444d91a2d601f2e67cd05febc0346a1/PyQt5-5.15.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c8b03dd9380bb13c804f0bdb0f4956067f281785b5e12303d529f0462f9afdc2", size = 6579776 }, + { url = "https://files.pythonhosted.org/packages/49/f5/3fb696f4683ea45d68b7e77302eff173493ac81e43d63adb60fa760b9f91/PyQt5-5.15.11-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:6cd75628f6e732b1ffcfe709ab833a0716c0445d7aec8046a48d5843352becb6", size = 7016415 }, + { url = "https://files.pythonhosted.org/packages/b4/8c/4065950f9d013c4b2e588fe33cf04e564c2322842d84dbcbce5ba1dc28b0/PyQt5-5.15.11-cp38-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:cd672a6738d1ae33ef7d9efa8e6cb0a1525ecf53ec86da80a9e1b6ec38c8d0f1", size = 8188103 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/ae5a5b4f9b826b29ea4be841b2f2d951bcf5ae1d802f3732b145b57c5355/PyQt5-5.15.11-cp38-abi3-win32.whl", hash = "sha256:76be0322ceda5deecd1708a8d628e698089a1cea80d1a49d242a6d579a40babd", size = 5433308 }, + { url = "https://files.pythonhosted.org/packages/56/d5/68eb9f3d19ce65df01b6c7b7a577ad3bbc9ab3a5dd3491a4756e71838ec9/PyQt5-5.15.11-cp38-abi3-win_amd64.whl", hash = "sha256:bdde598a3bb95022131a5c9ea62e0a96bd6fb28932cc1619fd7ba211531b7517", size = 6865864 }, +] + +[[package]] +name = "pyqt5-qt5" +version = "5.15.16" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/97/ef77663fb5d61b65f2e93edc135cc0b86724c1fc610c10a6867ae0c0baff/PyQt5_Qt5-5.15.16-1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2cfa8f50dd29618ef98f29355f83d8a5f3e41003be22128e9b5d94d214b6b468", size = 61080044 }, + { url = "https://files.pythonhosted.org/packages/ff/89/65a4b9c6bc89012182ff46bf8e503a8aa5e5570df7cb628211c93012beca/PyQt5_Qt5-5.15.16-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:18b6fec012de60921fcb131cf2a21368171dc29050d43e4b81a64be407a36105", size = 39957063 }, + { url = "https://files.pythonhosted.org/packages/0a/60/b6db285c87666b02aa11d0946d7bb381d8bdcc815cc5aa61fa91272b321b/PyQt5_Qt5-5.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1a0e7ae35a7615c74a293705204579650930486a89af23082462f429dae504a", size = 37122434 }, + { url = "https://files.pythonhosted.org/packages/dc/1a/c4f861c4d7a9844a207b8bc3aa9dd84c51f823784d405469cde83d736cf1/PyQt5_Qt5-5.15.16-py3-none-manylinux2014_x86_64.whl", hash = "sha256:5ee1754a6460849cba76c0f0c490c0ccc3b514abc780b141cf772db22b76b54b", size = 59854452 }, +] + +[[package]] +name = "pyqt5-sip" +version = "12.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/79/086b50414bafa71df494398ad277d72e58229a3d1c1b1c766d12b14c2e6d/pyqt5_sip-12.17.0.tar.gz", hash = "sha256:682dadcdbd2239af9fdc0c0628e2776b820e128bec88b49b8d692fe682f90b4f", size = 104042 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/23/1da570b7e143b6d216728c919cae2976f7dbff65db94e3d9f5b62df37ba5/PyQt5_sip-12.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec47914cc751608e587c1c2fdabeaf4af7fdc28b9f62796c583bea01c1a1aa3e", size = 122696 }, + { url = "https://files.pythonhosted.org/packages/61/d5/506b1c3ad06268c601276572f1cde1c0dffd074b44e023f4d80f5ea49265/PyQt5_sip-12.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2f2a8dcc7626fe0da73a0918e05ce2460c7a14ddc946049310e6e35052105434", size = 270932 }, + { url = "https://files.pythonhosted.org/packages/0b/9b/46159d8038374b076244a1930ead460e723453ec73f9b0330390ddfdd0ea/PyQt5_sip-12.17.0-cp310-cp310-win32.whl", hash = "sha256:0c75d28b8282be3c1d7dbc76950d6e6eba1e334783224e9b9835ce1a9c64f482", size = 49085 }, + { url = "https://files.pythonhosted.org/packages/fe/66/b3eb937a620ce2a5db5c377beeca870d60fafd87aecc1bcca6921bbcf553/PyQt5_sip-12.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:8c4bc535bae0dfa764e8534e893619fe843ce5a2e25f901c439bcb960114f686", size = 59040 }, + { url = "https://files.pythonhosted.org/packages/52/fd/7d6e3deca5ce37413956faf4e933ce6beb87ac0cc7b26d934b5ed998f88a/PyQt5_sip-12.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c912807dd638644168ea8c7a447bfd9d85a19471b98c2c588c4d2e911c09b0a", size = 122748 }, + { url = "https://files.pythonhosted.org/packages/29/4d/e5981cde03b091fd83a1ef4ef6a4ca99ce6921d61b80c0222fc8eafdc99a/PyQt5_sip-12.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:71514a7d43b44faa1d65a74ad2c5da92c03a251bdc749f009c313f06cceacc9a", size = 276401 }, + { url = "https://files.pythonhosted.org/packages/5f/30/4c282896b1e8841639cf2aca59acf57d8b261ed834ae976c959f25fa4a35/PyQt5_sip-12.17.0-cp311-cp311-win32.whl", hash = "sha256:023466ae96f72fbb8419b44c3f97475de6642fa5632520d0f50fc1a52a3e8200", size = 49091 }, + { url = "https://files.pythonhosted.org/packages/24/c1/50fc7301aa39a50f451fc1b6b219e778c540a823fe9533a57b4793c859fd/PyQt5_sip-12.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb565469d08dcb0a427def0c45e722323beb62db79454260482b6948bfd52d47", size = 59036 }, + { url = "https://files.pythonhosted.org/packages/a3/e6/e51367c28d69b5a462f38987f6024e766fd8205f121fe2f4d8ba2a6886b9/PyQt5_sip-12.17.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ea08341c8a5da00c81df0d689ecd4ee47a95e1ecad9e362581c92513f2068005", size = 124650 }, + { url = "https://files.pythonhosted.org/packages/64/3b/e6d1f772b41d8445d6faf86cc9da65910484ebd9f7df83abc5d4955437d0/PyQt5_sip-12.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4a92478d6808040fbe614bb61500fbb3f19f72714b99369ec28d26a7e3494115", size = 281893 }, + { url = "https://files.pythonhosted.org/packages/ed/c5/d17fc2ddb9156a593710c88afd98abcf4055a2224b772f8bec2c6eea879c/PyQt5_sip-12.17.0-cp312-cp312-win32.whl", hash = "sha256:b0ff280b28813e9bfd3a4de99490739fc29b776dc48f1c849caca7239a10fc8b", size = 49438 }, + { url = "https://files.pythonhosted.org/packages/fe/c5/1174988d52c732d07033cf9a5067142b01d76be7731c6394a64d5c3ef65c/PyQt5_sip-12.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:54c31de7706d8a9a8c0fc3ea2c70468aba54b027d4974803f8eace9c22aad41c", size = 58017 }, + { url = "https://files.pythonhosted.org/packages/fd/5d/f234e505af1a85189310521447ebc6052ebb697efded850d0f2b2555f7aa/PyQt5_sip-12.17.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c7a7ff355e369616b6bcb41d45b742327c104b2bf1674ec79b8d67f8f2fa9543", size = 124580 }, + { url = "https://files.pythonhosted.org/packages/cd/cb/3b2050e9644d0021bdf25ddf7e4c3526e1edd0198879e76ba308e5d44faf/PyQt5_sip-12.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:419b9027e92b0b707632c370cfc6dc1f3b43c6313242fc4db57a537029bd179c", size = 281563 }, + { url = "https://files.pythonhosted.org/packages/51/61/b8ebde7e0b32d0de44c521a0ace31439885b0423d7d45d010a2f7d92808c/PyQt5_sip-12.17.0-cp313-cp313-win32.whl", hash = "sha256:351beab964a19f5671b2a3e816ecf4d3543a99a7e0650f88a947fea251a7589f", size = 49383 }, + { url = "https://files.pythonhosted.org/packages/15/ed/ff94d6b2910e7627380cb1fc9a518ff966e6d78285c8e54c9422b68305db/PyQt5_sip-12.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:672c209d05661fab8e17607c193bf43991d268a1eefbc2c4551fbf30fd8bb2ca", size = 58022 }, + { url = "https://files.pythonhosted.org/packages/21/f7/3bed2754743ba52b8264c20a1c52df6ff9c5f6465c11ae108be3b841471a/PyQt5_sip-12.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d65a9c1b4cbbd8e856254609f56e897d2cb5c903f77b75fb720cb3a32c76b92b", size = 122688 }, + { url = "https://files.pythonhosted.org/packages/23/63/8a934ea1f759eee60b4e0143e5b109d16560bf67593bfed76cca55799a1a/PyQt5_sip-12.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:32b03e7e77ecd7b4119eba486b0706fa59b490bcceb585f9b6ddec8a582082db", size = 268531 }, + { url = "https://files.pythonhosted.org/packages/34/80/0df0cdb7b25a87346a493cedb20f6eeb0301e7fbc02ed23d9077df998291/PyQt5_sip-12.17.0-cp39-cp39-win32.whl", hash = "sha256:5b6c734f4ad28f3defac4890ed747d391d246af279200935d49953bc7d915b8c", size = 49005 }, + { url = "https://files.pythonhosted.org/packages/30/f5/2fd274c4fe9513d750eecfbe0c39937a179534446e148d8b9db4255f429a/PyQt5_sip-12.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:855e8f5787d57e26a48d8c3de1220a8e92ab83be8d73966deac62fdae03ea2f9", size = 59076 }, +] + +[[package]] +name = "pyqtprotractor" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "pyqt5" }, +] + +[package.metadata] +requires-dist = [{ name = "pyqt5", specifier = ">=5.15.11" }]