From 4d98aaa358ca0a4789f66060029c4c7c7cf42a3d Mon Sep 17 00:00:00 2001 From: Ivan Maslov Date: Thu, 14 Jul 2022 12:42:45 +0300 Subject: [PATCH] Screen module in progress --- Sources/GuideSphinx/Robot/07_screen.rst | 57 +++++++++- Sources/pyOpenRPA/Robot/Screen.py | 142 +++++++++++++++++++++--- Utils/Jupyter-notebooks/Screen.ipynb | 69 +++++++++--- 3 files changed, 237 insertions(+), 31 deletions(-) diff --git a/Sources/GuideSphinx/Robot/07_screen.rst b/Sources/GuideSphinx/Robot/07_screen.rst index fc0e64ad..4eac82c6 100644 --- a/Sources/GuideSphinx/Robot/07_screen.rst +++ b/Sources/GuideSphinx/Robot/07_screen.rst @@ -29,8 +29,15 @@ height - расстояние вниз от левой верхней точки width - расстояние вправо от левой верхней точки в пикселях ************************************************ -Символьное указание точки (inAnchorStr) +Символьное указание точки (inPointRuleStr) ************************************************ + +LU| |RU +-------- + |CC| +-------- +LD| |RD + Символьное указание точки - точка относительно которой выполнить изменение прямоугольной области. "CC", @@ -48,6 +55,54 @@ width - расстояние вправо от левой верхней точ - "RD" - правый край по горизонтали, нижний край по вертикали - "RU" - правый край по горизонтали, верхний край по вертикали + +************************************************ +Символьное указание области (inAnchorRuleStr) +************************************************ + +LU|CU|RU +-------- +LC|CC|RC +-------- +LD|CD|RD + +Символьное указание области поиска - область относительно которой выполнить поиск другой прямоугольной области. + + "CC", + +Формат образования кода: XY, где + +- X обозначает область по горизонтали (допустимые значения: "L", "C", "R") +- Y обозначает область по вертикали (допустимые значения: "U", "C", "D") + + +Допустимые значения: + +- "CC" - выбранная прямоугольная область +- "LU" - слева и сверху от выбранной прямоугольной области +- "LD" - слева и снизу от выбранной прямоугольной области +- "LС" - слева от выбранной прямоугольной области +- "RC" - справа от выбранной прямоугольной области +- "СU" - сверху от выбранной прямоугольной области +- "CD" - сверху от выбранной прямоугольной области +- "RD" - справа и снизу от выбранной прямоугольной области +- "RU" - справа и сверху от выбранной прямоугольной области + +Опция "S" (strict) - искомый объект должен всеми своими координатами находиться в обозначенной прямоугольной области + +Формат допускает комбинации нескольких областей в одной строке. +Пример: +"CC,LU,LD,S" +"CC|LU|LD|S" +"CCLULDS" + +Графическая интерпретация: ++|-|- +----- +-|+|- +----- ++|-|- + ************************************************** Описание функций ************************************************** diff --git a/Sources/pyOpenRPA/Robot/Screen.py b/Sources/pyOpenRPA/Robot/Screen.py index f0ff8267..50358503 100644 --- a/Sources/pyOpenRPA/Robot/Screen.py +++ b/Sources/pyOpenRPA/Robot/Screen.py @@ -64,7 +64,7 @@ def BoxMoveTo(inBox, inDXInt=None, inDYInt=None): return BoxCreate(inTopInt=lTopInt, inLeftInt=lLeftInt, inHeightInt=inBox.height, inWidthInt=inBox.width) -def BoxModify(inBox, inDWidthInt=None, inDHeightInt=None, inAnchorStr="CC"): +def BoxModify(inBox, inDWidthInt=None, inDHeightInt=None, inPointRuleStr="CC"): """Изменить ширину / высоту прямоугольной области. !ВНИМАНИЕ! ПОДДЕРЖИВАЕТ ПАКЕТНУЮ ОБРАТКУ ПРИ ПЕРЕДАЧЕ СПИСКА ЭКЗЕМПЛЯРОВ BOX @@ -91,31 +91,31 @@ def BoxModify(inBox, inDWidthInt=None, inDHeightInt=None, inAnchorStr="CC"): :type inDXInt: int, опциональный :param inDYInt: Смещение левой верхней координаты по оси Y в пикселях (вертикальная ось). :type inDYInt: int, опциональный - :param inAnchorStr: Символьное указание точки (подробнее см. выше), относительно которой выполнить изменение прямоугольной области. Допустимые значения: "CC" (по умолчанию), "LU", "LD", "RD", "RU" - :type inAnchorStr: str, опциональный + :param inPointRuleStr: Символьное указание точки (подробнее см. выше), относительно которой выполнить изменение прямоугольной области. Допустимые значения: "CC" (по умолчанию), "LU", "LD", "RD", "RU" + :type inPointRuleStr: str, опциональный :return: Экземпляр класса прямоугольной области Box :rtype: pyscreeze.Box """ if type(inBox) is list: lResult = [] for lBox in inBox: - lResult.append(BoxModify(lBox, inDWidthInt=inDWidthInt, inDHeightInt=inDHeightInt, inAnchorStr=inAnchorStr)) + lResult.append(BoxModify(lBox, inDWidthInt=inDWidthInt, inDHeightInt=inDHeightInt, inPointRuleStr=inPointRuleStr)) return lResult else: lTopInt = inBox.top lLeftInt = inBox.left lWidthInt = inBox.width + inDWidthInt lHeightInt = inBox.height + inDHeightInt - inAnchorStr = inAnchorStr.upper() # ВЕРХНИЙ РЕГИСТР + inPointRuleStr = inPointRuleStr.upper() # ВЕРХНИЙ РЕГИСТР if inDWidthInt: # Изменения по ширине - if "C" in inAnchorStr: + if "C" in inPointRuleStr: lLeftInt = round(lLeftInt - inDWidthInt / 2) - elif "R" in inAnchorStr: + elif "R" in inPointRuleStr: lLeftInt = lLeftInt - inDWidthInt if inDHeightInt: # Изменения по высоте - if "C" in inAnchorStr: + if "C" in inPointRuleStr: lTopInt = round(lTopInt - inDHeightInt / 2) - elif "D" in inAnchorStr: + elif "D" in inPointRuleStr: lTopInt = lTopInt - inDHeightInt return BoxCreate(inTopInt=lTopInt, inLeftInt=lLeftInt, inHeightInt=lHeightInt, inWidthInt=lWidthInt) @@ -143,11 +143,11 @@ def BoxDraw(inBox, inColorStr='green',inThicknessInt = 2): lBox2 = Screen.BoxCreate(inTopInt=60, inLeftInt=60, inHeightInt=100, inWidthInt=100) Screen.BoxDraw([lBox, lBox2]) - :param inBox: UIO объект, который будет подсвечен - :type inBox: object UIO, обязательный - :param inColorStr: цвет подсветки UIO объекта. Варианты: 'red', 'green', 'blue'. По умолчанию 'green' + :param inBox: Экземпляр класса прямоугольной области Box + :type inBox: pyscreeze.Box + :param inColorStr: цвет подсветки прямоугольной области. Варианты: 'red', 'green', 'blue'. По умолчанию 'green' :type inColorStr: str, необязательный - :param inThicknessInt: толщина подсветки UIO объекта. По умолчанию 2 + :param inThicknessInt: толщина подсветки прямоугольной области. По умолчанию 2 :type inThicknessInt: int, необязательный """ if type(inBox) is list: @@ -194,4 +194,118 @@ def BoxDraw(inBox, inColorStr='green',inThicknessInt = 2): win32functions.DeleteObject(brush_handle) win32functions.DeleteObject(pen_handle) # delete the Display context that we created - win32functions.DeleteDC(dc) \ No newline at end of file + win32functions.DeleteDC(dc) + + +def BoxAnchorRuleCheck(inBox, inAnchorBox=None, inAnchorRuleStr=None) -> bool: + """Выполнить проверку соответствия всем условиям вхождения inBox в inAnchorBox с учетом правил inAnchorRule + + .. code-block:: python + + # Screen: Взаимодействие с экраном + from pyOpenRPA.Robot import Screen + lBox1 = Screen.BoxCreate(inTopInt=265, inLeftInt=62, inHeightInt=100, inWidthInt=90) + lBox2 = Screen.BoxCreate(inTopInt=160, inLeftInt=160, inHeightInt=100, inWidthInt=100) + lBox3 = Screen.BoxCreate(inTopInt=460, inLeftInt=60, inHeightInt=100, inWidthInt=100) + + l = Screen.BoxAnchorRuleCheck(inBox=lBox1, inAnchorBox=[lBox2,lBox3], inAnchorRuleStr=["LD","CUS"]) + Screen.BoxDraw([lBox1,lBox2,lBox3]) + + :param inBox: Экземпляр класса прямоугольной области Box + :type inBox: pyscreeze.Box + :param inAnchorBox: Экземпляр класса прямоугольной области Box + :type inAnchorBox: pyscreeze.Box или list из pyscreeze.Box + :param inAnchorRuleStr: Символьное указание области проверки соответствия. Может принимать единственное значение (единый формат для всех inAnchorBox), или список форматов для каждого inAnchorBox (если inAnchorBox является списком Box) + :type inAnchorRuleStr: str или list из str + :return: True - соответствует всем правилам + :rtype: bool + """ + # Формирование стартовых переменных + if inAnchorBox is None: inAnchorBox = [] + if type(inAnchorBox) is not list: + inAnchorBox = [inAnchorBox] + lAnchorRuleStr = "CC,S" + if inAnchorRuleStr is None or inAnchorRuleStr=="": inAnchorRuleStr = [lAnchorRuleStr] + if type(inAnchorRuleStr) is not list: + inAnchorRuleStr = [inAnchorRuleStr] + + lResult = True + + # Дополнение списка правил до длины якорей, если они расходятся и список правил равен длине 1 или 0 (по умолчанию CC,S) + if len(inAnchorRuleStr)==1 and len(inAnchorBox)==1: + if inAnchorRuleStr[0]=="" or inAnchorRuleStr[0] is None: + inAnchorRuleStr = [lAnchorRuleStr] + elif len(inAnchorRuleStr)==1 and len(inAnchorBox)!=1: + if inAnchorRuleStr[0]!="" and inAnchorRuleStr[0] is not None: + lAnchorRuleStr = inAnchorRuleStr[0] + + if len(inAnchorRuleStr) != len(inAnchorBox): + inAnchorRuleStr = [] + for lItem in inAnchorBox: + inAnchorRuleStr.append(lAnchorRuleStr) + + for lIndexInt, lItemBox in enumerate(inAnchorBox): # Остановиться, если итог False + lItemRuleStr = inAnchorRuleStr[lIndexInt].upper() + print(lItemRuleStr) + # Подготовка вспомогательных областей + lScreenWidthPXInt = 9999 + lScreenHeightPXInt = 5555 + lAnchorLUBox = BoxCreate(inTopInt=0, inLeftInt=0, inHeightInt=lItemBox.top, inWidthInt=lItemBox.left) + lAnchorRUBox = BoxCreate(inTopInt=0, inLeftInt=lItemBox.left+lItemBox.width, inHeightInt=lItemBox.top, inWidthInt=lScreenWidthPXInt) + lAnchorCUBox = BoxCreate(inTopInt=0, inLeftInt=lItemBox.left, inHeightInt=lItemBox.top, inWidthInt=lItemBox.width) + lAnchorLCBox = BoxCreate(inTopInt=lItemBox.top, inLeftInt=0, inHeightInt=lItemBox.height, inWidthInt=lItemBox.left) + lAnchorRCBox = BoxCreate(inTopInt=lItemBox.top, inLeftInt=lItemBox.left+lItemBox.width, inHeightInt=lItemBox.height, inWidthInt=lScreenWidthPXInt) + lAnchorLDBox = BoxCreate(inTopInt=lItemBox.top+lItemBox.height, inLeftInt=0, inHeightInt=lScreenHeightPXInt, inWidthInt=lItemBox.left) + lAnchorRDBox = BoxCreate(inTopInt=lItemBox.top+lItemBox.height, inLeftInt=lItemBox.left+lItemBox.width, inHeightInt=lScreenHeightPXInt, inWidthInt=lScreenWidthPXInt) + lAnchorCDBox = BoxCreate(inTopInt=lItemBox.top+lItemBox.height, inLeftInt=lItemBox.left, inHeightInt=lScreenHeightPXInt, inWidthInt=lItemBox.width) + #import pdb + #pdb.set_trace() + if "S" not in lItemRuleStr: # Проверка без S - Strict + lResult = False + # Алгоритм проверки соответствия хотя бы на одно вхождение + if ("CC" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lItemBox)==True: lResult = True + elif ("LU" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorLUBox)==True: lResult = True + elif ("RU" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorRUBox)==True: lResult = True + elif ("CU" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorCUBox)==True: lResult = True + elif ("LC" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorLCBox)==True: lResult = True + elif ("RC" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorRCBox)==True: lResult = True + elif ("LD" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorLDBox)==True: lResult = True + elif ("RD" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorRDBox)==True: lResult = True + elif ("CD" in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorCDBox)==True: lResult = True + if lResult == False: break # Остановиться, если итог False + else: # Проверка с S - Strict + lResult = True + # Алгоритм проверки соответствия хотя бы на одно вхождение для того сегмента, который недоступен + if ("CC" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lItemBox)==True: lResult = False + elif ("LU" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorLUBox)==True: lResult = False + elif ("RU" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorRUBox)==True: lResult = False + elif ("CU" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorCUBox)==True: lResult = False + elif ("LC" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorLCBox)==True: lResult = False + elif ("RC" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorRCBox)==True: lResult = False + elif ("LD" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorLDBox)==True: lResult = False + elif ("RD" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorRDBox)==True: lResult = False + elif ("CD" not in lItemRuleStr) and BoxOverlay(inBox1=inBox, inBox2=lAnchorCDBox)==True: lResult = False + if lResult == False: break # Остановиться, если итог False + + return lResult + +def BoxOverlay(inBox1, inBox2) -> bool: + """Проверить наложение 2-х прямоугольных областей друг на друга. + + .. code-block:: python + + # Screen: Взаимодействие с экраном + from pyOpenRPA.Robot import Screen + lBox1 = Screen.BoxCreate(inTopInt=10, inLeftInt=10, inHeightInt=100, inWidthInt=1000) + lBox2 = Screen.BoxCreate(inTopInt=160, inLeftInt=160, inHeightInt=100, inWidthInt=100) + Screen.BoxDraw([lBox1, lBox2]) + Screen.BoxOverlay(lBox1,lBox2) + + :param inBox1: Экземпляр класса прямоугольной области Box + :type inBox1: pyscreeze.Box + :param inBox2: Экземпляр класса прямоугольной области Box + :type inBox2: pyscreeze.Box + :return: True - inBox1 наложен на inBox2 + :rtype: bool + """ + return not ((inBox1.left>inBox2.left + inBox2.width or inBox2.left>inBox1.left + inBox1.width) or (inBox1.top>inBox2.top + inBox2.height or inBox2.top>inBox1.top + inBox1.height)) \ No newline at end of file diff --git a/Utils/Jupyter-notebooks/Screen.ipynb b/Utils/Jupyter-notebooks/Screen.ipynb index 933ef211..ab2a7819 100644 --- a/Utils/Jupyter-notebooks/Screen.ipynb +++ b/Utils/Jupyter-notebooks/Screen.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -16,20 +16,18 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Box(left=10, top=10, width=100, height=100)\n", - "Box(left=110, top=210, width=100, height=100)\n", - "Box(left=115, top=215, width=90, height=90)\n", - "Box(left=110, top=210, width=90, height=90)\n", - "Box(left=110, top=220, width=90, height=90)\n", - "Box(left=120, top=210, width=90, height=90)\n", - "Box(left=120, top=220, width=90, height=90)\n" + "ename": "NameError", + "evalue": "name 'Screen' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# Создать пробную прямоугольную область\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mlBox\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mScreen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mBoxCreate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minTopInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0minLeftInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0minHeightInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m100\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0minWidthInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m100\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[0mlBoxX\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mScreen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mBoxCreate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minTopInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m60\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0minLeftInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m60\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0minHeightInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m100\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0minWidthInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m100\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mlBox\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;31m# Переместить пробную прямоугольную область\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mNameError\u001b[0m: name 'Screen' is not defined" ] } ], @@ -99,22 +97,61 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "True" + "False" ] }, - "execution_count": 7, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "type([]) is list" + "type([]) is not list" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def BoxOverlay(inBox1, inBox2) -> bool:\n", + "\n", + " return not (inBox1.left>inBox2.left + inBox2.width or inBox2.left>inBox1.left + inBox1.width) or (inBox1.top>inBox2.top + inBox2.height or inBox2.top>inBox1.top + inBox1.height)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "LD\n", + "CUS\n", + "True\n" + ] + } + ], + "source": [ + "lBox1 = Screen.BoxCreate(inTopInt=265, inLeftInt=62, inHeightInt=100, inWidthInt=90)\n", + "lBox2 = Screen.BoxCreate(inTopInt=160, inLeftInt=160, inHeightInt=100, inWidthInt=100)\n", + "lBox3 = Screen.BoxCreate(inTopInt=460, inLeftInt=60, inHeightInt=100, inWidthInt=100)\n", + "\n", + "lScreenWidthPXInt = 2000\n", + "lScreenHeightPXInt = 1000\n", + "\n", + "l = Screen.BoxAnchorRuleCheck(inBox=lBox1, inAnchorBox=[lBox2,lBox3], inAnchorRuleStr=[\"LD\",\"CUS\"])\n", + "Screen.BoxDraw([lBox1,lBox2,lBox3])\n", + "print(l)" ] }, {