Refactor and clean up codebase by removing unused functions and reorganizing imports

- Removed unused model keys and functions from properties.py.
- Deleted projection.py as its functions were no longer needed.
- Cleaned up model_catalog.py by removing unused model directory function.
- Updated imports in various test files to reflect new module structure.
- Removed deprecated test cases related to image editing tools.
- Added new tests for ONNX runtime provider selection logic.
This commit is contained in:
2026-09-09 10:31:56 +08:00
parent 4c902ed7db
commit f5aff50b13
47 changed files with 460 additions and 413 deletions

View File

@@ -12,6 +12,16 @@
- 复用来自 `common` 的函数,避免重复造轮子
- 确保文件名/文件夹名和内容边界一致
## 全面整理代码
当用户说「全面整理代码」时,依据本文件规范,直接执行以下整理:
- 按业务职责梳理代码结构与命名;简单功能用单文件,复杂 Operator 功能包统一以 `operators.py` 为入口,`__init__.py` 负责导出与注册清单。
- 沿真实调用链清理无用代码,不把仅有测试引用当作业务用途;查证动态调用、框架回调和注册入口后再删除。
- 精简测试,保留有效行为覆盖,使测试名称、归属与功能对应,并检查状态隔离。
- 同步整理 `tools/node_assets`、`docs/internals` 和相关引用,使名称、说明与实际实现一致。本触发指令包含内部文档整理。
- 保留已有的其他修改和有效功能,运行全量测试并确认正常退出,检查旧引用与最终差异,简要报告调整及验证结果;不构建文档或产物。
## 查询文档
下列是重要文档

View File

@@ -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()` 判断当前编辑器是否支持图片落点。

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 数据。

View File

@@ -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

View File

@@ -1,6 +1,6 @@
# Rectify 透视校正
`operators/image_edit_tool/rectify.py` 定义 `RectifyImagePerspective` 与 `RectifyTool`,将图片内的四角区域校正为矩形。
`operators/rectify_tool.py` 定义 `RectifyImagePerspective` 与 `RectifyTool`,将图片内的四角区域校正为矩形。
```mermaid
flowchart TD

View File

@@ -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

View File

@@ -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"

View File

@@ -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.

View File

@@ -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,

View File

@@ -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",
)

View File

@@ -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))

View File

@@ -1,6 +1,6 @@
"""Rectangular Plane and Depth Plane conversion operators."""
from .conversion import (
from .operators import (
ConvertToDepthPlane,
ConvertToPlane,
GenerateDepthPlane,

View File

@@ -1,4 +1,4 @@
from .main import CutoutSelectionToShape, CutoutTool, SelectCutoutSelection
from .operators import CutoutSelectionToShape, CutoutTool, SelectCutoutSelection
__all__ = (
"CutoutSelectionToShape",

View File

@@ -874,9 +874,6 @@ def teddy_balloon_heights(
)
DEFAULT_ALGORITHM = "POISSON"
def cotangent_poisson_field(
planar_points,
planar_faces,

View File

@@ -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")

View File

@@ -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",
)

View File

@@ -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,

View File

@@ -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,

View File

@@ -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")}

View File

@@ -1,7 +1 @@
from .projection import unproject_camera_depth, unproject_depth_frame
__all__ = (
"unproject_camera_depth",
"unproject_depth_frame",
)
"""Depth and normal artifact processing."""

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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)

View File

@@ -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"

View File

@@ -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,

View File

@@ -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"], ())

View File

@@ -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))

View File

@@ -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():

View File

@@ -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):

View File

@@ -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:

View File

@@ -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 (

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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])

View File

@@ -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():

View File

@@ -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):

View File

@@ -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"])

View File

@@ -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",
)

View File

@@ -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

View File

@@ -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()