Add comprehensive tests for image processing nodes and refactor node imports
- Introduced new test files for image layer, image plane, normal scale, object normal rotation, shadeless, and temporary attributes. - Implemented tests to validate the behavior of alpha curves, depth layers, and normal scales in various rendering engines. - Refactored import paths for node groups to align with the new directory structure. - Added checks for temporary attribute lifetimes and ensured proper cleanup without warnings. - Enhanced CLI commands to require the Blender group and validate the presence of generated node assets before packaging. - Updated packaging scripts to enforce the existence of necessary assets, preventing build failures.
This commit is contained in:
@@ -1,81 +1,29 @@
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import importlib.util
|
||||
import subprocess
|
||||
import sys
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory, mkdtemp
|
||||
from zipfile import ZipFile
|
||||
from tempfile import mkdtemp
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def project_env_value(name, path=None):
|
||||
env_path = path or PROJECT_ROOT / ".env"
|
||||
if not env_path.is_file():
|
||||
return None
|
||||
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if line.startswith("export "):
|
||||
line = line.removeprefix("export ").lstrip()
|
||||
key, separator, value = line.partition("=")
|
||||
if separator and key.strip() == name:
|
||||
value = value.strip()
|
||||
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
|
||||
value = value[1:-1]
|
||||
return value or None
|
||||
return None
|
||||
|
||||
|
||||
def blender_path(explicit=None):
|
||||
candidate = (
|
||||
explicit
|
||||
or os.environ.get("BLENDER_BIN")
|
||||
or project_env_value("BLENDER_BIN")
|
||||
or shutil.which("blender")
|
||||
)
|
||||
if not candidate:
|
||||
raise RuntimeError(
|
||||
"Blender not found: pass --blender, set BLENDER_BIN, add it to .env, "
|
||||
"or add blender to PATH"
|
||||
)
|
||||
path = Path(candidate).expanduser()
|
||||
if not path.is_file():
|
||||
raise RuntimeError(f"Blender executable does not exist: {path}")
|
||||
return path
|
||||
|
||||
|
||||
def run(command):
|
||||
print("+", subprocess.list2cmdline([str(part) for part in command]), flush=True)
|
||||
subprocess.run(command, cwd=PROJECT_ROOT, check=True)
|
||||
|
||||
|
||||
def run_blender_modules(executable, modules, arguments=()):
|
||||
with TemporaryDirectory(prefix="anyimage-node-dependencies-") as directory:
|
||||
for wheel in (PROJECT_ROOT / "src" / "anyimage" / "wheels").glob("scipy-*.whl"):
|
||||
with ZipFile(wheel) as archive:
|
||||
archive.extractall(Path(directory) / wheel.name.split("-")[2])
|
||||
paths = [str(PROJECT_ROOT), str(PROJECT_ROOT / "src"),
|
||||
str(next((PROJECT_ROOT / "src/anyimage/wheels").glob("blendjob-*.whl")))]
|
||||
bootstrap = (
|
||||
f"import sys, runpy; sys.path[:0] = {paths!r}; "
|
||||
f"sys.path.insert(0, {directory!r} + '/cp' + str(sys.version_info.major) + str(sys.version_info.minor)); "
|
||||
def require_bpy():
|
||||
if importlib.util.find_spec("bpy") is None:
|
||||
raise RuntimeError(
|
||||
"bpy is required; run with: uv run --group blender node-group ..."
|
||||
)
|
||||
for module_name in modules:
|
||||
run(
|
||||
(
|
||||
executable,
|
||||
"--factory-startup",
|
||||
"--background",
|
||||
"--python-exit-code", "1",
|
||||
"--python-expr", bootstrap + f"sys.argv = {[module_name, *arguments]!r}; "
|
||||
+ f"runpy.run_module({module_name!r}, run_name='__main__')",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def run_module(module_name, arguments=()):
|
||||
run((sys.executable, "-m", module_name, *arguments))
|
||||
|
||||
|
||||
def main():
|
||||
@@ -87,7 +35,6 @@ def main():
|
||||
("preview", "Preview node groups from current source"),
|
||||
):
|
||||
command = commands.add_parser(name, help=help_text)
|
||||
command.add_argument("--blender", help="Blender executable; overrides BLENDER_BIN, .env, and PATH")
|
||||
if name == "preview":
|
||||
command.add_argument("--output", type=Path, help="Output HTML path; defaults to a temporary directory")
|
||||
command.add_argument("--no-open", action="store_true", help="Save without opening a browser")
|
||||
@@ -98,21 +45,22 @@ def main():
|
||||
if options.command is None:
|
||||
parser.print_help()
|
||||
return
|
||||
executable = blender_path(options.blender)
|
||||
require_bpy()
|
||||
if options.command == "preview":
|
||||
output = (options.output or Path(mkdtemp(prefix="anyimage-node-preview-")) / "nodes.html").resolve()
|
||||
arguments = ["--output", str(output)]
|
||||
if options.fragment:
|
||||
arguments.append("--fragment")
|
||||
run_blender_modules(executable, ("tools.nodes.preview.export",), arguments)
|
||||
run_module("tools.nodes.preview.export", arguments)
|
||||
print(output)
|
||||
if not options.no_open and not options.fragment:
|
||||
webbrowser.open(output.as_uri())
|
||||
return
|
||||
modules = ("tools.nodes.build", "tools.nodes.check") if options.command == "build" else ("tools.nodes.check",)
|
||||
run_blender_modules(executable, modules)
|
||||
for module_name in modules:
|
||||
run_module(module_name)
|
||||
if not options.skip_tests:
|
||||
run((sys.executable, "-m", "pytest"))
|
||||
run((sys.executable, "-m", "pytest", "tests/nodes", "tests/tools/nodes"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user