Add tools and tests for AnyImage model management and documentation
- Implement sync_models.py for preparing and uploading ONNX artifacts. - Create build_node.py for building and validating Blender scripts. - Add docs.py for building and previewing documentation with Mike or MkDocs. - Introduce model_exports.py for exporting PyTorch models to ONNX format. - Develop pack.py for packaging the project with dependencies for different platforms. - Add test_sync_models.py to validate sync_models functionality. - Add test_tool_commands.py to ensure repository tools have correct command scripts. - Initialize tools package with __init__.py.
This commit is contained in:
Binary file not shown.
1
src/anyimage/common/__init__.py
Normal file
1
src/anyimage/common/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Shared Blender-side image, node, object, interaction, and AI Job helpers."""
|
||||
@@ -2,9 +2,9 @@ from pathlib import Path
|
||||
|
||||
import bpy
|
||||
|
||||
from ...preferences import addon_preferences
|
||||
from ...properties import ai_ready, model_ready, model_record
|
||||
from ...runtime import runtime
|
||||
from ..preferences import addon_preferences
|
||||
from ..properties import ai_ready, model_ready, model_record
|
||||
from ..runtime import runtime
|
||||
|
||||
BEN2_IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".webp"}
|
||||
BEN2_VIDEO_SUFFIXES = {
|
||||
@@ -3,7 +3,7 @@ import math
|
||||
|
||||
import bpy
|
||||
|
||||
from ...properties import ai_ready, ai_setup_label, ai_status
|
||||
from ..properties import ai_ready, ai_setup_label, ai_status
|
||||
from .image_data import (
|
||||
image_empty_bounds,
|
||||
is_image_empty,
|
||||
47
src/anyimage/common/node.py
Normal file
47
src/anyimage/common/node.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
NODE_ASSET_NAME = "O_AnyImage.blend"
|
||||
|
||||
|
||||
def node_asset_path():
|
||||
return Path(__file__).resolve().parents[1] / "assets" / NODE_ASSET_NAME
|
||||
|
||||
|
||||
def load_node_group(group_name):
|
||||
existing = bpy.data.node_groups.get(group_name)
|
||||
if existing is not None:
|
||||
return existing
|
||||
|
||||
asset_path = node_asset_path()
|
||||
if not asset_path.is_file():
|
||||
raise RuntimeError(f"Missing node asset: {asset_path}")
|
||||
if bpy.app.version < (5, 0, 0):
|
||||
directory = str(asset_path) + "/NodeTree/"
|
||||
result = bpy.ops.wm.append(
|
||||
filepath=directory + group_name,
|
||||
directory=directory,
|
||||
filename=group_name,
|
||||
link=False,
|
||||
do_reuse_local_id=True,
|
||||
)
|
||||
if "FINISHED" not in result:
|
||||
raise RuntimeError("Unable to append the node asset")
|
||||
node_group = bpy.data.node_groups.get(group_name)
|
||||
if node_group is None:
|
||||
raise RuntimeError("Appended node asset is unavailable")
|
||||
node_group.use_fake_user = True
|
||||
return node_group
|
||||
|
||||
with bpy.data.libraries.load(str(asset_path), link=True) as (source, target):
|
||||
if group_name not in source.node_groups:
|
||||
raise RuntimeError(f"Node asset is missing {group_name}")
|
||||
target.node_groups = [group_name]
|
||||
linked_group = target.node_groups[0]
|
||||
node_group = bpy.data.pack_linked_ids_hierarchy(root_id=linked_group)
|
||||
if linked_group != node_group and linked_group.users == 0:
|
||||
bpy.data.node_groups.remove(linked_group)
|
||||
node_group.use_fake_user = True
|
||||
return node_group
|
||||
@@ -1,5 +1,3 @@
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
|
||||
from .image_data import (
|
||||
@@ -8,50 +6,7 @@ from .image_data import (
|
||||
image_pixels,
|
||||
is_animated_image,
|
||||
)
|
||||
|
||||
|
||||
NODE_ASSET_NAME = "O_AnyImage.blend"
|
||||
|
||||
|
||||
def node_asset_path():
|
||||
return Path(__file__).resolve().parents[2] / "assets" / NODE_ASSET_NAME
|
||||
|
||||
|
||||
def load_asset_node_group(group_name):
|
||||
existing = bpy.data.node_groups.get(group_name)
|
||||
if existing is not None:
|
||||
return existing
|
||||
|
||||
asset_path = node_asset_path()
|
||||
if not asset_path.is_file():
|
||||
raise RuntimeError(f"Missing node asset: {asset_path}")
|
||||
if bpy.app.version < (5, 0, 0):
|
||||
directory = str(asset_path) + "/NodeTree/"
|
||||
result = bpy.ops.wm.append(
|
||||
filepath=directory + group_name,
|
||||
directory=directory,
|
||||
filename=group_name,
|
||||
link=False,
|
||||
do_reuse_local_id=True,
|
||||
)
|
||||
if "FINISHED" not in result:
|
||||
raise RuntimeError("Unable to append the node asset")
|
||||
node_group = bpy.data.node_groups.get(group_name)
|
||||
if node_group is None:
|
||||
raise RuntimeError("Appended node asset is unavailable")
|
||||
node_group.use_fake_user = True
|
||||
return node_group
|
||||
|
||||
with bpy.data.libraries.load(str(asset_path), link=True) as (source, target):
|
||||
if group_name not in source.node_groups:
|
||||
raise RuntimeError(f"Node asset is missing {group_name}")
|
||||
target.node_groups = [group_name]
|
||||
linked_group = target.node_groups[0]
|
||||
node_group = bpy.data.pack_linked_ids_hierarchy(root_id=linked_group)
|
||||
if linked_group != node_group and linked_group.users == 0:
|
||||
bpy.data.node_groups.remove(linked_group)
|
||||
node_group.use_fake_user = True
|
||||
return node_group
|
||||
from .node import load_node_group
|
||||
|
||||
|
||||
def modifier_input_identifier(node_group, name):
|
||||
@@ -139,7 +94,7 @@ def material_node_group(depth_plane=False):
|
||||
if depth_plane
|
||||
else IMAGE_MATERIAL_NODE_GROUP_NAME
|
||||
)
|
||||
return load_asset_node_group(group_name)
|
||||
return load_node_group(group_name)
|
||||
|
||||
|
||||
def create_image_material(
|
||||
@@ -224,7 +179,7 @@ def create_image_material(
|
||||
links.new(normal_texture.outputs["Color"], material_inputs.inputs["Normal"])
|
||||
if shadeless:
|
||||
shader = nodes.new("ShaderNodeGroup")
|
||||
shader.node_tree = load_asset_node_group(SHADELESS_NODE_GROUP_NAME)
|
||||
shader.node_tree = load_node_group(SHADELESS_NODE_GROUP_NAME)
|
||||
shader.name = SHADELESS_NODE_GROUP_NAME
|
||||
transparent = nodes.new("ShaderNodeBsdfTransparent")
|
||||
mix_shader = nodes.new("ShaderNodeMixShader")
|
||||
@@ -1,6 +1,6 @@
|
||||
import bpy
|
||||
|
||||
from .operators.common.image_data import is_image_empty
|
||||
from .common.image_data import is_image_empty
|
||||
from .operators.upscale import upscale_output_size
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from ..properties import (
|
||||
model_download_size,
|
||||
model_record,
|
||||
)
|
||||
from .common.ai_job import require_environment
|
||||
from ..common.ai_job import require_environment
|
||||
|
||||
|
||||
def format_download_size(size):
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""Shared Blender-side image data, interaction, mesh result, and AI Job helpers."""
|
||||
@@ -8,7 +8,7 @@ from ...server.media.resolution import (
|
||||
MAX_MAX_AI_INPUT_SIZE,
|
||||
MIN_MAX_AI_INPUT_SIZE,
|
||||
)
|
||||
from ..common.ai_job import (
|
||||
from ...common.ai_job import (
|
||||
invoke_ai_setup_if_needed,
|
||||
moge2_parameters,
|
||||
production_device,
|
||||
@@ -16,7 +16,7 @@ from ..common.ai_job import (
|
||||
require_input_path,
|
||||
require_model,
|
||||
)
|
||||
from ..common.image_data import (
|
||||
from ...common.image_data import (
|
||||
cleanup_image_input,
|
||||
image_user_settings,
|
||||
is_image_empty,
|
||||
@@ -24,7 +24,7 @@ from ..common.image_data import (
|
||||
source_start_frame,
|
||||
video_total_frames,
|
||||
)
|
||||
from ..common.mesh_result import create_image_material
|
||||
from ...common.object import create_image_material
|
||||
from .object import (
|
||||
create_color_image,
|
||||
create_depth_plane_from_result,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import bpy
|
||||
|
||||
from ..common.mesh_result import modifier_input_identifier, set_modifier_input
|
||||
from .nodes import mesh_plane_node_group
|
||||
from ...common.object import modifier_input_identifier, set_modifier_input
|
||||
from ...common.node import load_node_group
|
||||
|
||||
|
||||
MESH_PLANE_NODE_GROUP_NAME = "O Mesh Plane"
|
||||
@@ -41,7 +41,7 @@ def add_mesh_plane_processing(
|
||||
subdivisions=0,
|
||||
thickness=0.0,
|
||||
):
|
||||
node_group = mesh_plane_node_group()
|
||||
node_group = load_node_group(MESH_PLANE_NODE_GROUP_NAME)
|
||||
modifier = plane_object.modifiers.new(
|
||||
name=MESH_PLANE_NODE_GROUP_NAME,
|
||||
type="NODES",
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
"""Asset-backed Geometry Nodes for Plane conversion."""
|
||||
|
||||
from ..common.mesh_result import load_asset_node_group
|
||||
|
||||
|
||||
MESH_PLANE_NODE_GROUP_NAME = "O Mesh Plane"
|
||||
DEPTH_PLANE_NODE_GROUP_NAME = "O Image Depth Plane"
|
||||
|
||||
|
||||
def mesh_plane_node_group():
|
||||
return load_asset_node_group(MESH_PLANE_NODE_GROUP_NAME)
|
||||
|
||||
|
||||
def depth_plane_node_group():
|
||||
return load_asset_node_group(DEPTH_PLANE_NODE_GROUP_NAME)
|
||||
|
||||
|
||||
def plane_node_group(shape):
|
||||
if shape == "PLANE":
|
||||
return mesh_plane_node_group()
|
||||
if shape == "DEPTH_PLANE":
|
||||
return depth_plane_node_group()
|
||||
raise ValueError(f"Unsupported Plane shape: {shape}")
|
||||
@@ -2,26 +2,29 @@
|
||||
|
||||
import bpy
|
||||
|
||||
from ..common.image_data import (
|
||||
from ...common.image_data import (
|
||||
color_data_name,
|
||||
image_empty_bounds,
|
||||
image_pixels,
|
||||
load_color_result_image,
|
||||
require_image_empty,
|
||||
)
|
||||
from ..common.depth_data import (
|
||||
from ...common.depth_data import (
|
||||
depth_uniform_scale,
|
||||
load_depth_metadata,
|
||||
load_depth_result_image,
|
||||
)
|
||||
from ..common.mesh_result import (
|
||||
from ...common.object import (
|
||||
create_image_material,
|
||||
finalize_object_result,
|
||||
modifier_input_identifier,
|
||||
set_modifier_input,
|
||||
)
|
||||
from ...common.node import load_node_group
|
||||
from .mesh_plane import create_mesh_plane_mesh, create_mesh_plane_object
|
||||
from .nodes import depth_plane_node_group
|
||||
|
||||
|
||||
DEPTH_PLANE_NODE_GROUP_NAME = "O Image Depth Plane"
|
||||
|
||||
|
||||
def create_color_image(source_object):
|
||||
@@ -71,7 +74,7 @@ def create_depth_plane_object(
|
||||
plane.matrix_world = source_object.matrix_world.copy()
|
||||
plane["anyimage_mesh_shape"] = "DEPTH_PLANE"
|
||||
|
||||
node_group = depth_plane_node_group()
|
||||
node_group = load_node_group(DEPTH_PLANE_NODE_GROUP_NAME)
|
||||
modifier = plane.modifiers.new(name=node_group.name, type="NODES")
|
||||
modifier.node_group = node_group
|
||||
modifier.show_group_selector = False
|
||||
|
||||
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
||||
|
||||
from ...preferences import addon_preferences
|
||||
from ...server.media.resolution import limited_image_size
|
||||
from ..common.image_data import image_empty_bounds
|
||||
from ...common.image_data import image_empty_bounds
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -4,9 +4,10 @@ from ...preferences import (
|
||||
MAX_CUTOUT_MESH_RESOLUTION,
|
||||
MIN_CUTOUT_MESH_RESOLUTION,
|
||||
)
|
||||
from ..common.image_data import image_rgba
|
||||
from ..common.image_region import rasterize_region_path
|
||||
from ...common.image_data import image_rgba
|
||||
from ...common.image_region import rasterize_region_path
|
||||
from .geometry import CUTOUT_ALPHA_THRESHOLD
|
||||
from .shape import CUTOUT_SHAPE_LABELS
|
||||
|
||||
|
||||
def validate_cutout_region(
|
||||
@@ -76,7 +77,7 @@ def open_shape_pie(
|
||||
_pie_button(
|
||||
pie,
|
||||
"SURFACE",
|
||||
"Surface",
|
||||
CUTOUT_SHAPE_LABELS["SURFACE"],
|
||||
"MESH_GRID",
|
||||
source_name,
|
||||
region_path_json,
|
||||
@@ -87,7 +88,7 @@ def open_shape_pie(
|
||||
_pie_button(
|
||||
pie,
|
||||
"BALLOON",
|
||||
"Balloon",
|
||||
CUTOUT_SHAPE_LABELS["BALLOON"],
|
||||
"MESH_UVSPHERE",
|
||||
source_name,
|
||||
region_path_json,
|
||||
@@ -98,7 +99,7 @@ def open_shape_pie(
|
||||
_pie_button(
|
||||
pie,
|
||||
"DEPTH_BALLOON",
|
||||
"Depth Balloon",
|
||||
CUTOUT_SHAPE_LABELS["DEPTH_BALLOON"],
|
||||
"META_BALL",
|
||||
source_name,
|
||||
region_path_json,
|
||||
@@ -109,7 +110,7 @@ def open_shape_pie(
|
||||
_pie_button(
|
||||
pie,
|
||||
"DEPTH_SURFACE",
|
||||
"Depth Surface",
|
||||
CUTOUT_SHAPE_LABELS["DEPTH_SURFACE"],
|
||||
"SURFACE_NCURVE",
|
||||
source_name,
|
||||
region_path_json,
|
||||
|
||||
@@ -22,7 +22,7 @@ from ...server.media.resolution import (
|
||||
MAX_MAX_AI_INPUT_SIZE,
|
||||
MIN_MAX_AI_INPUT_SIZE,
|
||||
)
|
||||
from ..common.ai_job import (
|
||||
from ...common.ai_job import (
|
||||
invoke_ai_setup_if_needed,
|
||||
moge2_parameters,
|
||||
production_device,
|
||||
@@ -30,7 +30,7 @@ from ..common.ai_job import (
|
||||
require_input_path,
|
||||
require_model,
|
||||
)
|
||||
from ..common.image_data import (
|
||||
from ...common.image_data import (
|
||||
cleanup_image_input,
|
||||
image_rgba,
|
||||
load_normal_result_image,
|
||||
@@ -39,8 +39,8 @@ from ..common.image_data import (
|
||||
source_start_frame,
|
||||
video_total_frames,
|
||||
)
|
||||
from ..common.depth_data import load_depth_metadata, load_depth_result_image
|
||||
from ..common.image_interaction import (
|
||||
from ...common.depth_data import load_depth_metadata, load_depth_result_image
|
||||
from ...common.image_interaction import (
|
||||
ImageRegionGesture,
|
||||
apply_region_settings,
|
||||
draw_region_settings,
|
||||
@@ -49,7 +49,7 @@ from ..common.image_interaction import (
|
||||
resolve_image_edit_click,
|
||||
serialize_matrix,
|
||||
)
|
||||
from ..common.image_region import (
|
||||
from ...common.image_region import (
|
||||
ImageEditWarning,
|
||||
RegionMask,
|
||||
RegionPath,
|
||||
|
||||
@@ -4,19 +4,19 @@ from enum import StrEnum
|
||||
|
||||
import bpy
|
||||
|
||||
from ..common.image_data import (
|
||||
from ...common.image_data import (
|
||||
color_data_name,
|
||||
image_rgba,
|
||||
)
|
||||
from ..common.depth_data import camera_depth_values, depth_uniform_scale
|
||||
from ..common.mesh_result import (
|
||||
from ...common.depth_data import camera_depth_values, depth_uniform_scale
|
||||
from ...common.object import (
|
||||
IMAGE_MATERIAL_NODE_GROUP_NAME,
|
||||
create_image_material,
|
||||
finalize_object_result,
|
||||
load_asset_node_group,
|
||||
modifier_input_identifier,
|
||||
set_modifier_input,
|
||||
)
|
||||
from ...common.node import load_node_group
|
||||
from .geometry import (
|
||||
DEFAULT_DEPTH_BASELINE,
|
||||
DEFAULT_DEPTH_DIRECTION,
|
||||
@@ -123,7 +123,7 @@ def _cropped_color_image(source_object, region_mask, source_rgba):
|
||||
|
||||
|
||||
def load_cutout_node_group():
|
||||
return load_asset_node_group(CUTOUT_NODE_GROUP_NAME)
|
||||
return load_node_group(CUTOUT_NODE_GROUP_NAME)
|
||||
|
||||
|
||||
def _connect_balloon_normal_scale(material):
|
||||
|
||||
@@ -4,7 +4,7 @@ import bpy
|
||||
|
||||
from ..runtime import JobOperatorBase
|
||||
from ..properties import BEN2_MODEL_KEY, REALESRGAN_GENERAL_WDN_MODEL_KEY
|
||||
from .common.ai_job import (
|
||||
from ..common.ai_job import (
|
||||
require_background_input_path,
|
||||
da3_parameters,
|
||||
require_input_path,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import bpy
|
||||
|
||||
from ..common.image_interaction import draw_region_settings
|
||||
from ...common.image_interaction import draw_region_settings
|
||||
from .edit_mask import EditImageMask
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import bpy
|
||||
|
||||
from ...properties import BEN2_MODEL_KEY, refine_region_property
|
||||
from ...runtime import JobOperatorBase
|
||||
from ..common.ai_job import (
|
||||
from ...common.ai_job import (
|
||||
production_device,
|
||||
require_environment,
|
||||
require_model,
|
||||
)
|
||||
from ..common.image_data import (
|
||||
from ...common.image_data import (
|
||||
cleanup_image_input,
|
||||
create_pixel_result_image,
|
||||
image_pixels,
|
||||
@@ -19,13 +19,13 @@ from ..common.image_data import (
|
||||
require_image_empty,
|
||||
video_total_frames,
|
||||
)
|
||||
from ..common.image_interaction import (
|
||||
from ...common.image_interaction import (
|
||||
ImageRegionGesture,
|
||||
gesture_property,
|
||||
image_edit_poll,
|
||||
report_image_edit_exception,
|
||||
)
|
||||
from ..common.image_region import (
|
||||
from ...common.image_region import (
|
||||
ImageEditWarning,
|
||||
RegionPath,
|
||||
crop_pixels_to_alpha,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import bpy
|
||||
|
||||
from ..common.image_interaction import draw_region_settings
|
||||
from ...common.image_interaction import draw_region_settings
|
||||
from .edit_mask import EditImageMask
|
||||
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import math
|
||||
|
||||
import bpy
|
||||
|
||||
from ..common.image_interaction import (
|
||||
from ...common.image_interaction import (
|
||||
deserialize_matrix,
|
||||
drawing_in_region,
|
||||
screen_path_to_image_pixels,
|
||||
serialize_matrix,
|
||||
)
|
||||
from ..common.image_region import crop_pixels_to_alpha
|
||||
from ..common.image_data import (
|
||||
from ...common.image_region import crop_pixels_to_alpha
|
||||
from ...common.image_data import (
|
||||
create_pixel_result_image,
|
||||
image_base_name,
|
||||
image_pixels,
|
||||
@@ -19,7 +19,7 @@ from ..common.image_data import (
|
||||
require_image_empty,
|
||||
video_total_frames,
|
||||
)
|
||||
from ..common.image_interaction import (
|
||||
from ...common.image_interaction import (
|
||||
active_view3d_tool_id,
|
||||
draw_dashed_line,
|
||||
draw_polygon_fill,
|
||||
|
||||
@@ -2,7 +2,7 @@ import bpy
|
||||
|
||||
from ..properties import BEN2_MODEL_KEY
|
||||
from ..runtime import JobOperatorBase
|
||||
from .common.ai_job import (
|
||||
from ..common.ai_job import (
|
||||
require_background_input_path,
|
||||
invoke_ai_setup_if_needed,
|
||||
production_device,
|
||||
@@ -10,7 +10,7 @@ from .common.ai_job import (
|
||||
require_model,
|
||||
)
|
||||
|
||||
from .common.image_data import (
|
||||
from ..common.image_data import (
|
||||
cleanup_image_input,
|
||||
default_extract_frames,
|
||||
is_image_empty,
|
||||
@@ -70,7 +70,7 @@ class RemoveImageBackground(bpy.types.Operator):
|
||||
|
||||
|
||||
def apply_background_response(result, source_object_name):
|
||||
from .common.image_data import (
|
||||
from ..common.image_data import (
|
||||
load_color_result_image,
|
||||
replace_empty_image,
|
||||
require_image_empty,
|
||||
|
||||
@@ -8,14 +8,14 @@ from ..server.media.resolution import (
|
||||
MIN_MAX_AI_INPUT_SIZE,
|
||||
)
|
||||
from ..runtime import JobOperatorBase
|
||||
from .common.ai_job import (
|
||||
from ..common.ai_job import (
|
||||
require_input_path,
|
||||
invoke_ai_setup_if_needed,
|
||||
production_device,
|
||||
require_environment,
|
||||
require_model,
|
||||
)
|
||||
from .common.image_data import (
|
||||
from ..common.image_data import (
|
||||
cleanup_image_input,
|
||||
default_extract_frames,
|
||||
is_image_empty,
|
||||
|
||||
Reference in New Issue
Block a user