diff --git a/AGENTS.md b/AGENTS.md index 7d734b9..12bdbaa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,16 @@ - 复用来自 `common` 的函数,避免重复造轮子 - 确保文件名/文件夹名和内容边界一致 +## 全面整理代码 + +当用户说「全面整理代码」时,依据本文件规范,直接执行以下整理: + +- 按业务职责梳理代码结构与命名;简单功能用单文件,复杂 Operator 功能包统一以 `operators.py` 为入口,`__init__.py` 负责导出与注册清单。 +- 沿真实调用链清理无用代码,不把仅有测试引用当作业务用途;查证动态调用、框架回调和注册入口后再删除。 +- 精简测试,保留有效行为覆盖,使测试名称、归属与功能对应,并检查状态隔离。 +- 同步整理 `tools/node_assets`、`docs/internals` 和相关引用,使名称、说明与实际实现一致。本触发指令包含内部文档整理。 +- 保留已有的其他修改和有效功能,运行全量测试并确认正常退出,检查旧引用与最终差异,简要报告调整及验证结果;不构建文档或产物。 + ## 查询文档 下列是重要文档 diff --git a/docs/internals/operators/clipboard-image.md b/docs/internals/operators/clipboard-image.md index b339844..c8769a6 100644 --- a/docs/internals/operators/clipboard-image.md +++ b/docs/internals/operators/clipboard-image.md @@ -22,7 +22,7 @@ flowchart TD ### 判断是否交给 Blender -`operators/clipboard_image/__init__.py` 注册 `TrackNativeCopy`、`PasteClipboardImage` 和 3D View、Node Editor 快捷键。`TrackNativeCopy` 记录 Blender Copy 时的系统剪贴板变化标记;粘贴时若标记没有变化,`PasteClipboardImage` 返回 `PASS_THROUGH`,让 Blender 继续处理对象或节点粘贴。 +`operators/clipboard_image/operators.py` 定义 `TrackNativeCopy`、`PasteClipboardImage` 和 3D View、Node Editor 快捷键。`TrackNativeCopy` 记录 Blender Copy 时的系统剪贴板变化标记;粘贴时若标记没有变化,`PasteClipboardImage` 返回 `PASS_THROUGH`,让 Blender 继续处理对象或节点粘贴。 同一文件随后调用 `actions.target_for_context()` 判断当前编辑器是否支持图片落点。 diff --git a/docs/internals/operators/convert-to-plane.md b/docs/internals/operators/convert-to-plane.md index 328a28b..a5858e9 100644 --- a/docs/internals/operators/convert-to-plane.md +++ b/docs/internals/operators/convert-to-plane.md @@ -1,6 +1,6 @@ # Plane 转换 -`operators/convert_to_plane/conversion.py` 提供 `ConvertToPlane`、`ConvertToDepthPlane` 与 `GenerateDepthPlane`。对象和材质导入位于 `object.py`,基础矩形网格位于 `image_plane.py`。 +`operators/convert_to_plane/operators.py` 提供 `ConvertToPlane`、`ConvertToDepthPlane` 与 `GenerateDepthPlane`。对象和材质导入位于 `object.py`,基础矩形网格位于 `image_plane.py`。 ```mermaid flowchart TD diff --git a/docs/internals/operators/cutout-tool.md b/docs/internals/operators/cutout-tool.md index e51e273..110dfd0 100644 --- a/docs/internals/operators/cutout-tool.md +++ b/docs/internals/operators/cutout-tool.md @@ -1,6 +1,6 @@ # Cutout -Cutout 在静态 Image Empty 上圈定 Selection,按轮廓创建 Flat、Solid、Depth Solid 或 Depth Balloon。入口位于 `operators/cutout_tool/main.py`,菜单与采样级别映射位于 `interaction.py`,标识和资产对应关系位于 `shape.py`。 +Cutout 在静态 Image Empty 上圈定 Selection,按轮廓创建 Flat、Solid、Depth Solid 或 Depth Balloon。入口位于 `operators/cutout_tool/operators.py`,菜单与采样级别映射位于 `interaction.py`,标识和资产对应关系位于 `shape.py`。 ```mermaid flowchart TD diff --git a/docs/internals/operators/frame.md b/docs/internals/operators/frame.md index 0636e18..89908d7 100644 --- a/docs/internals/operators/frame.md +++ b/docs/internals/operators/frame.md @@ -1,6 +1,6 @@ # Frame 多图合成 -`operators/image_edit_tool/frame.py` 定义 `FrameImages`、`FrameTool` 和工具激活入口。Frame 将选中的静态 Image Empty 按当前视图合成为一张图片。 +`operators/frame_tool.py` 定义 `FrameImages` 和 `FrameTool`。Frame 将选中的静态 Image Empty 按当前视图合成为一张图片。 ```mermaid flowchart TD diff --git a/docs/internals/operators/index.md b/docs/internals/operators/index.md index f9c3071..b56f8ac 100644 --- a/docs/internals/operators/index.md +++ b/docs/internals/operators/index.md @@ -6,10 +6,10 @@ | `operators/clipboard_image/` | [剪贴板图片](clipboard-image.md) | | `operators/convert_to_plane/` | [Plane 与 Depth Plane](convert-to-plane.md) | | `operators/cutout_tool/` | [Cutout](cutout-tool.md) | -| `operators/image_edit_tool/frame.py` | [Frame 多图合成](frame.md) | -| `operators/image_edit_tool/mask.py` | [Mask Alpha 编辑](mask.md) | -| `operators/image_edit_tool/rectify.py` | [Rectify 透视校正](rectify.md) | +| `operators/frame_tool.py` | [Frame 多图合成](frame.md) | +| `operators/mask_tool.py` | [Mask Alpha 编辑](mask.md) | +| `operators/rectify_tool.py` | [Rectify 透视校正](rectify.md) | | `operators/remove_background.py` | [Remove Background](remove-background.md) | | `operators/upscale.py` | [Upscale](upscale.md) | -类型在 `operators/__init__.py` 和扩展 `__init__.py` 汇总注册。AI Operator 使用普通字典提交任务,并在主线程响应中读取文件和修改 Blender 数据。 +单文件功能按业务命名;复杂功能包在 `operators.py` 定义 Operator、对应 WorkSpaceTool 和入口协调逻辑,其余模块承载具体实现。包的 `__init__.py` 汇总导出,类型在 `operators/__init__.py` 和扩展 `__init__.py` 汇总注册。AI Operator 使用普通字典提交任务,并在主线程响应中读取文件和修改 Blender 数据。 diff --git a/docs/internals/operators/mask.md b/docs/internals/operators/mask.md index 997648d..4561c26 100644 --- a/docs/internals/operators/mask.md +++ b/docs/internals/operators/mask.md @@ -1,6 +1,6 @@ # Mask Alpha 编辑 -`operators/image_edit_tool/mask.py` 定义 `EditImageAlpha` 和 `MaskTool`,使用 `common/viewport.py` 的 `ImageGesture` 管理 Lasso、Brush 与 Polyline。 +`operators/mask_tool.py` 定义 `EditImageAlpha` 和 `MaskTool`,使用 `common/viewport.py` 的 `ImageGesture` 管理 Lasso、Brush 与 Polyline。 ```mermaid flowchart TD diff --git a/docs/internals/operators/rectify.md b/docs/internals/operators/rectify.md index 0fa3b6b..d4d7c4e 100644 --- a/docs/internals/operators/rectify.md +++ b/docs/internals/operators/rectify.md @@ -1,6 +1,6 @@ # Rectify 透视校正 -`operators/image_edit_tool/rectify.py` 定义 `RectifyImagePerspective` 与 `RectifyTool`,将图片内的四角区域校正为矩形。 +`operators/rectify_tool.py` 定义 `RectifyImagePerspective` 与 `RectifyTool`,将图片内的四角区域校正为矩形。 ```mermaid flowchart TD diff --git a/src/anyimage/__init__.py b/src/anyimage/__init__.py index f095341..13fa08c 100644 --- a/src/anyimage/__init__.py +++ b/src/anyimage/__init__.py @@ -5,11 +5,9 @@ from .operators.clipboard_image import CLASSES as CLIPBOARD_CLASSES from .menu import AnyImageImageMenu, draw_image_context_menu from .operators import CLASSES as OPERATOR_CLASSES from .operators.cutout_tool import CutoutTool -from .operators.image_edit_tool import ( - FrameTool, - MaskTool, - RectifyTool, -) +from .operators.frame_tool import FrameTool +from .operators.mask_tool import MaskTool +from .operators.rectify_tool import RectifyTool from .panel import ServerPanel from .preferences import AnyImagePreferences from .properties import AnyImageSettings diff --git a/src/anyimage/common/material.py b/src/anyimage/common/material.py index e4fdd80..e9194df 100644 --- a/src/anyimage/common/material.py +++ b/src/anyimage/common/material.py @@ -12,8 +12,6 @@ from .image import ( from .node import load_node_group -DISPLACEMENT_STRENGTH = 1.0 -DISPLACEMENT_MIDLEVEL = 0.0 IMAGE_MATERIAL_NODE_GROUP_NAME = "O Image Layer" DEPTH_MATERIAL_NODE_GROUP_NAME = "O Image Depth Layer" SHADELESS_NODE_GROUP_NAME = "O Shadeless" diff --git a/src/anyimage/licenses/scikit-image.txt b/src/anyimage/licenses/scikit-image.txt new file mode 100644 index 0000000..710ea3c --- /dev/null +++ b/src/anyimage/licenses/scikit-image.txt @@ -0,0 +1,152 @@ +Files: * +Copyright: 2009-2022 the scikit-image team +License: BSD-3-Clause + +Files: doc/source/themes/scikit-image/layout.html +Copyright: 2007-2010 the Sphinx team +License: BSD-3-Clause + +Files: skimage/feature/_canny.py + skimage/filters/edges.py + skimage/filters/_rank_order.py + skimage/morphology/_skeletonize.py + skimage/morphology/tests/test_watershed.py + skimage/morphology/watershed.py + skimage/segmentation/heap_general.pxi + skimage/segmentation/heap_watershed.pxi + skimage/segmentation/_watershed.py + skimage/segmentation/_watershed_cy.pyx +Copyright: 2003-2009 Massachusetts Institute of Technology + 2009-2011 Broad Institute + 2003 Lee Kamentsky + 2003-2005 Peter J. Verveer +License: BSD-3-Clause + +Files: skimage/filters/thresholding.py + skimage/graph/_mcp.pyx + skimage/graph/heap.pyx +Copyright: 2009-2015 Board of Regents of the University of + Wisconsin-Madison, Broad Institute of MIT and Harvard, + and Max Planck Institute of Molecular Cell Biology and + Genetics + 2009 Zachary Pincus + 2009 Almar Klein +License: BSD-2-Clause + +File: skimage/morphology/grayreconstruct.py + skimage/morphology/tests/test_reconstruction.py +Copyright: 2003-2009 Massachusetts Institute of Technology + 2009-2011 Broad Institute + 2003 Lee Kamentsky +License: BSD-3-Clause + +File: skimage/morphology/_grayreconstruct.pyx +Copyright: 2003-2009 Massachusetts Institute of Technology + 2009-2011 Broad Institute + 2003 Lee Kamentsky + 2022 Gregory Lee (added a 64-bit integer variant for large images) +License: BSD-3-Clause + +File: skimage/segmentation/_expand_labels.py +Copyright: 2020 Broad Institute + 2020 CellProfiler team +License: BSD-3-Clause + +File: skimage/exposure/_adapthist.py +Copyright: 1994 Karel Zuiderveld +License: BSD-3-Clause + +Function: skimage/morphology/_skeletonize_various_cy.pyx:_skeletonize_loop +Copyright: 2003-2009 Massachusetts Institute of Technology + 2009-2011 Broad Institute + 2003 Lee Kamentsky +License: BSD-3-Clause + +Function: skimage/_shared/version_requirements.py:_check_version +Copyright: 2013 The IPython Development Team +License: BSD-3-Clause + +Function: skimage/_shared/version_requirements.py:is_installed +Copyright: 2009-2011 Pierre Raybaut +License: MIT + +File: skimage/feature/_fisher_vector.py +Copyright: 2014 2014 Dan Oneata +License: MIT + +File: skimage/_vendored/numpy_lookfor.py +Copyright: 2005-2023, NumPy Developers +License: BSD-3-Clause + +File: skimage/transform/_thin_plate_splines.py +Copyright: 2007 Zachary Pincus +License: BSD-3-Clause + +License: BSD-2-Clause + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: BSD-3-Clause + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. +. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: MIT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/anyimage/operators/__init__.py b/src/anyimage/operators/__init__.py index 492ff1b..1b90ef1 100644 --- a/src/anyimage/operators/__init__.py +++ b/src/anyimage/operators/__init__.py @@ -3,7 +3,9 @@ from .cutout_tool import ( CutoutSelectionToShape, SelectCutoutSelection, ) -from .image_edit_tool import CLASSES as IMAGE_EDIT_TOOL_CLASSES +from .frame_tool import FrameImages +from .mask_tool import EditImageAlpha +from .rectify_tool import RectifyImagePerspective from .convert_to_plane import ( ConvertToDepthPlane, ConvertToPlane, @@ -29,7 +31,9 @@ CLASSES = ( SelectCutoutSelection, RunBackgroundRemoval, ClearModels, - *IMAGE_EDIT_TOOL_CLASSES, + FrameImages, + EditImageAlpha, + RectifyImagePerspective, RemoveImageBackground, UpscaleImage, ConvertToPlane, diff --git a/src/anyimage/operators/clipboard_image/__init__.py b/src/anyimage/operators/clipboard_image/__init__.py index f5e0536..96c5e43 100644 --- a/src/anyimage/operators/clipboard_image/__init__.py +++ b/src/anyimage/operators/clipboard_image/__init__.py @@ -1,209 +1,15 @@ -import sys - -import bpy - -from .actions import ( - PasteTargetError, - acquire_packed_image, - paste_image, - paste_location, - target_for_context, -) -from .clipboard import ( - ClipboardImageError, - ClipboardImageUnavailable, - clipboard_change_token, - clipboard_image_supported, - read_clipboard_image, +from .operators import ( + CLASSES, + PasteClipboardImage, + TrackNativeCopy, + register_keymaps, + unregister_keymaps, ) - -_keymap_items = [] -_native_copy_token = None - - -class TrackNativeCopy(bpy.types.Operator): - bl_idname = "anyimage.track_native_copy" - bl_label = "Track Native Copy" - bl_description = "Preserve Blender's native copy and paste priority" - bl_options = {"INTERNAL"} - - def execute(self, _context): - global _native_copy_token - - _native_copy_token = clipboard_change_token() - return {"PASS_THROUGH"} - - -class PasteClipboardImage(bpy.types.Operator): - bl_idname = "anyimage.paste_clipboard_image" - bl_label = "Paste Clipboard Image" - bl_description = "Paste and pack the clipboard image for the current editor" - bl_options = {"REGISTER", "UNDO"} - - import_as: bpy.props.EnumProperty( - name="Import As", - description="Choose the 3D View representation for the image", - items=( - ( - "PLANE", - "Plane", - "Create a mesh plane with an image material", - ), - ( - "REFERENCE", - "Reference Image", - "Create an image Empty like dragging an image into Blender", - ), - ), - default="REFERENCE", - ) - shadeless: bpy.props.BoolProperty( - name="Shadeless", - description=( - "Use an Emission shader so the image is unaffected by scene lighting" - ), - default=False, - ) - paste_target: bpy.props.StringProperty( - options={"HIDDEN", "SKIP_SAVE"}, - ) - location: bpy.props.FloatVectorProperty( - size=3, - options={"HIDDEN", "SKIP_SAVE"}, - ) - location_set: bpy.props.BoolProperty( - default=False, - options={"HIDDEN", "SKIP_SAVE"}, - ) - - @classmethod - def poll(cls, context): - if not clipboard_image_supported(): - return False - return target_for_context(context) is not None - - def execute(self, context): - return self._paste(context) - - def invoke(self, context, event): - self.paste_target = target_for_context(context) or "" - location = paste_location(context, event) - if location is not None: - values = tuple(location) - self.location = (*values, 0.0)[:3] - self.location_set = True - return self._paste(context, event) - - def draw(self, _context): - if self.paste_target != "PLANE": - return - self.layout.prop(self, "import_as") - if self.import_as == "PLANE": - self.layout.prop(self, "shadeless") - - def _paste(self, context, event=None): - if _should_defer_to_native_paste(): - return {"PASS_THROUGH"} - - if not self.paste_target: - self.paste_target = target_for_context(context) or "" - - try: - image_data, suffix = read_clipboard_image() - except ClipboardImageUnavailable: - return {"PASS_THROUGH"} - except ClipboardImageError as error: - self.report({"ERROR"}, str(error)) - return {"CANCELLED"} - - image = None - image_reused = False - try: - image, image_reused = acquire_packed_image(image_data, suffix) - location = None - if self.location_set: - location = self.location - if self.paste_target == "NODE": - location = location[:2] - paste_image( - context, - image, - event, - location=location, - import_as=self.import_as, - shadeless=self.shadeless, - ) - except (OSError, RuntimeError, PasteTargetError) as error: - if image is not None and not image_reused and image.users == 0: - bpy.data.images.remove(image) - self.report({"ERROR"}, f"Unable to paste clipboard image: {error}") - return {"CANCELLED"} - - self.report({"INFO"}, f"Pasted and packed {image.name}") - return {"FINISHED"} - - -CLASSES = (TrackNativeCopy, PasteClipboardImage) - - -def register_keymaps(): - _register_keymaps() - - -def unregister_keymaps(): - global _native_copy_token - _unregister_keymaps() - _native_copy_token = None - - -def _should_defer_to_native_paste(): - if _native_copy_token is None: - return False - return clipboard_change_token() == _native_copy_token - - -def _register_keymaps(): - if not clipboard_image_supported(): - return - - window_manager = getattr(bpy.context, "window_manager", None) - keyconfigs = getattr(window_manager, "keyconfigs", None) - addon_keyconfig = getattr(keyconfigs, "addon", None) - if addon_keyconfig is None: - return - - keymap_definitions = ( - ("3D View", "VIEW_3D"), - ("Node Editor", "NODE_EDITOR"), - ) - for keymap_name, space_type in keymap_definitions: - keymap = addon_keyconfig.keymaps.new( - name=keymap_name, - space_type=space_type, - ) - modifiers = ( - {"oskey": True} - if sys.platform == "darwin" - else {"ctrl": True} - ) - keymap_item = keymap.keymap_items.new( - PasteClipboardImage.bl_idname, - "V", - "PRESS", - **modifiers, - ) - _keymap_items.append((keymap, keymap_item)) - copy_keymap_item = keymap.keymap_items.new( - TrackNativeCopy.bl_idname, - "C", - "PRESS", - **modifiers, - ) - _keymap_items.append((keymap, copy_keymap_item)) - - -def _unregister_keymaps(): - for keymap, keymap_item in _keymap_items: - keymap.keymap_items.remove(keymap_item) - _keymap_items.clear() +__all__ = ( + "CLASSES", + "PasteClipboardImage", + "TrackNativeCopy", + "register_keymaps", + "unregister_keymaps", +) diff --git a/src/anyimage/operators/clipboard_image/operators.py b/src/anyimage/operators/clipboard_image/operators.py new file mode 100644 index 0000000..617df6c --- /dev/null +++ b/src/anyimage/operators/clipboard_image/operators.py @@ -0,0 +1,201 @@ +import sys + +import bpy + +from .actions import ( + PasteTargetError, + acquire_packed_image, + paste_image, + paste_location, + target_for_context, +) +from .clipboard import ( + ClipboardImageError, + ClipboardImageUnavailable, + clipboard_change_token, + clipboard_image_supported, + read_clipboard_image, +) + + +_keymap_items = [] +_native_copy_token = None + + +class TrackNativeCopy(bpy.types.Operator): + bl_idname = "anyimage.track_native_copy" + bl_label = "Track Native Copy" + bl_description = "Preserve Blender's native copy and paste priority" + bl_options = {"INTERNAL"} + + def execute(self, _context): + global _native_copy_token + + _native_copy_token = clipboard_change_token() + return {"PASS_THROUGH"} + + +class PasteClipboardImage(bpy.types.Operator): + bl_idname = "anyimage.paste_clipboard_image" + bl_label = "Paste Clipboard Image" + bl_description = "Paste and pack the clipboard image for the current editor" + bl_options = {"REGISTER", "UNDO"} + + import_as: bpy.props.EnumProperty( + name="Import As", + description="Choose the 3D View representation for the image", + items=( + ( + "PLANE", + "Plane", + "Create a mesh plane with an image material", + ), + ( + "REFERENCE", + "Reference Image", + "Create an image Empty like dragging an image into Blender", + ), + ), + default="REFERENCE", + ) + shadeless: bpy.props.BoolProperty( + name="Shadeless", + description=( + "Use an Emission shader so the image is unaffected by scene lighting" + ), + default=False, + ) + paste_target: bpy.props.StringProperty( + options={"HIDDEN", "SKIP_SAVE"}, + ) + location: bpy.props.FloatVectorProperty( + size=3, + options={"HIDDEN", "SKIP_SAVE"}, + ) + location_set: bpy.props.BoolProperty( + default=False, + options={"HIDDEN", "SKIP_SAVE"}, + ) + + @classmethod + def poll(cls, context): + if not clipboard_image_supported(): + return False + return target_for_context(context) is not None + + def execute(self, context): + return self._paste(context) + + def invoke(self, context, event): + self.paste_target = target_for_context(context) or "" + location = paste_location(context, event) + if location is not None: + values = tuple(location) + self.location = (*values, 0.0)[:3] + self.location_set = True + return self._paste(context, event) + + def draw(self, _context): + if self.paste_target != "PLANE": + return + self.layout.prop(self, "import_as") + if self.import_as == "PLANE": + self.layout.prop(self, "shadeless") + + def _paste(self, context, event=None): + if _should_defer_to_native_paste(): + return {"PASS_THROUGH"} + + if not self.paste_target: + self.paste_target = target_for_context(context) or "" + + try: + image_data, suffix = read_clipboard_image() + except ClipboardImageUnavailable: + return {"PASS_THROUGH"} + except ClipboardImageError as error: + self.report({"ERROR"}, str(error)) + return {"CANCELLED"} + + image = None + image_reused = False + try: + image, image_reused = acquire_packed_image(image_data, suffix) + location = None + if self.location_set: + location = self.location + if self.paste_target == "NODE": + location = location[:2] + paste_image( + context, + image, + event, + location=location, + import_as=self.import_as, + shadeless=self.shadeless, + ) + except (OSError, RuntimeError, PasteTargetError) as error: + if image is not None and not image_reused and image.users == 0: + bpy.data.images.remove(image) + self.report({"ERROR"}, f"Unable to paste clipboard image: {error}") + return {"CANCELLED"} + + self.report({"INFO"}, f"Pasted and packed {image.name}") + return {"FINISHED"} + + +CLASSES = (TrackNativeCopy, PasteClipboardImage) + + +def unregister_keymaps(): + global _native_copy_token + for keymap, keymap_item in _keymap_items: + keymap.keymap_items.remove(keymap_item) + _keymap_items.clear() + _native_copy_token = None + + +def _should_defer_to_native_paste(): + if _native_copy_token is None: + return False + return clipboard_change_token() == _native_copy_token + + +def register_keymaps(): + if not clipboard_image_supported(): + return + + window_manager = getattr(bpy.context, "window_manager", None) + keyconfigs = getattr(window_manager, "keyconfigs", None) + addon_keyconfig = getattr(keyconfigs, "addon", None) + if addon_keyconfig is None: + return + + keymap_definitions = ( + ("3D View", "VIEW_3D"), + ("Node Editor", "NODE_EDITOR"), + ) + for keymap_name, space_type in keymap_definitions: + keymap = addon_keyconfig.keymaps.new( + name=keymap_name, + space_type=space_type, + ) + modifiers = ( + {"oskey": True} + if sys.platform == "darwin" + else {"ctrl": True} + ) + keymap_item = keymap.keymap_items.new( + PasteClipboardImage.bl_idname, + "V", + "PRESS", + **modifiers, + ) + _keymap_items.append((keymap, keymap_item)) + copy_keymap_item = keymap.keymap_items.new( + TrackNativeCopy.bl_idname, + "C", + "PRESS", + **modifiers, + ) + _keymap_items.append((keymap, copy_keymap_item)) diff --git a/src/anyimage/operators/convert_to_plane/__init__.py b/src/anyimage/operators/convert_to_plane/__init__.py index 1cc5436..e3ff2ed 100644 --- a/src/anyimage/operators/convert_to_plane/__init__.py +++ b/src/anyimage/operators/convert_to_plane/__init__.py @@ -1,6 +1,6 @@ """Rectangular Plane and Depth Plane conversion operators.""" -from .conversion import ( +from .operators import ( ConvertToDepthPlane, ConvertToPlane, GenerateDepthPlane, diff --git a/src/anyimage/operators/convert_to_plane/conversion.py b/src/anyimage/operators/convert_to_plane/operators.py similarity index 100% rename from src/anyimage/operators/convert_to_plane/conversion.py rename to src/anyimage/operators/convert_to_plane/operators.py diff --git a/src/anyimage/operators/cutout_tool/__init__.py b/src/anyimage/operators/cutout_tool/__init__.py index 7f3716f..1e7be1a 100644 --- a/src/anyimage/operators/cutout_tool/__init__.py +++ b/src/anyimage/operators/cutout_tool/__init__.py @@ -1,4 +1,4 @@ -from .main import CutoutSelectionToShape, CutoutTool, SelectCutoutSelection +from .operators import CutoutSelectionToShape, CutoutTool, SelectCutoutSelection __all__ = ( "CutoutSelectionToShape", diff --git a/src/anyimage/operators/cutout_tool/geometry.py b/src/anyimage/operators/cutout_tool/geometry.py index 350a7a1..e49c891 100644 --- a/src/anyimage/operators/cutout_tool/geometry.py +++ b/src/anyimage/operators/cutout_tool/geometry.py @@ -874,9 +874,6 @@ def teddy_balloon_heights( ) -DEFAULT_ALGORITHM = "POISSON" - - def cotangent_poisson_field( planar_points, planar_faces, diff --git a/src/anyimage/operators/cutout_tool/main.py b/src/anyimage/operators/cutout_tool/operators.py similarity index 100% rename from src/anyimage/operators/cutout_tool/main.py rename to src/anyimage/operators/cutout_tool/operators.py diff --git a/src/anyimage/operators/image_edit_tool/frame.py b/src/anyimage/operators/frame_tool.py similarity index 97% rename from src/anyimage/operators/image_edit_tool/frame.py rename to src/anyimage/operators/frame_tool.py index 0aae4b8..69f1a2d 100644 --- a/src/anyimage/operators/image_edit_tool/frame.py +++ b/src/anyimage/operators/frame_tool.py @@ -2,7 +2,7 @@ import math import bpy -from ...common.image import ( +from ..common.image import ( MAX_PROJECTIVE_OUTPUT_PIXELS, PROJECTIVE_CHUNK_ROWS, create_image_edit_result, @@ -16,8 +16,8 @@ from ...common.image import ( transform_homography, video_total_frames, ) -from ...preferences import configured_max_frame_resolution -from ...common.viewport import ( +from ..preferences import configured_max_frame_resolution +from ..common.viewport import ( active_image_edit_source, active_view3d_tool_id, draw_dashed_line, @@ -28,7 +28,7 @@ from ...common.viewport import ( rectangle_screen_path, report_image_edit_exception, ) -from ...common.selection import ImageEditWarning, clip_polygon_halfplanes +from ..common.selection import ImageEditWarning, clip_polygon_halfplanes FRAME_TOOL_ID = "anyimage.frame" PROJECTIVE_RELATIVE_EPSILON = 1e-12 @@ -646,17 +646,3 @@ class FrameTool(bpy.types.WorkSpaceTool): bl_icon = "ops.sculpt.border_mask" bl_operator = FrameImages.bl_idname bl_keymap = image_edit_drag_keymap(FrameImages.bl_idname) - - -class ActivateImageEditTool(bpy.types.Operator): - bl_idname = "anyimage.activate_image_edit_tool" - bl_label = "Image Edit" - bl_description = "Activate Frame in the 3D Viewport" - bl_options = {"INTERNAL"} - - @classmethod - def poll(cls, context): - return image_edit_poll(context) - - def execute(self, _context): - return bpy.ops.wm.tool_set_by_id(name=FRAME_TOOL_ID, space_type="VIEW_3D") diff --git a/src/anyimage/operators/image_edit_tool/__init__.py b/src/anyimage/operators/image_edit_tool/__init__.py deleted file mode 100644 index af86b44..0000000 --- a/src/anyimage/operators/image_edit_tool/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from .frame import ActivateImageEditTool, FrameImages, FrameTool -from .mask import EditImageAlpha, MaskTool -from .rectify import RectifyImagePerspective, RectifyTool - - -CLASSES = ( - FrameImages, - EditImageAlpha, - ActivateImageEditTool, - RectifyImagePerspective, -) - -__all__ = ( - "CLASSES", - "FrameTool", - "MaskTool", - "RectifyTool", -) diff --git a/src/anyimage/operators/image_edit_tool/mask.py b/src/anyimage/operators/mask_tool.py similarity index 97% rename from src/anyimage/operators/image_edit_tool/mask.py rename to src/anyimage/operators/mask_tool.py index f88bbab..47c4850 100644 --- a/src/anyimage/operators/image_edit_tool/mask.py +++ b/src/anyimage/operators/mask_tool.py @@ -1,25 +1,25 @@ import bpy -from ...common.image import ( +from ..common.image import ( create_image_edit_result, image_empty_bounds, image_rgba, replace_empty_image, require_image_empty, ) -from ...common.selection import ( +from ..common.selection import ( rasterize_selection_path, rasterize_selection_union, SelectionPath, ) -from ...common.viewport import ( +from ..common.viewport import ( brush_footprint_polygons, ImageGesture, image_edit_point_keymap, report_image_edit_exception, screen_path_to_image_pixels, ) -from ...properties import ( +from ..properties import ( mask_gesture_property, mask_mode_property, mask_radius_property, diff --git a/src/anyimage/operators/image_edit_tool/rectify.py b/src/anyimage/operators/rectify_tool.py similarity index 99% rename from src/anyimage/operators/image_edit_tool/rectify.py rename to src/anyimage/operators/rectify_tool.py index d4528b2..6dca61f 100644 --- a/src/anyimage/operators/image_edit_tool/rectify.py +++ b/src/anyimage/operators/rectify_tool.py @@ -3,7 +3,7 @@ import math import bpy -from ...common.viewport import ( +from ..common.viewport import ( active_view3d_tool_id, deserialize_matrix, draw_dashed_line, @@ -16,8 +16,8 @@ from ...common.viewport import ( screen_path_to_image_pixels, serialize_matrix, ) -from ...common.selection import clip_polygon_halfplanes, trim_pixels_to_alpha -from ...common.image import ( +from ..common.selection import clip_polygon_halfplanes, trim_pixels_to_alpha +from ..common.image import ( create_image_edit_result, image_base_name, image_pixels, diff --git a/src/anyimage/properties.py b/src/anyimage/properties.py index 4b5d52a..9991fa8 100644 --- a/src/anyimage/properties.py +++ b/src/anyimage/properties.py @@ -9,8 +9,6 @@ from .server import model_catalog as shared_model_catalog BEN2_MODEL_KEY = "BEN2_BASE" REALESRGAN_GENERAL_WDN_MODEL_KEY = shared_model_catalog.DEFAULT_UPSCALE_MODEL_KEY -SAFMN_MODEL_KEY = shared_model_catalog.SAFMN_MODEL.key -REALESRGAN_X4PLUS_MODEL_KEY = shared_model_catalog.REALESRGAN_X4PLUS.key DEFAULT_MOGE2_MODEL_KEY = "MOGE2_VITS_NORMAL" REQUIRED_MODEL_KEYS = ( DEFAULT_MOGE2_MODEL_KEY, @@ -85,10 +83,6 @@ def model_ready(model_key): ) -def required_models_ready(): - return not ai_status()["missing_models"] - - def ai_status(): environment_ready = runtime.environment_ready() ready_models = {model["key"] for model in model_catalog() if model.get("ready")} diff --git a/src/anyimage/server/geometry/__init__.py b/src/anyimage/server/geometry/__init__.py index 41ef5a5..26c0c0e 100644 --- a/src/anyimage/server/geometry/__init__.py +++ b/src/anyimage/server/geometry/__init__.py @@ -1,7 +1 @@ -from .projection import unproject_camera_depth, unproject_depth_frame - - -__all__ = ( - "unproject_camera_depth", - "unproject_depth_frame", -) +"""Depth and normal artifact processing.""" diff --git a/src/anyimage/server/geometry/projection.py b/src/anyimage/server/geometry/projection.py deleted file mode 100644 index fd2e087..0000000 --- a/src/anyimage/server/geometry/projection.py +++ /dev/null @@ -1,31 +0,0 @@ -def unproject_depth_frame(depth, intrinsics, extrinsics): - import numpy as np - - height, width = depth.shape - x, y = np.meshgrid( - np.arange(width, dtype=np.float32), - np.arange(height, dtype=np.float32), - ) - pixels = np.stack((x, y, np.ones_like(x)), axis=-1).reshape(-1, 3) - rays = (np.linalg.inv(intrinsics) @ pixels.T).T - camera_points = rays * depth.reshape(-1, 1) - camera_points = np.column_stack( - (camera_points, np.ones(camera_points.shape[0], dtype=np.float32)) - ) - world_to_camera = np.eye(4, dtype=np.float32) - world_to_camera[: extrinsics.shape[0], :4] = extrinsics - world_points = (np.linalg.inv(world_to_camera) @ camera_points.T).T - return world_points[:, :3].reshape(height, width, 3) - - -def unproject_camera_depth(depth, intrinsics): - import numpy as np - - height, width = depth.shape - x, y = np.meshgrid( - np.arange(width, dtype=np.float32), - np.arange(height, dtype=np.float32), - ) - pixels = np.stack((x, y, np.ones_like(x)), axis=-1).reshape(-1, 3) - rays = (np.linalg.inv(intrinsics) @ pixels.T).T - return (rays * depth.reshape(-1, 1)).reshape(height, width, 3) diff --git a/src/anyimage/server/model_catalog.py b/src/anyimage/server/model_catalog.py index d9d55d7..04f55ef 100644 --- a/src/anyimage/server/model_catalog.py +++ b/src/anyimage/server/model_catalog.py @@ -178,7 +178,6 @@ MOGE2_MODELS = { ),), ), } -MOGE2_MODEL = MOGE2_MODELS["MOGE2_VITS_NORMAL"] DOWNLOADABLE_MODELS = { BEN2_MODEL.key: BEN2_MODEL, **UPSCALE_MODELS, @@ -193,11 +192,6 @@ def get_downloadable_model(model_key): raise ValueError(f"Unknown model: {model_key}") from error -def model_directory_name(model_key): - """Return the model's folder name inside the shared models directory.""" - return get_downloadable_model(model_key).directory_name - - def model_record(model, ready=False): """Return public model metadata exposed by the Models Resource.""" return { diff --git a/src/anyimage/server/models/onnx_moge2.py b/src/anyimage/server/models/onnx_moge2.py index f712932..6cdd04f 100644 --- a/src/anyimage/server/models/onnx_moge2.py +++ b/src/anyimage/server/models/onnx_moge2.py @@ -4,7 +4,6 @@ from pathlib import Path from .onnx_runtime import create_session as create_runtime_session from .onnx_runtime import run_session -from .onnx_runtime import select_providers as select_providers ONNX_FILENAME = "model.onnx" @@ -15,10 +14,6 @@ COREML_PROVIDER_OPTIONS = { } -def is_onnx_model(model_directory): - return (Path(model_directory) / ONNX_FILENAME).is_file() - - def create_session(model_directory, requested_device="auto"): path = Path(model_directory) / ONNX_FILENAME return create_runtime_session( diff --git a/src/anyimage/server/models/onnx_runtime.py b/src/anyimage/server/models/onnx_runtime.py index 7b82c41..b3404ee 100644 --- a/src/anyimage/server/models/onnx_runtime.py +++ b/src/anyimage/server/models/onnx_runtime.py @@ -95,15 +95,3 @@ def run_session( "gpu:0", ) return session.run(output_names, input_feed, run_options=run_options) - - -def provider_label(session): - provider = session.get_providers()[0] - labels = { - "CUDAExecutionProvider": "CUDA", - "CoreMLExecutionProvider": "CoreML", - "DmlExecutionProvider": "DirectML", - "OpenVINOExecutionProvider": "OpenVINO", - "CPUExecutionProvider": "CPU", - } - return labels.get(provider, provider) diff --git a/tests/blender_test_support.py b/tests/blender_test_support.py index c4cfa1a..3519d84 100644 --- a/tests/blender_test_support.py +++ b/tests/blender_test_support.py @@ -114,13 +114,13 @@ class BlenderTestCase(unittest.TestCase): cls.ai = importlib.import_module("anyimage.common.ai") cls.image_data = importlib.import_module("anyimage.common.image") cls.convert_to_plane = importlib.import_module( - "anyimage.operators.convert_to_plane.conversion" + "anyimage.operators.convert_to_plane.operators" ) cls.convert_to_plane_object = importlib.import_module( "anyimage.operators.convert_to_plane.object" ) cls.cutout_main = importlib.import_module( - "anyimage.operators.cutout_tool.main" + "anyimage.operators.cutout_tool.operators" ) cls.remove_background = importlib.import_module( "anyimage.operators.remove_background" @@ -128,14 +128,14 @@ class BlenderTestCase(unittest.TestCase): cls.upscale = importlib.import_module("anyimage.operators.upscale") cls.image_interaction = importlib.import_module("anyimage.common.viewport") cls.edit_mask = importlib.import_module( - "anyimage.operators.image_edit_tool.mask" + "anyimage.operators.mask_tool" ) cls.image_selection = importlib.import_module("anyimage.common.selection") cls.image_frame = importlib.import_module( - "anyimage.operators.image_edit_tool.frame" + "anyimage.operators.frame_tool" ) cls.rectify = importlib.import_module( - "anyimage.operators.image_edit_tool.rectify" + "anyimage.operators.rectify_tool" ) cls.cutout_geometry = importlib.import_module( "anyimage.operators.cutout_tool.geometry" diff --git a/tests/test_addon_registration.py b/tests/test_addon_registration.py index 8201c33..90efb9e 100644 --- a/tests/test_addon_registration.py +++ b/tests/test_addon_registration.py @@ -65,10 +65,6 @@ class AddonRegistrationTest(BlenderTestCase): frame_operator = self.image_frame.FrameImages self.assertIn(operator, self.anyimage.CLASSES) self.assertIn(frame_operator, self.anyimage.CLASSES) - self.assertIn( - self.image_frame.ActivateImageEditTool, - self.anyimage.CLASSES, - ) self.assertIn( self.rectify.RectifyImagePerspective, self.anyimage.CLASSES, diff --git a/tests/test_ai_setup.py b/tests/test_ai_setup.py index e6289d0..f4a3777 100644 --- a/tests/test_ai_setup.py +++ b/tests/test_ai_setup.py @@ -206,4 +206,4 @@ class AiSetupTest(BlenderTestCase): side_effect=AssertionError("UI must not start the Server"), ), ): - self.assertTrue(properties.required_models_ready()) + self.assertEqual(properties.ai_status()["missing_models"], ()) diff --git a/tests/test_alpha_presentation.py b/tests/test_alpha_presentation.py index 6a06e01..78fd59d 100644 --- a/tests/test_alpha_presentation.py +++ b/tests/test_alpha_presentation.py @@ -191,8 +191,8 @@ def test_isolated_decode_failure_releases_copy(): def test_mask_pack_reopen_add_and_repeat_frame_preserve_hidden_color(tmp_path): from anyimage.common.selection import SelectionMask - from anyimage.operators.image_edit_tool.mask import apply_alpha_mask - from anyimage.operators.image_edit_tool import frame + from anyimage.operators.mask_tool import apply_alpha_mask + from anyimage.operators import frame_tool as frame source = bpy.data.images.new("Roundtrip source", width=2, height=2, alpha=True) rgba = np.tile(np.array([0.2, 0.4, 0.8, 1], dtype=np.float32), (2, 2, 1)) diff --git a/tests/test_brush_strokes.py b/tests/test_brush_strokes.py index eee2bf3..6e84156 100644 --- a/tests/test_brush_strokes.py +++ b/tests/test_brush_strokes.py @@ -8,7 +8,7 @@ import pytest sys.path.insert(0, str(Path(__file__).parents[1] / "src")) from anyimage.common import selection, viewport -from anyimage.operators.image_edit_tool import mask +from anyimage.operators import mask_tool as mask def test_simplified_segments_bound_every_original_sample(): diff --git a/tests/test_clipboard_image.py b/tests/test_clipboard_image.py index 21f30b1..1aec8f3 100644 --- a/tests/test_clipboard_image.py +++ b/tests/test_clipboard_image.py @@ -64,10 +64,10 @@ class ClipboardImageTest(unittest.TestCase): operators.__path__ = [str(PROJECT_ROOT / "src" / "anyimage" / "operators")] sys.modules["anyimage.operators"] = operators cls.addon.clipboard_image = importlib.import_module( - "anyimage.operators.clipboard_image" + "anyimage.operators.clipboard_image.operators" ) - cls.actions = cls.addon.clipboard_image.actions - cls.clipboard = cls.addon.clipboard_image.clipboard + cls.actions = importlib.import_module("anyimage.operators.clipboard_image.actions") + cls.clipboard = importlib.import_module("anyimage.operators.clipboard_image.clipboard") @classmethod def tearDownClass(cls): diff --git a/tests/test_cutout_interaction.py b/tests/test_cutout_interaction.py index 0fcc3d9..34d5656 100644 --- a/tests/test_cutout_interaction.py +++ b/tests/test_cutout_interaction.py @@ -9,7 +9,7 @@ sys.path.insert(0, str(SOURCE_ROOT)) from anyimage.common.selection import SelectionPath from anyimage.common import viewport from anyimage.operators.cutout_tool import interaction -from anyimage.operators.cutout_tool import main as cutout_main +from anyimage.operators.cutout_tool import operators as cutout_main class Pie: diff --git a/tests/test_cutout_objects.py b/tests/test_cutout_objects.py index cbe0031..e8c1eaa 100644 --- a/tests/test_cutout_objects.py +++ b/tests/test_cutout_objects.py @@ -17,7 +17,7 @@ from anyimage.common import material as material_data from anyimage.common import object as object_data from anyimage.common.selection import ImageEditWarning, SelectionPath, rasterize_selection_path from anyimage.operators.cutout_tool import geometry as cutout_geometry -from anyimage.operators.cutout_tool import main as cutout_main +from anyimage.operators.cutout_tool import operators as cutout_main from anyimage.operators.cutout_tool import object as cutout_object from anyimage.operators.cutout_tool import shape as cutout_shape from anyimage.server.geometry.depth_texture import ( diff --git a/tests/test_cutout_volume.py b/tests/test_cutout_volume.py index a5e3760..5ff8eeb 100644 --- a/tests/test_cutout_volume.py +++ b/tests/test_cutout_volume.py @@ -12,7 +12,7 @@ from cutout import build_cutout_group from cutout_volume import build_volume_fields from cutout_rounding import build_volume_rounding from anyimage.operators.cutout_tool.object import load_cutout_node_group -from anyimage.operators.cutout_tool.main import normal_mode_for_shape +from anyimage.operators.cutout_tool.operators import normal_mode_for_shape from anyimage.operators.cutout_tool.geometry import refine_balloon_triangles, poisson_balloon_profile from anyimage.common.object import modifier_input_identifier, set_modifier_input diff --git a/tests/test_extension_packaging.py b/tests/test_extension_packaging.py index 5ba8664..c435901 100644 --- a/tests/test_extension_packaging.py +++ b/tests/test_extension_packaging.py @@ -76,6 +76,7 @@ class ExtensionPackagingTest(unittest.TestCase): self.assertIn("__init__.py", names) self.assertIn("operators/clipboard_image/__init__.py", names) + self.assertIn("operators/clipboard_image/operators.py", names) self.assertIn("operators/clipboard_image/actions.py", names) self.assertIn("operators/clipboard_image/clipboard.py", names) self.assertIn("blender_manifest.toml", names) @@ -91,7 +92,7 @@ class ExtensionPackagingTest(unittest.TestCase): "common/selection.py", "common/viewport.py", "operators/cutout_tool/__init__.py", - "operators/cutout_tool/main.py", + "operators/cutout_tool/operators.py", "operators/cutout_tool/interaction.py", "operators/cutout_tool/geometry.py", "operators/cutout_tool/fine_mesh.py", @@ -99,12 +100,11 @@ class ExtensionPackagingTest(unittest.TestCase): "operators/cutout_tool/shape.py", "operators/convert_to_plane/__init__.py", "operators/convert_to_plane/object.py", - "operators/convert_to_plane/conversion.py", + "operators/convert_to_plane/operators.py", "operators/convert_to_plane/image_plane.py", - "operators/image_edit_tool/__init__.py", - "operators/image_edit_tool/frame.py", - "operators/image_edit_tool/mask.py", - "operators/image_edit_tool/rectify.py", + "operators/frame_tool.py", + "operators/mask_tool.py", + "operators/rectify_tool.py", "operators/ai_setup.py", "operators/remove_background.py", ): @@ -121,7 +121,6 @@ class ExtensionPackagingTest(unittest.TestCase): ) self.assertIn("server/media/input.py", names) self.assertIn("common/depth.py", names) - self.assertIn("server/geometry/projection.py", names) self.assertIn("server/models/geometry.py", names) self.assertIn("server/jobs/remove_background.py", names) self.assertIn("server/jobs/cutout.py", names) diff --git a/tests/test_frame_antialiasing.py b/tests/test_frame_antialiasing.py index f917d11..5f39b0c 100644 --- a/tests/test_frame_antialiasing.py +++ b/tests/test_frame_antialiasing.py @@ -7,7 +7,7 @@ import pytest sys.path.insert(0, str(Path(__file__).parents[1] / "src")) from anyimage.common.image import premultiplied_rgba -from anyimage.operators.image_edit_tool import frame +from anyimage.operators import frame_tool as frame def source(rgba, *, bounds=(-1, 1, -1, 1), depth=1, slope=0, active=True): @@ -39,7 +39,7 @@ def test_white_half_coverage_does_not_mix_canvas_black(): def test_uncovered_canvas_has_no_rgb_to_reveal_with_mask_add(): from anyimage.common.selection import SelectionMask - from anyimage.operators.image_edit_tool.mask import apply_alpha_mask + from anyimage.operators.mask_tool import apply_alpha_mask result = render((source([[[1, 1, 1, 1]]], bounds=(-0.5, 0.5, -0.5, 0.5)),), (8, 8)) uncovered = result[..., 3] == 0 @@ -113,7 +113,7 @@ def test_packed_frame_mask_add_keeps_boundary_color(background): from PIL import Image from anyimage.common.image import create_image_edit_result, image_rgba from anyimage.common.selection import SelectionMask - from anyimage.operators.image_edit_tool.mask import apply_alpha_mask + from anyimage.operators.mask_tool import apply_alpha_mask bottom = source([[[background, background, background, 0]]], depth=2, active=False) top = source([[[1, 0, 0, 1]]], bounds=(-1, 0, -1, 1)) diff --git a/tests/test_frame_projection.py b/tests/test_frame_projection.py index 7c44171..2c15ddd 100644 --- a/tests/test_frame_projection.py +++ b/tests/test_frame_projection.py @@ -6,7 +6,7 @@ import pytest sys.path.insert(0, str(Path(__file__).parents[1] / "src")) from anyimage.common.selection import clip_polygon_halfplanes -from anyimage.operators.image_edit_tool import frame +from anyimage.operators import frame_tool as frame @pytest.mark.parametrize("focal", [1, 1.7, 2, 4]) diff --git a/tests/test_image_edit_integration.py b/tests/test_image_edit_integration.py index 5625a49..65afff9 100644 --- a/tests/test_image_edit_integration.py +++ b/tests/test_image_edit_integration.py @@ -12,8 +12,8 @@ sys.path.insert(0, str(SOURCE_ROOT)) from anyimage.common import image as image_data from anyimage.common.selection import SelectionMask -from anyimage.operators.image_edit_tool import mask as edit_mask -from anyimage.operators.image_edit_tool import frame as edit_frame +from anyimage.operators import mask_tool as edit_mask +from anyimage.operators import frame_tool as edit_frame def test_image_edit_result_uses_complete_shared_source_name(): diff --git a/tests/test_image_menu.py b/tests/test_image_menu.py index b04978b..548a5fd 100644 --- a/tests/test_image_menu.py +++ b/tests/test_image_menu.py @@ -93,27 +93,6 @@ class ImageMenuTest(BlenderTestCase): self.assertNotIn(("anyimage.setup_ai_environment", True), calls) - def test_image_edit_activation_defaults_to_frame(self): - calls = [] - previous_ops = getattr(self.fake_bpy, "ops", None) - self.fake_bpy.ops = SimpleNamespace( - wm=SimpleNamespace( - tool_set_by_id=lambda **options: calls.append(options) or {"FINISHED"} - ) - ) - try: - result = self.image_frame.ActivateImageEditTool().execute(None) - finally: - if previous_ops is None: - del self.fake_bpy.ops - else: - self.fake_bpy.ops = previous_ops - - self.assertEqual(result, {"FINISHED"}) - self.assertEqual( - calls, - [{"name": "anyimage.frame", "space_type": "VIEW_3D"}], - ) def test_image_operators_stay_clickable_until_server_is_busy(self): diff --git a/tests/test_mask_undo.py b/tests/test_mask_undo.py index 5258576..c4d5f79 100644 --- a/tests/test_mask_undo.py +++ b/tests/test_mask_undo.py @@ -8,7 +8,7 @@ import pytest sys.path.insert(0, str(Path(__file__).parents[1] / "src")) from anyimage.common import image, viewport -from anyimage.operators.image_edit_tool import mask +from anyimage.operators import mask_tool as mask @pytest.mark.parametrize("gesture", ["LASSO", "BRUSH", "POLYLINE"]) diff --git a/tests/test_model_catalog.py b/tests/test_model_catalog.py index b3ba52b..fe11b71 100644 --- a/tests/test_model_catalog.py +++ b/tests/test_model_catalog.py @@ -41,7 +41,7 @@ class ModelCatalogTest(unittest.TestCase): def test_ben2_model_directory_uses_repository_name(self): self.assertEqual( - self.models.model_directory_name("BEN2_BASE"), + self.models.get_downloadable_model("BEN2_BASE").directory_name, "BEN2-ONNX", ) diff --git a/tests/test_model_moge2_onnx.py b/tests/test_model_moge2_onnx.py index e1925e2..2d9e675 100644 --- a/tests/test_model_moge2_onnx.py +++ b/tests/test_model_moge2_onnx.py @@ -67,16 +67,6 @@ class Moge2OnnxTest(unittest.TestCase): def setUpClass(cls): cls.moge = load_module() - def test_auto_provider_prefers_accelerated_runtime(self): - providers = self.moge.select_providers( - "auto", - ["CPUExecutionProvider", "DmlExecutionProvider"], - ) - - self.assertEqual( - providers, - ["DmlExecutionProvider", "CPUExecutionProvider"], - ) def test_coreml_session_requires_static_input_partitions(self): calls = [] @@ -107,13 +97,6 @@ class Moge2OnnxTest(unittest.TestCase): ], ) - def test_explicit_unavailable_provider_falls_back_to_cpu(self): - providers = self.moge.select_providers( - "cuda", - ["CPUExecutionProvider"], - ) - - self.assertEqual(providers, ["CPUExecutionProvider"]) def test_infer_uses_dynamic_token_input_and_numpy_postprocess(self): height, width = 8, 12 diff --git a/tests/test_model_onnx_runtime.py b/tests/test_model_onnx_runtime.py index 200b135..c21ff7e 100644 --- a/tests/test_model_onnx_runtime.py +++ b/tests/test_model_onnx_runtime.py @@ -190,5 +190,27 @@ class OnnxRuntimeTest(unittest.TestCase): ) + def test_auto_provider_prefers_accelerated_runtime(self): + providers = onnx_runtime.select_providers( + "auto", + ["CPUExecutionProvider", "DmlExecutionProvider"], + ) + + self.assertEqual( + providers, + ["DmlExecutionProvider", "CPUExecutionProvider"], + ) + + + def test_explicit_unavailable_provider_falls_back_to_cpu(self): + providers = onnx_runtime.select_providers( + "cuda", + ["CPUExecutionProvider"], + ) + + self.assertEqual(providers, ["CPUExecutionProvider"]) + + + if __name__ == "__main__": unittest.main()