ok-script 0.0.86__tar.gz → 0.0.87__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. {ok-script-0.0.86 → ok-script-0.0.87}/PKG-INFO +1 -1
  2. {ok-script-0.0.86 → ok-script-0.0.87}/ok/OK.py +1 -1
  3. ok-script-0.0.87/ok/capture/adb/WindowsCaptureFactory.py +42 -0
  4. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/WindowsGraphicsCaptureMethod.py +66 -59
  5. ok-script-0.0.87/ok/feature/CompressCoco.py +57 -0
  6. {ok-script-0.0.86 → ok-script-0.0.87}/ok/feature/FeatureSet.py +68 -53
  7. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/debug/LogWindow.py +10 -9
  8. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/TaskOpButton.py +2 -8
  9. {ok-script-0.0.86 → ok-script-0.0.87}/ok/task/BaseTask.py +12 -14
  10. {ok-script-0.0.86 → ok-script-0.0.87}/ok/task/ExecutorOperation.py +8 -5
  11. {ok-script-0.0.86 → ok-script-0.0.87}/ok/task/TaskExecutor.py +29 -34
  12. {ok-script-0.0.86 → ok-script-0.0.87}/ok/task/TriggerTask.py +0 -1
  13. {ok-script-0.0.86 → ok-script-0.0.87}/ok_script.egg-info/PKG-INFO +1 -1
  14. {ok-script-0.0.86 → ok-script-0.0.87}/ok_script.egg-info/SOURCES.txt +1 -0
  15. {ok-script-0.0.86 → ok-script-0.0.87}/setup.py +1 -1
  16. ok-script-0.0.86/ok/capture/adb/WindowsCaptureFactory.py +0 -41
  17. {ok-script-0.0.86 → ok-script-0.0.87}/README.md +0 -0
  18. {ok-script-0.0.86 → ok-script-0.0.87}/ok/__init__.py +0 -0
  19. {ok-script-0.0.86 → ok-script-0.0.87}/ok/alas/__init__.py +0 -0
  20. {ok-script-0.0.86 → ok-script-0.0.87}/ok/alas/emulator_base.py +0 -0
  21. {ok-script-0.0.86 → ok-script-0.0.87}/ok/alas/emulator_windows.py +0 -0
  22. {ok-script-0.0.86 → ok-script-0.0.87}/ok/alas/platform_base.py +0 -0
  23. {ok-script-0.0.86 → ok-script-0.0.87}/ok/alas/platform_windows.py +0 -0
  24. {ok-script-0.0.86 → ok-script-0.0.87}/ok/alas/utils.py +0 -0
  25. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/BaseCaptureMethod.py +0 -0
  26. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/HwndWindow.py +0 -0
  27. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/__init__.py +0 -0
  28. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/adb/ADBCaptureMethod.py +0 -0
  29. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/adb/DeviceManager.py +0 -0
  30. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/adb/__init__.py +0 -0
  31. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/adb/bin/__init__.py +0 -0
  32. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/image/ImageCaptureMethod.py +0 -0
  33. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/image/__init__.py +0 -0
  34. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/BaseWindowsCaptureMethod.py +0 -0
  35. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/BitBltCaptureMethod.py +0 -0
  36. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/DesktopDuplicationCaptureMethod.py +0 -0
  37. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/__init__.py +0 -0
  38. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/d3d11.py +0 -0
  39. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/dump.py +0 -0
  40. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/utils.py +0 -0
  41. {ok-script-0.0.86 → ok-script-0.0.87}/ok/capture/windows/window.py +0 -0
  42. {ok-script-0.0.86 → ok-script-0.0.87}/ok/color/Color.py +0 -0
  43. {ok-script-0.0.86 → ok-script-0.0.87}/ok/color/__init__.py +0 -0
  44. {ok-script-0.0.86 → ok-script-0.0.87}/ok/config/Config.py +0 -0
  45. {ok-script-0.0.86 → ok-script-0.0.87}/ok/config/InfoDict.py +0 -0
  46. {ok-script-0.0.86 → ok-script-0.0.87}/ok/config/__init__.py +0 -0
  47. {ok-script-0.0.86 → ok-script-0.0.87}/ok/feature/Box.py +0 -0
  48. {ok-script-0.0.86 → ok-script-0.0.87}/ok/feature/Feature.py +0 -0
  49. {ok-script-0.0.86 → ok-script-0.0.87}/ok/feature/FindFeature.py +0 -0
  50. {ok-script-0.0.86 → ok-script-0.0.87}/ok/feature/__init__.py +0 -0
  51. {ok-script-0.0.86 → ok-script-0.0.87}/ok/firebase/Analytics.py +0 -0
  52. {ok-script-0.0.86 → ok-script-0.0.87}/ok/firebase/__init__.py +0 -0
  53. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/App.py +0 -0
  54. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/Communicate.py +0 -0
  55. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/MainWindow.py +0 -0
  56. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/MessageWindow.py +0 -0
  57. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/StartController.py +0 -0
  58. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/TabTitles.py +0 -0
  59. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/__init__.py +0 -0
  60. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/about/AboutTab.py +0 -0
  61. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/about/HttpTextEdit.py +0 -0
  62. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/about/VersionCard.py +0 -0
  63. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/about/__init__.py +0 -0
  64. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/common/OKIcon.py +0 -0
  65. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/common/__init__.py +0 -0
  66. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/common/config.py +0 -0
  67. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/common/style_sheet.py +0 -0
  68. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/debug/AspectRatioWidget.py +0 -0
  69. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/debug/DebugTab.py +0 -0
  70. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/debug/FrameWidget.py +0 -0
  71. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/debug/InfoWidget.py +0 -0
  72. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/debug/Screenshot.py +0 -0
  73. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/debug/__init__.py +0 -0
  74. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/i18n/__init__.py +0 -0
  75. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/i18n/path.py +0 -0
  76. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/i18n/update.py +0 -0
  77. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/icon/__init__.py +0 -0
  78. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/icon/icon.py +0 -0
  79. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/overlay/OverlayWindow.py +0 -0
  80. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/overlay/__init__.py +0 -0
  81. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/qss/__init__.py +0 -0
  82. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/resources.py +0 -0
  83. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/settings/SettingTab.py +0 -0
  84. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/settings/__init__.py +0 -0
  85. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/start/SelectCaptureListView.py +0 -0
  86. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/start/StartCard.py +0 -0
  87. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/start/StartTab.py +0 -0
  88. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/start/__init__.py +0 -0
  89. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/ConfigItemFactory.py +0 -0
  90. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/ConfigLabelAndWidget.py +0 -0
  91. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/LabelAndDoubleSpinBox.py +0 -0
  92. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/LabelAndDropDown.py +0 -0
  93. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/LabelAndLineEdit.py +0 -0
  94. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/LabelAndList.py +0 -0
  95. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/LabelAndSpinBox.py +0 -0
  96. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/LabelAndSwitchButton.py +0 -0
  97. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/LabelAndWidget.py +0 -0
  98. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/ModifyListDialog.py +0 -0
  99. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/ModifyListItem.py +0 -0
  100. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/OneTimeTaskTab.py +0 -0
  101. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/StartButton.py +0 -0
  102. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/TaskCard.py +0 -0
  103. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/TooltipTableWidget.py +0 -0
  104. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/TriggerTaskTab.py +0 -0
  105. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/tasks/__init__.py +0 -0
  106. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/util/Alert.py +0 -0
  107. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/util/InitWorker.py +0 -0
  108. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/util/__init__.py +0 -0
  109. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/Card.py +0 -0
  110. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/EmptyCard.py +0 -0
  111. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/FramelessDialog.py +0 -0
  112. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/ListTableWidgetItem.py +0 -0
  113. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/NumericTableWidgetItem.py +0 -0
  114. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/StartLoadingDialog.py +0 -0
  115. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/StatusBar.py +0 -0
  116. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/Tab.py +0 -0
  117. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/TabWidget.py +0 -0
  118. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/UpdateConfigWidgetItem.py +0 -0
  119. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/YesNonWidgetItem.py +0 -0
  120. {ok-script-0.0.86 → ok-script-0.0.87}/ok/gui/widget/__init__.py +0 -0
  121. {ok-script-0.0.86 → ok-script-0.0.87}/ok/interaction/ADBInteraction.py +0 -0
  122. {ok-script-0.0.86 → ok-script-0.0.87}/ok/interaction/BaseInteraction.py +0 -0
  123. {ok-script-0.0.86 → ok-script-0.0.87}/ok/interaction/DoNothingInteraction.py +0 -0
  124. {ok-script-0.0.86 → ok-script-0.0.87}/ok/interaction/Win32Interaction.py +0 -0
  125. {ok-script-0.0.86 → ok-script-0.0.87}/ok/interaction/__init__.py +0 -0
  126. {ok-script-0.0.86 → ok-script-0.0.87}/ok/logging/Logger.py +0 -0
  127. {ok-script-0.0.86 → ok-script-0.0.87}/ok/logging/__init__.py +0 -0
  128. {ok-script-0.0.86 → ok-script-0.0.87}/ok/ocr/OCR.py +0 -0
  129. {ok-script-0.0.86 → ok-script-0.0.87}/ok/ocr/__init__.py +0 -0
  130. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Foundation/Collections.py +0 -0
  131. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Foundation/__init__.py +0 -0
  132. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Globalization/__init__.py +0 -0
  133. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Graphics/Capture/__init__.py +0 -0
  134. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +0 -0
  135. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Graphics/DirectX/__init__.py +0 -0
  136. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Graphics/Imaging/__init__.py +0 -0
  137. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Graphics/__init__.py +0 -0
  138. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Media/Ocr/__init__.py +0 -0
  139. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Media/__init__.py +0 -0
  140. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Security/Cryptography/__init__.py +0 -0
  141. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Security/__init__.py +0 -0
  142. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Storage/Streams/__init__.py +0 -0
  143. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/Storage/__init__.py +0 -0
  144. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/Windows/__init__.py +0 -0
  145. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/__init__.py +0 -0
  146. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/delegate.py +0 -0
  147. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/export.py +0 -0
  148. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/idldsl.py +0 -0
  149. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/inspectable.py +0 -0
  150. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/roapi.py +0 -0
  151. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/types.py +0 -0
  152. {ok-script-0.0.86 → ok-script-0.0.87}/ok/rotypes/winstring.py +0 -0
  153. {ok-script-0.0.86 → ok-script-0.0.87}/ok/save/BlackBarProcessor.py +0 -0
  154. {ok-script-0.0.86 → ok-script-0.0.87}/ok/save/PostProcessor.py +0 -0
  155. {ok-script-0.0.86 → ok-script-0.0.87}/ok/save/SaveByInterval.py +0 -0
  156. {ok-script-0.0.86 → ok-script-0.0.87}/ok/save/SaveByKeyPress.py +0 -0
  157. {ok-script-0.0.86 → ok-script-0.0.87}/ok/save/SaveMethodBase.py +0 -0
  158. {ok-script-0.0.86 → ok-script-0.0.87}/ok/save/__init__.py +0 -0
  159. {ok-script-0.0.86 → ok-script-0.0.87}/ok/scene/FeatureScene.py +0 -0
  160. {ok-script-0.0.86 → ok-script-0.0.87}/ok/scene/Scene.py +0 -0
  161. {ok-script-0.0.86 → ok-script-0.0.87}/ok/scene/__init__.py +0 -0
  162. {ok-script-0.0.86 → ok-script-0.0.87}/ok/stats/StreamStats.py +0 -0
  163. {ok-script-0.0.86 → ok-script-0.0.87}/ok/stats/__init__.py +0 -0
  164. {ok-script-0.0.86 → ok-script-0.0.87}/ok/task/__init__.py +0 -0
  165. {ok-script-0.0.86 → ok-script-0.0.87}/ok/update/GithubMultiDownloader.py +0 -0
  166. {ok-script-0.0.86 → ok-script-0.0.87}/ok/update/Updater.py +0 -0
  167. {ok-script-0.0.86 → ok-script-0.0.87}/ok/update/__init__.py +0 -0
  168. {ok-script-0.0.86 → ok-script-0.0.87}/ok/update/gen_md5.py +0 -0
  169. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/Handler.py +0 -0
  170. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/__init__.py +0 -0
  171. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/exit_event.py +0 -0
  172. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/json.py +0 -0
  173. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/list.py +0 -0
  174. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/path.py +0 -0
  175. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/thread.py +0 -0
  176. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/win32_process.py +0 -0
  177. {ok-script-0.0.86 → ok-script-0.0.87}/ok/util/yaml.py +0 -0
  178. {ok-script-0.0.86 → ok-script-0.0.87}/ok_script.egg-info/dependency_links.txt +0 -0
  179. {ok-script-0.0.86 → ok-script-0.0.87}/ok_script.egg-info/requires.txt +0 -0
  180. {ok-script-0.0.86 → ok-script-0.0.87}/ok_script.egg-info/top_level.txt +0 -0
  181. {ok-script-0.0.86 → ok-script-0.0.87}/setup.cfg +0 -0
  182. {ok-script-0.0.86 → ok-script-0.0.87}/tests/__init__.py +0 -0
  183. {ok-script-0.0.86 → ok-script-0.0.87}/tests/test_updater.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ok-script
