207 lines
6.0 KiB
Python
207 lines
6.0 KiB
Python
"""Tests for remeshing nodes."""
|
|
|
|
import pytest
|
|
from nodes.remeshing import (
|
|
PyMeshLabRemeshNode,
|
|
CGALIsotropicRemeshNode,
|
|
MeshDecimationNode,
|
|
MeshSubdivisionNode,
|
|
LaplacianSmoothingNode,
|
|
BlenderVoxelRemeshNode,
|
|
BlenderQuadriflowRemeshNode,
|
|
InstantMeshesRemeshNode,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.optional
|
|
def test_pymeshlab_remesh(sphere_mesh, save_mesh_helper, render_helper):
|
|
"""Test PyMeshLab isotropic remeshing."""
|
|
pytest.importorskip("pymeshlab")
|
|
|
|
# Save original
|
|
save_mesh_helper(sphere_mesh, "00_original", "obj")
|
|
render_helper(sphere_mesh, "00_original")
|
|
|
|
node = PyMeshLabRemeshNode()
|
|
|
|
try:
|
|
remeshed = node.remesh(
|
|
trimesh=sphere_mesh,
|
|
target_edge_length=0.1,
|
|
iterations=10
|
|
)[0]
|
|
except ValueError as e:
|
|
if "PyMeshLab meshing filter not available" in str(e):
|
|
pytest.skip("PyMeshLab meshing filter not available (missing OpenGL libraries)")
|
|
raise
|
|
|
|
assert remeshed is not None
|
|
assert remeshed.faces.shape[0] > 0
|
|
|
|
# Save remeshed result
|
|
save_mesh_helper(remeshed, "01_pymeshlab_remeshed", "obj")
|
|
render_helper(remeshed, "01_pymeshlab_remeshed")
|
|
|
|
|
|
@pytest.mark.optional
|
|
def test_cgal_remesh(sphere_mesh, save_mesh_helper, render_helper):
|
|
"""Test CGAL isotropic remeshing."""
|
|
pytest.importorskip("cgal")
|
|
|
|
# Save original
|
|
save_mesh_helper(sphere_mesh, "00_original", "obj")
|
|
render_helper(sphere_mesh, "00_original")
|
|
|
|
node = CGALIsotropicRemeshNode()
|
|
remeshed = node.remesh(
|
|
trimesh=sphere_mesh,
|
|
target_edge_length=0.1,
|
|
iterations=5,
|
|
protect_boundaries="false"
|
|
)[0]
|
|
|
|
assert remeshed is not None
|
|
assert remeshed.faces.shape[0] > 0
|
|
|
|
# Save remeshed result
|
|
save_mesh_helper(remeshed, "01_cgal_remeshed", "obj")
|
|
render_helper(remeshed, "01_cgal_remeshed")
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_mesh_decimation(sphere_mesh, save_mesh_helper, render_helper):
|
|
"""Test mesh decimation - reduces triangle count while preserving shape."""
|
|
# Save original
|
|
save_mesh_helper(sphere_mesh, "00_original", "obj")
|
|
render_helper(sphere_mesh, "00_original")
|
|
|
|
original_face_count = sphere_mesh.faces.shape[0]
|
|
target_faces = original_face_count // 4
|
|
|
|
node = MeshDecimationNode()
|
|
decimated, info = node.decimate(
|
|
trimesh=sphere_mesh,
|
|
target_face_count=target_faces
|
|
)
|
|
|
|
assert decimated is not None
|
|
assert decimated.faces.shape[0] < original_face_count
|
|
assert "face" in info.lower()
|
|
|
|
# Save decimated result
|
|
save_mesh_helper(decimated, "01_decimated", "obj")
|
|
render_helper(decimated, "01_decimated")
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.parametrize("iterations", [1, 2, 3])
|
|
def test_mesh_subdivision(cube_mesh, iterations, save_mesh_helper, render_helper):
|
|
"""Test mesh subdivision - increases mesh resolution."""
|
|
# Save original (only on first iteration to avoid duplication across params)
|
|
save_mesh_helper(cube_mesh, "00_original", "obj")
|
|
render_helper(cube_mesh, "00_original")
|
|
|
|
original_face_count = cube_mesh.faces.shape[0]
|
|
|
|
node = MeshSubdivisionNode()
|
|
subdivided, info = node.subdivide(
|
|
trimesh=cube_mesh,
|
|
iterations=iterations,
|
|
method="loop"
|
|
)
|
|
|
|
assert subdivided is not None
|
|
assert subdivided.faces.shape[0] > original_face_count
|
|
|
|
# Save with iteration count in name
|
|
save_mesh_helper(subdivided, f"01_subdivided_x{iterations}", "obj")
|
|
render_helper(subdivided, f"01_subdivided_x{iterations}")
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_laplacian_smoothing(sphere_mesh, save_mesh_helper, render_helper):
|
|
"""Test Laplacian smoothing - smooths mesh surface."""
|
|
# Save original
|
|
save_mesh_helper(sphere_mesh, "00_original", "obj")
|
|
render_helper(sphere_mesh, "00_original")
|
|
|
|
node = LaplacianSmoothingNode()
|
|
smoothed = node.smooth(
|
|
trimesh=sphere_mesh,
|
|
iterations=10,
|
|
lambda_factor=0.5
|
|
)[0]
|
|
|
|
assert smoothed is not None
|
|
assert smoothed.vertices.shape == sphere_mesh.vertices.shape
|
|
|
|
# Save smoothed result
|
|
save_mesh_helper(smoothed, "01_smoothed", "obj")
|
|
render_helper(smoothed, "01_smoothed")
|
|
|
|
|
|
@pytest.mark.blender
|
|
@pytest.mark.slow
|
|
def test_blender_voxel_remesh(sphere_mesh, save_mesh_helper, render_helper):
|
|
"""Test Blender voxel remeshing."""
|
|
# Save original
|
|
save_mesh_helper(sphere_mesh, "00_original", "obj")
|
|
render_helper(sphere_mesh, "00_original")
|
|
|
|
node = BlenderVoxelRemeshNode()
|
|
remeshed = node.voxel_remesh(trimesh=sphere_mesh, voxel_size=0.1)[0]
|
|
|
|
assert remeshed is not None
|
|
|
|
# Save remeshed result
|
|
save_mesh_helper(remeshed, "01_voxel_remeshed", "obj")
|
|
render_helper(remeshed, "01_voxel_remeshed")
|
|
|
|
|
|
@pytest.mark.blender
|
|
@pytest.mark.slow
|
|
def test_blender_quadriflow_remesh(sphere_mesh, save_mesh_helper, render_helper):
|
|
"""Test Blender Quadriflow remeshing."""
|
|
# Save original
|
|
save_mesh_helper(sphere_mesh, "00_original", "obj")
|
|
render_helper(sphere_mesh, "00_original")
|
|
|
|
node = BlenderQuadriflowRemeshNode()
|
|
remeshed = node.quadriflow_remesh(
|
|
trimesh=sphere_mesh,
|
|
target_face_count=500
|
|
)[0]
|
|
|
|
assert remeshed is not None
|
|
|
|
# Save remeshed result
|
|
save_mesh_helper(remeshed, "01_quadriflow_remeshed", "obj")
|
|
render_helper(remeshed, "01_quadriflow_remeshed")
|
|
|
|
|
|
@pytest.mark.optional
|
|
@pytest.mark.slow
|
|
def test_instant_meshes_remesh(sphere_mesh, save_mesh_helper, render_helper):
|
|
"""Test InstantMeshes remeshing."""
|
|
pytest.importorskip("PyNanoInstantMeshes")
|
|
|
|
# Save original
|
|
save_mesh_helper(sphere_mesh, "00_original", "obj")
|
|
render_helper(sphere_mesh, "00_original")
|
|
|
|
node = InstantMeshesRemeshNode()
|
|
remeshed, info = node.instant_remesh(
|
|
trimesh=sphere_mesh,
|
|
target_vertex_count=500,
|
|
deterministic="true",
|
|
crease_angle=30.0
|
|
)
|
|
|
|
assert remeshed is not None
|
|
assert "vertices" in info.lower()
|
|
|
|
# Save remeshed result
|
|
save_mesh_helper(remeshed, "01_instant_remeshed", "obj")
|
|
render_helper(remeshed, "01_instant_remeshed")
|