feat: Implement depth surface strip cleanup and organize tool CLI

- Added a proposal for depth surface strip cleanup, detailing the rationale and changes to the depth plane and cutout nodes.
- Created specifications for the new cleanup requirements, including scenarios for strip removal and boundary edge detection.
- Developed tasks for implementing shared strip detection logic and integrating new interfaces for depth nodes.
- Established a new CLI structure for organizing node groups and model commands, replacing old entry points with a more intuitive command structure.
- Introduced a prototype for panorama surface generation, including requirements for distance reconstruction and geometry node surfaces.
- Added comprehensive tests for depth strip cleanup functionality, ensuring correct behavior for various mesh topologies and edge cases.
This commit is contained in:
2026-09-10 00:53:43 +08:00
parent 1622afa7a2
commit 27ca25224a
42 changed files with 1970 additions and 70 deletions

View File

@@ -3,16 +3,13 @@ import os
import shutil
import subprocess
import sys
import webbrowser
from pathlib import Path
from tempfile import TemporaryDirectory
from tempfile import TemporaryDirectory, mkdtemp
from zipfile import ZipFile
PROJECT_ROOT = Path(__file__).resolve().parents[2]
BLENDER_MODULES = (
"tools.nodes.build",
"tools.nodes.check",
)
def project_env_value(name, path=None):
@@ -57,22 +54,7 @@ def run(command):
subprocess.run(command, cwd=PROJECT_ROOT, check=True)
def main():
parser = argparse.ArgumentParser(
description="Build and validate O_AnyImage.blend.",
)
parser.add_argument(
"--blender",
help="Blender executable; overrides BLENDER_BIN, .env, and PATH.",
)
parser.add_argument(
"--skip-tests",
action="store_true",
help="Skip the Python test suite.",
)
options = parser.parse_args()
executable = blender_path(options.blender)
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:
@@ -83,16 +65,52 @@ def main():
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)); "
)
for module_name in BLENDER_MODULES:
for module_name in modules:
run(
(
executable,
"--factory-startup",
"--background",
"--python-exit-code", "1",
"--python-expr", bootstrap + f"runpy.run_module({module_name!r}, run_name='__main__')",
"--python-expr", bootstrap + f"sys.argv = {[module_name, *arguments]!r}; "
+ f"runpy.run_module({module_name!r}, run_name='__main__')",
)
)
def main():
parser = argparse.ArgumentParser(description="Build, check, or preview node groups.")
commands = parser.add_subparsers(dest="command")
for name, help_text in (
("build", "Build assets, validate them, and run tests"),
("check", "Validate saved assets and run tests"),
("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")
command.add_argument("--fragment", action="store_true", help="Save an HTML fragment without opening it")
else:
command.add_argument("--skip-tests", action="store_true", help="Skip pytest, keeping asset validation")
options = parser.parse_args()
if options.command is None:
parser.print_help()
return
executable = blender_path(options.blender)
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)
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)
if not options.skip_tests:
run((sys.executable, "-m", "pytest"))