3
- Version: 0.0.86
3
+ Version: 0.0.87
4
4
  Summary: Automation with Computer Vision for Python
5
5
  Home-page: https://github.com/ok-oldking/ok-script
6
6
  Author: ok-oldking
@@ -97,7 +97,7 @@ class OK:
97
97
  if template_matching is not None:
98
98
  coco_feature_json = self.config.get('template_matching').get('coco_feature_json')
99
99
  from ok.feature.FeatureSet import FeatureSet
100
- self.feature_set = FeatureSet(coco_feature_json,
100
+ self.feature_set = FeatureSet(self.debug, coco_feature_json,
101
101
  default_horizontal_variance=template_matching.get(
102
102
  'default_horizontal_variance', 0),
103
103
  default_vertical_variance=template_matching.get('default_vertical_variance',
@@ -0,0 +1,42 @@
1
+ from ok.logging.Logger import get_logger
2
+
3
+ logger = get_logger(__name__)
4
+
5
+
6
+ def update_capture_method(config, capture_method, hwnd, require_bg=False, use_bit_blt_only=False):
7
+ try:
8
+ if config.get('can_bit_blt'):
9
+ logger.debug(
10
+ f"try BitBlt method {config} {hwnd} current_type:{type(capture_method)}")
11
+ from ok.capture.windows.BitBltCaptureMethod import BitBltCaptureMethod
12
+ target_method = BitBltCaptureMethod
13
+ capture_method = get_capture(capture_method, target_method, hwnd)
14
+ if capture_method.test_exclusive_full_screen():
15
+ return capture_method
16
+ else:
17
+ logger.info("test_exclusive_full_screen failed, can't use BitBlt")
18
+ if use_bit_blt_only:
19
+ return None
20
+ from ok.capture.windows.WindowsGraphicsCaptureMethod import windows_graphics_available
21
+ if windows_graphics_available():
22
+ from ok.capture.windows.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod
23
+ target_method = WindowsGraphicsCaptureMethod
24
+ capture_method = get_capture(capture_method, target_method, hwnd)
25
+ return capture_method
26
+
27
+ if not require_bg:
28
+ from ok.capture.windows.DesktopDuplicationCaptureMethod import DesktopDuplicationCaptureMethod
29
+ target_method = DesktopDuplicationCaptureMethod
30
+ capture_method = get_capture(capture_method, target_method, hwnd)
31
+ return capture_method
32
+ except Exception as e:
33
+ logger.error(f'update_capture_method exception, return None: ', e)
34
+
35
+
36
+ def get_capture(capture_method, target_method, hwnd):
37
+ if not isinstance(capture_method, target_method):
38
+ if capture_method is not None:
39
+ capture_method.close()
40
+ capture_method = target_method(hwnd)
41
+ capture_method.hwnd_window = hwnd
42
+ return capture_method
@@ -23,7 +23,7 @@ logger = get_logger(__name__)
23
23
  class WindowsGraphicsCaptureMethod(BaseWindowsCaptureMethod):
24
24
  name = "Windows Graphics Capture"
25
25
  description = "fast, most compatible, capped at 60fps"
26
- last_frame = None
26
+ last_dx_frame = None
27
27
  last_frame_time = 0
28
28
  frame_pool = None
29
29
  item = None
@@ -38,67 +38,71 @@ class WindowsGraphicsCaptureMethod(BaseWindowsCaptureMethod):
38
38
 
39
39
  def frame_arrived_callback(self, x, y):
40
40
  try:
41
- frame = self.frame_pool.TryGetNextFrame()
41
+ self.last_frame_time = time.time()
42
+ self.last_dx_frame = self.frame_pool.TryGetNextFrame()
42
43
  except Exception as e:
43
44
  logger.error(f"TryGetNextFrame error {e}")
44
45
  self.close()
45
46
  return
47
+
48
+ def convert_dx_frame(self):
49
+ frame = self.last_dx_frame
46
50
  if not frame:
51
+ return None
52
+
53
+ need_reset_framepool = False
54
+ if frame.ContentSize.Width != self.last_size.Width or frame.ContentSize.Height != self.last_size.Height:
55
+ need_reset_framepool = True
56
+ self.last_size = frame.ContentSize
57
+
58
+ if need_reset_framepool:
59
+ logger.info('need_reset_framepool')
60
+ self.reset_framepool(frame.ContentSize)
47
61
  return
48
- self.last_frame_time = time.time()
49
- img = None
50
- with frame:
51
- need_reset_framepool = False
52
- need_reset_device = False
53
- if frame.ContentSize.Width != self.last_size.Width or frame.ContentSize.Height != self.last_size.Height:
54
- need_reset_framepool = True
55
- self.last_size = frame.ContentSize
62
+ need_reset_device = False
56
63
 
57
- if need_reset_framepool:
58
- self.reset_framepool(frame.ContentSize)
59
- return
60
- tex = None
64
+ tex = None
61
65
 
62
- cputex = None
63
- try:
64
- from ok.rotypes.Windows.Graphics.DirectX.Direct3D11 import IDirect3DDxgiInterfaceAccess
65
- from ok.rotypes.roapi import GetActivationFactory
66
- tex = frame.Surface.astype(IDirect3DDxgiInterfaceAccess).GetInterface(
67
- d3d11.ID3D11Texture2D.GUID).astype(d3d11.ID3D11Texture2D)
68
- desc = tex.GetDesc()
69
- desc2 = d3d11.D3D11_TEXTURE2D_DESC()
70
- desc2.Width = desc.Width
71
- desc2.Height = desc.Height
72
- desc2.MipLevels = desc.MipLevels
73
- desc2.ArraySize = desc.ArraySize
74
- desc2.Format = desc.Format
75
- desc2.SampleDesc = desc.SampleDesc
76
- desc2.Usage = d3d11.D3D11_USAGE_STAGING
77
- desc2.CPUAccessFlags = d3d11.D3D11_CPU_ACCESS_READ
78
- desc2.BindFlags = 0
79
- desc2.MiscFlags = 0
80
- cputex = self.dxdevice.CreateTexture2D(ctypes.byref(desc2), None)
81
- self.immediatedc.CopyResource(cputex, tex)
82
- mapinfo = self.immediatedc.Map(cputex, 0, d3d11.D3D11_MAP_READ, 0)
83
- img = np.ctypeslib.as_array(ctypes.cast(mapinfo.pData, PBYTE),
84
- (desc.Height, mapinfo.RowPitch // 4, 4))[
85
- :, :desc.Width].copy()
86
- self.immediatedc.Unmap(cputex, 0)
87
- except OSError as e:
88
- if e.winerror == d3d11.DXGI_ERROR_DEVICE_REMOVED or e.winerror == d3d11.DXGI_ERROR_DEVICE_RESET:
89
- need_reset_framepool = True
90
- need_reset_device = True
91
- else:
92
- raise
93
- finally:
94
- if tex is not None:
95
- tex.Release()
96
- if cputex is not None:
97
- cputex.Release()
98
- if need_reset_framepool:
99
- self.reset_framepool(frame.ContentSize, need_reset_device)
100
- return self.get_frame()
101
- self.last_frame = img
66
+ cputex = None
67
+ try:
68
+ from ok.rotypes.Windows.Graphics.DirectX.Direct3D11 import IDirect3DDxgiInterfaceAccess
69
+ from ok.rotypes.roapi import GetActivationFactory
70
+ tex = frame.Surface.astype(IDirect3DDxgiInterfaceAccess).GetInterface(
71
+ d3d11.ID3D11Texture2D.GUID).astype(d3d11.ID3D11Texture2D)
72
+ desc = tex.GetDesc()
73
+ desc2 = d3d11.D3D11_TEXTURE2D_DESC()
74
+ desc2.Width = desc.Width
75
+ desc2.Height = desc.Height
76
+ desc2.MipLevels = desc.MipLevels
77
+ desc2.ArraySize = desc.ArraySize
78
+ desc2.Format = desc.Format
79
+ desc2.SampleDesc = desc.SampleDesc
80
+ desc2.Usage = d3d11.D3D11_USAGE_STAGING
81
+ desc2.CPUAccessFlags = d3d11.D3D11_CPU_ACCESS_READ
82
+ desc2.BindFlags = 0
83
+ desc2.MiscFlags = 0
84
+ cputex = self.dxdevice.CreateTexture2D(ctypes.byref(desc2), None)
85
+ self.immediatedc.CopyResource(cputex, tex)
86
+ mapinfo = self.immediatedc.Map(cputex, 0, d3d11.D3D11_MAP_READ, 0)
87
+ img = np.ctypeslib.as_array(ctypes.cast(mapinfo.pData, PBYTE),
88
+ (desc.Height, mapinfo.RowPitch // 4, 4))[
89
+ :, :desc.Width].copy()
90
+ self.immediatedc.Unmap(cputex, 0)
91
+ return img
92
+ except OSError as e:
93
+ if e.winerror == d3d11.DXGI_ERROR_DEVICE_REMOVED or e.winerror == d3d11.DXGI_ERROR_DEVICE_RESET:
94
+ need_reset_framepool = True
95
+ need_reset_device = True
96
+ else:
97
+ raise e
98
+ finally:
99
+ if tex is not None:
100
+ tex.Release()
101
+ if cputex is not None:
102
+ cputex.Release()
103
+ if need_reset_framepool:
104
+ self.reset_framepool(frame.ContentSize, need_reset_device)
105
+ return self.get_frame()
102
106
 
103
107
  @property
104
108
  def hwnd_window(self):
@@ -139,7 +143,7 @@ class WindowsGraphicsCaptureMethod(BaseWindowsCaptureMethod):
139
143
  self.evtoken = item.add_Closed(delegate)
140
144
  self.frame_pool = Direct3D11CaptureFramePool.CreateFreeThreaded(self.rtdevice,
141
145
  DirectXPixelFormat.B8G8R8A8UIntNormalized,
142
- 1, item.Size)
146
+ 2, item.Size)
143
147
  self.session = self.frame_pool.CreateCaptureSession(item)
144
148
  pool = self.frame_pool
145
149
  pool.add_FrameArrived(
@@ -193,10 +197,11 @@ class WindowsGraphicsCaptureMethod(BaseWindowsCaptureMethod):
193
197
  @override
194
198
  def do_get_frame(self):
195
199
  if self.start_or_stop():
196
- frame = self.last_frame
197
- self.last_frame = None
200
+ frame = self.convert_dx_frame()
201
+ if frame is None:
202
+ return
203
+ self.last_dx_frame = None
198
204
  latency = time.time() - self.last_frame_time
199
- self.last_frame_time = time.time()
200
205
 
201
206
  frame = self.crop_image(frame)
202
207
 
@@ -209,14 +214,16 @@ class WindowsGraphicsCaptureMethod(BaseWindowsCaptureMethod):
209
214
  logger.warning(f"latency too large return None frame: {latency}")
210
215
  return None
211
216
  else:
217
+ # logger.debug(f'frame latency: {latency}')
212
218
  return frame
213
219
 
214
220
  def reset_framepool(self, size, reset_device=False):
221
+ logger.info(f'reset_framepool')
215
222
  from ok.rotypes.Windows.Graphics.DirectX import DirectXPixelFormat
216
223
  if reset_device:
217
224
  self.create_device()
218
225
  self.frame_pool.Recreate(self.rtdevice,
219
- DirectXPixelFormat.B8G8R8A8UIntNormalized, 1, size)
226
+ DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, size)
220
227
 
221
228
  def crop_image(self, frame):
222
229
  if frame is not None:
@@ -0,0 +1,57 @@
1
+ import json
2
+ import os
3
+ import sys
4
+
5
+ import cv2
6
+ from PIL import Image
7
+
8
+
9
+ def compress_coco(coco_json) -> None:
10
+ coco_folder = os.path.dirname(coco_json)
11
+
12
+ image_dict = {}
13
+ with open(coco_json, 'r') as file:
14
+ data = json.load(file)
15
+
16
+ image_map = {image['id']: image['file_name'] for image in data['images']}
17
+ category_map = {category['id']: category['name'] for category in data['categories']}
18
+
19
+ for annotation in data['annotations']:
20
+ image_id = annotation['image_id']
21
+ category_id = annotation['category_id']
22
+ bbox = annotation['bbox']
23
+
24
+ # Load and scale the image
25
+ image_path = str(os.path.join(coco_folder, image_map[image_id]))
26
+ image = cv2.imread(image_path)
27
+ original_height, original_width = image.shape[:2]
28
+ if image is None:
29
+ print(f'Could not read image {image_path}')
30
+ continue
31
+ x, y, w, h = bbox
32
+ # Crop the image to the bounding box
33
+ image = image[round(y):round(y + h), round(x):round(x + w), :3]
34
+
35
+ image_features = image_dict.get(image_path, [])
36
+ image_features.append((round(x), round(y), image, original_width, original_height))
37
+ image_dict[image_path] = image_features
38
+
39
+ # Loop through the image_dict and write all the image_feature associated with it in a new PNG
40
+ for image_path, features in image_dict.items():
41
+ background = None
42
+ for feature in features:
43
+ x, y, image, original_width, original_height = feature
44
+ # Create a white background
45
+ if not background:
46
+ background = Image.new('RGBA', (original_width, original_height), (255, 255, 255, 255))
47
+ # Convert the OpenCV image (numpy array) to a PIL image
48
+ image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGBA))
49
+ # Paste the image onto the background
50
+ background.paste(image, (x, y), image)
51
+ # Save the image with compression level 9
52
+ background.save(image_path, 'PNG', optimize=True, compress_level=9)
53
+
54
+
55
+ if __name__ == '__main__':
56
+ json_file = sys.argv[1]
57
+ compress_coco(json_file)
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import os
3
3
  import sys
4
+ import threading
4
5
  from typing import Dict
5
6
  from typing import List
6
7
 
@@ -21,7 +22,7 @@ class FeatureSet:
21
22
  feature_dict: Dict[str, Feature] = {}
22
23
  box_dict: Dict[str, Box] = {}
23
24
 
24
- def __init__(self, coco_json: str, default_horizontal_variance=0,
25
+ def __init__(self, debug, coco_json: str, default_horizontal_variance=0,
25
26
  default_vertical_variance=0, default_threshold=0.95) -> None:
26
27
  """
27
28
  Initialize the FeatureSet by loading images and annotations from a COCO dataset.
@@ -33,6 +34,7 @@ class FeatureSet:
33
34
  """
34
35
  self.coco_json = resource_path(coco_json)
35
36
  self.coco_folder = os.path.dirname(self.coco_json)
37
+ self.debug = debug
36
38
 
37
39
  logger.debug(f'Loading features from {self.coco_json}')
38
40
 
@@ -44,6 +46,7 @@ class FeatureSet:
44
46
  self.default_threshold = default_threshold
45
47
  self.default_horizontal_variance = default_horizontal_variance
46
48
  self.default_vertical_variance = default_vertical_variance
49
+ self.lock = threading.Lock()
47
50
 
48
51
  def check_size(self, frame):
49
52
  height, width = frame.shape[:2]
@@ -52,6 +55,8 @@ class FeatureSet:
52
55
  self.width = width
53
56
  self.height = height
54
57
  self.process_data()
58
+ elif not self.feature_dict:
59
+ self.process_data()
55
60
 
56
61
  def process_data(self) -> None:
57
62
  """
@@ -61,49 +66,60 @@ class FeatureSet:
61
66
  width (int): Target width for scaling images.
62
67
  height (int): Target height for scaling images.
63
68
  """
64
- self.feature_dict.clear()
65
- self.box_dict.clear()
66
- with open(self.coco_json, 'r') as file:
67
- data = json.load(file)
68
-
69
- # Create a map from image ID to file name
70
- image_map = {image['id']: image['file_name'] for image in data['images']}
71
-
72
- # Create a map from category ID to category name
73
- category_map = {category['id']: category['name'] for category in data['categories']}
74
-
75
- for annotation in data['annotations']:
76
- image_id = annotation['image_id']
77
- category_id = annotation['category_id']
78
- bbox = annotation['bbox']
79
-
80
- # Load and scale the image
81
- image_path = str(os.path.join(self.coco_folder, image_map[image_id]))
82
- image = cv2.imread(image_path)
83
- _, original_width = image.shape[:2]
84
- if image is None:
85
- logger.error(f'Could not read image {image_path}')
86
- continue
87
- x, y, w, h = bbox
88
- if self.width != image.shape[1] or self.height != image.shape[0]:
89
- scale_x, scale_y = self.width / image.shape[1], self.height / image.shape[0]
90
- logger.debug(f'scaling images {scale_x}, {scale_y}')
91
- image = cv2.resize(image, (self.width, self.height))
92
- # Calculate the scaled bounding box
93
- x, y, w, h = x * scale_x, y * scale_y, w * scale_x, h * scale_y
94
-
95
- # Crop the image to the bounding box
96
- image = image[int(y):int(y + h), int(x):int(x + w), :3]
97
-
98
- # Store in featureDict using the category name
99
- category_name = category_map[category_id]
100
- logger.debug(
101
- f"loaded {category_name} resized width {self.width} / original_width:{original_width},scale_x:{self.width / original_width}")
102
- if category_name in self.feature_dict:
103
- raise ValueError(f"Multiple boxes found for category {category_name}")
104
- if not category_name.startswith('box_'):
105
- self.feature_dict[category_name] = Feature(image, x, y, w, h)
106
- self.box_dict[category_name] = Box(x, y, w, h, name=category_name)
69
+ with self.lock:
70
+ if self.debug:
71
+ folder_size = sum(os.path.getsize(os.path.join(dirpath, filename))
72
+ for dirpath, dirnames, filenames in os.walk(self.coco_folder)
73
+ for filename in filenames)
74
+ # Convert size to MB
75
+ folder_size_mb = folder_size / (1024 * 1024)
76
+ if folder_size_mb > 5:
77
+ from ok.feature.CompressCoco import compress_coco
78
+ logger.info(f'template folder greater than 5MB try to compress the COCO dataset')
79
+ compress_coco(self.coco_json)
80
+ self.feature_dict.clear()
81
+ self.box_dict.clear()
82
+ with open(self.coco_json, 'r') as file:
83
+ data = json.load(file)
84
+
85
+ # Create a map from image ID to file name
86
+ image_map = {image['id']: image['file_name'] for image in data['images']}
87
+
88
+ # Create a map from category ID to category name
89
+ category_map = {category['id']: category['name'] for category in data['categories']}
90
+
91
+ for annotation in data['annotations']:
92
+ image_id = annotation['image_id']
93
+ category_id = annotation['category_id']
94
+ bbox = annotation['bbox']
95
+
96
+ # Load and scale the image
97
+ image_path = str(os.path.join(self.coco_folder, image_map[image_id]))
98
+ image = cv2.imread(image_path)
99
+ _, original_width = image.shape[:2]
100
+ if image is None:
101
+ logger.error(f'Could not read image {image_path}')
102
+ continue
103
+ x, y, w, h = bbox
104
+ if self.width != image.shape[1] or self.height != image.shape[0]:
105
+ scale_x, scale_y = self.width / image.shape[1], self.height / image.shape[0]
106
+ logger.debug(f'scaling images {scale_x}, {scale_y}')
107
+ image = cv2.resize(image, (self.width, self.height))
108
+ # Calculate the scaled bounding box
109
+ x, y, w, h = x * scale_x, y * scale_y, w * scale_x, h * scale_y
110
+
111
+ # Crop the image to the bounding box
112
+ image = image[round(y):round(y + h), round(x):round(x + w), :3]
113
+
114
+ # Store in featureDict using the category name
115
+ category_name = category_map[category_id]
116
+ logger.debug(
117
+ f"loaded {category_name} resized width {self.width} / original_width:{original_width},scale_x:{self.width / original_width}")
118
+ if category_name in self.feature_dict:
119
+ raise ValueError(f"Multiple boxes found for category {category_name}")
120
+ if not category_name.startswith('box_'):
121
+ self.feature_dict[category_name] = Feature(image, x, y, w, h)
122
+ self.box_dict[category_name] = Box(x, y, w, h, name=category_name)
107
123
 
108
124
  def get_box_by_name(self, mat, category_name: str) -> Box:
109
125
  self.check_size(mat)
@@ -130,7 +146,6 @@ class FeatureSet:
130
146
 
131
147
  # Save the image
132
148
  cv2.imwrite(file_path, image.mat)
133
- print(f"Saved {file_path}")
134
149
 
135
150
  def find_one(self, mat: np.ndarray, category_name: str, horizontal_variance: float = 0,
136
151
  vertical_variance: float = 0,
@@ -183,16 +198,16 @@ class FeatureSet:
183
198
  width = to_x - x
184
199
  if height == -1:
185
200
  height = to_y - y
186
- search_x1 = int(x * frame_width)
187
- search_y1 = int(y * frame_height)
188
- search_x2 = int((x + width) * frame_width)
189
- search_y2 = int((y + height) * frame_height)
201
+ search_x1 = round(x * frame_width)
202
+ search_y1 = round(y * frame_height)
203
+ search_x2 = round((x + width) * frame_width)
204
+ search_y2 = round((y + height) * frame_height)
190
205
  else:
191
206
  # Define search area using variance
192
- search_x1 = max(0, int(feature.x - self.width * horizontal_variance))
193
- search_y1 = max(0, int(feature.y - self.height * vertical_variance))
194
- search_x2 = min(self.width, int(feature.x + feature_width + self.width * horizontal_variance))
195
- search_y2 = min(self.height, int(feature.y + feature_height + self.height * vertical_variance))
207
+ search_x1 = max(0, round(feature.x - self.width * horizontal_variance))
208
+ search_y1 = max(0, round(feature.y - self.height * vertical_variance))
209
+ search_x2 = min(self.width, round(feature.x + feature_width + self.width * horizontal_variance))
210
+ search_y2 = min(self.height, round(feature.y + feature_height + self.height * vertical_variance))
196
211
 
197
212
  search_area = mat[search_y1:search_y2, search_x1:search_x2, :3]
198
213
  # Crop the search area from the image
@@ -60,28 +60,25 @@ class LogModel(QAbstractListModel):
60
60
  # Create colored text based on level
61
61
  color_format = self.get_color_format(level)
62
62
  colored_text = ColoredText(message, color_format, level)
63
-
63
+ self.logs.append(colored_text)
64
64
  if len(self.logs) >= 500:
65
65
  self.beginRemoveRows(QModelIndex(), 0, 0)
66
66
  self.logs.pop(0)
67
67
  self.endRemoveRows()
68
68
 
69
69
  self.beginInsertRows(QModelIndex(), self.rowCount(QModelIndex()), self.rowCount(QModelIndex()))
70
- self.logs.append(colored_text)
70
+ self.do_filter_logs()
71
71
  self.endInsertRows()
72
72
 
73
- self.filter_logs(self.current_level, self.current_keyword)
73
+ def do_filter_logs(self):
74
74
 
75
- def filter_logs(self, level, keyword):
76
- self.current_level = level
77
- self.current_keyword = keyword
78
- keyword = keyword.lower()
75
+ keyword = self.current_keyword.lower()
79
76
 
80
77
  # Get the numeric severity for the current level
81
- current_level_severity = level_severity.get(level, 0)
78
+ current_level_severity = level_severity.get(self.current_level, 0)
82
79
 
83
80
  # Filter logs based on severity level and keyword
84
- if level == "ALL":
81
+ if self.current_level == "ALL":
85
82
  self.filtered_logs = [log for log in self.logs if keyword in log.text.lower()]
86
83
  else:
87
84
  self.filtered_logs = [
@@ -90,6 +87,10 @@ class LogModel(QAbstractListModel):
90
87
  and keyword in log.text.lower()
91
88
  ]
92
89
 
90
+ def filter_logs(self, level, keyword):
91
+ self.current_level = level
92
+ self.current_keyword = keyword
93
+ self.do_filter_logs()
93
94
  self.layoutChanged.emit()
94
95
  self.log_list.scrollToBottom()
95
96
 
@@ -15,19 +15,13 @@ class TaskOpButton(QPushButton):
15
15
 
16
16
  def update_task(self, task: BaseTask):
17
17
  if task.enabled:
18
- if task.done:
19
- self.setText(self.tr("Rerun"))
20
- else:
21
- self.setText(self.tr("Disable"))
18
+ self.setText(self.tr("Disable"))
22
19
  else:
23
20
  self.setText(self.tr("Enable"))
24
21
 
25
22
  @Slot()
26
23
  def toggle_text(self):
27
24
  if self.task.enabled:
28
- if self.task.done:
29
- self.task.enable()
30
- else:
31
- self.task.disable()
25
+ self.task.disable()
32
26
  else:
33
27
  self.task.disable()
@@ -1,4 +1,5 @@
1
1
  import threading
2
+ import time
2
3
 
3
4
  from ok.config.Config import Config
4
5
  from ok.config.InfoDict import InfoDict
@@ -27,8 +28,17 @@ class BaseTask(ExecutorOperation):
27
28
  self._paused = False
28
29
  self.lock = threading.Lock()
29
30
  self._handler = None
30
- self._done = False
31
31
  self.running = False
32
+ self.check_trigger_interval = 0
33
+ self.last_check_trigger_time = 0
34
+
35
+ def should_check_trigger(self):
36
+ now = time.time()
37
+ time_diff = now - self.last_check_trigger_time
38
+ if time_diff > self.check_trigger_interval:
39
+ self.last_check_trigger_time = now
40
+ return True
41
+ return False
32
42
 
33
43
  def get_status(self):
34
44
  if self.running:
@@ -46,15 +56,6 @@ class BaseTask(ExecutorOperation):
46
56
  self._enabled = True
47
57
  self.info_clear()
48
58
  communicate.task.emit(self)
49
- self._done = False
50
-
51
- @property
52
- def done(self):
53
- return self._done
54
-
55
- def set_done(self):
56
- self._done = True
57
- self.disable()
58
59
 
59
60
  @property
60
61
  def handler(self) -> Handler:
@@ -156,10 +157,7 @@ class BaseTask(ExecutorOperation):
156
157
  def run(self):
157
158
  pass
158
159
 
159
- def check_trigger(self):
160
- return True
161
-
162
- def check_condition(self):
160
+ def trigger(self):
163
161
  return True
164
162
 
165
163
  def on_destroy(self):
@@ -34,9 +34,11 @@ class ExecutorOperation:
34
34
  def is_scene(self, the_scene):
35
35
  return isinstance(self.executor.current_scene, the_scene)
36
36
 
37
- def click(self, x=-1, y=-1, move_back=False, name=None):
38
- frame = self.executor.nullable_frame()
39
- communicate.emit_draw_box("click", [Box(max(0, x - 10), max(0, y - 10), 20, 20, name="click")], "green", frame)
37
+ def click(self, x: int | Box = -1, y=-1, move_back=False, name=None):
38
+ if isinstance(x, Box):
39
+ self.click_box(x, move_back=move_back)
40
+ return
41
+ communicate.emit_draw_box("click", [Box(max(0, x - 10), max(0, y - 10), 20, 20, name="click")], "green")
40
42
  self.executor.reset_scene()
41
43
  self.executor.interaction.click(x, y, move_back, name=name)
42
44
 
@@ -132,7 +134,8 @@ class ExecutorOperation:
132
134
  def move_relative(self, x, y):
133
135
  self.executor.interaction.move_relative(x, y)
134
136
 
135
- def click_box(self, box: Box | List[Box] = None, relative_x=0.5, relative_y=0.5, raise_if_not_found=True):
137
+ def click_box(self, box: Box | List[Box] = None, relative_x=0.5, relative_y=0.5, raise_if_not_found=True,
138
+ move_back=False):
136
139
  if isinstance(box, list):
137
140
  if len(box) > 0:
138
141
  box = box[0]
@@ -143,7 +146,7 @@ class ExecutorOperation:
143
146
  raise Exception(f"click_box box is None")
144
147
  return
145
148
  x, y = box.relative_with_variance(relative_x, relative_y)
146
- self.click(x, y, name=box.name)
149
+ self.click(x, y, name=box.name, move_back=move_back)
147
150
 
148
151
  def wait_scene(self, scene_type=None, time_out=0, pre_action=None, post_action=None):
149
152
  return self.executor.wait_scene(scene_type, time_out, pre_action, post_action)