feat(node_assets): add image plane geometry nodes and validation scripts

- Implemented `image_plane.py` to build the Image Plane Geometry Nodes group with customizable parameters for geometry, subdivide, and thickness.
- Created `image_plane_validation.py` to validate the functionality of the image plane, ensuring correct geometry generation and material assignment.
- Developed `material_layers.py` to construct shader node groups for image materials, including depth and normal handling.
- Authored `plane_layouts.py` to define node arrangements for image geometry assets.
- Introduced `shadeless.py` to create a shadeless shader node group that displays color without scene lighting while preserving shadows and reflections.
This commit is contained in:
2026-09-08 23:24:27 +08:00
parent 196b964efc
commit 4c902ed7db
155 changed files with 9700 additions and 10393 deletions

View File

@@ -1,14 +1,13 @@
import json
import sys
from pathlib import Path
from types import MethodType, SimpleNamespace
from types import SimpleNamespace
SOURCE_ROOT = Path(__file__).parents[1] / "src"
sys.path.insert(0, str(SOURCE_ROOT))
from anyimage.common.selection import SelectionPath
from anyimage.common import viewport
from anyimage.common.viewport import ImageGesture
from anyimage.operators.cutout_tool import interaction
from anyimage.operators.cutout_tool import main as cutout_main
@@ -173,69 +172,27 @@ def test_cutout_mesh_detail_maps_to_denser_sampling():
] == [32, 16, 8, 4]
def test_cutout_reuses_image_lasso_gesture_and_double_click_selects_all(monkeypatch):
source = cutout_source(size=(65536, 32768))
def test_cutout_lasso_submission_passes_only_the_selected_path(monkeypatch):
source = cutout_source()
window_manager = WindowManager()
context = cutout_context(window_manager)
monkeypatch.setattr(
cutout_main, "resolve_image_edit_click", lambda *_args: (source, True)
)
monkeypatch.setattr(cutout_main, "video_total_frames", lambda _image: 1)
monkeypatch.setattr(cutout_main, "require_image_empty", lambda _name: source)
monkeypatch.setattr(cutout_main, "serialize_matrix", lambda _matrix: "matrix")
monkeypatch.setattr(
cutout_main,
"image_rgba",
lambda *_args: (_ for _ in ()).throw(AssertionError("unexpected pixel read")),
)
monkeypatch.setattr(
cutout_main,
"rasterize_selection_path",
lambda *_args, **_kwargs: (_ for _ in ()).throw(
AssertionError("unexpected Selection rasterization")
),
)
operator = SimpleNamespace(
full_image=True,
report=lambda *_args: None,
)
source.matrix_world = object()
operator._submit_selection_path = MethodType(
cutout_main.SelectCutoutSelection._submit_selection_path,
operator,
)
result = cutout_main.SelectCutoutSelection.invoke(
operator, context, SimpleNamespace(value="DOUBLE_CLICK")
)
assert issubclass(cutout_main.SelectCutoutSelection, ImageGesture)
monkeypatch.setattr(interaction, "ai_ready", lambda: False)
path = SelectionPath(points=((0, 0), (10, 0), (0, 10)))
operator = SimpleNamespace(source_object_name=source.name, _pie_event=object())
result = cutout_main.SelectCutoutSelection._submit_selection_path(operator, context, path)
assert result == {"FINISHED"}
selection_path = SelectionPath.from_json(window_manager.pie.calls[0][2].selection_path_json)
assert selection_path.points == (
(0, 0),
(65536, 0),
(65536, 32768),
(0, 32768),
)
assert window_manager.pie.calls[0][2].point_spacing == 32
assert not hasattr(cutout_main, "pending_selection")
assert SelectionPath.from_json(window_manager.pie.calls[0][2].selection_path_json) == path
def test_cutout_tool_prioritizes_double_click_before_lasso_drag():
assert cutout_main.CutoutTool.bl_keymap[0][1]["value"] == "DOUBLE_CLICK"
assert cutout_main.CutoutTool.bl_keymap[1][1]["value"] == "CLICK_DRAG"
assert [entry[0] for entry in cutout_main.CutoutTool.bl_keymap[2:]] == [
"view3d.select",
"view3d.select",
]
assert cutout_main.CutoutTool.bl_keymap[2][2] == {
"properties": [("deselect_all", False)]
}
assert cutout_main.CutoutTool.bl_keymap[3][2] == {
"properties": [("toggle", True)]
}
def test_cutout_tool_only_binds_lasso_drag_and_native_selection():
keymap = cutout_main.CutoutTool.bl_keymap
assert keymap[0][1]["value"] == "CLICK_DRAG"
assert [entry[0] for entry in keymap[1:]] == ["view3d.select", "view3d.select"]
assert all(entry[1]["value"] != "DOUBLE_CLICK" for entry in keymap)
assert "full_image" not in cutout_main.SelectCutoutSelection.__annotations__
assert keymap[1][2] == {"properties": [("deselect_all", False)]}
assert keymap[2][2] == {"properties": [("toggle", True)]}
def test_cutout_f_has_no_option_shortcut(monkeypatch):