Add operators for model downloading and surface generation
- Implemented `DownloadModel` and `DownloadRequiredModels` operators for downloading models. - Created `ClearModels` operator to clear model resources. - Added `ConvertToSurface` operator for converting image empties into textured surfaces. - Introduced `GenerateDa3Surface` and `GenerateMoge2Surface` operators for generating surfaces using DA3 and MoGe-2 models respectively. - Developed utility functions for handling image mesh data, assigning attributes, and managing geometry nodes. - Established runtime configuration and post-installation model downloading in `runtime.py`.
This commit is contained in:
@@ -71,11 +71,32 @@ class AddonTest(unittest.TestCase):
|
||||
sys.path.insert(0, str(SOURCE_ROOT))
|
||||
cls.paths_before_import = tuple(sys.path)
|
||||
cls.addon = importlib.import_module("snapform")
|
||||
cls.job_runtime = importlib.import_module("snapform.runtime")
|
||||
cls.model_operators = importlib.import_module("snapform.operators.models")
|
||||
cls.image_depth = importlib.import_module(
|
||||
"snapform.blender.depth_plane"
|
||||
"snapform.operators.material"
|
||||
)
|
||||
cls.image_data = importlib.import_module(
|
||||
"snapform.operators.image"
|
||||
)
|
||||
cls.depth_operator = importlib.import_module(
|
||||
"snapform.operators.depth_plane.operator"
|
||||
)
|
||||
cls.image_background = importlib.import_module(
|
||||
"snapform.operators.background"
|
||||
)
|
||||
cls.image_mesh = importlib.import_module(
|
||||
"snapform.blender.surface"
|
||||
"snapform.operators.surface"
|
||||
)
|
||||
cls.balloon = importlib.import_module("snapform.operators.balloon.operator")
|
||||
cls.balloon_geometry = importlib.import_module(
|
||||
"snapform.operators.balloon.geometry"
|
||||
)
|
||||
cls.balloon_poisson = importlib.import_module(
|
||||
"snapform.operators.balloon.poisson"
|
||||
)
|
||||
cls.balloon_teddy = importlib.import_module(
|
||||
"snapform.operators.balloon.teddy"
|
||||
)
|
||||
cls.paths_after_import = tuple(sys.path)
|
||||
cls.fake_bpy = fake_bpy
|
||||
@@ -113,7 +134,7 @@ class AddonTest(unittest.TestCase):
|
||||
if hasattr(self.fake_bpy.types.Scene, "snapform_settings"):
|
||||
del self.fake_bpy.types.Scene.snapform_settings
|
||||
|
||||
def test_snapform_has_no_runtime_boundary_modules(self):
|
||||
def test_runtime_and_operator_support_modules_have_explicit_boundaries(self):
|
||||
for name in (
|
||||
"client.py",
|
||||
"environment.py",
|
||||
@@ -122,7 +143,12 @@ class AddonTest(unittest.TestCase):
|
||||
"status_bar.py",
|
||||
):
|
||||
self.assertFalse((SOURCE_ROOT / "snapform" / name).exists(), name)
|
||||
self.assertFalse(hasattr(self.addon, "client"))
|
||||
self.assertTrue((SOURCE_ROOT / "snapform" / "runtime.py").is_file())
|
||||
self.assertFalse((SOURCE_ROOT / "snapform" / "job_runtime.py").exists())
|
||||
self.assertFalse((SOURCE_ROOT / "snapform" / "client").exists())
|
||||
self.assertFalse((SOURCE_ROOT / "snapform" / "ui").exists())
|
||||
self.assertFalse((SOURCE_ROOT / "snapform" / "operators.py").exists())
|
||||
self.assertFalse((SOURCE_ROOT / "snapform" / "blender").exists())
|
||||
|
||||
def test_mps_requires_m4_or_newer(self):
|
||||
properties = self.addon.properties
|
||||
@@ -184,7 +210,7 @@ class AddonTest(unittest.TestCase):
|
||||
blendjob = importlib.import_module("blendjob")
|
||||
self.assertFalse(hasattr(blendjob, "JobRequest"))
|
||||
|
||||
def test_job_operators_are_flat_and_use_plain_properties(self):
|
||||
def test_operator_modules_use_plain_properties(self):
|
||||
operators = self.addon.operators
|
||||
for name in (
|
||||
"SnapFormJobMixin",
|
||||
@@ -195,18 +221,18 @@ class AddonTest(unittest.TestCase):
|
||||
"BackgroundRequestMixin",
|
||||
):
|
||||
self.assertFalse(hasattr(operators, name), name)
|
||||
for operator in operators.JOB_OPERATOR_CLASSES:
|
||||
for operator in operators.CLASSES:
|
||||
for name in getattr(operator, "__annotations__", {}):
|
||||
self.assertFalse(name.startswith("snapform_"), (operator, name))
|
||||
|
||||
def test_concrete_job_operator_is_the_blender_operator(self):
|
||||
operator = self.addon.operators.GenerateMoge2Depth
|
||||
self.assertEqual(operator.bl_idname, "snapform.generate_moge2_depth")
|
||||
self.assertIn(self.addon.job_runtime.JobOperatorBase, operator.__mro__)
|
||||
self.assertIn(self.job_runtime.JobOperatorBase, operator.__mro__)
|
||||
self.assertIn(self.fake_bpy.types.Operator, operator.__mro__)
|
||||
|
||||
def test_default_request_sends_every_operator_property(self):
|
||||
class Example(self.addon.job_runtime.JobOperatorBase):
|
||||
class Example(self.job_runtime.JobOperatorBase):
|
||||
value: object()
|
||||
enabled: object()
|
||||
|
||||
@@ -219,7 +245,7 @@ class AddonTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_overridden_request_purely_replaces_default_properties(self):
|
||||
class Example(self.addon.job_runtime.JobOperatorBase):
|
||||
class Example(self.job_runtime.JobOperatorBase):
|
||||
value: object()
|
||||
|
||||
def request(self, context):
|
||||
@@ -248,7 +274,7 @@ class AddonTest(unittest.TestCase):
|
||||
def mark_job_complete(self, _job_id):
|
||||
pass
|
||||
|
||||
class Example(self.addon.job_runtime.JobOperatorBase):
|
||||
class Example(self.job_runtime.JobOperatorBase):
|
||||
job_type = "example"
|
||||
value: object()
|
||||
|
||||
@@ -335,7 +361,7 @@ class AddonTest(unittest.TestCase):
|
||||
(job_type, parameters)
|
||||
)
|
||||
)
|
||||
self.addon.job_runtime.post_install(fake_runtime)
|
||||
self.job_runtime.post_install(fake_runtime)
|
||||
self.assertEqual(
|
||||
calls,
|
||||
[
|
||||
@@ -355,7 +381,7 @@ class AddonTest(unittest.TestCase):
|
||||
RuntimeError,
|
||||
"MoGe-2 ViT-S Normal: network unavailable; BEN2 Base: network unavailable",
|
||||
):
|
||||
self.addon.job_runtime.post_install(
|
||||
self.job_runtime.post_install(
|
||||
SimpleNamespace(request=request)
|
||||
)
|
||||
|
||||
@@ -452,7 +478,7 @@ class AddonTest(unittest.TestCase):
|
||||
|
||||
def test_required_model_operator_requests_both_models(self):
|
||||
operator = self.addon.operators.DownloadRequiredModels()
|
||||
with patch.object(self.addon.operators, "require_environment"):
|
||||
with patch.object(self.model_operators, "require_environment"):
|
||||
parameters = operator.request(None)
|
||||
|
||||
self.assertEqual(
|
||||
@@ -494,7 +520,11 @@ class AddonTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(
|
||||
calls[0],
|
||||
("snapform.balloon_lasso", True),
|
||||
)
|
||||
self.assertIn(
|
||||
("snapform.download_required_models", True),
|
||||
calls,
|
||||
)
|
||||
self.assertIn(("snapform.convert_to_depth_plane", False), calls)
|
||||
self.assertIn(("snapform.convert_to_surface", False), calls)
|
||||
@@ -532,24 +562,336 @@ class AddonTest(unittest.TestCase):
|
||||
):
|
||||
menu.draw(None)
|
||||
|
||||
self.assertEqual(calls[0], ("snapform.install_environment", True))
|
||||
self.assertEqual(
|
||||
calls[1],
|
||||
("snapform.install_environment_mirror", True),
|
||||
calls[0],
|
||||
("snapform.balloon_lasso", True),
|
||||
)
|
||||
self.assertIn(("snapform.install_environment", True), calls)
|
||||
self.assertIn(("snapform.install_environment_mirror", True), calls)
|
||||
self.assertNotIn(("snapform.download_required_models", True), calls)
|
||||
self.assertIn(("snapform.convert_to_depth_plane", False), calls)
|
||||
self.assertIn(("snapform.convert_to_surface", False), calls)
|
||||
self.assertIn(("snapform.remove_image_background", False), calls)
|
||||
|
||||
def test_balloon_contour_is_smooth_and_evenly_resampled(self):
|
||||
import numpy as np
|
||||
|
||||
contour = self.balloon_geometry.smooth_lasso_contour(
|
||||
((0, 0), (3, 0), (4, 0), (4, 2), (0, 2)),
|
||||
0.25,
|
||||
)
|
||||
closed = np.vstack((contour, contour[0]))
|
||||
lengths = np.linalg.norm(np.diff(closed, axis=0), axis=1)
|
||||
|
||||
self.assertGreater(len(contour), 20)
|
||||
self.assertGreater(self.balloon_geometry.polygon_area(contour), 0.0)
|
||||
self.assertLess(float(lengths.std() / lengths.mean()), 0.15)
|
||||
|
||||
def test_balloon_interior_points_have_uniform_spacing(self):
|
||||
import numpy as np
|
||||
|
||||
contour = np.asarray(((0, 0), (4, 0), (4, 4), (0, 4)), dtype=float)
|
||||
points = self.balloon_geometry.uniform_interior_points(contour, 0.5)
|
||||
distances = np.linalg.norm(points[:, None] - points[None, :], axis=2)
|
||||
np.fill_diagonal(distances, np.inf)
|
||||
nearest = distances.min(axis=1)
|
||||
|
||||
self.assertGreater(len(points), 20)
|
||||
self.assertAlmostEqual(float(np.median(nearest)), 0.5, delta=0.02)
|
||||
self.assertGreater(float(nearest.min()), 0.49)
|
||||
|
||||
def test_balloon_default_sampling_stays_coarse(self):
|
||||
import numpy as np
|
||||
|
||||
polygon = np.asarray(
|
||||
((100, 100), (900, 100), (950, 500), (800, 900),
|
||||
(200, 900), (50, 500)),
|
||||
dtype=float,
|
||||
)
|
||||
contour, interior, _spacing = (
|
||||
self.balloon_geometry.prepare_planar_points(
|
||||
polygon,
|
||||
(1000, 1000),
|
||||
(0.0, 1.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
|
||||
self.assertLess(len(contour), 150)
|
||||
self.assertLess(len(interior), 400)
|
||||
def test_balloon_sampling_uses_world_distance(self):
|
||||
import numpy as np
|
||||
|
||||
polygon = np.asarray(
|
||||
((0, 0), (1000, 0), (1000, 1000), (0, 1000)),
|
||||
dtype=float,
|
||||
)
|
||||
unit_contour, unit_interior, unit_spacing = (
|
||||
self.balloon_geometry.prepare_planar_points(
|
||||
polygon,
|
||||
(1000, 1000),
|
||||
(0.0, 1.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
scaled_contour, scaled_interior, scaled_spacing = (
|
||||
self.balloon_geometry.prepare_planar_points(
|
||||
polygon,
|
||||
(1000, 1000),
|
||||
(0.0, 2.0, 0.0, 2.0),
|
||||
world_metric=((0.25, 0.0), (0.0, 0.25)),
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(unit_spacing, 0.1)
|
||||
self.assertEqual(scaled_spacing, 0.1)
|
||||
self.assertEqual(len(unit_contour), len(scaled_contour))
|
||||
self.assertEqual(len(unit_interior), len(scaled_interior))
|
||||
|
||||
def test_poisson_balloon_is_zero_on_boundary_and_positive_inside(self):
|
||||
import numpy as np
|
||||
|
||||
points = np.asarray(
|
||||
((0, 0), (2, 0), (2, 2), (0, 2), (1, 1)),
|
||||
dtype=float,
|
||||
)
|
||||
faces = ((0, 1, 4), (1, 2, 4), (2, 3, 4), (3, 0, 4))
|
||||
boundary = np.asarray((True, True, True, True, False))
|
||||
heights = self.balloon_poisson.poisson_balloon_heights(
|
||||
points, faces, boundary, 0.45
|
||||
)
|
||||
|
||||
self.assertTrue((heights[:4] == 0.0).all())
|
||||
self.assertGreater(heights[4], 0.0)
|
||||
|
||||
def test_poisson_balloon_scales_in_world_units(self):
|
||||
import numpy as np
|
||||
|
||||
points = np.asarray(
|
||||
((0, 0), (2, 0), (2, 2), (0, 2), (1, 1)),
|
||||
dtype=float,
|
||||
)
|
||||
faces = ((0, 1, 4), (1, 2, 4), (2, 3, 4), (3, 0, 4))
|
||||
boundary = np.asarray((True, True, True, True, False))
|
||||
original = self.balloon_poisson.poisson_balloon_heights(
|
||||
points, faces, boundary, 0.45
|
||||
)
|
||||
scaled = self.balloon_poisson.poisson_balloon_heights(
|
||||
points * 2.0, faces, boundary, 0.45
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(scaled[4], original[4] * 2.0, places=6)
|
||||
|
||||
def test_poisson_balloon_makes_wide_regions_thicker(self):
|
||||
import numpy as np
|
||||
|
||||
faces = ((0, 1, 4), (1, 2, 4), (2, 3, 4), (3, 0, 4))
|
||||
boundary = np.asarray((True, True, True, True, False))
|
||||
wide = np.asarray(
|
||||
((0, 0), (4, 0), (4, 2), (0, 2), (2, 1)),
|
||||
dtype=float,
|
||||
)
|
||||
narrow = np.asarray(
|
||||
((0, 0), (4, 0), (4, 0.6), (0, 0.6), (2, 0.3)),
|
||||
dtype=float,
|
||||
)
|
||||
wide_height = self.balloon_poisson.poisson_balloon_heights(
|
||||
wide, faces, boundary, 0.45
|
||||
)[4]
|
||||
narrow_height = self.balloon_poisson.poisson_balloon_heights(
|
||||
narrow, faces, boundary, 0.45
|
||||
)[4]
|
||||
|
||||
self.assertGreater(wide_height, narrow_height)
|
||||
|
||||
def test_teddy_balloon_uses_chordal_axis_radius(self):
|
||||
import numpy as np
|
||||
|
||||
points = np.asarray(
|
||||
((0, 0), (4, 0), (4, 2), (0, 2), (2, 1)),
|
||||
dtype=float,
|
||||
)
|
||||
faces = ((0, 1, 4), (1, 2, 4), (2, 3, 4), (3, 0, 4))
|
||||
boundary = np.asarray((True, True, True, True, False))
|
||||
nodes = np.asarray(((1, 1), (3, 1)), dtype=float)
|
||||
with patch.object(
|
||||
self.balloon_teddy,
|
||||
"chordal_axis",
|
||||
return_value=(nodes, [(0, 1)], np.asarray((1.0, 1.0))),
|
||||
):
|
||||
heights = self.balloon_teddy.teddy_balloon_heights(
|
||||
points, faces, points[:4], boundary, 0.45
|
||||
)
|
||||
|
||||
self.assertTrue((heights[:4] == 0.0).all())
|
||||
self.assertAlmostEqual(heights[4], 0.45, places=6)
|
||||
|
||||
def test_balloon_dispatches_distinct_height_algorithms(self):
|
||||
import numpy as np
|
||||
|
||||
points = np.asarray(((0, 0),), dtype=float)
|
||||
boundary = np.asarray((True,))
|
||||
with patch.object(
|
||||
self.balloon_geometry,
|
||||
"poisson_balloon_heights",
|
||||
return_value=np.asarray((1.0,)),
|
||||
) as poisson, patch.object(
|
||||
self.balloon_geometry,
|
||||
"teddy_balloon_heights",
|
||||
return_value=np.asarray((2.0,)),
|
||||
) as teddy:
|
||||
poisson_result = self.balloon_geometry.balloon_heights(
|
||||
"POISSON", points, (), points, boundary, 0.45
|
||||
)
|
||||
teddy_result = self.balloon_geometry.balloon_heights(
|
||||
"TEDDY", points, (), points, boundary, 0.45
|
||||
)
|
||||
|
||||
self.assertEqual(poisson_result[0], 1.0)
|
||||
self.assertEqual(teddy_result[0], 2.0)
|
||||
poisson.assert_called_once()
|
||||
teddy.assert_called_once()
|
||||
def test_balloon_geometry_has_a_shared_closed_seam(self):
|
||||
from collections import Counter
|
||||
|
||||
import numpy as np
|
||||
|
||||
planar = np.asarray(
|
||||
((0, 0), (2, 0), (2, 2), (0, 2), (1, 1)),
|
||||
dtype=float,
|
||||
)
|
||||
faces_2d = ((0, 1, 4), (1, 2, 4), (2, 3, 4), (3, 0, 4))
|
||||
boundary = np.asarray((True, True, True, True, False))
|
||||
heights = np.asarray((0, 0, 0, 0, 1), dtype=float)
|
||||
vertices, faces, uv = self.balloon_geometry.build_closed_geometry(
|
||||
planar,
|
||||
faces_2d,
|
||||
boundary,
|
||||
heights,
|
||||
(0.0, 2.0, 0.0, 2.0),
|
||||
)
|
||||
edges = Counter()
|
||||
for face in faces:
|
||||
for start, end in zip(face, face[1:] + face[:1]):
|
||||
edges[tuple(sorted((start, end)))] += 1
|
||||
|
||||
self.assertEqual(len(vertices), 6)
|
||||
self.assertEqual(len(vertices), len(uv))
|
||||
self.assertEqual(uv.dtype, np.float32)
|
||||
self.assertTrue(edges)
|
||||
self.assertTrue(all(users == 2 for users in edges.values()))
|
||||
|
||||
def test_balloon_controller_and_extract_operator_are_registered(self):
|
||||
self.assertIn(self.balloon.ExtractBalloon, self.addon.CLASSES)
|
||||
self.assertIn(self.balloon.BalloonLasso, self.addon.CLASSES)
|
||||
self.assertLess(
|
||||
self.addon.CLASSES.index(self.balloon.ExtractBalloon),
|
||||
self.addon.CLASSES.index(self.balloon.BalloonLasso),
|
||||
)
|
||||
def test_balloon_redo_properties_use_mode_and_world_resolution(self):
|
||||
import inspect
|
||||
|
||||
spacing, inflation = self.balloon.balloon_geometry_parameters(2.0, 0.2)
|
||||
|
||||
self.assertEqual(spacing, 0.2)
|
||||
self.assertEqual(inflation, 0.9)
|
||||
operator = self.balloon.ExtractBalloon
|
||||
self.assertIn("REGISTER", operator.bl_options)
|
||||
self.assertIn("mode", operator.__annotations__)
|
||||
self.assertIn("thickness", operator.__annotations__)
|
||||
self.assertIn("resolution", operator.__annotations__)
|
||||
self.assertIn("source_matrix_data", operator.__annotations__)
|
||||
resolution_source = inspect.getsource(self.balloon.resolution_property)
|
||||
thickness_source = inspect.getsource(self.balloon.thickness_property)
|
||||
self.assertIn('subtype="DISTANCE"', resolution_source)
|
||||
self.assertIn('unit="LENGTH"', resolution_source)
|
||||
self.assertIn("soft_max=5.0", thickness_source)
|
||||
self.assertNotIn(" max=", thickness_source)
|
||||
|
||||
def test_balloon_lasso_passes_hud_and_navigation_events(self):
|
||||
regions = (
|
||||
SimpleNamespace(type="WINDOW", x=0, y=0, width=800, height=600),
|
||||
SimpleNamespace(type="HUD", x=20, y=20, width=240, height=180),
|
||||
)
|
||||
context = SimpleNamespace(area=SimpleNamespace(regions=regions))
|
||||
|
||||
self.assertTrue(
|
||||
self.balloon.event_over_interface_region(
|
||||
context,
|
||||
SimpleNamespace(mouse_x=40, mouse_y=40),
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
self.balloon.event_over_interface_region(
|
||||
context,
|
||||
SimpleNamespace(mouse_x=500, mouse_y=400),
|
||||
)
|
||||
)
|
||||
for event_type in (
|
||||
"MIDDLEMOUSE",
|
||||
"WHEELUPMOUSE",
|
||||
"TRACKPADPAN",
|
||||
"NDOF_MOTION",
|
||||
):
|
||||
self.assertIn(event_type, self.balloon.VIEW_NAVIGATION_EVENTS)
|
||||
self.assertEqual(self.balloon.BalloonLasso.bl_options, {"INTERNAL"})
|
||||
def test_balloon_lasso_dispatches_individual_redo_operator(self):
|
||||
import inspect
|
||||
|
||||
execute_source = inspect.getsource(self.balloon.ExtractBalloon.execute)
|
||||
invoke_source = inspect.getsource(self.balloon.BalloonLasso.invoke)
|
||||
modal_source = inspect.getsource(self.balloon.BalloonLasso.modal)
|
||||
|
||||
self.assertIn("json.loads(self.selection_data)", execute_source)
|
||||
self.assertIn("deserialize_matrix(self.source_matrix_data)", execute_source)
|
||||
self.assertIn("select_result=not self.keep_source_selected", execute_source)
|
||||
self.assertIn("serialize_matrix(source_object.matrix_world)", invoke_source)
|
||||
self.assertIn("bpy.ops.snapform.extract_balloon(", modal_source)
|
||||
self.assertIn('"EXEC_DEFAULT"', modal_source)
|
||||
self.assertIn("keep_source_selected=True", modal_source)
|
||||
self.assertIn('event.type == "ESC"', modal_source)
|
||||
self.assertIn('return {"RUNNING_MODAL"}', modal_source)
|
||||
self.assertIn('return {"PASS_THROUGH"}', modal_source)
|
||||
self.assertIn("event_over_interface_region(context, event)", modal_source)
|
||||
self.assertNotIn("extract_balloon_object(", modal_source)
|
||||
def test_balloon_matrix_serialization_keeps_all_components(self):
|
||||
import json
|
||||
|
||||
matrix = tuple(
|
||||
tuple(row * 4 + column for column in range(4))
|
||||
for row in range(4)
|
||||
)
|
||||
values = json.loads(self.balloon.serialize_matrix(matrix))
|
||||
|
||||
self.assertEqual(values, list(range(16)))
|
||||
|
||||
def test_extract_balloon_preserves_the_source_object(self):
|
||||
import inspect
|
||||
|
||||
source = inspect.getsource(self.balloon.extract_balloon_object)
|
||||
self.assertNotIn("objects.remove(source_object", source)
|
||||
self.assertIn("balloon_name = source_name", source)
|
||||
self.assertIn("selected_object = balloon if select_result else source_object", source)
|
||||
self.assertIn("balloon.matrix_world = matrix_world.copy()", source)
|
||||
self.assertIn("create_material(", source)
|
||||
self.assertIn("color_image = image.copy()", source)
|
||||
self.assertIn("color_image.name = color_data_name(source_object)", source)
|
||||
self.assertIn("balloon_name,\n color_image,\n 0.0,", source)
|
||||
self.assertIn(
|
||||
"surface_group_name=TEXTURE_MATERIAL_NODE_GROUP_NAME",
|
||||
source,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.image_depth.TEXTURE_MATERIAL_NODE_GROUP_NAME,
|
||||
"O Texture Layer",
|
||||
)
|
||||
|
||||
def test_image_operators_require_environment_and_required_models(self):
|
||||
context = SimpleNamespace(
|
||||
object=SimpleNamespace(data=SimpleNamespace()),
|
||||
)
|
||||
operators = (
|
||||
(self.image_depth, self.addon.ConvertToDepthPlane),
|
||||
(self.image_mesh, self.addon.ConvertToSurface),
|
||||
(self.image_depth, self.addon.RemoveImageBackground),
|
||||
(self.depth_operator, self.depth_operator.ConvertToDepthPlane),
|
||||
(self.image_mesh, self.image_mesh.ConvertToSurface),
|
||||
(self.image_background, self.image_background.RemoveImageBackground),
|
||||
)
|
||||
|
||||
states = (
|
||||
@@ -719,8 +1061,8 @@ class AddonTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
self.addon.operators, "require_environment"
|
||||
), patch.object(self.addon.operators, "require_model"):
|
||||
self.depth_operator, "require_environment"
|
||||
), patch.object(self.depth_operator, "require_model"):
|
||||
parameters = operator.request(context)
|
||||
|
||||
self.assertEqual(parameters["input"], __file__)
|
||||
@@ -738,7 +1080,7 @@ class AddonTest(unittest.TestCase):
|
||||
context = object()
|
||||
|
||||
with patch.object(
|
||||
self.addon.operators, "build_moge2_depth_response"
|
||||
self.depth_operator, "build_moge2_depth_response"
|
||||
) as build:
|
||||
operator.response(context, result)
|
||||
|
||||
@@ -752,7 +1094,7 @@ class AddonTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_entry_operator_dispatches_plain_properties(self):
|
||||
operator = self.addon.ConvertToDepthPlane()
|
||||
operator = self.depth_operator.ConvertToDepthPlane()
|
||||
operator.model = "MOGE2_VITS_NORMAL"
|
||||
operator.resolution_level = 7
|
||||
operator.keep_original = False
|
||||
@@ -776,7 +1118,7 @@ class AddonTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
self.image_depth,
|
||||
self.depth_operator,
|
||||
"prepare_image_input",
|
||||
return_value=(Path(__file__), False),
|
||||
):
|
||||
|
||||
@@ -23,25 +23,66 @@ def load_image_depth():
|
||||
)
|
||||
fake_bpy.types = SimpleNamespace(Menu=object, Operator=object)
|
||||
fake_bpy.path = SimpleNamespace(abspath=lambda path: path)
|
||||
with patch.dict(sys.modules, {"bpy": fake_bpy}):
|
||||
specification = importlib.util.spec_from_file_location(
|
||||
"snapform_image_depth_test",
|
||||
PROJECT_ROOT
|
||||
/ "src"
|
||||
/ "snapform"
|
||||
/ "blender"
|
||||
/ "depth_plane.py",
|
||||
)
|
||||
module = importlib.util.module_from_spec(specification)
|
||||
specification.loader.exec_module(module)
|
||||
return module
|
||||
fake_bpy.context = SimpleNamespace(scene=None)
|
||||
|
||||
module_paths = (
|
||||
(
|
||||
"snapform.operators.image",
|
||||
PROJECT_ROOT / "src" / "snapform" / "operators" / "image.py",
|
||||
),
|
||||
(
|
||||
"snapform.operators.material",
|
||||
PROJECT_ROOT / "src" / "snapform" / "operators" / "material.py",
|
||||
),
|
||||
(
|
||||
"snapform.operators.depth_plane.data",
|
||||
PROJECT_ROOT / "src" / "snapform" / "operators" / "depth_plane" / "data.py",
|
||||
),
|
||||
(
|
||||
"snapform.operators.depth_plane.build",
|
||||
PROJECT_ROOT / "src" / "snapform" / "operators" / "depth_plane" / "build.py",
|
||||
),
|
||||
)
|
||||
packages = {
|
||||
name: ModuleType(name)
|
||||
for name in (
|
||||
"snapform",
|
||||
"snapform.operators",
|
||||
"snapform.operators.depth_plane",
|
||||
)
|
||||
}
|
||||
for package in packages.values():
|
||||
package.__path__ = []
|
||||
|
||||
loaded = []
|
||||
with patch.dict(sys.modules, {"bpy": fake_bpy, **packages}):
|
||||
for module_name, module_path in module_paths:
|
||||
specification = importlib.util.spec_from_file_location(
|
||||
module_name,
|
||||
module_path,
|
||||
)
|
||||
module = importlib.util.module_from_spec(specification)
|
||||
sys.modules[module_name] = module
|
||||
specification.loader.exec_module(module)
|
||||
loaded.append(module)
|
||||
|
||||
namespace = SimpleNamespace()
|
||||
for module in loaded:
|
||||
for name, value in vars(module).items():
|
||||
if not name.startswith("__"):
|
||||
setattr(namespace, name, value)
|
||||
return namespace
|
||||
|
||||
class ImageDepthTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.image_depth = load_image_depth()
|
||||
|
||||
def test_material_asset_path_survives_package_split(self):
|
||||
self.assertEqual(
|
||||
self.image_depth.node_asset_path(),
|
||||
PROJECT_ROOT / "src" / "snapform" / "assets" / "O_SnapForm.blend",
|
||||
)
|
||||
def test_material_color_space_matches_scene_view_transform(self):
|
||||
mappings = {
|
||||
"ACES 1.3": "ACES 1.3 sRGB",
|
||||
@@ -651,7 +692,7 @@ class ImageDepthTest(unittest.TestCase):
|
||||
"img.001.depth",
|
||||
)
|
||||
|
||||
def test_moge_map_names_use_source_image_name(self):
|
||||
def test_moge_normal_name_uses_source_image_name(self):
|
||||
source_object = SimpleNamespace(
|
||||
name="obj_img",
|
||||
data=SimpleNamespace(name="img.png"),
|
||||
@@ -661,10 +702,6 @@ class ImageDepthTest(unittest.TestCase):
|
||||
self.image_depth.normal_data_name(source_object),
|
||||
"img.normal",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.image_depth.mask_data_name(source_object),
|
||||
"img.mask",
|
||||
)
|
||||
|
||||
def test_copy_depth_image_renames_still_source(self):
|
||||
class FakeImage:
|
||||
|
||||
@@ -61,8 +61,10 @@ class PackTest(unittest.TestCase):
|
||||
|
||||
self.assertIn("__init__.py", names)
|
||||
self.assertIn("blender_manifest.toml", names)
|
||||
self.assertIn("blender/depth_plane.py", names)
|
||||
self.assertIn("blender/surface.py", names)
|
||||
self.assertIn("operators/depth_plane/operator.py", names)
|
||||
self.assertIn("operators/image.py", names)
|
||||
self.assertIn("operators/balloon/operator.py", names)
|
||||
self.assertIn("operators/surface.py", names)
|
||||
self.assertIn(
|
||||
"assets/O_SnapForm.blend",
|
||||
names,
|
||||
|
||||
Reference in New Issue
Block a user