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

@@ -4,7 +4,6 @@ import tomllib
import unittest
from pathlib import Path
from unittest.mock import patch
from zipfile import ZipFile
PROJECT_ROOT = Path(__file__).resolve().parents[1]
@@ -19,9 +18,34 @@ def load_pack():
return module
class ArchiveContents:
def __init__(self, path, records):
self.files = {}
records[path] = self
def __enter__(self):
return self
def __exit__(self, *args):
pass
def write(self, source, target):
self.files[Path(target).as_posix()] = None
def writestr(self, target, value):
self.files[target] = value.encode()
def namelist(self):
return list(self.files)
def read(self, name):
return self.files[name]
class ExtensionPackagingTest(unittest.TestCase):
def test_builds_platform_specific_extension_zips(self):
def test_selects_platform_archive_contents_and_manifest(self):
pack = load_pack()
records = {}
with tempfile.TemporaryDirectory() as directory:
directory_path = Path(directory)
wheel_path = directory_path / "blendjob-0.1.16-py3-none-any.whl"
@@ -29,10 +53,9 @@ class ExtensionPackagingTest(unittest.TestCase):
original_dist = pack.DIST_DIR
pack.DIST_DIR = directory_path / "dist"
try:
with patch.object(
pack,
"download_wheels",
return_value=[wheel_path],
with (
patch.object(pack, "download_wheels", return_value=[wheel_path]),
patch.object(pack, "ZipFile", side_effect=lambda path, *args: ArchiveContents(path, records)),
):
output_paths = pack.build_all()
finally:
@@ -47,7 +70,7 @@ class ExtensionPackagingTest(unittest.TestCase):
)
for output_path in output_paths:
platform = output_path.stem.split(".")[-1]
with ZipFile(output_path) as archive:
with records[output_path] as archive:
names = set(archive.namelist())
manifest = archive.read("blender_manifest.toml").decode("utf-8")
@@ -77,7 +100,7 @@ class ExtensionPackagingTest(unittest.TestCase):
"operators/convert_to_plane/__init__.py",
"operators/convert_to_plane/object.py",
"operators/convert_to_plane/conversion.py",
"operators/convert_to_plane/mesh_plane.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",
@@ -116,13 +139,15 @@ class ExtensionPackagingTest(unittest.TestCase):
self.assertNotIn("server/jobs/prepare_geometry_data.py", names)
self.assertNotIn("server/geometry/cutout.py", names)
self.assertIn("server/models/ben2.py", names)
self.assertIn("server/models/da3.py", names)
self.assertNotIn("server/models/da3.py", names)
self.assertIn("server/models/moge2.py", names)
self.assertIn("server/jobs/debug_predictions.py", names)
self.assertNotIn("server/jobs/debug_predictions.py", names)
self.assertNotIn("operators/debug.py", names)
self.assertFalse(any(name.startswith("server/outputs/") for name in names))
self.assertIn("server/model_catalog.py", names)
self.assertIn("server/model_download.py", names)
self.assertIn("server/models/onnx_ben2.py", names)
self.assertIn("server/models/onnx_da3.py", names)
self.assertNotIn("server/models/onnx_da3.py", names)
self.assertIn("server/models/onnx_moge2.py", names)
self.assertIn("server/models/onnx_upscale.py", names)
self.assertIn("server/models/upscale.py", names)
@@ -176,6 +201,7 @@ class ExtensionPackagingTest(unittest.TestCase):
def test_downloads_both_python_versions_for_each_platform(self):
pack = load_pack()
records = {}
for platform, config in pack.PLATFORM_CONFIGS.items():
with self.subTest(platform=platform), tempfile.TemporaryDirectory() as directory:
destination = Path(directory)
@@ -222,9 +248,12 @@ class ExtensionPackagingTest(unittest.TestCase):
[wheel.name for wheel in wheels], sorted({wheel.name for wheel in wheels})
)
self.assertFalse(any(wheel.name.startswith("numpy-") for wheel in wheels))
with patch.object(pack, "DIST_DIR", destination / "dist"):
with (
patch.object(pack, "DIST_DIR", destination / "dist"),
patch.object(pack, "ZipFile", side_effect=lambda path, *args: ArchiveContents(path, records)),
):
output = pack._build_extension(platform, wheels)
with ZipFile(output) as archive:
with records[output] as archive:
manifest = tomllib.loads(archive.read("blender_manifest.toml").decode())
expected = {f"wheels/{wheel.name}" for wheel in wheels}
self.assertEqual({name[2:] for name in manifest["wheels"]}, expected)
@@ -321,13 +350,17 @@ class ExtensionPackagingTest(unittest.TestCase):
def test_archive_excludes_blender_numpy(self):
pack = load_pack()
records = {}
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
wheel = root / "numpy-1.26.4-cp311-cp311-win_amd64.whl"
wheel.write_bytes(b"numpy")
with patch.object(pack, "DIST_DIR", root / "dist"):
with (
patch.object(pack, "DIST_DIR", root / "dist"),
patch.object(pack, "ZipFile", side_effect=lambda path, *args: ArchiveContents(path, records)),
):
output = pack._build_extension("windows-x64", [wheel])
with ZipFile(output) as archive:
with records[output] as archive:
self.assertFalse(any("numpy-" in name for name in archive.namelist()))
self.assertEqual(
tomllib.loads(archive.read("blender_manifest.toml").decode())[