From 4123f98f8956b6c032a88324d6ca35723b3a02fc Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Thu, 7 Nov 2024 12:02:06 -0500 Subject: [PATCH 001/102] many working examples --- CMakeLists.txt | 148 +- cmake/PyiglDependencies.cmake | 47 - igl/__init__.py | 1 - include/default_types.h | 25 + missing.sh | 23 + skip.txt | 1 + src/AABB.cpp | 127 + src/active_set.cpp | 140 - src/adjacency_list.cpp | 47 - src/adjacency_matrix.cpp | 59 - src/all_pairs_distances.cpp | 63 - src/ambient_occlusion.cpp | 62 - src/arap_linear_block.cpp | 138 - src/arap_rhs.cpp | 77 - src/average_from_edges_onto_vertices.cpp | 41 + src/average_onto_faces.cpp | 57 - src/average_onto_vertices.cpp | 52 - src/avg_edge_length.cpp | 49 - src/barycenter.cpp | 84 +- src/barycentric_coordinates.cpp | 167 +- src/bbw.cpp | 82 + src/bfs.cpp | 72 - src/bfs_orient.cpp | 49 - src/biharmonic_coordinates.cpp | 102 - src/bijective_composite_harmonic_mapping.cpp | 128 - src/blue_noise.cpp | 63 - src/bone_parents.cpp | 50 - src/boundary_conditions.cpp | 114 - src/boundary_facets.cpp | 98 +- src/boundary_loop.cpp | 88 - src/bounding_box.cpp | 83 - src/bounding_box_diagonal.cpp | 60 - src/circulation.cpp | 79 - src/circumradius.cpp | 57 - src/collapse_small_triangles.cpp | 75 - src/comb_cross_field.cpp | 69 - src/comb_frame_field.cpp | 83 - src/comb_line_field.cpp | 62 - src/compute_frame_field_bisectors.cpp | 139 - src/connect_boundary_to_infinity.cpp | 144 - src/connected_components.cpp | 60 - src/copyleft/cgal/convex_hull.cpp | 35 - src/copyleft/cgal/intersect_other.cpp | 94 - src/copyleft/cgal/mesh_boolean.cpp | 70 - .../cgal/remesh_self_intersections.cpp | 86 - src/copyleft/tetgen/tetrahedralize.cpp | 69 - src/cotmatrix.cpp | 127 +- src/cotmatrix_entries.cpp | 62 - src/cotmatrix_intrinsic.cpp | 62 - src/cross_field_missmatch.cpp | 75 - src/crouzeix_raviart_cotmatrix.cpp | 116 - src/crouzeix_raviart_massmatrix.cpp | 119 - src/cut_mesh.cpp | 66 - src/cut_mesh_from_singularities.cpp | 59 - src/cut_to_disk.cpp | 71 - src/cylinder.cpp | 61 - src/decimate.cpp | 84 - src/deform_skeleton.cpp | 70 - src/dihedral_angles.cpp | 73 - src/direct_delta_mush.cpp | 116 - src/directed_edge_orientations.cpp | 63 - src/directed_edge_parents.cpp | 51 - src/doublearea.cpp | 119 +- src/dqs.cpp | 81 - src/ears.cpp | 55 - src/edge_collapse_is_valid.cpp | 82 - src/edge_flaps.cpp | 72 - src/edge_lengths.cpp | 107 +- src/edge_topology.cpp | 53 - src/edges.cpp | 48 - src/edges_to_path.cpp | 74 - src/euler_characteristic.cpp | 54 - src/exact_geodesic.cpp | 64 - src/exterior_edges.cpp | 64 - src/extract_manifold_patches.cpp | 52 - src/extract_non_manifold_edge_curves.cpp | 69 - src/face_occurrences.cpp | 55 - src/faces_first.cpp | 72 - src/facet_components.cpp | 45 - src/false_barycentric_subdivision.cpp | 67 - src/fast_winding_number.cpp | 118 - src/find_cross_field_singularities.cpp | 123 - src/fit_cubic_bezier.cpp | 57 - src/fit_plane.cpp | 68 - src/flip_avoiding_line_search.cpp | 93 - src/flip_edge.cpp | 90 - src/flipped_triangles.cpp | 51 - src/forward_kinematics.cpp | 107 - src/gaussian_curvature.cpp | 53 - src/grad.cpp | 60 - src/grad_intrinsic.cpp | 68 - src/harmonic.cpp | 258 -- src/hausdorff.cpp | 79 - src/heat_geodesic.cpp | 63 - src/hessian.cpp | 56 - src/hessian_energy.cpp | 64 - src/in_element.cpp | 63 - src/include/common.h | 331 --- src/include/typedefs.h | 52 - src/inradius.cpp | 53 - src/internal_angles.cpp | 145 - src/intrinsic_delaunay_cotmatrix.cpp | 61 - src/intrinsic_delaunay_triangulation.cpp | 144 - src/is_border_vertex.cpp | 55 - src/is_delaunay.cpp | 59 - src/is_edge_manifold.cpp | 34 - src/is_intrinsic_delaunay.cpp | 60 - src/is_irregular_vertex.cpp | 49 - src/isolines.cpp | 68 - src/iterative_closest_point.cpp | 141 - src/lbs_matrix.cpp | 220 -- src/line_segment_in_rectangle.cpp | 68 - src/local_basis.cpp | 57 - src/look_at.cpp | 64 - src/loop.cpp | 108 - src/lscm.cpp | 74 - src/map_vertices_to_circle.cpp | 58 - src/marching_cubes.cpp | 87 - src/marching_tets.cpp | 67 - src/massmatrix.cpp | 121 +- src/massmatrix_intrinsic.cpp | 81 - src/min_quad_with_fixed.cpp | 96 - src/module.cpp | 11 + src/moments.cpp | 42 - src/mvc.cpp | 66 - src/normal_derivative.cpp | 61 - src/offset_surface.cpp | 80 - src/orient_halfedges.cpp | 40 + src/orient_outward.cpp | 68 - src/orientable_patches.cpp | 50 - src/oriented_facets.cpp | 55 - src/outer_element.cpp | 180 -- src/partition.cpp | 74 - src/path_to_edges.cpp | 59 - src/per_corner_normals.cpp | 55 - src/per_edge_normals.cpp | 68 - src/per_face_normals.cpp | 147 +- src/per_vertex_attribute_smoothing.cpp | 60 - src/per_vertex_normals.cpp | 62 - src/piecewise_constant_winding_number.cpp | 74 - src/planarize_quad_mesh.cpp | 60 - src/point_in_circle.cpp | 57 - src/point_mesh_squared_distance.cpp | 141 +- src/point_simplex_squared_distance.cpp | 70 - src/polar_dec.cpp | 59 - src/principal_curvature.cpp | 63 - src/procrustes.cpp | 77 - src/project.cpp | 72 - src/project_isometrically_to_plane.cpp | 59 - src/project_to_line.cpp | 85 - src/project_to_line_segment.cpp | 85 - src/pso.cpp | 124 - src/qslim.cpp | 87 - src/quad_grid.cpp | 60 - src/quad_planarity.cpp | 53 - src/ramer_douglas_peucker.cpp | 62 - src/random_points_on_mesh.cpp | 59 - src/random_points_on_mesh_intrinsic.cpp | 47 - src/random_search.cpp | 66 - src/ray_box_intersect.cpp | 80 - src/ray_mesh_intersect.cpp | 72 - src/ray_sphere_intersect.cpp | 73 - src/readDMAT.cpp | 73 - src/readMESH.cpp | 67 - src/readMSH.cpp | 70 - src/readOBJ.cpp | 71 - src/readOFF.cpp | 86 - src/readTGF.cpp | 75 - src/read_triangle_mesh.cpp | 99 +- src/remove_duplicate_vertices.cpp | 76 - src/remove_unreferenced.cpp | 65 - src/resolve_duplicated_faces.cpp | 71 - src/rigid_alignment.cpp | 74 - src/rotate_vectors.cpp | 74 - src/sample_edges.cpp | 69 - src/segment_segment_intersect.cpp | 85 - src/shape_diameter_function.cpp | 71 - src/sharp_edges.cpp | 74 - src/signed_angle.cpp | 94 - src/signed_distance.cpp | 108 - src/simplify_polyhedron.cpp | 73 - src/snap_points.cpp | 61 - src/solid_angle.cpp | 70 - src/sort_angles.cpp | 62 - src/sparse_voxel_grid.cpp | 78 - src/swept_volume_bounding_box.cpp | 59 - src/tet_tet_adjacency.cpp | 50 - src/topological_hole_fill.cpp | 58 - src/triangle/triangulate.cpp | 70 - src/triangle_fan.cpp | 56 - src/triangle_triangle_adjacency.cpp | 62 - src/triangles_from_strip.cpp | 63 - src/triangulated_grid.cpp | 58 - src/two_axis_valuator_fixed_up.cpp | 96 - src/uniformly_sample_two_manifold.cpp | 127 - src/unique_edge_map.cpp | 66 - src/unique_simplices.cpp | 55 - src/unproject.cpp | 61 - src/unproject_in_mesh.cpp | 89 - src/unproject_on_line.cpp | 82 - src/unproject_on_plane.cpp | 71 - src/unproject_onto_mesh.cpp | 88 - src/unproject_ray.cpp | 69 - src/upsample.cpp | 59 - src/vector_area_matrix.cpp | 49 - src/vertex_components.cpp | 85 - src/vertex_triangle_adjacency.cpp | 65 - src/volume.cpp | 198 -- src/winding_number.cpp | 111 - src/writeDMAT.cpp | 58 - src/writeMESH.cpp | 44 - src/writeMSH.cpp | 74 - src/writeOBJ.cpp | 59 - src/writeOFF.cpp | 83 - src/write_triangle_mesh.cpp | 130 +- tests/test.py | 68 + tests/test_basic.py | 2636 ----------------- 217 files changed, 1124 insertions(+), 18335 deletions(-) delete mode 100644 cmake/PyiglDependencies.cmake create mode 100644 include/default_types.h create mode 100644 missing.sh create mode 100644 skip.txt create mode 100644 src/AABB.cpp delete mode 100644 src/active_set.cpp delete mode 100644 src/adjacency_list.cpp delete mode 100644 src/adjacency_matrix.cpp delete mode 100644 src/all_pairs_distances.cpp delete mode 100644 src/ambient_occlusion.cpp delete mode 100644 src/arap_linear_block.cpp delete mode 100644 src/arap_rhs.cpp create mode 100644 src/average_from_edges_onto_vertices.cpp delete mode 100644 src/average_onto_faces.cpp delete mode 100644 src/average_onto_vertices.cpp delete mode 100644 src/avg_edge_length.cpp create mode 100644 src/bbw.cpp delete mode 100644 src/bfs.cpp delete mode 100644 src/bfs_orient.cpp delete mode 100644 src/biharmonic_coordinates.cpp delete mode 100644 src/bijective_composite_harmonic_mapping.cpp delete mode 100644 src/blue_noise.cpp delete mode 100644 src/bone_parents.cpp delete mode 100644 src/boundary_conditions.cpp delete mode 100644 src/boundary_loop.cpp delete mode 100644 src/bounding_box.cpp delete mode 100644 src/bounding_box_diagonal.cpp delete mode 100644 src/circulation.cpp delete mode 100644 src/circumradius.cpp delete mode 100644 src/collapse_small_triangles.cpp delete mode 100644 src/comb_cross_field.cpp delete mode 100644 src/comb_frame_field.cpp delete mode 100644 src/comb_line_field.cpp delete mode 100644 src/compute_frame_field_bisectors.cpp delete mode 100644 src/connect_boundary_to_infinity.cpp delete mode 100644 src/connected_components.cpp delete mode 100644 src/copyleft/cgal/convex_hull.cpp delete mode 100644 src/copyleft/cgal/intersect_other.cpp delete mode 100644 src/copyleft/cgal/mesh_boolean.cpp delete mode 100644 src/copyleft/cgal/remesh_self_intersections.cpp delete mode 100644 src/copyleft/tetgen/tetrahedralize.cpp delete mode 100644 src/cotmatrix_entries.cpp delete mode 100644 src/cotmatrix_intrinsic.cpp delete mode 100644 src/cross_field_missmatch.cpp delete mode 100644 src/crouzeix_raviart_cotmatrix.cpp delete mode 100644 src/crouzeix_raviart_massmatrix.cpp delete mode 100644 src/cut_mesh.cpp delete mode 100644 src/cut_mesh_from_singularities.cpp delete mode 100644 src/cut_to_disk.cpp delete mode 100644 src/cylinder.cpp delete mode 100644 src/decimate.cpp delete mode 100644 src/deform_skeleton.cpp delete mode 100644 src/dihedral_angles.cpp delete mode 100644 src/direct_delta_mush.cpp delete mode 100644 src/directed_edge_orientations.cpp delete mode 100644 src/directed_edge_parents.cpp delete mode 100644 src/dqs.cpp delete mode 100644 src/ears.cpp delete mode 100644 src/edge_collapse_is_valid.cpp delete mode 100644 src/edge_flaps.cpp delete mode 100644 src/edge_topology.cpp delete mode 100644 src/edges.cpp delete mode 100644 src/edges_to_path.cpp delete mode 100644 src/euler_characteristic.cpp delete mode 100644 src/exact_geodesic.cpp delete mode 100644 src/exterior_edges.cpp delete mode 100644 src/extract_manifold_patches.cpp delete mode 100644 src/extract_non_manifold_edge_curves.cpp delete mode 100644 src/face_occurrences.cpp delete mode 100644 src/faces_first.cpp delete mode 100644 src/facet_components.cpp delete mode 100644 src/false_barycentric_subdivision.cpp delete mode 100644 src/fast_winding_number.cpp delete mode 100644 src/find_cross_field_singularities.cpp delete mode 100644 src/fit_cubic_bezier.cpp delete mode 100644 src/fit_plane.cpp delete mode 100644 src/flip_avoiding_line_search.cpp delete mode 100644 src/flip_edge.cpp delete mode 100644 src/flipped_triangles.cpp delete mode 100644 src/forward_kinematics.cpp delete mode 100644 src/gaussian_curvature.cpp delete mode 100644 src/grad.cpp delete mode 100644 src/grad_intrinsic.cpp delete mode 100644 src/harmonic.cpp delete mode 100644 src/hausdorff.cpp delete mode 100644 src/heat_geodesic.cpp delete mode 100644 src/hessian.cpp delete mode 100644 src/hessian_energy.cpp delete mode 100644 src/in_element.cpp delete mode 100644 src/include/common.h delete mode 100644 src/include/typedefs.h delete mode 100644 src/inradius.cpp delete mode 100644 src/internal_angles.cpp delete mode 100644 src/intrinsic_delaunay_cotmatrix.cpp delete mode 100644 src/intrinsic_delaunay_triangulation.cpp delete mode 100644 src/is_border_vertex.cpp delete mode 100644 src/is_delaunay.cpp delete mode 100644 src/is_edge_manifold.cpp delete mode 100644 src/is_intrinsic_delaunay.cpp delete mode 100644 src/is_irregular_vertex.cpp delete mode 100644 src/isolines.cpp delete mode 100644 src/iterative_closest_point.cpp delete mode 100644 src/lbs_matrix.cpp delete mode 100644 src/line_segment_in_rectangle.cpp delete mode 100644 src/local_basis.cpp delete mode 100644 src/look_at.cpp delete mode 100644 src/loop.cpp delete mode 100644 src/lscm.cpp delete mode 100644 src/map_vertices_to_circle.cpp delete mode 100644 src/marching_cubes.cpp delete mode 100644 src/marching_tets.cpp delete mode 100644 src/massmatrix_intrinsic.cpp delete mode 100644 src/min_quad_with_fixed.cpp create mode 100644 src/module.cpp delete mode 100644 src/moments.cpp delete mode 100644 src/mvc.cpp delete mode 100644 src/normal_derivative.cpp delete mode 100644 src/offset_surface.cpp create mode 100644 src/orient_halfedges.cpp delete mode 100644 src/orient_outward.cpp delete mode 100644 src/orientable_patches.cpp delete mode 100644 src/oriented_facets.cpp delete mode 100644 src/outer_element.cpp delete mode 100644 src/partition.cpp delete mode 100644 src/path_to_edges.cpp delete mode 100644 src/per_corner_normals.cpp delete mode 100644 src/per_edge_normals.cpp delete mode 100644 src/per_vertex_attribute_smoothing.cpp delete mode 100644 src/per_vertex_normals.cpp delete mode 100644 src/piecewise_constant_winding_number.cpp delete mode 100644 src/planarize_quad_mesh.cpp delete mode 100644 src/point_in_circle.cpp delete mode 100644 src/point_simplex_squared_distance.cpp delete mode 100644 src/polar_dec.cpp delete mode 100644 src/principal_curvature.cpp delete mode 100644 src/procrustes.cpp delete mode 100644 src/project.cpp delete mode 100644 src/project_isometrically_to_plane.cpp delete mode 100644 src/project_to_line.cpp delete mode 100644 src/project_to_line_segment.cpp delete mode 100644 src/pso.cpp delete mode 100644 src/qslim.cpp delete mode 100644 src/quad_grid.cpp delete mode 100644 src/quad_planarity.cpp delete mode 100644 src/ramer_douglas_peucker.cpp delete mode 100644 src/random_points_on_mesh.cpp delete mode 100644 src/random_points_on_mesh_intrinsic.cpp delete mode 100644 src/random_search.cpp delete mode 100644 src/ray_box_intersect.cpp delete mode 100644 src/ray_mesh_intersect.cpp delete mode 100644 src/ray_sphere_intersect.cpp delete mode 100644 src/readDMAT.cpp delete mode 100644 src/readMESH.cpp delete mode 100644 src/readMSH.cpp delete mode 100644 src/readOBJ.cpp delete mode 100644 src/readOFF.cpp delete mode 100644 src/readTGF.cpp delete mode 100644 src/remove_duplicate_vertices.cpp delete mode 100644 src/remove_unreferenced.cpp delete mode 100644 src/resolve_duplicated_faces.cpp delete mode 100644 src/rigid_alignment.cpp delete mode 100644 src/rotate_vectors.cpp delete mode 100644 src/sample_edges.cpp delete mode 100644 src/segment_segment_intersect.cpp delete mode 100644 src/shape_diameter_function.cpp delete mode 100644 src/sharp_edges.cpp delete mode 100644 src/signed_angle.cpp delete mode 100644 src/signed_distance.cpp delete mode 100644 src/simplify_polyhedron.cpp delete mode 100644 src/snap_points.cpp delete mode 100644 src/solid_angle.cpp delete mode 100644 src/sort_angles.cpp delete mode 100644 src/sparse_voxel_grid.cpp delete mode 100644 src/swept_volume_bounding_box.cpp delete mode 100644 src/tet_tet_adjacency.cpp delete mode 100644 src/topological_hole_fill.cpp delete mode 100644 src/triangle/triangulate.cpp delete mode 100644 src/triangle_fan.cpp delete mode 100644 src/triangle_triangle_adjacency.cpp delete mode 100644 src/triangles_from_strip.cpp delete mode 100644 src/triangulated_grid.cpp delete mode 100644 src/two_axis_valuator_fixed_up.cpp delete mode 100644 src/uniformly_sample_two_manifold.cpp delete mode 100644 src/unique_edge_map.cpp delete mode 100644 src/unique_simplices.cpp delete mode 100644 src/unproject.cpp delete mode 100644 src/unproject_in_mesh.cpp delete mode 100644 src/unproject_on_line.cpp delete mode 100644 src/unproject_on_plane.cpp delete mode 100644 src/unproject_onto_mesh.cpp delete mode 100644 src/unproject_ray.cpp delete mode 100644 src/upsample.cpp delete mode 100644 src/vector_area_matrix.cpp delete mode 100644 src/vertex_components.cpp delete mode 100644 src/vertex_triangle_adjacency.cpp delete mode 100644 src/volume.cpp delete mode 100644 src/winding_number.cpp delete mode 100644 src/writeDMAT.cpp delete mode 100644 src/writeMESH.cpp delete mode 100644 src/writeMSH.cpp delete mode 100644 src/writeOBJ.cpp delete mode 100644 src/writeOFF.cpp create mode 100644 tests/test.py delete mode 100644 tests/test_basic.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f10f3a3..415c273a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,41 @@ cmake_minimum_required(VERSION 3.16.0) project(pyigl) -if (NOT DEFINED PYLIBIGL_EXTERNAL) - set(PYLIBIGL_EXTERNAL ${CMAKE_CURRENT_SOURCE_DIR}/external) +# use C++20 +set(CMAKE_CXX_STANDARD 20) + +if (CMAKE_VERSION VERSION_LESS 3.18) + set(DEV_MODULE Development) +else() + set(DEV_MODULE Development.Module) endif() +find_package(Python 3.8 COMPONENTS Interpreter ${DEV_MODULE} REQUIRED) + +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() + +# Enable FetchContent to download dependencies at configure time +include(FetchContent) + +# Download and set up nanobind +FetchContent_Declare( + nanobind + GIT_REPOSITORY https://github.com/wjakob/nanobind.git + GIT_TAG v2.2.0 +) +FetchContent_MakeAvailable(nanobind) + +# Download and set up libigl +FetchContent_Declare( + libigl + GIT_REPOSITORY https://github.com/libigl/libigl.git + GIT_TAG 0e504e1b331e6696b485258a9aac779f0f20c5b0 +) +FetchContent_MakeAvailable(libigl) + message(STATUS "PYIGL_OUTPUT_DIRECTORY: ${PYIGL_OUTPUT_DIRECTORY}") if (NOT DEFINED PYIGL_OUTPUT_DIRECTORY) message(FATAL_ERROR "PYIGL_OUTPUT_DIRECTORY must be defined externally") @@ -23,101 +54,36 @@ include(CXXFeatures) # Generate position independent code by default set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE INTERNAL "") -option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) -option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) -option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) -# libigl options must come before include(PyiglDependencies) -include(PyiglDependencies) -if(NOT TARGET igl::core) - include(libigl) -endif() +option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" OFF) +option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" OFF) +option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" OFF) # A module for writing bindings with our framework -file(GLOB PYIGL_SOURCES src/*.cpp) -npe_add_module(pyigl - BINDING_SOURCES - ${PYIGL_SOURCES}) +file(GLOB PYIGL_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") +set(BINDING_SOURCES ${PYIGL_SOURCES}) +list(REMOVE_ITEM BINDING_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/module.cpp") # Exclude module.cpp + +# Generate the function calls based on filenames +set(BINDING_DECLARATIONS "") +foreach(source_file ${BINDING_SOURCES}) + get_filename_component(filename ${source_file} NAME_WE) + set(BINDING_DECLARATIONS "${BINDING_DECLARATIONS}extern void bind_${filename}(nb::module_ &m);\n") + set(BINDING_INVOCATIONS "${BINDING_INVOCATIONS} bind_${filename}(m);\n") +endforeach() +# make a temporary folder in the build directory to store the generated files +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include") +# write contents into BINDING_DECLARATIONS.h and BINDING_INVOCATIONS.h +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/include/BINDING_DECLARATIONS.in" "${BINDING_DECLARATIONS}") +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/include/BINDING_INVOCATIONS.in" "${BINDING_INVOCATIONS}") + +nanobind_add_module(pyigl ${PYIGL_SOURCES}) + target_link_libraries(pyigl PRIVATE igl::core) -target_include_directories(pyigl PRIVATE "src/include") +target_include_directories(pyigl PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/include") +target_include_directories(pyigl PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") set_target_properties(pyigl PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" RUNTIME_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" ) - -# don't need to worry about nested modules (opengl/** are the only ones and -# those probably aren't ever getting python bindings) -# -# prefix is either "", "copyleft", or "restricted" -function(pyigl_include prefix name) - string(TOUPPER "${prefix}" prefix_uc) - string(TOUPPER "${name}" name_uc) - if(prefix_uc) - string(PREPEND prefix_uc _) - endif() - string(TOLOWER "${prefix_uc}" prefix_lc) - if(LIBIGL${prefix_uc}_${name_uc}) - if(${prefix} STREQUAL "copyleft") - set(subpath "copyleft/${name}") - else() # "" or "restricted" - set(subpath "${name}") - endif() - file(GLOB sources src/${subpath}/*.cpp) - set(target_name "pyigl${prefix_lc}_${name}") - npe_add_module( ${target_name} BINDING_SOURCES ${sources}) - target_link_libraries( ${target_name} PRIVATE igl::core igl${prefix_lc}::${name}) - target_include_directories( ${target_name} PRIVATE "src/include") - set(output_dir "${PYIGL_OUTPUT_DIRECTORY}/${subpath}") - file(MAKE_DIRECTORY ${output_dir}) - file(WRITE "${output_dir}/__init__.py" "from .${target_name} import *") - # https://stackoverflow.com/a/56514534/148668 - set_target_properties(${target_name} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${output_dir}" - RUNTIME_OUTPUT_DIRECTORY "${output_dir}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE "${output_dir}" - RUNTIME_OUTPUT_DIRECTORY_RELEASE "${output_dir}") - # why do this? - target_link_libraries( pyigl INTERFACE ${target_name}) - endif() - # https://stackoverflow.com/a/69736197/148668 - # https://cmake.org/cmake/help/latest/manual/cmake.1.html#cmdoption-cmake-E-arg-copy - # until then just needlessly also copy TARGET_FILE in case TARGET_RUNTIME_DLLS is empty - if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - add_custom_command(TARGET ${target_name} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ $ $ - COMMAND_EXPAND_LISTS) - endif() -endfunction() - -pyigl_include("copyleft" "cgal") -pyigl_include("copyleft" "tetgen") -pyigl_include("restricted" "triangle") - - -file(GLOB PYIGL_CLASSES_SOURCES classes/*.cpp) -add_library(pyigl_classes MODULE ${PYIGL_CLASSES_SOURCES}) -# std::variant -target_compile_features(pyigl_classes PRIVATE cxx_std_17) -target_link_libraries(pyigl_classes PRIVATE npe igl::core) -target_link_libraries(pyigl_classes PRIVATE pybind11::module) -set_target_properties(pyigl_classes PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" SUFFIX "${PYTHON_MODULE_EXTENSION}") -target_include_directories(pyigl_classes PRIVATE "src/include") -target_include_directories(pyigl_classes PRIVATE "${PYLIBIGL_EXTERNAL}/numpyeigen/src") -set_target_properties(pyigl_classes PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" - RUNTIME_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" - RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" - ) - -# Sort projects inside the solution -set_property(GLOBAL PROPERTY USE_FOLDERS ON) - -if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - foreach(config ${CMAKE_CONFIGURATION_TYPES}) - string(TOUPPER ${config} config) - string(REPLACE /MD /MT CMAKE_C_FLAGS_${config} "${CMAKE_C_FLAGS_${config}}") - string(REPLACE /MD /MT CMAKE_CXX_FLAGS_${config} "${CMAKE_CXX_FLAGS_${config}}") - endforeach() -endif() diff --git a/cmake/PyiglDependencies.cmake b/cmake/PyiglDependencies.cmake deleted file mode 100644 index 8229ac05..00000000 --- a/cmake/PyiglDependencies.cmake +++ /dev/null @@ -1,47 +0,0 @@ -# Prepare dependencies -# -# For each third-party library, if the appropriate target doesn't exist yet, -# download it via external project, and add_subdirectory to build it alongside -# this project. - - -# Download and update 3rdparty libraries -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) -list(REMOVE_DUPLICATES CMAKE_MODULE_PATH) - -################################################################################ -# Required libraries -################################################################################ - - -include(FetchContent) - -FetchContent_Declare( - libigl - GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG f962e4a6b68afe978dc12a63702b7846a3e7a6ed -) -FetchContent_GetProperties(libigl) -FetchContent_MakeAvailable(libigl) - -FetchContent_Declare( - numpyeigen - GIT_REPOSITORY https://github.com/fwilliams/numpyeigen.git - GIT_TAG 14acc7a71285979016ef39041d8cd4df97e4e829) -# NumpyEigen's CMakeLists sets NPE_PYTHON_EXECUTABLE without a way to override, -# so we must include directly rather that using FetchContent_MakeAvailable -#FetchContent_MakeAvailable(numpyeigen) -# Check if population has already been performed -FetchContent_GetProperties(numpyeigen) -if(NOT numpyeigen_POPULATED) - # Fetch the content using previously declared details - FetchContent_Populate(numpyeigen) -endif() -# Push CMAKE_MODULE_PATH -set(PREV_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${numpyeigen_SOURCE_DIR}/cmake) -# Why isn't eigen_SOURCE_DIR defined? -SET(NPE_WITH_EIGEN "${libigl_SOURCE_DIR}/../eigen-src/" CACHE INTERNAL "") -include(numpyeigen) -# Pop CMAKE_MODULE_PATH -set(CMAKE_MODULE_PATH ${PREV_CMAKE_MODULE_PATH}) diff --git a/igl/__init__.py b/igl/__init__.py index 72d042f2..ebb03af8 100644 --- a/igl/__init__.py +++ b/igl/__init__.py @@ -7,5 +7,4 @@ # obtain one at http://mozilla.org/MPL/2.0/. from .pyigl import * from .helpers import * -from .pyigl_classes import * from ._version import __version__ diff --git a/include/default_types.h b/include/default_types.h new file mode 100644 index 00000000..dd7b25f3 --- /dev/null +++ b/include/default_types.h @@ -0,0 +1,25 @@ +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////////// +/// Default numeric and integer types. Passing anything else will result in an +/// implicit copy. Developers insisting on native executation of libigl on +/// single-precision (largely untested and possibly error prone) can change this +/// to float and recompile bindings at their own delight or peril. +/////////////////////////////////////////////////////////////////////////////////// +using Numeric = double; +using Integer = int64_t; +namespace Eigen +{ + typedef Matrix MatrixXN; + typedef Matrix MatrixXI; + typedef Matrix VectorXN; + typedef Matrix VectorXI; + typedef Matrix RowVectorXN; + typedef Matrix RowVectorXI; + typedef SparseMatrix SparseMatrixN; + typedef SparseMatrix SparseMatrixI; +} + + diff --git a/missing.sh b/missing.sh new file mode 100644 index 00000000..b9a52326 --- /dev/null +++ b/missing.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# +already=$(ls src/*.cpp | sed -e "s/src\/\(.*\).cpp$/\1/g") +skip="igl_inline +EPS +verbose +colon +sort +placeholders +list_to_matrix +parallel_for +PI" +# combine these into exclude +exclude=$(echo -e "$already\n$skip" | sort | uniq) +echo $exclude + +# but don't include any from exclude + +# Run grep and filter out excluded files +grep -ho '#include "[^"]\+"' "$1"/*.{h,cpp} | \ + sed 's/#include "\(.*\).h"/\1/' | \ +grep -v -F -w -f <(echo "$exclude") | \ +sort | uniq -c | sort -n diff --git a/skip.txt b/skip.txt new file mode 100644 index 00000000..21ee549f --- /dev/null +++ b/skip.txt @@ -0,0 +1 @@ +igl_inline diff --git a/src/AABB.cpp b/src/AABB.cpp new file mode 100644 index 00000000..606806e4 --- /dev/null +++ b/src/AABB.cpp @@ -0,0 +1,127 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + template + void init( + AABB &tree, + const nb::DRef &V, + const nb::DRef &Ele) + { + tree.init(V,Ele); + } + template + nb::object find( + AABB &tree, + const nb::DRef &V, + const nb::DRef &Ele, + const nb::DRef &q, + const bool first) + { + if(Ele.cols() != V.cols()+1) + { + throw std::runtime_error("find: Ele must have V.cols()+1 columns"); + } + std::vector vec = tree.find(V,Ele,q,first); + if(first) + { + return vec.size() ? nb::cast(vec[0]) : nb::none(); + }else + { + return nb::cast(vec); + } + } + + template + nb::object squared_distance( + AABB &tree, + const nb::DRef &V, + const nb::DRef &Ele, + const nb::DRef &P, + const bool return_I, + const bool return_C) + { + Eigen::VectorXN sqrD; + Eigen::VectorXI I; + Eigen::MatrixXN C; + tree.squared_distance(V,Ele,P,sqrD,I,C); + if(return_I && return_C) + { + return nb::make_tuple(sqrD,I,C); + }else if(return_I) + { + return nb::make_tuple(sqrD,I); + }else if(return_C) + { + return nb::make_tuple(sqrD,C); + } + return nb::cast(sqrD); + } + + template + nb::object intersect_ray( + AABB &tree, + const nb::DRef &V, + const nb::DRef &Ele, + const nb::DRef &orig, + const nb::DRef &dir, + const Numeric min_t, + const bool first) + { + if(first) + { + + Eigen::VectorXI I; + Eigen::VectorXN T; + Eigen::MatrixXN UV; + tree.intersect_ray(V,Ele,orig,dir,min_t,I,T,UV); + return nb::make_tuple(I,T,UV); + }else + { + if(std::isfinite(min_t)) + { + throw std::runtime_error("intersect_ray: min_t only supported for first=true"); + } + std::vector>> hits; + tree.intersect_ray(V,Ele,orig,dir,hits); + std::vector>> out; + for(const auto &hit : hits) + { + std::vector> hit_out; + for(const auto &h : hit) + { + hit_out.push_back(std::make_tuple(h.id,h.t,h.u,h.v)); + } + out.push_back(hit_out); + } + + return nb::cast(out); + } + } +} + +// Bind the wrapper to the Python module +void bind_AABB(nb::module_ &m) +{ + typedef igl::AABB,3> AABBN3; + nb::class_(m, "AABB") + .def(nb::init<>()) + .def("init", &pyigl::init, "V"_a, "Ele"_a) + .def("find", &pyigl::find, "V"_a, "Ele"_a, "q"_a, "first"_a=false) + .def("squared_distance", &pyigl::squared_distance, "V"_a, "Ele"_a, "P"_a, "return_I"_a=false,"return_C"_a=false) + .def("intersect_ray",&pyigl::intersect_ray, "V"_a, "Ele"_a, "orig"_a, "dir"_a, "min_t"_a=std::numeric_limits::infinity(), "first"_a=false) + ; +} + + + + diff --git a/src/active_set.cpp b/src/active_set.cpp deleted file mode 100644 index 4f4a6c2b..00000000 --- a/src/active_set.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_active_set = R"igl_Qu8mg5v7( -ACTIVE_SET Minimize quadratic energy - -0.5*Z'*A*Z + Z'*B + C with constraints -that Z(known) = Y, optionally also subject to the constraints Aeq*Z = Beq, -and further optionally subject to the linear inequality constraints that -Aieq*Z <= Bieq and constant inequality constraints lx <= x <= ux - -Parameters ----------- -A n by n matrix of quadratic coefficients -B n by 1 column of linear coefficients -known list of indices to known rows in Z -Y list of fixed values corresponding to known rows in Z -Aeq meq by n list of linear equality constraint coefficients -Beq meq by 1 list of linear equality constraint constant values -Aieq mieq by n list of linear inequality constraint coefficients -Bieq mieq by 1 list of linear inequality constraint constant values -lx n by 1 list of lower bounds [] implies -Inf -ux n by 1 list of upper bounds [] implies Inf -params struct of additional parameters (see below) -Z if not empty, is taken to be an n by 1 list of initial guess values (see output) -Returns -------- -Z n by 1 list of solution values -Returns SOLVER_STATUS_CONVERGED = 0, SOLVER_STATUS_MAX_ITER = 1, SOLVER_STATUS_ERROR = 2, - -See also --------- - -Notes ------ -For a harmonic solve on a mesh with 325K facets, matlab 2.2 secs, igl / min_quad_with_fixed.h 7.1 secs - -Known Bugs : rows of[Aeq; Aieq] **must **be linearly independent.Should be using QR decomposition otherwise : http : //www.okstate.edu/sas/v8/sashtml/ormp/chap5/sect32.htm - - -Examples --------- - -)igl_Qu8mg5v7"; - -//NONe and dense_like -npe_function(active_set) -npe_doc(ds_active_set) -npe_arg(A, sparse_float, sparse_double) -npe_arg(B, dense_float, dense_double) -npe_arg(known, dense_int32, dense_int64) -npe_arg(Y, npe_matches(B)) -npe_arg(Aeq, npe_matches(A)) -npe_arg(Beq, npe_matches(B)) -npe_arg(Aieq, npe_matches(A)) -npe_arg(Bieq, npe_matches(B)) -npe_arg(lx, npe_matches(B)) -npe_arg(ux, npe_matches(B)) - -npe_default_arg(Auu_pd, bool, false) -npe_default_arg(max_iter, int, 100) -npe_default_arg(inactive_threshold, double, igl::DOUBLE_EPS) -npe_default_arg(constraint_threshold, double, igl::DOUBLE_EPS) -npe_default_arg(solution_diff_threshold, double, igl::DOUBLE_EPS) - -npe_begin_code() - assert_cols_equals(A, A.rows(), "A"); - assert_rows_match(A, B, "A", "B"); - assert_cols_equals(B, 1, "B"); - - assert_cols_match(A, Aeq, "A", "Aeq"); - assert_rows_match(Aeq, Beq, "Aeq", "Beq"); - assert_cols_equals(Beq, 1, "Beq"); - - assert_rows_match(Aieq, Bieq, "Aieq", "Bieq"); - - if(Aieq.size() > 0) - { - assert_cols_match(A, Aieq, "A", "Aieq"); - assert_cols_equals(Bieq, 1, "Bieq"); - } - - if(lx.size() > 0) - { - assert_rows_match(A, lx, "A", "lx"); - assert_cols_equals(lx, 1, "lx"); - } - - if (ux.size() > 0) - { - assert_rows_match(A, ux, "A", "ux"); - assert_cols_equals(ux, 1, "ux"); - } - - Eigen::SparseMatrix A_copy = A.template cast(); - Eigen::VectorXd B_copy = B.template cast(); - Eigen::VectorXi known_copy = known.template cast(); - Eigen::VectorXd Y_copy = Y.template cast(); - Eigen::SparseMatrix Aeq_copy = Aeq.template cast(); - Eigen::VectorXd Beq_copy = Beq.template cast(); - Eigen::SparseMatrix Aieq_copy; - Eigen::VectorXd Bieq_copy; - - if(Aieq.size() > 0) - { - Aieq_copy = Aieq.template cast(); - Bieq_copy = Bieq.template cast(); - } - - Eigen::VectorXd lx_copy; - if(lx.size() > 0) - lx_copy = lx.template cast(); - Eigen::VectorXd ux_copy; - if (ux.size() > 0) - ux_copy = ux.template cast(); - - igl::active_set_params params; - params.Auu_pd = Auu_pd; - params.max_iter = max_iter; - params.inactive_threshold = inactive_threshold; - params.constraint_threshold = constraint_threshold; - params.solution_diff_threshold = solution_diff_threshold; - - Eigen::VectorXd Z; - - auto res = igl::active_set(A_copy, B_copy, known_copy, Y_copy, Aeq_copy, Beq_copy, Aieq_copy, Bieq_copy, lx_copy, ux_copy, params, Z); - EigenDenseLike Z_row_major = Z.template cast(); - - return std::make_tuple(int(res), npe::move(Z_row_major)); - - npe_end_code() diff --git a/src/adjacency_list.cpp b/src/adjacency_list.cpp deleted file mode 100644 index 7a544ed7..00000000 --- a/src/adjacency_list.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_adjacency_list = R"igl_Qu8mg5v7( -Constructs the graph adjacency list of a given mesh (v, f) - -Parameters ----------- -f : #f by dim array of fixed dimensional (e.g. triangle (#f by 3), - tet (#f by 4), quad (#f by 4), etc...) mesh faces - -Returns -------- -list of lists containing at index i the adjacent vertices of vertex i - -See also --------- -adjacency_matrix - -Notes ------ - -Examples --------- -# Mesh in (v, f) ->>> a = mesh_adjacency_list(f) -)igl_Qu8mg5v7"; - -npe_function(adjacency_list) -npe_doc(ds_adjacency_list) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - assert_valid_tet_or_tri_mesh_faces(f, "f"); - std::vector> a; - igl::adjacency_list(f, a); - return pybind11::detail::type_caster::cast(a, pybind11::return_value_policy::move, pybind11::none()); - -npe_end_code() diff --git a/src/adjacency_matrix.cpp b/src/adjacency_matrix.cpp deleted file mode 100644 index 2f67d2fb..00000000 --- a/src/adjacency_matrix.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_adjacency_matrix = R"igl_Qu8mg5v7( -Constructs the graph adjacency matrix of a given mesh (v, f). - -Parameters ----------- -f : #f by dim list of mesh simplices - -Returns -------- -a : max(f) by max(f) cotangent matrix, each row i corresponding to v(i, :) - -See also --------- -adjacency_list, edges, cotmatrix, diag - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>> a = adjacency_matrix(f) - -# Sum each row ->>> a_sum = np.sum(a, axis=1) - -# Convert row sums into diagonal of sparse matrix ->>> a_diag = diag(a_sum) - -# Build uniform laplacian ->>> u = a - a_diag -)igl_Qu8mg5v7"; - -npe_function(adjacency_matrix) -npe_doc(ds_adjacency_matrix) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_simplex_idxs(f, "f"); - EigenSparseLike a; - igl::adjacency_matrix(f, a); - return npe::move(a); - -npe_end_code() - - diff --git a/src/all_pairs_distances.cpp b/src/all_pairs_distances.cpp deleted file mode 100644 index 86efe66d..00000000 --- a/src/all_pairs_distances.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_all_pairs_distances = R"igl_Qu8mg5v7( -compute distances between each point i in V and point j in U - -Parameters ----------- -V #V by dim list of points -U #U by dim list of points -squared whether to return squared distances - -Returns -------- -D #V by #U matrix of distances, where D(i,j) gives the distance or squareed distance between V(i,:) and U(j,:) - -See also --------- - - -Notes ------ - - -Examples --------- -D = all_pairs_distances(u,v) - - -)igl_Qu8mg5v7"; - -npe_function(all_pairs_distances) -npe_doc(ds_all_pairs_distances) - -npe_arg(u, dense_float, dense_double) -npe_arg(v, npe_matches(u)) -npe_arg(squared, bool) - - -npe_begin_code() - assert_cols_match(u, v, "u", "v"); - assert_nonzero_rows(u, "u"); - assert_nonzero_rows(v, "v"); - //same input/output template - EigenDenseLike u_copy = u; - EigenDenseLike v_copy = v; - - EigenDenseLike D; - igl::all_pairs_distances(u_copy, v_copy, squared, D); - return npe::move(D); - -npe_end_code() - - diff --git a/src/ambient_occlusion.cpp b/src/ambient_occlusion.cpp deleted file mode 100644 index 6bd4f9b2..00000000 --- a/src/ambient_occlusion.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __missing miss the rest two functions with AABB and shoot_ray. __example - -#include -#include -#include -#include - -const char* ds_ambient_occlusion = R"igl_Qu8mg5v7( - -Parameters ----------- -V #V by 3 list of mesh vertex positions -F #F by 3 list of mesh face indices into V -P #P by 3 list of origin points -N #P by 3 list of origin normals - -Returns -------- -S #P list of ambient occusion values between 1 (fully occluded) and 0 (not occluded) - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(ambient_occlusion) -npe_doc(ds_ambient_occlusion) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(p, dense_float, dense_double) -npe_arg(n, npe_matches(p)) -npe_arg(num_samples, int) -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - assert_cols_equals(p, 3, "p"); - assert_cols_equals(n, 3, "n"); - assert_nonzero_rows(p, "p"); - assert_nonzero_rows(n, "n"); - assert_shapes_match(p, n, "p", "n"); - - EigenDenseLike s; - igl::ambient_occlusion(v, f, p, n, num_samples, s); - return npe::move(s); - -npe_end_code() diff --git a/src/arap_linear_block.cpp b/src/arap_linear_block.cpp deleted file mode 100644 index ca5e5bea..00000000 --- a/src/arap_linear_block.cpp +++ /dev/null @@ -1,138 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -const char* ds_arap_linear_block = R"igl_Qu8mg5v7( -Constructs a block of the matrix which constructs the -linear terms of a given arap energy. When treating rotations as knowns -(arranged in a column), then this constructs Kd of K such that the linear -portion of the energy is as a column: - K * R = [Kx Z ... Ky Z ... - Z Kx ... Z Ky ... - ... ] -These blocks are also used to build the "covariance scatter matrices". -Here we want to build a scatter matrix that multiplies against positions -(treated as known) producing covariance matrices to fit each rotation. -Notice that in the case of the RHS of the poisson solve the rotations are -known and the positions unknown, and vice versa for rotation fitting. -These linear block just relate the rotations to the positions, linearly in -each. - - -Parameters ----------- -v : #v by dim list of initial domain positions -f : #f by #simplex size list of triangle indices into V -d : coordinate of linear constructor to build - -Returns -------- -#v by #v/#f block of the linear constructor matrix corresponding to coordinate d - -See also --------- -arap, arap_dof - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -/* - * arap_linear_block - */ -npe_function(arap_linear_block) -npe_doc(ds_arap_linear_block) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(d, int) -npe_arg(energy, int) -npe_begin_code() - - assert_valid_tet_or_tri_mesh(v, f); - static_assert(int(igl::ARAPEnergyType::ARAP_ENERGY_TYPE_SPOKES) == 0, "ARAPEnergyType enum changed!"); - static_assert(int(igl::ARAPEnergyType::ARAP_ENERGY_TYPE_SPOKES_AND_RIMS) == 1, "ARAPEnergyType enum changed!"); - static_assert(int(igl::ARAPEnergyType::ARAP_ENERGY_TYPE_ELEMENTS) == 2, "ARAPEnergyType enum changed!"); - static_assert(int(igl::ARAPEnergyType::ARAP_ENERGY_TYPE_DEFAULT) == 3, "ARAPEnergyType enum changed!"); - static_assert(int(igl::ARAPEnergyType::NUM_ARAP_ENERGY_TYPES) == 4, "ARAPEnergyType enum changed!"); - - if (energy >= igl::NUM_ARAP_ENERGY_TYPES) { - std::string errmsg = - std::string("Invalid enum for energy type should be in the range 0 to ") + - std::to_string(igl::NUM_ARAP_ENERGY_TYPES-1); - throw pybind11::value_error(errmsg); - } - - EigenSparseLike kd; - igl::arap_linear_block(v, f, d, igl::ARAPEnergyType(energy), kd); - return npe::move(kd); - -npe_end_code() - - - - -/* - * arap_linear_block_spokes - */ -npe_function(arap_linear_block_spokes) -npe_doc(ds_arap_linear_block) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(d, int) -npe_begin_code() - - EigenSparseLike kd; - igl::arap_linear_block_spokes(v, f, d, kd); - return npe::move(kd); - -npe_end_code() - - - -/* - * arap_linear_block_spokes_and_rims - */ -npe_function(arap_linear_block_spokes_and_rims) -npe_doc(ds_arap_linear_block) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(d, int) -npe_begin_code() - - EigenSparseLike kd; - igl::arap_linear_block_spokes_and_rims(v, f, d, kd); - return npe::move(kd); - -npe_end_code() - - - - -/* - * arap_linear_block_spokes_and_rims - */ -npe_function(arap_linear_block_elements) -npe_doc(ds_arap_linear_block) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(d, int) -npe_begin_code() - - EigenSparseLike kd; - igl::arap_linear_block_elements(v, f, d, kd); - return npe::move(kd); - -npe_end_code() diff --git a/src/arap_rhs.cpp b/src/arap_rhs.cpp deleted file mode 100644 index 4da6e576..00000000 --- a/src/arap_rhs.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_arap_rhs = R"igl_Qu8mg5v7( -Build right-hand side constructor of global poisson solve for various ARAP energies - Inputs: - - Outputs: - K #V*dim by #(F|V)*dim*dim matrix such that: - b = K * reshape(permute(R,[3 1 2]),size(V|F,1)*size(V,2)*size(V,2),1); - -Parameters ----------- -v : #v by Vdim list of initial domain positions -f : #f by 3 list of triangle indices into v -d : dimension being used at solve time. For deformation usually dim = V.cols(), for surface parameterization V.cols() = 3 and dim = 2 -energy : ARAPEnergyType enum value defining which energy is being used. See igl.ARAPEnergyType for valid options and explanations. - -Returns -------- -#v*d by #(f|v)*dim*dim matrix such that: b = K * reshape(permute(R,[3 1 2]),size(V|F,1)*size(V,2)*size(V,2),1); - -See also --------- -arap_linear_block, arap - -Notes ------ - -Examples --------- -)igl_Qu8mg5v7"; - -npe_function(arap_rhs) -npe_doc(ds_arap_rhs) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(d, int) -npe_arg(energy, int) -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - - static_assert(int(igl::ARAPEnergyType::ARAP_ENERGY_TYPE_SPOKES) == 0, "ARAPEnergyType enum changed!"); - static_assert(int(igl::ARAPEnergyType::ARAP_ENERGY_TYPE_SPOKES_AND_RIMS) == 1, "ARAPEnergyType enum changed!"); - static_assert(int(igl::ARAPEnergyType::ARAP_ENERGY_TYPE_ELEMENTS) == 2, "ARAPEnergyType enum changed!"); - static_assert(int(igl::ARAPEnergyType::ARAP_ENERGY_TYPE_DEFAULT) == 3, "ARAPEnergyType enum changed!"); - static_assert(int(igl::ARAPEnergyType::NUM_ARAP_ENERGY_TYPES) == 4, "ARAPEnergyType enum changed!"); - - if (energy >= igl::NUM_ARAP_ENERGY_TYPES) { - std::string errmsg = - std::string("Invalid enum for energy type should be in the range 0 to ") + - std::to_string(igl::NUM_ARAP_ENERGY_TYPES-1); - throw pybind11::value_error(errmsg); - } else if (energy == igl::ARAP_ENERGY_TYPE_DEFAULT) { - // FIXME: arap_rhs should pick a default but it doesn't - std::string errmsg = - std::string("Invalid enum for energy type should be in the range 0 to ") + - std::to_string(igl::ARAP_ENERGY_TYPE_DEFAULT-1); - } - - EigenSparseLike k; - igl::arap_rhs(v, f, d, igl::ARAPEnergyType(energy), k); - return npe::move(k); - -npe_end_code() - - diff --git a/src/average_from_edges_onto_vertices.cpp b/src/average_from_edges_onto_vertices.cpp new file mode 100644 index 00000000..2fd714e6 --- /dev/null +++ b/src/average_from_edges_onto_vertices.cpp @@ -0,0 +1,41 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + nb::object average_from_edges_onto_vertices( + const nb::DRef &F, + const nb::DRef &E, + const nb::DRef &oE, + const nb::DRef &uE) + { + Eigen::VectorXN uV; + igl::average_from_edges_onto_vertices(F, E, oE, uE, uV); + return nb::cast(uV); + } +} + +// Bind the wrapper to the Python module +void bind_average_from_edges_onto_vertices(nb::module_ &m) +{ + m.def( + "average_from_edges_onto_vertices", + &pyigl::average_from_edges_onto_vertices, + "F"_a, "E"_a, "oE"_a, "uE"_a, +R"(Move a scalar field defined on edges to vertices by averaging + +@param[in] F #F by 3 triangle mesh connectivity +@param[in] E #E by 3 mapping from each halfedge to each edge +@param[in] oE #E by 3 orientation as generated by orient_halfedges +@param[in] uE #E by 1 list of scalars +@param[out] uV #V by 1 list of scalar defined on vertices + +\see orient_halfedges)" + ); +} diff --git a/src/average_onto_faces.cpp b/src/average_onto_faces.cpp deleted file mode 100644 index fc269b21..00000000 --- a/src/average_onto_faces.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -const char* doccc_faces_avg = R"igl_Qu8mg5v7( -Move a scalar field defined on vertices to faces by averaging - -Parameters ----------- -f : #f by ss list of simplexes/faces -s : #v by dim list of per-vertex values - -Returns -------- -#f by dim list of per-face values - -See also --------- -average_onto_vertices - -Notes ------ - -Examples --------- -)igl_Qu8mg5v7"; - -npe_function(average_onto_faces) -npe_doc(doccc_faces_avg) - -npe_arg(f, dense_int32, dense_int64) -npe_arg(s, dense_float, dense_double) - - -npe_begin_code() - assert_valid_tet_or_tri_mesh_faces(f); - EigenDenseLike SF; - igl::average_onto_faces(f, s, SF); - - // SF.setConstant(f.rows(), s.cols(), 0); - // for (int i = 0; i < f.rows(); ++i) - // for (int j = 0; j < f.cols(); ++j) - // SF.row(i) += s.row(f(i, j)); - // SF.array() /= s.cols(); - return npe::move(SF); - -npe_end_code() - diff --git a/src/average_onto_vertices.cpp b/src/average_onto_vertices.cpp deleted file mode 100644 index ba25f157..00000000 --- a/src/average_onto_vertices.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_average_onto_vertices = R"igl_Qu8mg5v7( -Move a scalar field defined on faces to vertices by averaging - -Parameters ----------- -v : #v by vdim array of mesh vertices -f : #f by simplex_count array of simplex indices -s : #f by dim scalar field defined on simplices - -Returns -------- -sv: #v by dim scalar field defined on vertices - -See also --------- -average_onto_faces - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(average_onto_vertices) -npe_doc(ds_average_onto_vertices) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(s, dense_float, dense_double) // TODO: Maybe do a matches here -npe_begin_code() - assert_valid_tet_or_tri_mesh_23d(v, f); - assert_shape_equals(s, f.rows(), v.cols(), "s"); - EigenDenseLike sv; - igl::average_onto_vertices(v, f, s, sv); - return npe::move(sv); - -npe_end_code() - - diff --git a/src/avg_edge_length.cpp b/src/avg_edge_length.cpp deleted file mode 100644 index 26c77229..00000000 --- a/src/avg_edge_length.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -const char* ds_avg_edge_length = R"igl_Qu8mg5v7( -Compute the average edge length for the given triangle mesh. - -Parameters ----------- -v : array_like #v by 3 vertex array -f : f #f by simplex-size list of mesh faces (must be simplex) - -Returns -------- -l : average edge length - -See also --------- -adjacency_matrix - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>> length = avg_edge_length(v, f) -)igl_Qu8mg5v7"; - -npe_function(avg_edge_length) -npe_doc(ds_avg_edge_length) -npe_arg(v, dense_double, dense_float) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tet_or_tri_mesh(v, f); - return igl::avg_edge_length(v, f); - -npe_end_code() - - diff --git a/src/barycenter.cpp b/src/barycenter.cpp index b21c9d2d..7266f0d4 100644 --- a/src/barycenter.cpp +++ b/src/barycenter.cpp @@ -1,51 +1,37 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include +#include "default_types.h" #include - -const char* ds_barycenter = R"igl_Qu8mg5v7( -Compute the barycenter of every simplex - -Parameters ----------- -v : #v x dim matrix of vertex coordinates -f : #f x simplex_size matrix of indices of simplex corners into V - -Returns -------- -A #f x dim matrix where each row is the barycenter of each simplex - -See also --------- - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(barycenter) -npe_doc(ds_barycenter) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - assert_valid_tet_or_tri_mesh_23d(v, f); - EigenDenseLike bc; - igl::barycenter(v, f, bc); - return npe::move(bc); - -npe_end_code() - +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for barycenter function + nb::object barycenter( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::MatrixXN BC; + igl::barycenter(V, F, BC); + return nb::cast(BC); + } +} + +// Bind the wrapper to the Python module +void bind_barycenter(nb::module_ &m) +{ + m.def( + "barycenter", + &pyigl::barycenter, + "V"_a, "F"_a, +R"(Computes the barycenter of every simplex. + +@param[in] V #V x dim matrix of vertex coordinates +@param[in] F #F x simplex_size matrix of indices of simplex corners into V +@param[out] BC #F x dim matrix of 3d vertices)" + ); +} diff --git a/src/barycentric_coordinates.cpp b/src/barycentric_coordinates.cpp index 1a8ccf0e..e81feb80 100644 --- a/src/barycentric_coordinates.cpp +++ b/src/barycentric_coordinates.cpp @@ -1,117 +1,56 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include +#include "default_types.h" #include -#include -const char* ds_barycentric_coordinates_tet = R"igl_Qu8mg5v7( +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for barycentric_coordinates function + nb::object barycentric_coordinates( + const nb::DRef &P, + const nb::DRef &A, + const nb::DRef &B, + const nb::DRef &C, + const nb::DRef &D) + { + Eigen::MatrixXN L; + if(D.size()>0) + { + igl::barycentric_coordinates(P, A, B, C, D, L); + }else + { + igl::barycentric_coordinates(P, A, B, C, L); + } + return nb::cast(L); + } +} + +// Bind the wrapper to the Python module +void bind_barycentric_coordinates(nb::module_ &m) +{ + m.def( + "barycentric_coordinates", + &pyigl::barycentric_coordinates, + "P"_a, + "A"_a, + "B"_a, + "C"_a, + "D"_a=Eigen::MatrixXN(), +R"(Compute barycentric coordinates of each point in a corresponding triangle/tetrahedron. + +@param[in] P #P by 3 Query points in 3d +@param[in] A #P by 3 (Tri|Tet) corners in 3d +@param[in] B #P by 3 (Tri|Tet) corners in 3d +@param[in] C #P by 3 (Tri|Tet) corners in 3d +@param[in] D #P by 3 Tet corners in 3d +@param[out] L #P by (3|4) list of barycentric coordinates + )" + ); +} -Compute barycentric coordinates in a tet corresponding to the Euclidean coordinates in `p`. -The input arrays `a`, `b`, `c` and `d` are the vertices of each tet. I.e. one tet is -`a[i, :], b[i, :], c[i, :], d[:, i]`. -Parameters ----------- -p : #P by 3 Query points in 3d -a : #P by 3 Tet corners in 3d -b : #P by 3 Tet corners in 3d -c : #P by 3 Tet corners in 3d -d : #P by 3 Tet corners in 3d - -Returns -------- -#P by 4 list of barycentric coordinates - -See also --------- - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(barycentric_coordinates_tet) -npe_doc(ds_barycentric_coordinates_tet) -npe_arg(p, dense_float, dense_double) -npe_arg(a, npe_matches(p)) -npe_arg(b, npe_matches(p)) -npe_arg(c, npe_matches(p)) -npe_arg(d, npe_matches(p)) -npe_begin_code() - - assert_rows_match(p, a, "p", "a"); - assert_rows_match(p, b, "p", "b"); - assert_rows_match(p, c, "p", "c"); - assert_rows_match(p, d, "p", "d"); - assert_cols_equals(p, 3, "p"); - assert_cols_equals(a, 3, "a"); - assert_cols_equals(b, 3, "b"); - assert_cols_equals(c, 3, "c"); - assert_cols_equals(d, 3, "d"); - EigenDenseLike l; - igl::barycentric_coordinates(p, a, b, c, d, l); - return npe::move(l); - -npe_end_code() - - - - - -const char* ds_barycentric_coordinates_tri = R"igl_Qu8mg5v7( - -Compute barycentric coordinates in a triangle corresponding to the Euclidean coordinates in `p`. -The input arrays `a`, `b`, and `c` are the vertices of each triangle. I.e. one triangle is -`a[i, :], b[i, :], c[i, :]`. - -Parameters ----------- -p : #P by 3 Query points in 3d -a : #P by 3 Tri corners in 3d -b : #P by 3 Tri corners in 3d -c : #P by 3 Tri corners in 3d - -Returns -------- -#P by 3 list of barycentric coordinates - -See also --------- - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(barycentric_coordinates_tri) -npe_doc(ds_barycentric_coordinates_tri) -npe_arg(p, dense_float, dense_double) -npe_arg(a, npe_matches(p)) -npe_arg(b, npe_matches(p)) -npe_arg(c, npe_matches(p)) -npe_begin_code() - assert_rows_match(p, a, "p", "a"); - assert_rows_match(p, b, "p", "b"); - assert_rows_match(p, c, "p", "c"); - assert_cols_equals(p, 3, "p"); - assert_cols_equals(a, 3, "a"); - assert_cols_equals(b, 3, "b"); - assert_cols_equals(c, 3, "c"); - - EigenDenseLike l; - igl::barycentric_coordinates(p, a, b, c, l); - return npe::move(l); - -npe_end_code() diff --git a/src/bbw.cpp b/src/bbw.cpp new file mode 100644 index 00000000..e8c908f3 --- /dev/null +++ b/src/bbw.cpp @@ -0,0 +1,82 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for bbw function + nb::object bbw( + const nb::DRef &V, + const nb::DRef &Ele, + const nb::DRef &b, + const nb::DRef &bc, + const nb::DRef &W0, + const bool partition_unity, + const int verbosity, + const int max_iter, + const double inactive_threshold, + const double constraint_threshold, + const double solution_diff_threshold) + { + igl::BBWData bbw_data; + bbw_data.partition_unity = partition_unity; + bbw_data.verbosity = verbosity; + bbw_data.active_set_params.max_iter = max_iter; + bbw_data.active_set_params.inactive_threshold = inactive_threshold; + bbw_data.active_set_params.constraint_threshold = constraint_threshold; + bbw_data.active_set_params.solution_diff_threshold = solution_diff_threshold; + bbw_data.W0 = W0; + + Eigen::MatrixXN W; + if(!igl::bbw(V, Ele, b, bc, bbw_data, W)) + { + throw std::runtime_error("bbw: failed to compute weights"); + } + return nb::cast(W); + } +} + +// Bind the wrapper to the Python module +void bind_bbw(nb::module_ &m) +{ + igl::BBWData bbw_data; + m.def( + "bbw", + &pyigl::bbw, + "V"_a, + "Ele"_a, + "b"_a, + "bc"_a, + "W0"_a=Eigen::MatrixXN(), + "partition_unity"_a=bbw_data.partition_unity, + "verbosity"_a=bbw_data.verbosity, + "max_iter"_a=bbw_data.active_set_params.max_iter, + "inactive_threshold"_a=bbw_data.active_set_params.inactive_threshold, + "constraint_threshold"_a=bbw_data.active_set_params.constraint_threshold, + "solution_diff_threshold"_a=bbw_data.active_set_params.solution_diff_threshold, +R"(Compute Bounded Biharmonic Weights on a given domain (V,Ele) with a given +set of boundary conditions + +@tparam DerivedV derived type of eigen matrix for V (e.g. MatrixXd) +@tparam DerivedF derived type of eigen matrix for F (e.g. MatrixXi) +@tparam Derivedb derived type of eigen matrix for b (e.g. VectorXi) +@tparam Derivedbc derived type of eigen matrix for bc (e.g. MatrixXd) +@tparam DerivedW derived type of eigen matrix for W (e.g. MatrixXd) +@param[in] V #V by dim vertex positions +@param[in] Ele #Elements by simplex-size list of element indices +@param[in] b #b boundary indices into V +@param[in] bc #b by #W list of boundary values +@param[in,out] data object containing options, initial guess --> solution and results +@param[out] W #V by #W list of *unnormalized* weights to normalize use + igl::normalize_row_sums(W,W))" + ); +} + + + diff --git a/src/bfs.cpp b/src/bfs.cpp deleted file mode 100644 index 1d169979..00000000 --- a/src/bfs.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_bfs = R"igl_Qu8mg5v7( -Construct an array indexing into a **directed** graph represented by an adjacency list using -breadth first search. I.e. the output is an array of vertices in breadth-first order. - -Parameters ----------- -A : #V list of adjacency lists or #V by #V adjacency matrix -s : starting node (index into A) - -Returns -------- -A tuple, (d, p) where: - * d is a #V list of indices into rows of A in the order in which graph nodes are discovered - * p is a #V list of indices of A of predecsors where -1 indicates root/not discovered. I.e. - p[i] is the index of the vertex v which preceded d[i] in the breadth first traversal. -Note that together, (d, p) form a spanning tree of the input graph - -See also --------- - -Notes ------ - -Examples --------- ->>> V, F, _ = igl.readOFF("test.off) ->>> A = igl.adjacency_matrix(V, F) ->>> d, p = igl.bfs(A, V[0]) - -)igl_Qu8mg5v7"; - -npe_function(bfs) -npe_doc(ds_bfs) -npe_arg(A, sparse_int32, sparse_int64) -npe_arg(s, int) -npe_begin_code() - - if (A.rows() != A.cols()) { - std::string errmsg = std::string("Invalid Adjacency matrix. Must be a square sparse matrix. Got shape: (") + - std::to_string(A.rows()) + std::string(", ") + std::to_string(A.cols()) + - std::string(")"); - throw pybind11::value_error(errmsg); - } - if (A.rows() == 0 && A.cols() == 0) { - throw pybind11::value_error("Invalid Adjacency matrix has shape 0x0."); - } - if (s < 0 || s >= A.rows()) { - std::string errmsg = std::string("Invalid start index, must be between 0 and the number of rows of A (") + - std::to_string(A.rows()) + std::string("). Got s = ") + std::to_string(s); - throw pybind11::index_error(errmsg); - } - EigenDense d; - EigenDense p; - igl::bfs(A, (size_t)s, d, p); - return std::make_tuple(npe::move(d), npe::move(p)); - -npe_end_code() - - - diff --git a/src/bfs_orient.cpp b/src/bfs_orient.cpp deleted file mode 100644 index 4f77c101..00000000 --- a/src/bfs_orient.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_bfs_orient = R"igl_Qu8mg5v7( -Consistently orient faces in orientable patches using BFS. - -Parameters ----------- -f : #F by 3 list of faces - -Returns -------- -A tuple, (ff, c) where: - * ff is a #F by 3 list of faces which are consistently oriented with - * c is a #F array of connected component ids - -See also --------- - -Notes ------ - -Examples --------- ->>> v, f, _ = igl.readOFF("test.off) ->>> ff, c = igl.bfs_orient(f) - -)igl_Qu8mg5v7"; - -npe_function(bfs_orient) -npe_doc(ds_bfs_orient) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - EigenDenseLike ff, c; - igl::bfs_orient(f, ff, c); - return std::make_tuple(npe::move(ff), npe::move(c)); - -npe_end_code() diff --git a/src/biharmonic_coordinates.cpp b/src/biharmonic_coordinates.cpp deleted file mode 100644 index fd30e3ca..00000000 --- a/src/biharmonic_coordinates.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - - - - - -#include - -const char *ds_biharmonic_coordinates = R"igl_Qu8mg5v7( - -Compute "discrete biharmonic generalized barycentric coordinates" as - described in "Linear Subspace Design for Real-Time Shape Deformation" - [Wang et al. 2015]. Not to be confused with "Bounded Biharmonic Weights - for Real-Time Deformation" [Jacobson et al. 2011] or "Biharmonic - Coordinates" (2D complex barycentric coordinates) [Weber et al. 2012]. - These weights minimize a discrete version of the squared Laplacian energy - subject to positional interpolation constraints at selected vertices - (point handles) and transformation interpolation constraints at regions - (region handles). - -Parameters ----------- -Templates: HType should be a simple index type e.g. `int`,`size_t` - -V #V by dim list of mesh vertex positions -T #T by dim+1 list of / triangle indices into V if dim=2 - \ tetrahedron indices into V if dim=3 -S #point-handles+#region-handles list of lists of selected vertices for - each handle. Point handles should have singleton lists and region - handles should have lists of size at least dim+1 (and these points - should be in general position). -k 2-->biharmonic, 3-->triharmonic - - -Returns -------- -W #V by #points-handles+(#region-handles * dim+1) matrix of weights so - that columns correspond to each handles generalized barycentric - coordinates (for point-handles) or animation space weights (for region - handles). -returns true only on success - - -See also --------- - - -Notes ------ -None - -Examples --------- -MatrixXd W; -igl::biharmonic_coordinates(V,F,S,W); -const size_t dim = T.cols()-1; -MatrixXd H(W.cols(),dim); -{ - int c = 0; - for(int h = 0;h >) -npe_default_arg(k, int, 2) - -npe_begin_code() - assert_valid_tet_or_tri_mesh(v, t, "v", "t"); - // TODO: t.rows = dim+1 - EigenDenseLike w; - Eigen::MatrixXi t_copy = t.template cast(); - igl::biharmonic_coordinates(v, t, s, k, w); - return npe::move(w); - -npe_end_code() diff --git a/src/bijective_composite_harmonic_mapping.cpp b/src/bijective_composite_harmonic_mapping.cpp deleted file mode 100644 index 6dd02d78..00000000 --- a/src/bijective_composite_harmonic_mapping.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_bijective_composite_harmonic_mapping = R"igl_Qu8mg5v7( - -Compute a planar mapping of a triangulated polygon (V,F) subjected to - boundary conditions (b,bc). The mapping should be bijective in the sense - that no triangles' areas become negative (this assumes they started - positive). This mapping is computed by "composing" harmonic mappings - between incremental morphs of the boundary conditions. This is a bit like - a discrete version of "Bijective Composite Mean Value Mappings" [Schneider - et al. 2013] but with a discrete harmonic map (cf. harmonic coordinates) - instead of mean value coordinates. This is inspired by "Embedding a - triangular graph within a given boundary" [Xu et al. 2011]. - -Parameters ----------- -V #V by 2 list of triangle mesh vertex positions -F #F by 3 list of triangle indices into V -b #b list of boundary indices into V -bc #b by 2 list of boundary conditions corresponding to b - -Returns -------- -U #V by 2 list of output mesh vertex locations -Returns true if and only if U contains a successful bijectie mapping - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(bijective_composite_harmonic_mapping) -npe_doc(ds_bijective_composite_harmonic_mapping) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(b, npe_matches(f)) -npe_arg(bc, npe_matches(v)) - - -npe_begin_code() - assert_valid_2d_tri_mesh(v, f); - assert_nonzero_rows(b, "b"); - assert_cols_equals(b, 1, "b"); - assert_shape_equals(bc, b.rows(), 2, "bc"); - - EigenDenseLike u; - bool success = igl::bijective_composite_harmonic_mapping(v, f, b, bc, u); - return std::make_pair(success, npe::move(u)); - -npe_end_code() - - -#include - -const char* ds_internal_bijective_composite_harmonic_mapping = R"igl_Qu8mg5v7( - -Parameters ----------- -min_steps minimum number of steps to take from V(b,:) to bc -max_steps minimum number of steps to take from V(b,:) to bc (if max_steps == min_steps then no further number of steps will be tried) -num_inner_iters number of iterations of harmonic solves to run after for each morph step (to try to push flips back in) -test_for_flips whether to check if flips occurred (and trigger more steps). if test_for_flips = false then this function always returns - true - - -Returns -------- - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(bijective_composite_harmonic_mapping_with_steps) -npe_doc(ds_internal_bijective_composite_harmonic_mapping) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(b, npe_matches(f)) -npe_arg(bc, npe_matches(v)) -npe_arg(min_steps, int) -npe_arg(max_steps, int) -npe_arg(num_inner_iters, int) -npe_arg(test_for_flips, bool) - - -npe_begin_code() - assert_valid_2d_tri_mesh(v, f); - assert_nonzero_rows(b, "b"); - assert_cols_equals(b, 1, "b"); - assert_shape_equals(bc, b.rows(), 2, "bc"); - - EigenDenseLike u; - bool success = igl::bijective_composite_harmonic_mapping(v, f, b, bc, min_steps, max_steps, num_inner_iters, test_for_flips, u); - return std::make_pair(success, npe::move(u)); - -npe_end_code() - - diff --git a/src/blue_noise.cpp b/src/blue_noise.cpp deleted file mode 100644 index b48f383c..00000000 --- a/src/blue_noise.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Peter Kulits -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -#include -#include -#include -#include - -const char* ds_blue_noise = R"igl_Qu8mg5v7( -"Fast Poisson Disk Sampling in Arbitrary Dimensions" [Bridson 2007] - -For very dense samplings this is faster than (up to 2x) cyCodeBase's -implementation of "Sample Elimination for Generating Poisson Disk Sample -Sets" [Yuksel 2015]. YMMV -Parameters ----------- - V #V by dim list of mesh vertex positions - F #F by 3 list of mesh triangle indices into rows of V - r Poisson disk radius (evaluated according to Euclidean distance on V) - -Returns -------- - B #P by 3 list of barycentric coordinates, ith row are coordinates of - ith sampled point in face FI(i) - FI #P list of indices into F - P #P by dim list of sample positions. - -See also --------- -random_points_on_mesh - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(blue_noise) -npe_doc(ds_blue_noise) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(r, double) - - -npe_begin_code() - - assert_valid_23d_tri_mesh(v, f); - EigenDenseLike b; - Eigen::VectorXi fi; - EigenDenseLike p; - igl::blue_noise(v, f, r, b, fi, p); - return std::make_tuple(npe::move(b), npe::move(fi), npe::move(p)); - -npe_end_code() diff --git a/src/bone_parents.cpp b/src/bone_parents.cpp deleted file mode 100644 index 26128c6a..00000000 --- a/src/bone_parents.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_bone_parents = R"igl_Qu8mg5v7( -BONE_PARENTS Recover "parent" bones from directed graph representation. -Parameters ----------- -BE #BE by 2 list of directed bone edges - -Returns -------- -P #BE by 1 list of parent indices into BE, -1 means root. - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(bone_parents) -npe_doc(ds_bone_parents) - -npe_arg(be, dense_int32, dense_int64) - - -npe_begin_code() - assert_cols_equals(be, 2, "be"); - EigenDenseLike p; - igl::bone_parents(be, p); - return npe::move(p); - -npe_end_code() - - diff --git a/src/boundary_conditions.cpp b/src/boundary_conditions.cpp deleted file mode 100644 index 7c332f75..00000000 --- a/src/boundary_conditions.cpp +++ /dev/null @@ -1,114 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include - - - -#include - -const char* ds_boundary_conditions = R"igl_Qu8mg5v7( - -Compute boundary conditions for automatic weights computation. This - function expects that the given mesh (V,Ele) has sufficient samples - (vertices) exactly at point handle locations and exactly along bone and - cage edges. - -Parameters ----------- -V #V by dim list of domain vertices -Ele #Ele by simplex-size list of simplex indices -C #C by dim list of handle positions -P #P by 1 list of point handle indices into C -BE #BE by 2 list of bone edge indices into C -CE #CE by 2 list of cage edge indices into *P* -CF #CF by 3 list of cage edge indices into *P* - -Returns -------- -b #b list of boundary indices (indices into V of vertices which have - known, fixed values) -bc #b by #weights list of known/fixed values for boundary vertices - (notice the #b != #weights in general because #b will include all the - intermediary samples along each bone, etc.. The ordering of the - weights corresponds to [P;BE] -Returns false if boundary conditions are suspicious: - P and BE are empty - bc is empty - some column of bc doesn't have a 0 (assuming bc has >1 columns) - some column of bc doesn't have a 1 (assuming bc has >1 columns) - -See also --------- - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(boundary_conditions) -npe_doc(ds_boundary_conditions) - -npe_arg(v, dense_float, dense_double) -npe_arg(ele, dense_int32, dense_int64) -npe_arg(c, npe_matches(v)) -npe_arg(p, npe_matches(ele)) -npe_arg(be, npe_matches(ele)) -npe_arg(ce, npe_matches(ele)) -npe_arg(cf, npe_matches(ele)) - -npe_begin_code() - assert_valid_tet_or_tri_mesh(v, ele); - assert_cols_match(v, c, "v", "c"); - assert_cols_equals(be, 2, "be"); - - if(p.size() > 0) - { - assert_cols_equals(p, 1, "p"); - } - - if(ce.size() > 0) - { - assert_cols_equals(ce, 2, "ce"); - } - if(cf.size() > 0) - { - assert_cols_equals(cf, 3, "cf"); - } - - //TODO: remove __copy - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi ele_copy = ele.template cast(); - Eigen::MatrixXd c_copy = c.template cast(); - Eigen::VectorXi p_copy; - if (p.size() > 0) - p_copy = p.template cast(); - Eigen::MatrixXi be_copy = be.template cast(); - Eigen::MatrixXi ce_copy; - if (ce.size() > 0) - ce_copy = ce.template cast(); - Eigen::MatrixXi cf_copy; - if (cf.size() > 0) - cf_copy = cf.template cast(); - Eigen::VectorXi b_copy; - Eigen::MatrixXd bc_copy; - bool success = igl::boundary_conditions(v_copy, ele_copy, c_copy, p_copy, be_copy, ce_copy, cf_copy, b_copy, bc_copy); - EigenDenseLike b = b_copy.template cast(); - EigenDenseLike bc = bc_copy.template cast(); - return std::make_tuple(success, npe::move(b), npe::move(bc)); - -npe_end_code() - - diff --git a/src/boundary_facets.cpp b/src/boundary_facets.cpp index 985ef899..6110eb8c 100644 --- a/src/boundary_facets.cpp +++ b/src/boundary_facets.cpp @@ -1,49 +1,59 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include +#include "default_types.h" #include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + nb::object boundary_facets( + const nb::DRef &T, + bool return_J, + bool return_K) + { + Eigen::MatrixXI F; + Eigen::VectorXI J; + Eigen::VectorXI K; + igl::boundary_facets(T,F,J,K); + if(return_J && return_K) + { + return nb::make_tuple(F,J,K); + }else if(return_J) + { + return nb::make_tuple(F,J); + }else if(return_K) + { + return nb::make_tuple(F,K); + } + return nb::cast(F); + } +} + +// Bind the wrapper to the Python module +void bind_boundary_facets(nb::module_ &m) +{ + m.def( + "boundary_facets", + &pyigl::boundary_facets, + "T"_a, + "return_J"_a=false, + "return_K"_a=false, +R"(Determine boundary faces (edges) of tetrahedra (triangles) stored in T +(analogous to qptoolbox's `outline` and `boundary_faces`). + +@param[in] T tetrahedron (triangle) index list, m by 4 (3), where m is the number of tetrahedra +@param[out] F list of boundary faces, n by 3 (2), where n is the number + of boundary faces. Faces are oriented so that igl::centroid(V,F,…) +computes the same sign volume as igl::volume(V,T) +@param[out] J list of indices into T, n by 1 +@param[out] K list of indices revealing across from which vertex is this facet)" + ); +} -const char* ds_boundary_facets = R"igl_Qu8mg5v7( -Determine boundary faces (edges) of tetrahedra (triangles). -Parameters ----------- -t : tetrahedron or triangle index list, m by 4/3, where m is the number of tetrahedra/triangles -Returns -------- -f : list of boundary faces, n by 3/2, where n is the number of boundary faces/edges - -See also --------- -None - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>> b = boundary_facets(f) -)igl_Qu8mg5v7"; - -npe_function(boundary_facets) -npe_doc(ds_boundary_facets) -npe_arg(t, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tet_or_tri_mesh_faces(t, "t"); - EigenDense f; - igl::boundary_facets(t, f); - return npe::move(f); - -npe_end_code() diff --git a/src/boundary_loop.cpp b/src/boundary_loop.cpp deleted file mode 100644 index a6a51a81..00000000 --- a/src/boundary_loop.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -const char* ds_boundary_loop = R"igl_Qu8mg5v7( -Compute ordered boundary loops for a manifold mesh and return the longest loop in terms of vertices. - -Parameters ----------- -f : #v by dim array of mesh faces - -Returns -------- -l : ordered list of boundary vertices of longest boundary loop - -See also --------- - - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>>l = boundary_loop(f) -)igl_Qu8mg5v7"; - -npe_function(boundary_loop) -npe_doc(ds_boundary_loop) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - EigenDenseLike l; - igl::boundary_loop(f, l); - return npe::move(l); - -npe_end_code() - - - -const char* ds_all_boundary_loop = R"igl_Qu8mg5v7xx( -Compute ordered boundary loops for a manifold mesh - -Parameters ----------- -f : #v by dim array of mesh faces - -Returns -------- -l : list of loops where l[i] = ordered list of boundary vertices in loop i - -See also --------- - - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>>l = all_boundary_loop(f) -)igl_Qu8mg5v7xx"; - -npe_function(all_boundary_loop) -npe_doc(ds_all_boundary_loop) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - assert_valid_tri_mesh_faces(f); - std::vector> l; - igl::boundary_loop(f, l); - return l; - -npe_end_code() - diff --git a/src/bounding_box.cpp b/src/bounding_box.cpp deleted file mode 100644 index df8eff23..00000000 --- a/src/bounding_box.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_bounding_box = R"igl_Qu8mg5v7( - -Build a triangle mesh of the bounding box of a given list of vertices - -Parameters ----------- -V #V by dim list of rest domain positions - -Returns -------- -BV 2^dim by dim list of bounding box corners positions -BF #BF by dim list of simplex facets - -See also --------- - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(bounding_box) -npe_doc(ds_bounding_box) - -npe_arg(v, dense_float, dense_double) - - -npe_begin_code() - - assert_nonzero_rows(v, "v"); - EigenDenseLike bv; - Eigen::MatrixXi bf; - igl::bounding_box(v, bv, bf); - EigenDenseInt bf_row_major = bf.template cast(); - return std::make_tuple(npe::move(bv), npe::move(bf_row_major)); - -npe_end_code() - - - - - - - -const char* ds_bounding_box_pad = R"igl_Qu8mg5v7( -See bounding_box for the documentation. -)igl_Qu8mg5v7"; - -npe_function(bounding_box) -npe_doc(ds_bounding_box) - -npe_arg(v, dense_float, dense_double) -npe_arg(pad, double) - - -npe_begin_code() - - assert_nonzero_rows(v, "v"); - EigenDenseLike bv; - Eigen::MatrixXi bf; - igl::bounding_box(v, pad, bv, bf); - EigenDenseInt bf_row_major = bf.template cast(); - return std::make_tuple(npe::move(bv), npe::move(bf_row_major)); - -npe_end_code() - - diff --git a/src/bounding_box_diagonal.cpp b/src/bounding_box_diagonal.cpp deleted file mode 100644 index 2962de69..00000000 --- a/src/bounding_box_diagonal.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include - - - - - - -#include - -const char* ds_bounding_box_diagonal = R"igl_Qu8mg5v7( - -Compute the length of the diagonal of a given meshes axis-aligned bounding - -Parameters ----------- -V #V by 3 list of vertex positions - -Returns -------- -Returns length of bounding box diagonal - -See also --------- - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(bounding_box_diagonal) -npe_doc(ds_bounding_box_diagonal) - -npe_arg(v, dense_float, dense_double) - - -npe_begin_code() - assert_nonzero_rows(v, "v"); - assert_cols_equals(v, 3, "v"); - // TODO: remove __copy - Eigen::MatrixXd v_copy = v.template cast(); - return igl::bounding_box_diagonal(v_copy); - -npe_end_code() - - diff --git a/src/circulation.cpp b/src/circulation.cpp deleted file mode 100644 index 4c205c75..00000000 --- a/src/circulation.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example -//difficult to test - -#include -#include -#include -#include - - - - - -#include - -const char* ds_circulation = R"igl_Qu8mg5v7( - -Return list of faces around the end point of an edge. Assumes - data-structures are built from an edge-manifold **closed** mesh. - -Parameters ----------- -e index into E of edge to circulate -ccw whether to _continue_ in ccw direction of edge (circulate around - E(e,1)) -EMAP #F*3 list of indices into E, mapping each directed edge to unique - unique edge in E -EF #E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of - F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " - e=(j->i) -EI #E by 2 list of edge flap corners (see above). - -Returns -------- -Returns list of faces touched by circulation (in cyclically order). - -See also --------- - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(circulation) -npe_doc(ds_circulation) - -npe_arg(e, int) -npe_arg(ccw, bool) -npe_arg(emap, dense_int32, dense_int64) -npe_arg(ef, npe_matches(emap)) -npe_arg(ei, npe_matches(emap)) - - -npe_begin_code() - assert_cols_equals(ef, 2, "ef"); - assert_cols_equals(ei, 2, "ei"); - assert_shapes_match(ef, ei, "ef", "ei"); - - // TODO: remove __copy - Eigen::VectorXi emap_copy = emap.template cast(); - Eigen::MatrixXi ef_copy = ef.template cast(); - Eigen::MatrixXi ei_copy = ei.template cast(); - auto res = igl::circulation(e, ccw, emap_copy, ef_copy, ei_copy); - //res is a std::vector - return res; - -npe_end_code() - diff --git a/src/circumradius.cpp b/src/circumradius.cpp deleted file mode 100644 index 9366e771..00000000 --- a/src/circumradius.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_circumradius = R"igl_Qu8mg5v7( - -Compute the circumradius of each triangle in a mesh (V,F) -Parameters ----------- -V #V by dim list of mesh vertex positions -F #F by 3 list of triangle indices into V - - -Returns -------- -R #F list of circumradii - -See also --------- - - -Notes ------ -None - -Examples --------- -R = circumradius(V, F) - - -)igl_Qu8mg5v7"; - -npe_function(circumradius) -npe_doc(ds_circumradius) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - // TODO: v can also be 2d - assert_valid_23d_tri_mesh(v, f); - EigenDense r; - igl::circumradius(v, f, r); - return npe::move(r); - -npe_end_code() - - diff --git a/src/collapse_small_triangles.cpp b/src/collapse_small_triangles.cpp deleted file mode 100644 index fa96b523..00000000 --- a/src/collapse_small_triangles.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include - - - - - -#include - -const char* ds_collapse_small_triangles = R"igl_Qu8mg5v7( - -Given a triangle mesh (V,F) compute a new mesh (VV,FF) which contains the - original faces and vertices of (V,F) except any small triangles have been - removed via collapse. - - We are *not* following the rules in "Mesh Optimization" [Hoppe et al] - Section 4.2. But for our purposes we don't care about this criteria. - -Parameters ----------- -V #V by 3 list of vertex positions -F #F by 3 list of triangle indices into V -eps epsilon for smallest allowed area treated as fraction of squared bounding box - diagonal - -Returns -------- -FF #FF by 3 list of triangle indices into V - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(collapse_small_triangles) -npe_doc(ds_collapse_small_triangles) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(eps, double) - - -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - // TODO: remove __copy - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXi ff; - igl::collapse_small_triangles(v_copy, f_copy, eps, ff); - EigenDenseInt ff_row_major = ff.template cast();; - return npe::move(ff_row_major); - -npe_end_code() - - diff --git a/src/comb_cross_field.cpp b/src/comb_cross_field.cpp deleted file mode 100644 index 82bf9228..00000000 --- a/src/comb_cross_field.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_comb_cross_field = R"igl_Qu8mg5v7( - -Parameters ----------- - V #V by 3 eigen Matrix of mesh vertex 3D positions - F #F by 3 eigen Matrix of face indices - PD1in #F by 3 eigen Matrix of the first per face cross field vector - PD2in #F by 3 eigen Matrix of the second per face cross field vector - -Returns -------- - PD1out #F by 3 eigen Matrix of the first combed cross field vector - PD2out #F by 3 eigen Matrix of the second combed cross field vector - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(comb_cross_field) -npe_doc(ds_comb_cross_field) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(pd1in, npe_matches(v)) -npe_arg(pd2in, npe_matches(v)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_rows_match(f, pd1in, "F", "PD1in"); - assert_rows_match(f, pd2in, "F", "PD2in"); - assert_cols_equals(pd1in, 3, "PD1in"); - assert_cols_equals(pd2in, 3, "PD2in"); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - EigenDenseLike pd1in_copy = pd1in; - EigenDenseLike pd2in_copy = pd2in; - - EigenDenseLike pd1out; - EigenDenseLike pd2out; - igl::comb_cross_field(v_copy, f_copy, pd1in_copy, pd2in_copy, pd1out, pd2out); - return std::make_tuple(npe::move(pd1out), npe::move(pd2out)); - -npe_end_code() - - diff --git a/src/comb_frame_field.cpp b/src/comb_frame_field.cpp deleted file mode 100644 index 1e905ae7..00000000 --- a/src/comb_frame_field.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_comb_frame_field = R"igl_Qu8mg5v7( - -Parameters ----------- -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face indices -PD1 #F by 3 eigen Matrix of the first per face cross field vector -PD2 #F by 3 eigen Matrix of the second per face cross field vector -BIS1_combed #F by 3 eigen Matrix of the first combed bisector field vector -BIS2_combed #F by 3 eigen Matrix of the second combed bisector field vector - -Returns -------- -PD1_combed #F by 3 eigen Matrix of the first combed cross field vector -PD2_combed #F by 3 eigen Matrix of the second combed cross field vector - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(comb_frame_field) -npe_doc(ds_comb_frame_field) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(pd1, npe_matches(v)) -npe_arg(pd2, npe_matches(pd1)) -npe_arg(bis1_combed, npe_matches(pd1)) -npe_arg(bis2_combed, npe_matches(pd1)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_rows_match(f, pd1, "F", "PD1"); - assert_rows_match(f, pd2, "F", "PD2"); - - assert_rows_match(f, bis1_combed, "F", "BIS1_combed"); - assert_rows_match(f, bis2_combed, "F", "BIS2_combed"); - - assert_cols_equals(pd1, 3, "PD1"); - assert_cols_equals(pd2, 3, "PD2"); - - assert_cols_equals(bis1_combed, 3, "BIS1_combed"); - assert_cols_equals(bis2_combed, 3, "BIS2_combed"); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - EigenDenseLike pd1_copy = pd1; - EigenDenseLike pd2_copy = pd2; - EigenDenseLike bis1_combed_copy = bis1_combed; - EigenDenseLike bis2_combed_copy = bis2_combed; - - EigenDenseLike pd1_combed; - EigenDenseLike pd2_combed; - - igl::comb_frame_field(v_copy, f_copy, pd1_copy, pd2_copy, bis1_combed_copy, bis2_combed_copy, pd1_combed, pd2_combed); - return std::make_tuple(npe::move(pd1_combed), npe::move(pd2_combed)); - -npe_end_code() - - diff --git a/src/comb_line_field.cpp b/src/comb_line_field.cpp deleted file mode 100644 index f96250cc..00000000 --- a/src/comb_line_field.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_comb_line_field = R"igl_Qu8mg5v7( - -Parameters ----------- -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face indices -PD1in #F by 3 eigen Matrix of the first per face cross field vector - -Returns -------- -PD1out #F by 3 eigen Matrix of the first combed cross field vector - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(comb_line_field) -npe_doc(ds_comb_line_field) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(pd1in, npe_matches(v)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_rows_match(f, pd1in, "F", "PD1in"); - assert_cols_equals(pd1in, 3, "PD1in"); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - EigenDenseLike pd1in_copy = pd1in; - - EigenDenseLike pd1out; - igl::comb_line_field(v_copy, f_copy, pd1in_copy, pd1out); - return npe::move(pd1out); - -npe_end_code() - - diff --git a/src/compute_frame_field_bisectors.cpp b/src/compute_frame_field_bisectors.cpp deleted file mode 100644 index e4f59009..00000000 --- a/src/compute_frame_field_bisectors.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_compute_frame_field_bisectors = R"igl_Qu8mg5v7( - -Compute bisectors of a frame field defined on mesh faces - -Parameters ----------- -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face (triangle) indices -B1 #F by 3 eigen Matrix of face (triangle) base vector 1 -B2 #F by 3 eigen Matrix of face (triangle) base vector 2 -PD1 #F by 3 eigen Matrix of the first per face frame field vector -PD2 #F by 3 eigen Matrix of the second per face frame field vector - -Returns -------- -BIS1 #F by 3 eigen Matrix of the first per face frame field bisector -BIS2 #F by 3 eigen Matrix of the second per face frame field bisector - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(compute_frame_field_bisectors) -npe_doc(ds_compute_frame_field_bisectors) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(b1, npe_matches(v)) -npe_arg(b2, npe_matches(v)) -npe_arg(pd1, npe_matches(v)) -npe_arg(pd2, npe_matches(v)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_rows_match(f, b1, "F", "B1"); - assert_rows_match(f, b2, "F", "B2"); - assert_cols_equals(b1, 3, "b1"); - assert_cols_equals(b2, 3, "b2"); - - assert_rows_match(f, pd1, "F", "PD1"); - assert_rows_match(f, pd2, "F", "PD2"); - assert_cols_equals(pd1, 3, "PD1"); - assert_cols_equals(pd2, 3, "PD2"); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - EigenDenseLike b1_copy = b1; - EigenDenseLike b2_copy = b2; - EigenDenseLike pd1_copy = pd1; - EigenDenseLike pd2_copy = pd2; - - EigenDenseLike bis1; - EigenDenseLike bis2; - igl::compute_frame_field_bisectors(v_copy, f_copy, b1_copy, b2_copy, pd1_copy, pd2_copy, bis1, bis2); - return std::make_tuple(npe::move(bis1), npe::move(bis2)); - -npe_end_code() - - - -const char* ds_compute_frame_field_bisectors_no_basis = R"igl_Qu8mg5v7( - -Wrapper without given basis vectors. - -Parameters ----------- - - -Returns -------- - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(compute_frame_field_bisectors_no_basis) -npe_doc(ds_compute_frame_field_bisectors_no_basis) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(pd1, npe_matches(v)) -npe_arg(pd2, npe_matches(v)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - assert_rows_match(f, pd1, "F", "PD1"); - assert_rows_match(f, pd2, "F", "PD2"); - assert_cols_equals(pd1, 3, "PD1"); - assert_cols_equals(pd2, 3, "PD2"); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - EigenDenseLike pd1_copy = pd1; - EigenDenseLike pd2_copy = pd2; - - EigenDenseLike bis1; - EigenDenseLike bis2; - - igl::compute_frame_field_bisectors(v_copy, f_copy, pd1_copy, pd2_copy, bis1, bis2); - return std::make_tuple(npe::move(bis1), npe::move(bis2)); - -npe_end_code() - - diff --git a/src/connect_boundary_to_infinity.cpp b/src/connect_boundary_to_infinity.cpp deleted file mode 100644 index 54cb9d58..00000000 --- a/src/connect_boundary_to_infinity.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include -#include - -const char* ds_connect_boundary_to_infinity = R"igl_Qu8mg5v7( - -Connect all boundary edges to a fictitious point at infinity. - -Parameters ----------- -F #F by 3 list of face indices into some V - -Returns -------- -FO #F+#O by 3 list of face indices into [V;inf inf inf], original F are - guaranteed to come first. If (V,F) was a manifold mesh, now it is - closed with a possibly non-manifold vertex at infinity (but it will be - edge-manifold). - -See also --------- - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(connect_boundary_to_infinity) -npe_doc(ds_connect_boundary_to_infinity) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - EigenDenseLike fo; - igl::connect_boundary_to_infinity(f, fo); - return npe::move(fo); - -npe_end_code() - - -const char* ds_connect_boundary_to_infinity_face = R"igl_Qu8mg5v7( - -Parameters ----------- -F #F by 3 list of face indices into some V - -Returns -------- -FO #F+#O by 3 list of face indices into VO - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(connect_boundary_to_infinity_face) -npe_doc(ds_connect_boundary_to_infinity_face) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - EigenDenseLike vo; - EigenDenseLike fo; - igl::connect_boundary_to_infinity(v, f, vo, fo); - return std::make_tuple(npe::move(vo), npe::move(fo)); - -npe_end_code() - - - - - - - -const char* ds_connect_boundary_to_infinity_index = R"igl_Qu8mg5v7( - -Parameters ----------- -inf_index index of point at infinity (usually V.rows() or F.maxCoeff()) - -Returns -------- - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(connect_boundary_to_infinity_index) -npe_doc(ds_connect_boundary_to_infinity_index) - -npe_arg(f, dense_int32, dense_int64) -npe_arg(inf_index, int) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - EigenDenseLike fo; - igl::connect_boundary_to_infinity(f, inf_index, fo); - return npe::move(fo); - -npe_end_code() - - diff --git a/src/connected_components.cpp b/src/connected_components.cpp deleted file mode 100644 index 85d15e41..00000000 --- a/src/connected_components.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -const char *ds_connected_components = R"igl_Qu8mg5v7( - -Determine the connected components of a graph described by the input - adjacency matrix (similar to MATLAB's graphconncomp). - -Parameters ----------- - -A #A by #A adjacency matrix (treated as describing an undirected graph) - -Returns -------- -Returns number of connected components -C #A list of component indices into [0,#K-1] -K #K list of sizes of each component - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(connected_components) -npe_doc(ds_connected_components) - -npe_arg(a, sparse_int32, sparse_int64) - - -npe_begin_code() - assert_nonzero_rows(a, "A"); - assert_cols_equals(a, a.rows(), "A"); - Eigen::SparseMatrix a_copy = a; - EigenDense c; - EigenDense k; - const int comps = igl::connected_components(a_copy, c, k); - return std::make_tuple(comps, npe::move(c), npe::move(k)); - -npe_end_code() - - diff --git a/src/copyleft/cgal/convex_hull.cpp b/src/copyleft/cgal/convex_hull.cpp deleted file mode 100644 index e2127735..00000000 --- a/src/copyleft/cgal/convex_hull.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include - -#include - -const char* ds_convex_hull = R"igl_Qu8mg5v7( -Given a set of points (V), compute the convex hull as a triangle mesh (F) - -Parameters ----------- -V : #V by 3 list of input points - -Returns -------- -F #F by 3 list of triangle indices into V -)igl_Qu8mg5v7"; - -npe_function(convex_hull) -npe_doc(ds_convex_hull) - -npe_arg(v, dense_float, dense_double) -npe_begin_code() - - EigenDenseInt g; - igl::copyleft::cgal::convex_hull(v, g); - return npe::move(g); - -npe_end_code() diff --git a/src/copyleft/cgal/intersect_other.cpp b/src/copyleft/cgal/intersect_other.cpp deleted file mode 100644 index 034e56bb..00000000 --- a/src/copyleft/cgal/intersect_other.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - -const char* ds_intersect_other = R"igl_Qu8mg5v7( -INTERSECT_OTHER Given a triangle mesh (VA,FA) and another mesh (VB,FB) find all -pairs of intersecting faces. Note that self-intersections are ignored. - - -Parameters ----------- -VA : #VA by 3 list of vertex positions of first mesh -FA : #FA by 3 list of triangle indices into VA -VB : #VB by 3 list of vertex positions of second mesh -FB : #FB by 3 list of triangle indices into VB -detect_only : avoid constructing intersections results when possible {false} -first_only : return after detecting the first intersection (if - first_only==true, then detect_only should also be true) {false} -stitch_all : whether to stitch all resulting constructed elements into a - (non-manifold) mesh {false} -slow_and_more_precise_rounding : whether to use slow and more precise - rounding (see assign_scalar) {false} - - -Returns -------- -IF : #intersecting face pairs by 2 list of intersecting face pairs, indexing F -VVAB : #VVAB by 3 list of vertex positions -FFAB : #FFAB by 3 list of triangle indices into VVAB -JAB : #FFAB list of indices into [FA;FB] denoting birth triangle -IMAB : #VVAB list of indices stitching duplicates (resulting from - mesh intersections) together - -See also --------- -mesh_boolean -)igl_Qu8mg5v7"; - -npe_function(intersect_other) -npe_doc( ds_intersect_other) - -npe_arg(VA, dense_float, dense_double) -npe_arg(FA, dense_int32, dense_int64) -npe_arg(VB, npe_matches(VA)) -npe_arg(FB, npe_matches(FA)) -npe_default_arg(detect_only, bool, false) -npe_default_arg(first_only, bool, false) -npe_default_arg(stitch_all, bool, false) -// Awaiting bump in libigl -//npe_default_arg(slow_and_more_precise_rounding, bool, false) - - -npe_begin_code() - Eigen::MatrixXd VAcpy = VA.template cast(); - Eigen::MatrixXi FAcpy = FA.template cast(); - Eigen::MatrixXd VBcpy = VB.template cast(); - Eigen::MatrixXi FBcpy = FB.template cast(); - - Eigen::MatrixXd VVABcpy; - Eigen::MatrixXi FFABcpy; - Eigen::VectorXi JABcpy; - Eigen::VectorXi IMABcpy; - Eigen::MatrixXi IFcpy; - igl::copyleft::cgal::RemeshSelfIntersectionsParam params; - params.detect_only = detect_only; - params.first_only = first_only; - params.stitch_all = stitch_all; - //params.slow_and_more_precise_rounding = slow_and_more_precise_rounding; - igl::copyleft::cgal::intersect_other( - VAcpy,FAcpy,VBcpy,FBcpy,params,IFcpy,VVABcpy,FFABcpy,JABcpy,IMABcpy); - - EigenDenseLike IF = IFcpy.cast(); - EigenDenseLike VVAB = VVABcpy.cast(); - EigenDenseLike FFAB = FFABcpy.cast(); - EigenDenseLike JAB = JABcpy.cast(); - EigenDenseLike IMAB = IMABcpy.cast(); - return std::make_tuple( - npe::move(IF), - npe::move(VVAB), - npe::move(FFAB), - npe::move( JAB), - npe::move(IMAB)); -npe_end_code() - - diff --git a/src/copyleft/cgal/mesh_boolean.cpp b/src/copyleft/cgal/mesh_boolean.cpp deleted file mode 100644 index 90cd3c04..00000000 --- a/src/copyleft/cgal/mesh_boolean.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - -const char* ds_mesh_boolean = R"igl_Qu8mg5v7( -MESH_BOOLEAN Compute boolean csg operations on "solid", consistently oriented -meshes. - - -Parameters ----------- -VA : #VA by 3 list of vertex positions of first mesh -FA : #FA by 3 list of triangle indices into VA -VB : #VB by 3 list of vertex positions of second mesh -FB : #FB by 3 list of triangle indices into VB - -Returns -------- -VC : #VC by 3 list of vertex positions of boolean result mesh -FC : #FC by 3 list of triangle indices into VC -J : #FC list of indices into [FA;FA.rows()+FB] revealing "birth" facet - -See also --------- -mesh_boolean_cork, intersect_other, remesh_self_intersections -)igl_Qu8mg5v7"; - -npe_function(mesh_boolean) -npe_doc(ds_mesh_boolean) - -npe_arg(va, dense_float, dense_double) -npe_arg(fa, dense_int32, dense_int64) -npe_arg(vb, npe_matches(va)) -npe_arg(fb, npe_matches(fa)) -npe_arg(type, std::string) - - -npe_begin_code() - Eigen::MatrixXd va_copy = va.template cast(); - Eigen::MatrixXd vb_copy = vb.template cast(); - Eigen::MatrixXi fa_copy = fa.template cast(); - Eigen::MatrixXi fb_copy = fb.template cast(); - - Eigen::MatrixXd vc_copy; - Eigen::MatrixXi fc_copy; - Eigen::VectorXi j_copy; - igl::copyleft::cgal::mesh_boolean( - va_copy, - fa_copy, - vb_copy, - fb_copy, - type, - vc_copy, - fc_copy, - j_copy); - - EigenDenseLike vc = vc_copy.cast(); - EigenDenseLike fc = fc_copy.cast(); - EigenDenseLike j = j_copy.cast(); - return std::make_tuple(npe::move(vc), npe::move(fc), npe::move(j)); -npe_end_code() - diff --git a/src/copyleft/cgal/remesh_self_intersections.cpp b/src/copyleft/cgal/remesh_self_intersections.cpp deleted file mode 100644 index 571afd6c..00000000 --- a/src/copyleft/cgal/remesh_self_intersections.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - -const char* ds_remesh_self_intersections = R"igl_Qu8mg5v7( -REMESH_SELF_INTERSECTIONS Given a triangle mesh (V,F) compute a new mesh (VV,FF) -which is the same as (V,F) except that any self-intersecting triangles in (V,F) -have been subdivided (new vertices and face created) so that the -self-intersection contour lies exactly on edges in (VV,FF). New vertices will -appear in original faces or on original edges. New vertices on edges are -"merged" only across original faces sharing that edge. This means that if the -input triangle mesh is a closed manifold the output will be too. - - -Parameters ----------- -V : #V by 3 list of vertex positions of mesh -F : #F by 3 list of triangle indices into rows of V -detect_only : avoid constructing intersections results when possible {false} -first_only : return after detecting the first intersection (if - first_only==true, then detect_only should also be true) {false} -stitch_all : whether to stitch all resulting constructed elements into a - (non-manifold) mesh {false} -slow_and_more_precise_rounding : whether to use slow and more precise - rounding (see assign_scalar) {false} - - -Returns -------- -VV : #VV by 3 list of vertex positions -FF : #FF by 3 list of triangle indices into VV -IF : #intersecting face pairs by 2 list of intersecting face pairs, indexing F -J : #FF list of indices into F denoting birth triangle -IM : #VV list of indices into VV of unique vertices. - -See also --------- -mesh_boolean -)igl_Qu8mg5v7"; - -npe_function(remesh_self_intersections) -npe_doc(ds_remesh_self_intersections) - -npe_arg(V, dense_float, dense_double) -npe_arg(F, dense_int32, dense_int64) -npe_default_arg(detect_only, bool, false) -npe_default_arg(first_only, bool, false) -npe_default_arg(stitch_all, bool, false) -// Awaiting bump in libigl -//npe_default_arg(slow_and_more_precise_rounding, bool, false) - - -npe_begin_code() - Eigen::MatrixXd Vcpy = V.template cast(); - Eigen::MatrixXi Fcpy = F.template cast(); - - Eigen::MatrixXd VVcpy; - Eigen::MatrixXi FFcpy; - Eigen::VectorXi Jcpy; - Eigen::VectorXi IMcpy; - Eigen::MatrixXi IFcpy; - igl::copyleft::cgal::RemeshSelfIntersectionsParam params; - params.detect_only = detect_only; - params.first_only = first_only; - params.stitch_all = stitch_all; - //params.slow_and_more_precise_rounding = slow_and_more_precise_rounding; - igl::copyleft::cgal::remesh_self_intersections( - Vcpy,Fcpy,params,VVcpy,FFcpy,IFcpy,Jcpy,IMcpy); - - EigenDenseLike VV = VVcpy.cast(); - EigenDenseLike FF = FFcpy.cast(); - EigenDenseLike IF = IFcpy.cast(); - EigenDenseLike J = Jcpy.cast(); - EigenDenseLike IM = IMcpy.cast(); - return std::make_tuple(npe::move(VV), npe::move(FF), npe::move(IF), npe::move(J), npe::move(IM)); -npe_end_code() - diff --git a/src/copyleft/tetgen/tetrahedralize.cpp b/src/copyleft/tetgen/tetrahedralize.cpp deleted file mode 100644 index ad0c139f..00000000 --- a/src/copyleft/tetgen/tetrahedralize.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include - - -#include -#include -#include - -#include - -const char* ds_tetrahedralize = R"igl_Qu8mg5v7( -)igl_Qu8mg5v7"; - -npe_function(tetrahedralize) -npe_doc(ds_tetrahedralize) - -npe_arg(V, dense_float, dense_double) -npe_default_arg(F, dense_int32, dense_int64, Eigen::MatrixXi(0,3)) -npe_default_arg(H, npe_matches(V) ,pybind11::array()) -npe_default_arg(switches, std::string, "Q") -npe_default_arg(return_adjacency_info, bool, false) -npe_default_arg(VM, npe_matches(F), pybind11::array()) -npe_default_arg(FM, npe_matches(F), pybind11::array()) -npe_default_arg( R, npe_matches(V), pybind11::array()) -npe_begin_code() -const bool has_markers = VM.size() > 0 || FM.size() > 0; -const bool has_regions = R.size() > 0; -EigenDenseLike TV; -EigenDenseLike TT, TF, TN, FT; -Eigen::Matrix TR,PT,TM; - -int num_regions; -igl::copyleft::tetgen::tetrahedralize( - V,F,H, - Eigen::Map>(VM.data(),VM.size()), - Eigen::Map>(FM.data(),FM.size()), - R,switches,TV,TT,TF,TM,TR,TN,PT,FT,num_regions); - -auto ret = std::list({npe::move(TV), npe::move(TT), npe::move(TF)}); - -if(has_markers) -{ - ret.push_back(npe::move(TM)); -} -if(has_regions) -{ - ret.push_back(npe::move(TR)); -} -if(return_adjacency_info) -{ - ret.push_back(npe::move(TN)); - ret.push_back(npe::move(PT)); - ret.push_back(npe::move(FT)); -} -if(has_regions) -{ - ret.push_back(pybind11::cast(num_regions)); -} - -return ret; - -npe_end_code() - diff --git a/src/cotmatrix.cpp b/src/cotmatrix.cpp index 62e44cc3..35d4f63b 100644 --- a/src/cotmatrix.cpp +++ b/src/cotmatrix.cpp @@ -1,56 +1,99 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include +#include "default_types.h" #include +#include +#include +#include +#include -const char* ds_cotmatrix = R"igl_Qu8mg5v7( -Constructs the cotangent stiffness matrix (discrete laplacian) for a given mesh -(v, f). +namespace nb = nanobind; +using namespace nb::literals; -Parameters ----------- -v : #v by dim list of mesh vertex positions -f : #f by simplex_size list of mesh faces (must be triangles) +namespace pyigl +{ + nb::object cotmatrix( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &I, + const nb::DRef &C, + bool return_M, + bool return_P) + { + const bool poly = I.size() != 0; + Eigen::SparseMatrixN L,M,P; + if(poly) + { + igl::cotmatrix(V,I,C,L,M,P); + }else + { + igl::cotmatrix(V,F,L); + } + if(poly) + { + if(return_M && return_P) + { + return nb::make_tuple(L,M,P); + }else if(return_M) + { + return nb::make_tuple(L,M); + }else if(return_P) + { + return nb::make_tuple(L,P); + } + }else + { + if(return_M || return_P) + { + throw std::runtime_error("cotmatrix: M and P only available for polygonal meshes"); + } + } + return nb::cast(L); + } +} -Returns -------- -l : #v by #v cotangent matrix, each row i corresponding to v(i, :) +// Bind the wrapper to the Python module +void bind_cotmatrix(nb::module_ &m) +{ + m.def( + "cotmatrix", + &pyigl::cotmatrix, + "V"_a, + "F"_a=Eigen::MatrixXI(), + "I"_a=Eigen::VectorXI(), + "C"_a=Eigen::VectorXI(), + "return_M"_a=false, + "return_P"_a=false, +R"(Constructs the cotangent stiffness matrix (discrete laplacian) for a given +mesh (V,F). -See also --------- -adjacency_matrix + @tparam DerivedV derived type of eigen matrix for V (e.g. derived from + MatrixXd) + @tparam DerivedF derived type of eigen matrix for F (e.g. derived from + MatrixXi) + @tparam Scalar scalar type for eigen sparse matrix (e.g. double) + @param[in] V #V by dim list of mesh vertex positions + @param[in] F #F by simplex_size list of mesh elements (triangles or tetrahedra) + @param[out] L #V by #V cotangent matrix, each row i corresponding to V(i,:) -Notes ------ -This Laplacian uses the convention that diagonal entries are +\see adjacency_matrix + +\note This Laplacian uses the convention that diagonal entries are **minus** the sum of off-diagonal entries. The diagonal entries are therefore in general negative and the matrix is **negative** semi-definite (immediately, -L is **positive** semi-definite) -Examples --------- -# Mesh in (v, f) ->>> l = cotmatrix(v, f) -)igl_Qu8mg5v7"; - -npe_function(cotmatrix) -npe_doc(ds_cotmatrix) -npe_arg(v, dense_double, dense_float) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() +Cotangent Laplacian (and mass matrix) for polygon meshes according to +"Polygon Laplacian Made Simple" [Bunge et al.\ 2020] - assert_valid_tet_or_tri_mesh_23d(v, f); - EigenSparseLike l; - igl::cotmatrix(v, f, l); - return npe::move(l); +@param[in] V #V by 3 list of mesh vertex positions +@param[in] I #I vectorized list of polygon corner indices into rows of some matrix V +@param[in] C #polygons+1 list of cumulative polygon sizes so that C(i+1)-C(i) = size of + the ith polygon, and so I(C(i)) through I(C(i+1)-1) are the indices of + the ith polygon +@param[out] L #V by #V polygon Laplacian made simple matrix +@param[out] M #V by #V mass matrix +@param[out] P #V+#polygons by #V prolongation operator)" + ); +} -npe_end_code() diff --git a/src/cotmatrix_entries.cpp b/src/cotmatrix_entries.cpp deleted file mode 100644 index 3028d396..00000000 --- a/src/cotmatrix_entries.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_cotmatrix_entries = R"igl_Qu8mg5v7( - -COTMATRIX_ENTRIES compute the cotangents of each angle in mesh (V,F) - -Parameters ----------- -V #V by dim list of rest domain positions -F #F by {3|4} list of {triangle|tetrahedra} indices into V - - -Returns -------- - C #F by 3 list of 1/2*cotangents corresponding angles - for triangles, columns correspond to edges [1,2],[2,0],[0,1] -OR - C #F by 6 list of 1/6*cotangents of dihedral angles*edge lengths - for tets, columns along edges [1,2],[2,0],[0,1],[3,0],[3,1],[3,2] - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(cotmatrix_entries) -npe_doc(ds_cotmatrix_entries) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tet_or_tri_mesh_23d(v, f); - EigenDenseLike c; - igl::cotmatrix_entries(v, f, c); - return npe::move(c); - -npe_end_code() - - diff --git a/src/cotmatrix_intrinsic.cpp b/src/cotmatrix_intrinsic.cpp deleted file mode 100644 index 1210ffed..00000000 --- a/src/cotmatrix_intrinsic.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_cotmatrix_intrinsic = R"igl_Qu8mg5v7( - -Constructs the cotangent stiffness matrix (discrete laplacian) for a given - mesh with faces F and edge lengths l. - -Parameters ----------- - -l #F by 3 list of (half-)edge lengths -F #F by 3 list of face indices into some (not necessarily - determined/embedable) list of vertex positions V. It is assumed #V == - F.maxCoeff()+1 - -Returns -------- - -L #V by #V sparse Laplacian matrix - -See also --------- - -cotmatrix, intrinsic_delaunay_cotmatrix - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(cotmatrix_intrinsic) -npe_doc(ds_cotmatrix_intrinsic) - -npe_arg(l, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_rows_match(l, f, "l", "f"); - assert_cols_equals(l, 3, "l"); - assert_valid_tri_mesh_faces(f); - - Eigen::SparseMatrix mat; - igl::cotmatrix_intrinsic(l, f, mat); - return npe::move(mat); - -npe_end_code() - - diff --git a/src/cross_field_missmatch.cpp b/src/cross_field_missmatch.cpp deleted file mode 100644 index fe978ad9..00000000 --- a/src/cross_field_missmatch.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_cross_field_mismatch = R"igl_Qu8mg5v7( - -Parameters ----------- -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face indices -PD1 #F by 3 eigen Matrix of the first per face cross field vector -PD2 #F by 3 eigen Matrix of the second per face cross field vector -isCombed boolean, specifying whether the field is combed (i.e. matching has been precomputed. - If not, the field is combed first. - - -Returns -------- -Handle_MMatch #F by 3 eigen Matrix containing the integer mismatch of the cross field - across all face edges - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(cross_field_mismatch) -npe_doc(ds_cross_field_mismatch) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(pd1, npe_matches(v)) -npe_arg(pd2, npe_matches(v)) -npe_arg(is_combed, bool) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - assert_rows_match(f, pd1, "F", "PD1"); - assert_rows_match(f, pd2, "F", "PD2"); - assert_cols_equals(pd1, 3, "PD1"); - assert_cols_equals(pd2, 3, "PD2"); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - EigenDenseLike pd1_copy = pd1; - EigenDenseLike pd2_copy = pd2; - - EigenDenseLike mismatch; - - igl::cross_field_mismatch(v_copy, f_copy, pd1_copy, pd2_copy, is_combed, mismatch); - return npe::move(mismatch); - -npe_end_code() - - diff --git a/src/crouzeix_raviart_cotmatrix.cpp b/src/crouzeix_raviart_cotmatrix.cpp deleted file mode 100644 index 5e177619..00000000 --- a/src/crouzeix_raviart_cotmatrix.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char* ds_crouzeix_raviart_cotmatrix = R"igl_Qu8mg5v7( - -CROUZEIX_RAVIART_COTMATRIX Compute the Crouzeix-Raviart cotangent - stiffness matrix. - -Parameters ----------- -V #V by dim list of vertex positions -F #F by 3/4 list of triangle/tetrahedron indices - -Returns -------- -L #E by #E edge/face-based diagonal cotangent matrix -E #E by 2/3 list of edges/faces -EMAP #F*3/4 list of indices mapping allE to E - -See also --------- -See also: crouzeix_raviart_massmatrix - -Notes ------ -None - -Examples --------- -See for example "Discrete Quadratic Curvature Energies" [Wardetzky, Bergou, - Harmon, Zorin, Grinspun 2007] - -)igl_Qu8mg5v7"; - -npe_function(crouzeix_raviart_cotmatrix) -npe_doc(ds_crouzeix_raviart_cotmatrix) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tet_or_tri_mesh_23d(v, f); - EigenSparseLike l; - EigenDenseLike e; - Eigen::Matrix emap; - igl::crouzeix_raviart_cotmatrix(v, f, l, e, emap); - return std::make_tuple(npe::move(l), npe::move(e), npe::move(emap)); - -npe_end_code() - - - -const char* ds_crouzeix_raviart_cotmatrix_known_e = R"igl_Qu8mg5v7( - -wrapper if E and EMAP are already computed (better match!) - -Parameters ----------- - - -Returns -------- - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(crouzeix_raviart_cotmatrix_known_e) -npe_doc(ds_crouzeix_raviart_cotmatrix_known_e) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(e, npe_matches(f)) -npe_arg(emap, npe_matches(f)) - - -npe_begin_code() - - assert_valid_tet_or_tri_mesh(v, f); - //assert_rows_equals(emap, 3/4*f.rows(), "emap"); - //assert_cols_equals(e, 2/3, "emap"); - assert_cols_equals(emap, 1, "emap"); - EigenSparseLike l; - igl::crouzeix_raviart_cotmatrix(v, f, e, emap, l); - return npe::move(l); - -npe_end_code() - - diff --git a/src/crouzeix_raviart_massmatrix.cpp b/src/crouzeix_raviart_massmatrix.cpp deleted file mode 100644 index 23959660..00000000 --- a/src/crouzeix_raviart_massmatrix.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// static assertion fail: YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY - -#include -#include -#include - - - - - - -#include - -const char* ds_crouzeix_raviart_massmatrix = R"igl_Qu8mg5v7( - -CROUZEIX_RAVIART_MASSMATRIX Compute the Crouzeix-Raviart mass matrix where - M(e,e) is just the sum of the areas of the triangles on either side of an - edge e. - -Parameters ----------- -V #V by dim list of vertex positions -F #F by 3/4 list of triangle/tetrahedron indices - -Returns -------- -M #E by #E edge/face-based diagonal mass matrix -E #E by 2/3 list of edges/faces -EMAP #F*3/4 list of indices mapping allE to E - -See also --------- -crouzeix_raviart_cotmatrix - -Notes ------ -See for example "Discrete Quadratic Curvature Energies" [Wardetzky, Bergou, - Harmon, Zorin, Grinspun 2007] - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(crouzeix_raviart_massmatrix) -npe_doc(ds_crouzeix_raviart_massmatrix) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tet_or_tri_mesh_23d(v, f); - EigenSparseLike m; - EigenDenseLike e; - Eigen::Matrix emap; - igl::crouzeix_raviart_massmatrix(v, f, m, e, emap); - return std::make_tuple(npe::move(m), npe::move(e), npe::move(emap)); - -npe_end_code() - - - -const char* ds_crouzeix_raviart_massmatrix_known_e = R"igl_Qu8mg5v7( - -wrapper if E and EMAP are already computed (better match!) - -Parameters ----------- - - -Returns -------- - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(crouzeix_raviart_massmatrix_known_e) -npe_doc(ds_crouzeix_raviart_massmatrix_known_e) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(e, npe_matches(f)) -npe_arg(emap, npe_matches(f)) - - -npe_begin_code() - - assert_valid_tet_or_tri_mesh(v, f); - //assert_rows_equals(emap, 3/4*f.rows(), "emap"); - //assert_cols_equals(e, 2/3, "emap"); - assert_cols_equals(emap, 1, "emap"); - EigenSparseLike m; - igl::crouzeix_raviart_massmatrix(v, f, e, emap, m); - return npe::move(m); - -npe_end_code() - - diff --git a/src/cut_mesh.cpp b/src/cut_mesh.cpp deleted file mode 100644 index a5f50bbb..00000000 --- a/src/cut_mesh.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Francis Williams -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_cut_mesh = R"igl_Qu8mg5v7( -Compute the barycenter of every simplex - -Parameters ----------- -v : #v x dim matrix of vertex coordinates -f : #f x simplex_size matrix of indices of simplex corners into V -cuts : #F by 3 list of boolean flags, indicating the edges that need to - be cut (has 1 at the face edges that are to be cut, 0 otherwise) - -Returns -------- -A pair (vcut, fcut) where: - * vcut is a #v by 3 list of the vertex positions - of the cut mesh. This matrix will be similar to the original vertices except - some rows will be duplicated. - * fcut is a #f by 3 list of the faces of the cut mesh (must be triangles). This - matrix will be similar to the original face matrix except some indices - will be redirected to point to the newly duplicated vertices. - -See also --------- - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(cut_mesh) -npe_doc(ds_cut_mesh) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(cuts, npe_matches(f)) -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_shapes_match(f, cuts, "f", "cuts"); - - // FIXME: Copying is bad m'kay but libIGL's templates are broken - EigenDenseLike Vcopy = v; - EigenDenseLike Fcopy = f; - EigenDenseLike cutsCopy = cuts; - - EigenDenseLike Vcut; - EigenDenseLike Fcut; - - igl::cut_mesh(Vcopy, Fcopy, cutsCopy, Vcut, Fcut); - - return std::make_tuple(npe::move(Vcut), npe::move(Fcut)); - -npe_end_code() diff --git a/src/cut_mesh_from_singularities.cpp b/src/cut_mesh_from_singularities.cpp deleted file mode 100644 index 87d96a89..00000000 --- a/src/cut_mesh_from_singularities.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Francis Williams -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_cut_mesh_from_singularities = R"igl_Qu8mg5v7( -Given a mesh (v,f) and the integer mismatch of a cross field per edge -(mismatch), finds and returns the cut_graph connecting the singularities -(seams) - -Parameters ----------- -v : #v by 3 array of triangle vertices (each row is a vertex) -f : #f by 3 array of triangle indices into v -mismatch : #f by 3 array of per-corner integer mismatches - -Returns -------- -seams : #f by 3 array of per corner booleans that denotes if an edge is a - seam or not - -See also --------- -cut_mesh - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(cut_mesh_from_singularities) -npe_doc(ds_cut_mesh_from_singularities) -npe_arg(v, dense_double, dense_float) -npe_arg(f, dense_int32, dense_int64) -npe_arg(mismatch, npe_matches(f)) -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_shapes_match(f, mismatch, "f", "mismatch"); - - // FIXME: LibIGL templates are broken so we need to do copies :'( - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - - Eigen::Matrix seams; - - igl::cut_mesh_from_singularities(v_copy, f_copy, mismatch, seams); - - return npe::move(seams); -npe_end_code() diff --git a/src/cut_to_disk.cpp b/src/cut_to_disk.cpp deleted file mode 100644 index 35b6b412..00000000 --- a/src/cut_to_disk.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -const char *ds_cut_to_disk = R"igl_Qu8mg5v7( - -Given a triangle mesh, computes a set of edge cuts sufficient to carve the -mesh into a topological disk, without disconnecting any connected components. -Nothing else about the cuts (including number, total length, or smoothness) -is guaranteed to be optimal. - -Simply-connected components without boundary (topological spheres) are left -untouched (delete any edge if you really want a disk). -All other connected components are cut into disks. Meshes with boundary are -supported; boundary edges will be included as cuts. - -The cut mesh itself can be materialized using cut_mesh(). - -Implements the triangle-deletion approach described by Gu et al's -"Geometry Images." - -Parameters ----------- - -F #F by 3 list of the faces (must be triangles) - -Returns -------- - -cuts List of cuts. Each cut is a sequence of vertex indices (where - pairs of consecutive vertices share a face), is simple, and is either - a closed loop (in which the first and last indices are identical) or - an open curve. Cuts are edge-disjoint. - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(cut_to_disk) -npe_doc(ds_cut_to_disk) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_tri_mesh_faces(f); - - std::vector > cuts; - igl::cut_to_disk(f, cuts); - return cuts; - -npe_end_code() diff --git a/src/cylinder.cpp b/src/cylinder.cpp deleted file mode 100644 index e8d551e3..00000000 --- a/src/cylinder.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include -#include - -const char* ds_cylinder = R"igl_Qu8mg5v7( - -Construct a triangle mesh of a cylinder (without caps) - -Parameters ----------- -axis_devisions number of vertices _around the cylinder_ -height_devisions number of vertices _up the cylinder_ - -Returns -------- -V #V by 3 list of mesh vertex positions -F #F by 3 list of triangle indices into V - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(cylinder) -npe_doc(ds_cylinder) - -npe_arg(axis_devisions, int) -npe_arg(height_devisions, int) - - -npe_begin_code() - - Eigen::MatrixXd v; - Eigen::MatrixXi f; - igl::cylinder(axis_devisions, height_devisions, v, f); - EigenDenseFloat v_row_major = v; - EigenDenseInt f_row_major = f.template cast(); - return std::make_tuple(npe::move(v_row_major), npe::move(f_row_major)); - -npe_end_code() - - diff --git a/src/decimate.cpp b/src/decimate.cpp deleted file mode 100644 index c957c045..00000000 --- a/src/decimate.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __miss 4 functions - -#include -#include -#include - - - - - - -#include - -const char* ds_decimate = R"igl_Qu8mg5v7( - -Assumes (V,F) is a manifold mesh (possibly with boundary) Collapses edges - until desired number of faces is achieved. This uses default edge cost and - merged vertex placement functions {edge length, edge midpoint}. - -Parameters ----------- -V #V by dim list of vertex positions -F #F by 3 list of face indices into V. -max_m desired number of output faces -block_intersections whether to block intersections - -Returns -------- -U #U by dim list of output vertex posistions (can be same ref as V) -G #G by 3 list of output face indices into U (can be same ref as G) -J #G list of indices into F of birth face -I #U list of indices into V of birth vertices -Returns true if m was reached (otherwise #G > m) - -See also --------- - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(decimate) -npe_doc(ds_decimate) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(max_m, size_t) -npe_arg(block_intersections, bool) - - -npe_begin_code() - - assert_valid_23d_tri_mesh(v, f); - // TODO: remove __copy - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXd u; - Eigen::MatrixXi g; - Eigen::VectorXi j; - Eigen::VectorXi i; - bool reach = igl::decimate(v_copy, f_copy, max_m, block_intersections, u, g, j, i); - EigenDenseFloat u_row_major = u; - EigenDenseInt g_row_major = g.template cast(); - // FIXME: vector not allowing row major, but they should be essentially the same so i feel we can leave it as col major - Eigen::Matrix j_row_major = j.template cast(); - Eigen::Matrix i_row_major = i.template cast(); - return std::make_tuple(reach, npe::move(u_row_major), npe::move(g_row_major), npe::move(j_row_major), npe::move(i_row_major)); - -npe_end_code() - - diff --git a/src/deform_skeleton.cpp b/src/deform_skeleton.cpp deleted file mode 100644 index e3b82f02..00000000 --- a/src/deform_skeleton.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_deform_skeleton = R"igl_Qu8mg5v7( -Deform a skeleton. -Parameters ----------- -C #C by 3 list of joint positions -BE #BE by 2 list of bone edge indices -T #BE*4 by 3 list of stacked transformation matrix - -Returns -------- -CT #BE*2 by 3 list of deformed joint positions -BET #BE by 2 list of bone edge indices (maintains order) -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(deform_skeleton) -npe_doc(ds_deform_skeleton) - -npe_arg(c, dense_float, dense_double) -npe_arg(be, dense_int32, dense_int64) -npe_arg(t, dense_float, dense_double) - -npe_begin_code() - assert_nonzero_rows(c, "C"); - assert_cols_equals(c, 3, "C"); - assert_rows_equals(t, be.rows()*4, "T"); - assert_cols_equals(be, 2, "BE"); - assert_cols_equals(t, 3, "T"); - - Eigen::MatrixXd c_copy = c.template cast(); - Eigen::MatrixXi be_copy = be.template cast(); - Eigen::MatrixXd t_copy = t.template cast(); - - Eigen::MatrixXd ct; - Eigen::MatrixXi bet; - igl::deform_skeleton(c_copy, be_copy, t_copy, ct, bet); - - EigenDenseLike ct_row_major = ct.template cast(); - EigenDenseLike bet_row_major = bet.template cast(); - return std::make_tuple(npe::move(ct_row_major), npe::move(bet_row_major)); - -npe_end_code() diff --git a/src/dihedral_angles.cpp b/src/dihedral_angles.cpp deleted file mode 100644 index 0b5d1643..00000000 --- a/src/dihedral_angles.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_dihedral_angles = R"igl_Qu8mg5v7( -Compute dihedral angles for all tets of a given tet mesh (v, t). - -Parameters ----------- -v : #v by 3 list of vertex positions -t : #v by 4 list of tet indices - -Returns -------- -theta : #t by 6 list of dihedral angles (in radians) -cos_theta : #t by 6 list of cosine of dihedral angles (in radians) - -See also --------- - - -Notes ------ -None - -Examples --------- -# TetMesh in (v, t) ->>> theta, cos_theta = dihedral_angles(v, t) -)igl_Qu8mg5v7"; - -npe_function(dihedral_angles) -npe_doc(ds_dihedral_angles) -npe_arg(v, dense_float, dense_double) -npe_arg(t, dense_int32, dense_int64) - -npe_begin_code() - assert_valid_tet_mesh(v, t); - EigenDenseLike theta; - EigenDenseLike cos_theta; - igl::dihedral_angles(v, t, theta, cos_theta); - return std::make_tuple(npe::move(theta), npe::move(cos_theta)); - -npe_end_code() - - -const char* ds_dihedral_angles_intrinsic = R"igl_Qu8mg5v7( -See dihedral_angles for the documentation. -)igl_Qu8mg5v7"; - -npe_function(dihedral_angles_intrinsic) -npe_doc(ds_dihedral_angles_intrinsic) -npe_arg(l, dense_float, dense_double) -npe_arg(a, npe_matches(l)) - -npe_begin_code() - - EigenDenseLike theta; - EigenDenseLike cos_theta; - igl::dihedral_angles_intrinsic(l, a, theta, cos_theta); - return std::make_tuple(npe::move(theta), npe::move(cos_theta)); - -npe_end_code() - - diff --git a/src/direct_delta_mush.cpp b/src/direct_delta_mush.cpp deleted file mode 100644 index 82580c24..00000000 --- a/src/direct_delta_mush.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Kishore Venkateshan -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_direct_delta_mush = R"igl_Qu8mg5v7( -Perform Direct Delta Mush Skinning. -Computes Direct Delta Mesh Skinning (Variant 0) from -"Direct Delta Mush Skinning and Variants" - -Parameters ----------- -v #V by 3 list of rest pose vertex positions -t #E*4 by 3 list of bone pose transformations -omega #V by #E*10 list of precomputated matrix values - -Returns -------- -u #V by 3 list of output vertex positions - -See also --------- - - -Notes ------ -None - -Examples --------- -)igl_Qu8mg5v7"; - -npe_function(direct_delta_mush) -npe_doc(ds_direct_delta_mush) - -npe_arg(v, dense_double) -npe_arg(t, dense_double) -npe_arg(omega, dense_double) - -npe_begin_code() - assert_cols_equals(v, 3, "v"); - assert_valid_bone_transforms(t, "t"); - assert_rows_equals(t, (omega.cols() * 4) / 10, "t"); - - std::vector> - t_affine(t.rows() / 4); - - for(int bone = 0; bone < t_affine.size(); ++bone) - { - t_affine[bone] = Eigen::Affine3d::Identity(); - t_affine[bone].matrix().block(0, 0, 3, 4) = t.block(bone * 4, 0, 4, 3).transpose(); - } - - EigenDenseLike u; - igl::direct_delta_mush(v, t_affine, omega, u); - return npe::move(u); - -npe_end_code() - -const char* ds_direct_delta_mush_precomp = R"igl_Qu8mg5v7( -Do the Omega precomputation necessary for Direct Delta Mush Skinning. - -Parameters ----------- -v #V by 3 list of rest pose vertex positions -f #F by 3 list of triangle indices into rows of V -w #V by #Edges list of weights -p number of smoothing iterations -lambda rotation smoothing step size -kappa translation smoothness step size -alpha translation smoothness blending weight - -Returns -------- -omega : #V by #E*10 list of precomputated matrix values - -See also --------- - - -Notes ------ -None - -Examples --------- -)igl_Qu8mg5v7"; - -npe_function(direct_delta_mush_precomputation) -npe_doc(ds_direct_delta_mush_precomp) - -npe_arg(v, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(w, npe_matches(v)) -npe_arg(p, int) -npe_arg(lambda, double) -npe_arg(kappa, double) -npe_arg(alpha, double) - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - Eigen::MatrixXd w_copy = w.template cast(); - - EigenDenseLike omega; - igl::direct_delta_mush_precomputation(v, f, w_copy, p, lambda, kappa, alpha, omega); - return npe::move(omega); - -npe_end_code() diff --git a/src/directed_edge_orientations.cpp b/src/directed_edge_orientations.cpp deleted file mode 100644 index 1a71a302..00000000 --- a/src/directed_edge_orientations.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - -#include - -const char *ds_directed_edge_orientations = R"igl_Qu8mg5v7( - Determine rotations that take each edge from the x-axis to its given rest - orientation. -Parameters ----------- -C #C by 3 list of edge vertex positions -E #E by 2 list of directed edges - -Returns -------- -Q #E list of quaternions - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(directed_edge_orientations) -npe_doc(ds_directed_edge_orientations) - -npe_arg(c, dense_float, dense_double) -npe_arg(e, dense_int32, dense_int64) - - -npe_begin_code() - assert_cols_equals(c, 3, "C"); - assert_cols_equals(e, 2, "E"); - Eigen::MatrixXd c_copy = c.template cast(); - std::vector > q; - igl::directed_edge_orientations(c_copy, e, q); - EigenDenseLike q_copy(q.size(), 4); - for(size_t i = 0; i < q.size(); ++i){ - q_copy(i, 0) = q[i].x(); - q_copy(i, 1) = q[i].y(); - q_copy(i, 2) = q[i].z(); - q_copy(i, 3) = q[i].w(); - } - return npe::move(q_copy); - - npe_end_code() diff --git a/src/directed_edge_parents.cpp b/src/directed_edge_parents.cpp deleted file mode 100644 index 23f827fb..00000000 --- a/src/directed_edge_parents.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_directed_edge_parents = R"igl_Qu8mg5v7( -Recover "parents" (preceding edges) in a tree given just directed edges. - -Parameters ----------- -e : #e by 2 list of directed edges - -Returns -------- -p : #e list of parent indices into e. (-1) means root - -See also --------- - - -Notes ------ -None - -Examples --------- -e.np.random.randint(0, 10, size=(10, 2)) -p = directed_edge_parents(e) -)igl_Qu8mg5v7"; - -npe_function(directed_edge_parents) -npe_doc(ds_directed_edge_parents) -npe_arg(e, dense_int32, dense_int64) -npe_begin_code() - - assert_nonzero_rows(e, "e"); - assert_cols_equals(e, 2, "e"); - EigenDenseLike p; - igl::directed_edge_parents(e, p); - return npe::move(p); - -npe_end_code() - - diff --git a/src/doublearea.cpp b/src/doublearea.cpp index e70b06ab..3f15c15f 100644 --- a/src/doublearea.cpp +++ b/src/doublearea.cpp @@ -1,53 +1,84 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include +#include "default_types.h" #include +#include +#include +#include +#include -const char* ds_doublearea = R"igl_Qu8mg5v7( -Computes twice the area for each input triangle[quad] +namespace nb = nanobind; +using namespace nb::literals; -Parameters ----------- -v : #v by dim array of mesh vertex positions -f : #f by simplex_size array of mesh faces (must be triangles or quads) +namespace pyigl +{ + // Wrapper for doublearea function + nb::object doublearea( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &A, + const nb::DRef &B, + const nb::DRef &C, + const nb::DRef &l, + const Numeric nan_replacement) + { + Eigen::VectorXN dblA; + if(l.size() > 0) + { + igl::doublearea(l, nan_replacement, dblA); + }else + { + // if not isnan(nan_replacement) then throw error + if(!std::isnan(nan_replacement)) + { + throw std::invalid_argument("nan_replacement must be NaN if l is not provided"); + } + if(A.size() > 0) + { + igl::doublearea(A, B, C, dblA); + } + else + { + igl::doublearea(V, F, dblA); + } + } + return nb::cast(dblA); + } +} -Returns -------- -d_area : #f list of triangle[quad] double areas (SIGNED only for 2D input) +// Bind the wrapper to the Python module +void bind_doublearea(nb::module_ &m) +{ + m.def( + "doublearea", + &pyigl::doublearea, + "V"_a=Eigen::MatrixXN(), + "F"_a=Eigen::MatrixXI(), + "A"_a=Eigen::MatrixXN(), + "B"_a=Eigen::MatrixXN(), + "C"_a=Eigen::MatrixXN(), + "l"_a=Eigen::MatrixXN(), + "nan_replacement"_a=std::numeric_limits::quiet_NaN(), +R"(Computes twice the area for each input triangle or quad. -See also --------- -None +@param[in] V eigen matrix #V by 3 +@param[in] F #F by (3|4) list of mesh face indices into rows of V -Notes ------ -Known bug: For dim==3 complexity is O(#V + #F)!! Not just O(#F). This is a big deal if you have 1million unreferenced vertices and 1 face +@param[in] l #F by dim list of edge lengths using for triangles, columns correspond to edges 23,31,12 +@param[in] nan_replacement what value should be used for triangles whose given + edge lengths do not obey the triangle inequality. These may be very + wrong (e.g., [100 1 1]) or may be nearly degenerate triangles whose + floating point side length computation leads to breach of the triangle + inequality. One may wish to set this parameter to 0 if side lengths l + are _known_ to come from a valid embedding (e.g., some mesh (V,F)). In + that case, the only circumstance the triangle inequality is broken is + when the triangle is nearly degenerate and floating point error + dominates: hence replacing with zero is reasonable. -Examples --------- -# Mesh in (v, f) ->>> dbl_area = doublearea(v, f) -)igl_Qu8mg5v7"; +@param[in] A #F by dim list of triangle corner positions +@param[in] B #F by dim list of triangle corner positions +@param[in] C #F by dim list of triangle corner positions -npe_function(doublearea) -npe_doc(ds_doublearea) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - -npe_begin_code() - - assert_valid_tet_or_tri_mesh_23d(v, f); - EigenDenseLike d_area; - igl::doublearea(v, f, d_area); - return npe::move(d_area); - -npe_end_code() +@param[out] dblA #F list of triangle double areas + )" + ); +} diff --git a/src/dqs.cpp b/src/dqs.cpp deleted file mode 100644 index 206df46b..00000000 --- a/src/dqs.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_dqs = R"igl_Qu8mg5v7( - Dual quaternion skinning - -Parameters ----------- -V #V by 3 list of rest positions -W #W by #C list of weights -vQ #C list of rotation quaternions -vT #C list of translation vectors - -Returns -------- -U #V by 3 list of new positions - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(dqs) -npe_doc(ds_dqs) - -npe_arg(v, dense_float, dense_double) -npe_arg(w, npe_matches(v)) -npe_arg(v_q, npe_matches(v)) -npe_arg(v_t, npe_matches(v)) - -npe_begin_code() - assert_cols_equals(v, 3, "V"); - assert_rows_equals(v_q, w.cols(), "vQ"); - assert_cols_equals(v_q, 4, "vQ"); - assert_cols_equals(v_t, 3, "vT"); - assert_rows_match(v_q, v_t, "vQ", "vT"); - - typedef Eigen::Quaternion Quat; - - std::vector> v_q_copy(v_q.rows()); - for(size_t i = 0; i < v_q_copy.size(); ++i){ - v_q_copy[i].x() = v_q(i, 0); - v_q_copy[i].y() = v_q(i, 1); - v_q_copy[i].z() = v_q(i, 2); - v_q_copy[i].w() = v_q(i, 3); - } - - std::vector> v_t_copy(v_t.rows()); - for(size_t i = 0; i < v_t_copy.size(); ++i) - v_t_copy[i] = v_t.row(i); - - EigenDenseLike u; - igl::dqs(v, w, v_q_copy, v_t_copy, u); - return npe::move(u); - -npe_end_code() - - diff --git a/src/ears.cpp b/src/ears.cpp deleted file mode 100644 index 4b2931af..00000000 --- a/src/ears.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_ears = R"igl_Qu8mg5v7( -FIND_EARS Find all ears (faces with two boundary edges) in a given mesh -Parameters ----------- -F #F by 3 list of triangle mesh indices - -Returns -------- - ears #ears list of indices into F of ears - ear_opp #ears list of indices indicating which edge is non-boundary - (connecting to flops) - -See also --------- - - -Notes ------ -None - -Examples --------- - ears,ear_opp = find_ears(F) - -)igl_Qu8mg5v7"; - -npe_function(ears) -npe_doc(ds_ears) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - Eigen::Matrix ear; - Eigen::Matrix ear_opp; - igl::ears(f, ear, ear_opp); - return std::make_tuple(npe::move(ear), npe::move(ear_opp)); - -npe_end_code() - - diff --git a/src/edge_collapse_is_valid.cpp b/src/edge_collapse_is_valid.cpp deleted file mode 100644 index b62a788a..00000000 --- a/src/edge_collapse_is_valid.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - -#include - -const char *ds_edge_collapse_is_valid = R"igl_Qu8mg5v7( - - Assumes (V,F) is a closed manifold mesh (except for previouslly collapsed faces which should be set to: - [IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL]. - Tests whether collapsing exactly two faces and exactly 3 edges from E (e - and one side of each face gets collapsed to the other) will result in a - mesh with the same topology. - -Parameters ----------- - e index into E of edge to try to collapse. E(e,:) = [s d] or [d s] so that sj) is the edge of F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) "e=(j->i) - EI #E by 2 list of edge flap corners (see above). - -Returns -------- - Returns true if edge collapse is valid - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(edge_collapse_is_valid) -npe_doc(ds_edge_collapse_is_valid) - -npe_arg(edge, int) -npe_arg(F, dense_int32, dense_int64) -npe_arg(E, npe_matches(F)) -npe_arg(EMAP, npe_matches(F)) -npe_arg(EF, npe_matches(F)) -npe_arg(EI, npe_matches(F)) - - -npe_begin_code() - assert_valid_tri_mesh_faces(F); - assert_cols_equals(E, 2, "E"); - assert_rows_match(F, EMAP, "F", "EMAP"); - assert_cols_equals(EMAP, 3, "EMAP"); - assert_rows_match(E, EI, "E", "EI"); - assert_cols_equals(EI, 2, "EI"); - assert_rows_match(E, EF, "E", "EF"); - assert_cols_equals(EF, 2, "EF"); - - const Eigen::MatrixXi F_copy = F.template cast(); - const Eigen::MatrixXi E_copy = E.template cast(); - const Eigen::VectorXi EMAP_copy = EMAP.template cast(); - const Eigen::MatrixXi EF_copy = EF.template cast(); - const Eigen::MatrixXi EI_copy = EI.template cast(); - - bool ok = igl::edge_collapse_is_valid(edge, F_copy, E_copy, EMAP_copy, EF_copy, EI_copy); - return ok; - -npe_end_code() - diff --git a/src/edge_flaps.cpp b/src/edge_flaps.cpp deleted file mode 100644 index 55881cf2..00000000 --- a/src/edge_flaps.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -const char *ds_edge_flaps = R"igl_Qu8mg5v7( - -Determine "edge flaps": two faces on either side of a unique edge (assumes edge-manifold mesh) - - -Parameters ----------- - -F #F by 3 list of face indices - -Returns -------- - -E #E by 2 list of edge indices into V. -EMAP #F*3 list of indices into E, mapping each directed edge to unique - unique edge in E -EF #E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of - F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " - e=(j->i) -EI #E by 2 list of edge flap corners (see above). - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(edge_flaps) -npe_doc(ds_edge_flaps) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_tri_mesh_faces(f); - - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXi e_copy; - Eigen::VectorXi emap_copy; - Eigen::MatrixXi ef_copy; - Eigen::MatrixXi ei_copy; - igl::edge_flaps(f_copy, e_copy, emap_copy, ef_copy, ei_copy); - - EigenDense e = e_copy.template cast(); - EigenDense emap = emap_copy.template cast(); - EigenDense ef = ef_copy.template cast(); - EigenDense ei = ei_copy.template cast(); - return std::make_tuple(npe::move(e), npe::move(emap), npe::move(ef), npe::move(ei)); - -npe_end_code() - - diff --git a/src/edge_lengths.cpp b/src/edge_lengths.cpp index 672ad801..2c8b3c05 100644 --- a/src/edge_lengths.cpp +++ b/src/edge_lengths.cpp @@ -1,69 +1,40 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include +#include "default_types.h" #include - -const char *ds_edge_lengths = R"igl_Qu8mg5v7( - - - Constructs a list of lengths of edges opposite each index in a face - (triangle/tet) list - -Parameters ----------- -V eigen matrix #V by 3 -F #F by 2 list of mesh edges or -F #F by 3 list of mesh faces (must be triangles) or -T #T by 4 list of mesh elements (must be tets) - -Returns -------- -L #F by {1|3|6} list of edge lengths - for edges, column of lengths - for triangles, columns correspond to edges [1,2],[2,0],[0,1] - for tets, columns correspond to edges - [3 0],[3 1],[3 2],[1 2],[2 0],[0 1] - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(edge_lengths) -npe_doc(ds_edge_lengths) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_cols_equals(v, 3, "V"); - assert_nonzero_rows(v, "V"); - assert_nonzero_rows(f, "F"); - - const auto dim = f.cols(); - if(dim != 2 && dim != 3 && dim != 4) - throw pybind11::value_error("Invalid dimension for F must be 2, 3, or 4 but got " + std::to_string(dim)); - - EigenDenseLike l; - igl::edge_lengths(v, f, l); - return npe::move(l); - -npe_end_code() - - +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for edge_lengths function + nb::object edge_lengths( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::MatrixXN L; + igl::edge_lengths(V, F, L); + return nb::cast(L); + } +} + +// Bind the wrapper to the Python module +void bind_edge_lengths(nb::module_ &m) +{ + m.def( + "edge_lengths", + &pyigl::edge_lengths, + "V"_a, "F"_a, +R"(Constructs a list of lengths of edges opposite each index in a face +(triangle/tet) list. + +@param[in] V eigen matrix #V by 3 +@param[in] F #F by (2|3|4) list of mesh simplex indices into rows of V +@param[out] L #F by {1|3|6} list of edge lengths + - For edges, column of lengths + - For triangles, columns correspond to edges [1,2],[2,0],[0,1] + - For tets, columns correspond to edges [3 0],[3 1],[3 2],[1 2],[2 0],[0 1])" + ); +} diff --git a/src/edge_topology.cpp b/src/edge_topology.cpp deleted file mode 100644 index 4805b29c..00000000 --- a/src/edge_topology.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_edge_topology = R"igl_Qu8mg5v7( -Initialize Edges and their topological relations (assumes an edge-manifold mesh) - -Parameters ----------- -v : #v by dim, list of mesh vertex positions (unused) -f : #f by 3, list of triangle indices into V - -Returns -------- -ev : #e by 2, list of edges described as pair of vertices. -fe : #f by 3, list storing triangle-edge relation. -ef : #e by w, list storing edge-triangle relation, uses -1 to indicate boundaries. - -See also --------- - - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>> ev, fe, ef = edge_topology(v, f) -)igl_Qu8mg5v7"; - -npe_function(edge_topology) -npe_doc(ds_edge_topology) -npe_arg(v, dense_double, dense_float) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_23d_tri_mesh(v, f); - EigenDenseLike ev, fe, ef; - igl::edge_topology(v, f, ev, fe, ef); - -return std::make_tuple(npe::move(ev), npe::move(fe), npe::move(ef)); - -npe_end_code() diff --git a/src/edges.cpp b/src/edges.cpp deleted file mode 100644 index 5ce6cab4..00000000 --- a/src/edges.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_edges = R"igl_Qu8mg5v7( -Constructs a list of unique edges represented in a given mesh (v, f) - -Parameters ----------- -f : #F by dim list of mesh faces (must be triangles or tets) - -Returns -------- -#e by 2 list of edges in no particular order - -See also --------- -adjacency_matrix - -Notes ------ - -Examples --------- ->>> V, F, _ = igl.readOFF("test.off) ->>> E = igl.edges(F) - -)igl_Qu8mg5v7"; - -npe_function(edges) -npe_doc(ds_edges) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tet_or_tri_mesh_faces(f); - EigenDenseLike e; - igl::edges(f, e); - return npe::move(e); - -npe_end_code() diff --git a/src/edges_to_path.cpp b/src/edges_to_path.cpp deleted file mode 100644 index 66989594..00000000 --- a/src/edges_to_path.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_edges_to_path = R"igl_Qu8mg5v7( - -EDGES_TO_PATH Given a set of undirected, unique edges such that all form a - single connected compoent with exactly 0 or 2 nodes with valence =1, - determine the/a path visiting all nodes. - -Parameters ----------- -E #E by 2 list of undirected edges - -Returns -------- -I #E+1 list of nodes in order tracing the chain (loop), if the output - is a loop then I(1) == I(end) -J #I-1 list of indices into E of edges tracing I -K #I-1 list of indices into columns of E {1,2} so that K(i) means that - E(i,K(i)) comes before the other (i.e., E(i,3-K(i)) ). This means that - I(i) == E(J(i),K(i)) for i<#I, or - I == E(sub2ind(size(E),J([1:end end]),[K;3-K(end)])))) - - -See also --------- - - -Notes ------ -None - -Examples --------- - - - -)igl_Qu8mg5v7"; - -npe_function(edges_to_path) -npe_doc(ds_edges_to_path) - -npe_arg(e, dense_int32, dense_int64) - - -npe_begin_code() - assert_nonzero_rows(e, "e"); - assert_cols_equals(e, 2, "e"); - - Eigen::MatrixXi e_copy = e.template cast(); - Eigen::VectorXi i_copy; - Eigen::VectorXi j_copy; - Eigen::VectorXi k_copy; - - igl::edges_to_path(e_copy, i_copy, j_copy, k_copy); - - EigenDenseLike i = i_copy.template cast(); - EigenDenseLike j = i_copy.template cast(); - EigenDenseLike k = i_copy.template cast(); - - return std::make_tuple(npe::move(i), npe::move(j), npe::move(k)); - -npe_end_code() - - diff --git a/src/euler_characteristic.cpp b/src/euler_characteristic.cpp deleted file mode 100644 index eb4aa117..00000000 --- a/src/euler_characteristic.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include -#include - -const char* ds_euler_characteristic = R"igl_Qu8mg5v7( - -Computes the Euler characteristic of a given mesh (V,F) - -Parameters ----------- -F #F by dim list of mesh faces (must be triangles) - -Returns -------- -Returns An int containing the Euler characteristic - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(euler_characteristic) -npe_doc(ds_euler_characteristic) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - return igl::euler_characteristic(f); - -npe_end_code() - - - diff --git a/src/exact_geodesic.cpp b/src/exact_geodesic.cpp deleted file mode 100644 index d210c027..00000000 --- a/src/exact_geodesic.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_exact_geodesic = R"igl_Qu8mg5v7( -Exact geodesic algorithm for the calculation of geodesics on a triangular mesh. - -Parameters ----------- -v : #v by 3 array of 3D vertex positions -f : #f by 3 array of mesh faces -vs : #vs by 1 array specifying indices of source vertices -fs : #fs by 1 array specifying indices of source faces -vt : #vt by 1 array specifying indices of target vertices -ft : #ft by 1 array specifying indices of target faces - -Returns -------- -d : #vt+#ft by 1 array of geodesic distances of each target w.r.t. the nearest one in the source set - -See also --------- - - -Notes ------ -Specifying a face as target/source means its center. -Implementation from https:code.google.com/archive/p/geodesic/ with the algorithm first described by Mitchell, Mount and Papadimitriou in 1987. - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(exact_geodesic) -npe_doc(ds_exact_geodesic) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(vs, npe_matches(f)) //TODO somehow the matching is not working in this version, maybe a bug in numpyeigen? Therefore we hand in empty arrays for now -npe_arg(vt, npe_matches(f)) -npe_default_arg(fs, npe_matches(f), pybind11::array()) -npe_default_arg(ft, npe_matches(f), pybind11::array()) -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - //assert_cols_equals(vs, 1, "vs"); - //assert_cols_equals(fs, 1, "fs"); - //assert_cols_equals(vt, 1, "vt"); - //assert_cols_equals(ft, 1, "ft"); - Eigen::Matrix d; - igl::exact_geodesic(v, f, vs, fs, vt, ft, d); - return npe::move(d); - -npe_end_code() - - diff --git a/src/exterior_edges.cpp b/src/exterior_edges.cpp deleted file mode 100644 index fa4bc463..00000000 --- a/src/exterior_edges.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// need assertion for a.rows = b.rows-1 -// __copy -#include -#include -#include - - - - - -#include - -const char* ds_exterior_edges = R"igl_Qu8mg5v7( - -EXTERIOR_EDGES Determines boundary "edges" and also edges with an - odd number of occurrences where seeing edge (i,j) counts as +1 and seeing - the opposite edge (j,i) counts as -1 - -Parameters ----------- -F #F by simplex_size list of "faces" - -Returns -------- -E #E by simplex_size-1 list of exterior edges - -See also --------- - - -Notes ------ -None - -Examples --------- - - - -)igl_Qu8mg5v7"; - -npe_function(exterior_edges) -npe_doc(ds_exterior_edges) - -npe_arg(f, dense_int32, dense_int64) - -npe_begin_code() - assert_valid_tet_or_tri_mesh_faces(f); - - Eigen::MatrixXi e_copy; - Eigen::MatrixXi f_copy = f.template cast(); - igl::exterior_edges(f_copy, e_copy); - EigenDenseLike e = e_copy.template cast(); - return npe::move(e); - -npe_end_code() - diff --git a/src/extract_manifold_patches.cpp b/src/extract_manifold_patches.cpp deleted file mode 100644 index c2384e62..00000000 --- a/src/extract_manifold_patches.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_extract_manifold_patches = R"igl_Qu8mg5v7( -Extract a set of maximal patches from a given mesh. - A maximal patch is a subset of the input faces that are connected via - manifold edges; a patch is as large as possible. - -Parameters ----------- - F #F by 3 list representing triangles. - -Returns -------- -number of manifold patches. -P #F list of patch indices. - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(extract_manifold_patches) -npe_doc(ds_extract_manifold_patches) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_tri_mesh_faces(f); - EigenDenseLike p; - int n_patches = igl::extract_manifold_patches(f, p); - return std::make_tuple(n_patches, npe::move(p)); - -npe_end_code() diff --git a/src/extract_non_manifold_edge_curves.cpp b/src/extract_non_manifold_edge_curves.cpp deleted file mode 100644 index c6ec869f..00000000 --- a/src/extract_non_manifold_edge_curves.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - - - -#include - -const char *ds_extract_non_manifold_edge_curves = R"igl_Qu8mg5v7( - -Extract non-manifold curves from a given mesh. - A non-manifold curves are a set of connected non-manifold edges that - does not touch other non-manifold edges except at the end points. - They are also maximal in the sense that they cannot be expanded by - including more edges. - -Assumes the input mesh have all self-intersection resolved. See ``igl::cgal::remesh_self_intersection`` for more details. - -Parameters ----------- -F #F by 3 list representing triangles. -uE2E #uE list of lists of indices into E of coexisting edges. - -Returns -------- - -curves An array of arries of unique edge indices. - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(extract_non_manifold_edge_curves) -npe_doc(ds_extract_non_manifold_edge_curves) - -npe_arg(f, dense_int32, dense_int64) -npe_arg(u_e2_e, std::vector >) - - -npe_begin_code() - assert_valid_tri_mesh_faces(f); - - Eigen::Matrix emap; - - - std::vector > curves; - igl::extract_non_manifold_edge_curves(f, emap, u_e2_e, curves); - return curves; - -npe_end_code() - - diff --git a/src/face_occurrences.cpp b/src/face_occurrences.cpp deleted file mode 100644 index 6f165e65..00000000 --- a/src/face_occurrences.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - -#include - -const char *ds_face_occurrences = R"igl_Qu8mg5v7( - Count the occruances of each face (row) in a list of face indices (irrespecitive of order) -Parameters ----------- - F #F by simplex-size - -Returns -------- - C #F list of counts - -See also --------- - - -Notes ------ - Known bug: triangles/tets only (where ignoring order still gives simplex) - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(face_occurrences) -npe_doc(ds_face_occurrences) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_tet_or_tri_mesh_faces(f); - - EigenDenseLike c; - igl::face_occurrences(f, c); - return npe::move(c); - -npe_end_code() - - diff --git a/src/faces_first.cpp b/src/faces_first.cpp deleted file mode 100644 index 6d46c416..00000000 --- a/src/faces_first.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -const char *ds_faces_first = R"igl_Qu8mg5v7( - - FACES_FIRST Reorder vertices so that vertices in face list come before - vertices that don't appear in the face list. This is especially useful if - the face list contains only surface faces and you want surface vertices - listed before internal vertices - [RV,RF,IM] = faces_first(V,T); - -Parameters ----------- - V # vertices by 3 vertex positions - F # faces by 3 list of face indices - -Returns -------- - RV # vertices by 3 vertex positions, order such that if the jth vertex is - some face in F, and the kth vertex is not then j comes before k - RF # faces by 3 list of face indices, reindexed to use RV - IM #V by 1 list of indices such that: RF = IM(F) and RT = IM(T) - and RV(IM,:) = V - -See also --------- - - -Notes ------ -None - -Examples --------- - Tet mesh in (V,T,F) - faces_first(V,F,IM); - T = T.unaryExpr(bind1st(mem_fun( static_cast(&VectorXi::operator())), - &IM)).eval(); -)igl_Qu8mg5v7"; - -npe_function(faces_first) -npe_doc(ds_faces_first) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - - EigenDenseLike rv; - EigenDenseLike rf; - Eigen::Matrix im; - igl::faces_first(v_copy, f_copy, rv, rf, im); - - EigenDenseLike im_res = im; - return std::make_tuple(npe::move(rv), npe::move(rf), npe::move(im_res)); -npe_end_code() diff --git a/src/facet_components.cpp b/src/facet_components.cpp deleted file mode 100644 index b5384b48..00000000 --- a/src/facet_components.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Nico -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_facet_components = R"igl_Qu8mg5v7( -Compute connected components of facets based on edge-edge adjacency, - -Parameters ----------- -f : #f x 3 array of triangle indices - -Returns -------- -An array, c, with shape (#f,), of component ids - -See also --------- -vertex_components -vertex_components_from_adjacency_matrix - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(facet_components) -npe_doc(ds_facet_components) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - assert_valid_tri_mesh_faces(f); - EigenDenseLike c; - igl::facet_components(f, c); - return npe::move(c); -npe_end_code() diff --git a/src/false_barycentric_subdivision.cpp b/src/false_barycentric_subdivision.cpp deleted file mode 100644 index 42c68a81..00000000 --- a/src/false_barycentric_subdivision.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_false_barycentric_subdivision = R"igl_Qu8mg5v7( - - Refine the mesh by adding the barycenter of each face - -Parameters ----------- - V #V by 3 coordinates of the vertices - F #F by 3 list of mesh faces (must be triangles) - -Returns -------- - VD #V + #F by 3 coordinate of the vertices of the dual mesh - The added vertices are added at the end of VD (should not be - same references as (V,F) - FD #F*3 by 3 faces of the dual mesh - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(false_barycentric_subdivision) -npe_doc(ds_false_barycentric_subdivision) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - //TODO: only 2 templates in igl... - - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - - Eigen::MatrixXd vd_copy; - Eigen::MatrixXi fd_copy; - - igl::false_barycentric_subdivision(v_copy, f_copy, vd_copy, fd_copy); - - EigenDenseLike vd = vd_copy.template cast(); - EigenDenseLike fd = fd_copy.template cast(); - return std::make_tuple(npe::move(vd), npe::move(fd)); - -npe_end_code() - - diff --git a/src/fast_winding_number.cpp b/src/fast_winding_number.cpp deleted file mode 100644 index 05987c09..00000000 --- a/src/fast_winding_number.cpp +++ /dev/null @@ -1,118 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_fast_winding_number_for_points = R"igl_Qu8mg5v7xxx1( -Evaluate the fast winding number for point data, with default expansion -order and beta (both are set to 2). - -This function performes the precomputation and evaluation all in one. -If you need to acess the precomuptation for repeated evaluations, use the -two functions designed for exposed precomputation (described above). - -Parameters ----------- -P #P by 3 list of point locations -N #P by 3 list of point normals -A #P by 1 list of point areas -Q #Q by 3 list of query points for the winding number - -Returns -------- -WN #Q by 1 list of windinng number values at each query point - -See also --------- - - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7xxx1"; - -npe_function(fast_winding_number_for_points) -npe_doc(ds_fast_winding_number_for_points) -npe_arg(p, dense_float, dense_double) -npe_arg(n, npe_matches(p)) -npe_arg(a, npe_matches(p)) - -npe_arg(q, npe_matches(p)) - -npe_begin_code() - assert_nonzero_rows(p, "p"); - assert_cols_equals(p, 3, "p"); - - assert_cols_equals(n, 3, "p"); - assert_rows_match(p, n, "p", "n"); - - assert_size_equals(a, p.rows(), "a"); - - assert_cols_equals(q, 3, "q"); - assert_nonzero_rows(q, "q"); - - EigenDenseLike a_copy = a; - - EigenDenseLike wn; - igl::fast_winding_number(p, n, a_copy, q, wn); - return npe::move(wn); - -npe_end_code() - - - - -const char *ds_fast_winding_number_for_meshes = R"igl_Qu8mg5v7xxx2( -Compute approximate winding number of a triangle soup mesh according to -"Fast Winding Numbers for Soups and Clouds" [Barill et al. 2018]. - -Parameters ----------- -V #V by 3 list of mesh vertex positions -F #F by 3 list of triangle mesh indices into rows of V -Q #Q by 3 list of query points for the winding number - -Returns -------- -WN #Q by 1 list of windinng number values at each query point - -See also --------- - - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7xxx2"; - -npe_function(fast_winding_number_for_meshes) -npe_doc(ds_fast_winding_number_for_meshes) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - -npe_arg(q, npe_matches(v)) - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_cols_equals(q, 3, "q"); - assert_nonzero_rows(q, "q"); - - EigenDenseLike wn; - igl::fast_winding_number(v, f, q, wn); - return npe::move(wn); - -npe_end_code() \ No newline at end of file diff --git a/src/find_cross_field_singularities.cpp b/src/find_cross_field_singularities.cpp deleted file mode 100644 index 338f1546..00000000 --- a/src/find_cross_field_singularities.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_find_cross_field_singularities = R"igl_Qu8mg5v7( -Computes singularities of a cross field, assumed combed - -Parameters ----------- -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face indices -Handle_MMatch #F by 3 eigen Matrix containing the integer missmatch of the cross field - across all face edges - -Returns -------- -isSingularity #V by 1 boolean eigen Vector indicating the presence of a singularity on a vertex -singularityIndex #V by 1 integer eigen Vector containing the singularity indices - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(find_cross_field_singularities) -npe_doc(ds_find_cross_field_singularities) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(handle_m_match, npe_matches(f)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_rows_match(f, handle_m_match, "F", "Handle_MMatch"); - assert_cols_equals(handle_m_match, 3, "Handle_MMatch"); - - EigenDenseLike is_singularity; - EigenDenseLike singularity_index; - igl::find_cross_field_singularities(v, f, handle_m_match, is_singularity, singularity_index); - return std::make_tuple(npe::move(is_singularity), npe::move(singularity_index)); - -npe_end_code() - - - -const char *ds_find_cross_field_singularities_from_field = R"igl_Qu8mg5v7( - -Wrapper that calculates the missmatch if it is not provided. -Note that the field in PD1 and PD2 MUST BE combed (see igl::comb_cross_field). - -Parameters ----------- - -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face (quad) indices -PD1 #F by 3 eigen Matrix of the first per face cross field vector -PD2 #F by 3 eigen Matrix of the second per face cross field vector - -Returns -------- -isSingularity #V by 1 boolean eigen Vector indicating the presence of a singularity on a vertex -singularityIndex #V by 1 integer eigen Vector containing the singularity indices - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(find_cross_field_singularities_from_field) -npe_doc(ds_find_cross_field_singularities_from_field) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(pd1, npe_matches(v)) -npe_arg(pd2, npe_matches(v)) -npe_default_arg(is_combed, bool, false) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_rows_match(f, pd1, "F", "PD1"); - assert_rows_match(f, pd2, "F", "PD2"); - assert_cols_equals(pd1, 3, "PD1"); - assert_cols_equals(pd2, 3, "PD2"); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - EigenDenseLike pd1_copy = pd1; - EigenDenseLike pd2_copy = pd2; - - EigenDenseLike is_singularity; - EigenDenseLike singularity_index; - igl::find_cross_field_singularities(v_copy, f_copy, pd1_copy, pd2_copy, is_singularity, singularity_index, is_combed); - return std::make_tuple(npe::move(is_singularity), npe::move(singularity_index)); - -npe_end_code() - - diff --git a/src/fit_cubic_bezier.cpp b/src/fit_cubic_bezier.cpp deleted file mode 100644 index a6e4ffe9..00000000 --- a/src/fit_cubic_bezier.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -// On macos with cp36, termios.h is getting included for some reason and it -// defines B0 -#ifdef B0 -# undef B0 -#endif -#include -#include -#include - - -const char *ds_fit_cubic_bezier = R"igl_Qu8mg5v7( -Fit a cubic bezier spline (G1 continuous) to an ordered list of input -points in any dimension, according to "An algorithm for automatically -fitting digitized curves" [Schneider 1990]. - -Parameters ----------- - d #d by dim list of points along a curve to be fit with a cubic bezier - spline (should probably be roughly uniformly spaced). If d(0)==d(end), - then will treat as a closed curve. - error maximum squared distance allowed -Returns -------- - cubics #cubics list of 4 by dim lists of cubic control points - -)igl_Qu8mg5v7"; - -npe_function(fit_cubic_bezier) -npe_doc(ds_fit_cubic_bezier) - -npe_arg(d, dense_float, dense_double) -npe_arg(error, double) - -npe_begin_code() - // igl::fit_cubic_bezier is hard-coded to double, so for now copy. - Eigen::MatrixXd d_cpy = d.template cast(); - std::vector c_cpy; - igl::fit_cubic_bezier(d_cpy,error,c_cpy); - std::vector> c(c_cpy.size()); - std::transform (c_cpy.begin(), c_cpy.end(), c.begin(), - [](const Eigen::MatrixXd & ci){ return ci.cast();}); - // numpyeigen's pybind11 fork `numpy_hacks_stable` is printing "Encapsulate move!" - // https://github.com/fwilliams/numpyeigen/issues/58 - return pybind11::detail::type_caster::cast(c, pybind11::return_value_policy::move, pybind11::none()); - -npe_end_code() - diff --git a/src/fit_plane.cpp b/src/fit_plane.cpp deleted file mode 100644 index a8910fc1..00000000 --- a/src/fit_plane.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include - - - - - - -#include - -const char* ds_fit_plane = R"igl_Qu8mg5v7( - -This function fits a plane to a point cloud. - -Parameters ----------- -V #Vx3 matrix. The 3D point cloud, one row for each vertex. - -Returns -------- -N 1x3 Vector. The normal of the fitted plane. -C 1x3 Vector. A point that lies in the fitted plane. - -See also --------- - -Notes ------ -From http:missingbytes.blogspot.com/2012/06/fitting-plane-to-point-cloud.html - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(fit_plane) -npe_doc(ds_fit_plane) - -npe_arg(v, dense_float, dense_double) - - -npe_begin_code() - - assert_nonzero_rows(v, "v"); - assert_cols_equals(v, 3, "v"); - // TODO: remove __copy - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::RowVector3d n; - Eigen::RowVector3d c; - igl::fit_plane(v_copy, n, c); - Eigen::Matrix n_row_major = n; - Eigen::Matrix c_row_major = c; - return std::make_tuple(npe::move(n_row_major), npe::move(c_row_major)); - -npe_end_code() - - diff --git a/src/flip_avoiding_line_search.cpp b/src/flip_avoiding_line_search.cpp deleted file mode 100644 index d6204f85..00000000 --- a/src/flip_avoiding_line_search.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include -#include -#include - - - - - - -#include - -const char* ds_flip_avoiding_line_search = R"igl_Qu8mg5v7( - -A bisection line search for a mesh based energy that avoids triangle flips as suggested in -"Bijective Parameterization with Free Boundaries" (Smith J. and Schaefer S., 2015). - -The user specifies an initial vertices position (that has no flips) and target one (that my have flipped triangles). -This method first computes the largest step in direction of the destination vertices that does not incur flips, and then minimizes a given energy using this maximal step and a bisection linesearch (see igl::line_search). - -Supports both triangle and tet meshes. - -Parameters ----------- -F #F by 3/4 list of mesh faces or tets -cur_v #V by dim list of variables -dst_v #V by dim list of target vertices. This mesh may have flipped triangles -energy A function to compute the mesh-based energy (return an energy that is bigger than 0) -cur_energy(OPTIONAL) The energy at the given point. Helps save redundant c omputations. This is optional. If not specified, the function will compute it. - - -Returns -------- -cur_v #V by dim list of variables at the new location -Returns the energy at the new point - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(flip_avoiding_line_search) -npe_doc(ds_flip_avoiding_line_search) - -npe_arg(f, dense_int32, dense_int64) -npe_arg(cur_v, dense_float, dense_double) -npe_arg(dst_v, npe_matches(cur_v)) -npe_arg(energy, std::function) -npe_arg(cur_energy, double) - -npe_begin_code() - assert_shapes_match(cur_v, dst_v, "cur_v", "dst_v"); - assert_valid_tet_or_tri_mesh_faces(f); - if(f.cols() == 3) - { - assert_cols_equals(cur_v, 2, "cur_v"); - assert_cols_equals(dst_v, 2, "dst_v"); - } - else - { - assert_cols_equals(cur_v, 3, "cur_v"); - assert_cols_equals(dst_v, 3, "dst_v"); - } - - //TODO: remove __copy - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXd cur_v_copy = cur_v.template cast(); - Eigen::MatrixXd dst_v_copy = dst_v.template cast(); - double ret_energy = igl::flip_avoiding_line_search(f_copy, cur_v_copy, dst_v_copy, energy, cur_energy); - EigenDenseFloat cur_v_row_major = cur_v_copy; - return std::make_tuple(ret_energy, npe::move(cur_v_row_major)); - -npe_end_code() diff --git a/src/flip_edge.cpp b/src/flip_edge.cpp deleted file mode 100644 index 5cd4d2ae..00000000 --- a/src/flip_edge.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Soboleva Natalia -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -#include - - -const char* ds_flip_edge = R"igl_Qu8mg5v7( - -Flip an edge in a triangle mesh. The edge specified by uei must have -exactly two adjacent faces. Violation will result in exception. -Another warning: edge flipping could convert manifold mesh into -non-manifold. - -Parameters ----------- -F #F by 3 list of triangles. -E #F*3 by 2 list of all of directed edges -uE #uE by 2 list of unique undirected edges -EMAP #F*3 list of indices into uE, mapping each directed edge to unique - undirected edge -uE2E #uE list of lists of indices into E of coexisting edges -ue #index into uE the edge to be flipped. - -Returns -------- - -Updated F, E, uE, EMAP and uE2E. - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(flip_edge) -npe_doc(ds_flip_edge) - -npe_arg(f, dense_int32, dense_int64) -npe_arg(e, npe_matches(f)) -npe_arg(u_e, npe_matches(f)) -npe_arg(emap, npe_matches(f)) -npe_arg(u_e2_e, std::vector >) -npe_arg(uei, size_t) - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - EigenDenseLike f_copy = f; - EigenDenseLike e_copy = e; - EigenDenseLike u_e_copy = u_e; - EigenDenseLike emap_copy = emap; - - std::vector > u_e2_e_tmp; - u_e2_e_tmp.reserve(u_e2_e.size()); - - std::transform(begin(u_e2_e), end(u_e2_e), std::back_inserter(u_e2_e_tmp), - [](const std::vector& vi) { - return std::vector(begin(vi), end(vi)); - }); - - igl::flip_edge(f_copy, e_copy, u_e_copy, emap_copy, u_e2_e_tmp, uei); - - std::vector > u_e2_e_copy; - u_e2_e_copy.reserve(u_e2_e_tmp.size()); - std::transform(begin(u_e2_e_tmp), end(u_e2_e_tmp), std::back_inserter(u_e2_e_copy), - [](const std::vector& vi) { - return std::vector(begin(vi), end(vi)); - }); - return std::make_tuple(npe::move(f_copy), npe::move(e_copy), npe::move(u_e_copy), npe::move(emap_copy), u_e2_e_copy); - -npe_end_code() diff --git a/src/flipped_triangles.cpp b/src/flipped_triangles.cpp deleted file mode 100644 index 00d4d9f3..00000000 --- a/src/flipped_triangles.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_flipped_triangles = R"igl_Qu8mg5v7( - Finds the ids of the flipped triangles of the mesh V,F in the UV mapping uv - -Parameters ----------- - V #V by 2 list of mesh vertex positions - F #F by 3 list of mesh faces (must be triangles) - -Returns -------- - X #flipped list of containing the indices into F of the flipped triangles - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(flipped_triangles) -npe_doc(ds_flipped_triangles) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_2d_tri_mesh(v, f); - EigenDenseLike x; - igl::flipped_triangles(v, f, x); - return npe::move(x); - -npe_end_code() diff --git a/src/forward_kinematics.cpp b/src/forward_kinematics.cpp deleted file mode 100644 index 6b9c449b..00000000 --- a/src/forward_kinematics.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - -#include - -const char *ds_forward_kinematics = R"igl_Qu8mg5v7( -Given a skeleton and a set of relative bone rotations compute absolute rigid transformations for each bone. -Parameters ----------- -C #C by dim list of joint positions -BE #BE by 2 list of bone edge indices -P #BE list of parent indices into BE -dQ #BE list of relative rotations -dT #BE list of relative translations - -Returns -------- -vQ #BE list of absolute rotations -vT #BE list of absolute translations - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(forward_kinematics) -npe_doc(ds_forward_kinematics) - -npe_arg(c, dense_float, dense_double) -npe_arg(be, dense_int32, dense_int64) -npe_arg(p, npe_matches(be)) -npe_arg(d_q, npe_matches(c)) -npe_arg(d_t, dense_float, dense_double) -//TODO missing none for d_t - - -npe_begin_code() - assert_cols_equals(c, 3, "C"); - assert_cols_equals(be, 2, "BE"); - - assert_rows_match(d_q, be, "dQ", "BE"); - assert_cols_equals(d_q, 4, "dQ"); - if(d_t.size() > 0){ - assert_cols_equals(d_t, 3, "dT"); - assert_rows_match(d_q, d_t, "dQ", "dT"); - } - - Eigen::MatrixXd c_copy = c.template cast(); - Eigen::MatrixXi be_copy = be.template cast(); - Eigen::VectorXi p_copy = p.template cast(); - - std::vector> d_q_copy(d_q.rows()); - for (size_t i = 0; i < d_q_copy.size(); ++i) - { - d_q_copy[i].x() = d_q(i, 0); - d_q_copy[i].y() = d_q(i, 1); - d_q_copy[i].z() = d_q(i, 2); - d_q_copy[i].w() = d_q(i, 3); - } - - std::vector d_t_copy(d_t.rows()); - for (size_t i = 0; i < d_t_copy.size(); ++i) - d_t_copy[i] = d_t.row(i).template cast(); - - std::vector > v_q; - std::vector v_t; - if (d_t_copy.empty()) - igl::forward_kinematics(c_copy, be_copy, p_copy, d_q_copy, v_q, v_t); - else - igl::forward_kinematics(c_copy, be_copy, p_copy, d_q_copy, d_t_copy, v_q, v_t); - - EigenDenseLike v_q_out(v_q.size(), 4); - EigenDenseLike v_t_out(v_t.size(), 3); - - for (size_t i = 0; i < v_q.size(); ++i) - { - v_q_out(i, 0) = v_q[i].x(); - v_q_out(i, 1) = v_q[i].y(); - v_q_out(i, 2) = v_q[i].z(); - v_q_out(i, 3) = v_q[i].w(); - } - - for (size_t i = 0; i < v_t.size(); ++i) - v_t_out.row(i) = v_t[i].template cast(); - - return std::make_tuple(npe::move(v_q_out), npe::move(v_t_out)); - - npe_end_code() \ No newline at end of file diff --git a/src/gaussian_curvature.cpp b/src/gaussian_curvature.cpp deleted file mode 100644 index 12d567eb..00000000 --- a/src/gaussian_curvature.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_gaussian_curvature = R"igl_Qu8mg5v7( -Compute discrete local integral gaussian curvature (angle deficit, without -averaging by local area). - -Parameters ----------- -v : #v by 3 array of mesh vertex 3D positions -f : #f by 3 array of face (triangle) indices - -Returns -------- -k : #v by 1 array of discrete gaussian curvature values - -See also --------- -principal_curvature - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>> k = gaussian_curvature(v, f) -)igl_Qu8mg5v7"; - -npe_function(gaussian_curvature) -npe_doc(ds_gaussian_curvature) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - EigenDenseLike k; - igl::gaussian_curvature(v, f, k); - return npe::move(k); - -npe_end_code() - - diff --git a/src/grad.cpp b/src/grad.cpp deleted file mode 100644 index 755049d0..00000000 --- a/src/grad.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_grad = R"igl_Qu8mg5v7( -Compute the numerical gradient operator. - -Parameters ----------- -v : #v by 3 list of mesh vertex positions -f : #f by 3 list of mesh face indices [or a #faces by 4 list of tetrahedral indices] -uniform : boolean (default false). Use a uniform mesh instead of the vertices v - -Returns -------- -g : #faces * dim by #v gradient operator - -See also --------- -cotmatrix, massmatrix - -Notes ------ -Gradient of a scalar function defined on piecewise linear elements (mesh) -is constant on each triangle [tetrahedron] i,j,k: -grad(Xijk) = (Xj-Xi) * (Vi - Vk)^R90 / 2A + (Xk-Xi) * (Vj - Vi)^R90 / 2A -where Xi is the scalar value at vertex i, Vi is the 3D position of vertex -i, and A is the area of triangle (i,j,k). ^R90 represent a rotation of -90 degrees. - -Examples --------- -# Mesh in (v, f) ->>> g = grad(v, f) -)igl_Qu8mg5v7"; - -npe_function(grad) -npe_doc(ds_grad) -npe_arg(v, dense_double, dense_float) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(uniform, bool, false) - -npe_begin_code() - - assert_valid_tet_or_tri_mesh(v, f); - EigenSparseLike g; - igl::grad(v, f, g, uniform); - return npe::move(g); - -npe_end_code() - - diff --git a/src/grad_intrinsic.cpp b/src/grad_intrinsic.cpp deleted file mode 100644 index 0c9c181d..00000000 --- a/src/grad_intrinsic.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_grad_intrinsic = R"igl_Qu8mg5v7( - -GRAD_INTRINSIC Construct an intrinsic gradient operator. - - -Parameters ----------- - -l #F by 3 list of edge lengths -F #F by 3 list of triangle indices into some vertex list V - -Returns -------- -G #F*2 by #V gradient matrix: G=[Gx;Gy] where x runs along the 23 edge and - y runs in the counter-clockwise 90° rotation. - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(grad_intrinsic) -npe_doc(ds_grad_intrinsic) - -npe_arg(l, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_rows_match(l, f, "l", "f"); - assert_cols_equals(l, 3, "l"); - assert_valid_tri_mesh_faces(f); - - EigenDense f_copy = f; - - Eigen::SparseMatrix g; - igl::grad_intrinsic(l, f_copy, g); - return npe::move(g); - -npe_end_code() - - diff --git a/src/harmonic.cpp b/src/harmonic.cpp deleted file mode 100644 index 82e82588..00000000 --- a/src/harmonic.cpp +++ /dev/null @@ -1,258 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_harmonic_w = R"igl_Qu8mg5v7( -Compute k-harmonic weight functions "coordinates". - -Parameters ----------- - V #V by dim vertex positions - F #F by simplex-size list of element indices - b #b boundary indices into V - bc #b by #W list of boundary values - k power of harmonic operation (1: harmonic, 2: biharmonic, etc) - -Returns -------- - W #V by #W list of weights - -See also --------- - - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(harmonic) -npe_doc(ds_harmonic_w) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(b, npe_matches(f)) -npe_arg(bc, npe_matches(v)) -npe_arg(k, int) - -npe_begin_code() - - assert_valid_tet_or_tri_mesh_23d(v, f); - assert_nonzero_rows(bc, "bc"); - assert_rows_match(b, bc, "b", "bc"); - EigenDenseLike w; - igl::harmonic(v, f, b, bc, k, w); - return npe::move(w); - -npe_end_code() -#include - - -const char* ds_harmonic_ul = R"igl_Qu8mg5v7( - -Compute harmonic map using uniform laplacian operator - -Parameters ----------- - F #F by simplex-size list of element indices - b #b boundary indices into V - bc #b by #W list of boundary values - k power of harmonic operation (1: harmonic, 2: biharmonic, etc) - -Returns -------- - W #V by #W list of weights - -See also --------- - - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(harmonic_uniform_laplacian) -npe_doc(ds_harmonic_ul) - -npe_arg(f, dense_int32, dense_int64) -npe_arg(b, npe_matches(f)) -npe_arg(bc, dense_float, dense_double) -npe_arg(k, int) - - -npe_begin_code() - - assert_valid_tet_or_tri_mesh_faces(f, "f"); - assert_nonzero_rows(bc, "bc"); - assert_rows_match(b, bc, "b", "bc"); - - EigenDenseLike w; - igl::harmonic(f, b, bc, k, w); - return npe::move(w); - -npe_end_code() - - - - - - -#include - -const char* ds_harmonic = R"igl_Qu8mg5v7( - -Compute a harmonic map using a given Laplacian and mass matrix - -Parameters ----------- - L #V by #V discrete (integrated) Laplacian - M #V by #V mass matrix - b #b boundary indices into V - bc #b by #W list of boundary values - k power of harmonic operation (1: harmonic, 2: biharmonic, etc) - -Returns -------- - W #V by #V list of weights - -See also --------- - - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(harmonic_from_laplacian_and_mass) -npe_doc(ds_harmonic) -//TODO: l and bc need to have same type, matching missing -npe_arg(l, sparse_float, sparse_double) -npe_arg(m, npe_matches(l)) -npe_arg(b, dense_int32, dense_int64) -npe_arg(bc, dense_float, dense_double) -// npe_arg(bc, npe_dense_like(l)) -npe_arg(k, int) - - -npe_begin_code() - - assert_shapes_match(l, m, "l", "m"); - assert_nonzero_rows(l, "l"); - assert_nonzero_rows(bc, "bc"); - assert_rows_match(b, bc, "b", "bc"); - - Eigen::Matrix bc_copy = bc.template cast(); - - Eigen::Matrix w; - igl::harmonic(l, m, b, bc_copy, k, w); - return npe::move(w); - -npe_end_code() -#include - -const char* ds_harmonic_int_lapl = R"igl_Qu8mg5v7( - - Build the discrete k-harmonic operator (computing integrated quantities). - That is, if the k-harmonic PDE is Q x = 0, then this minimizes x' Q x - - -Parameters ----------- - L #V by #V discrete (integrated) Laplacian - M #V by #V mass matrix - k power of harmonic operation (1: harmonic, 2: biharmonic, etc) - -Returns -------- - Q #V by #V discrete (integrated) k-Laplacian -See also --------- - - -Notes ------ -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(harmonic_integrated_from_laplacian_and_mass) -npe_doc(ds_harmonic_int_lapl) - -npe_arg(l, sparse_float, sparse_double) -npe_arg(m, npe_matches(l)) -npe_arg(k, int) - - -npe_begin_code() - assert_shapes_match(l, m, "l", "m"); - assert_nonzero_rows(l, "l"); - EigenSparseLike q; - igl::harmonic(l, m, k, q); - return npe::move(q); - -npe_end_code() -#include - -const char* ds_harmonic_int = R"igl_Qu8mg5v7( - -Parameters ----------- - V #V by dim vertex positions - F #F by simplex-size list of element indices - k power of harmonic operation (1: harmonic, 2: biharmonic, etc) - -Returns -------- - Q #V by #V discrete (integrated) k-Laplacian - -See also --------- - - -Notes ------ - - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(harmonic_integrated) -npe_doc(ds_harmonic_int) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(k, int) - - -npe_begin_code() - - assert_valid_tet_or_tri_mesh(v, f); - EigenSparseLike q; - igl::harmonic(v, f, k, q); - return npe::move(q); - -npe_end_code() - - diff --git a/src/hausdorff.cpp b/src/hausdorff.cpp deleted file mode 100644 index cf7e604d..00000000 --- a/src/hausdorff.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_hausdorff = R"igl_Qu8mg5v7( - - HAUSDORFF compute the Hausdorff distance between mesh (VA,FA) and mesh - (VB,FB). This is the - - d(A,B) = max ( max min d(a,b) , max min d(b,a) ) - a∈A b∈B b∈B a∈A - -Parameters ----------- - VA #VA by 3 list of vertex positions - FA #FA by 3 list of face indices into VA - VB #VB by 3 list of vertex positions - FB #FB by 3 list of face indices into VB - -Returns -------- - d hausdorff distance - pair 2 by 3 list of "determiner points" so that pair(1,:) is from A - and pair(2,:) is from B - -See also --------- - - -Notes ------ - Known issue: This is only computing max(min(va,B),min(vb,A)). This is - better than max(min(va,Vb),min(vb,Va)). This (at least) is missing - "edge-edge" cases like the distance between the two different - triangulations of a non-planar quad in 3D. Even simpler, consider the - Hausdorff distance between the non-convex, block letter V polygon (with 7 - vertices) in 2D and its convex hull. The Hausdorff distance is defined by - the midpoint in the middle of the segment across the concavity and some - non-vertex point _on the edge_ of the V. - -Examples --------- -)igl_Qu8mg5v7"; - -npe_function(hausdorff) -npe_doc(ds_hausdorff) - -npe_arg(va, dense_float, dense_double) -npe_arg(fa, dense_int32, dense_int64) -npe_arg(vb, npe_matches(va)) -npe_arg(fb, npe_matches(fa)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(va, fa, "va", "fa"); - assert_valid_3d_tri_mesh(vb, fb, "vb", "fb"); - - double d; - igl::hausdorff(va, fa, vb, fb, d); - return d; - -npe_end_code() - - - diff --git a/src/heat_geodesic.cpp b/src/heat_geodesic.cpp deleted file mode 100644 index bbffe719..00000000 --- a/src/heat_geodesic.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_heat_geodesic = R"igl_Qu8mg5v7xxx( -Compute fast approximate geodesic distances using precomputed data from a set of selected source vertices (gamma) - -Parameters ----------- -V #V by dim list of mesh vertex positions -F #F by 3 list of mesh face indices into V -t "heat" parameter (smaller --> more accurate, less stable) -gamma #gamma list of indices into V of source vertices - -Returns -------- -D #V list of distances to gamma - -See also --------- - - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7xxx"; - -npe_function(heat_geodesic) -npe_doc(ds_heat_geodesic) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(t, double) -npe_arg(gamma, npe_matches(f)) - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - Eigen::Matrix gamma_copy = gamma; - - Eigen::Matrix d_copy; - igl::HeatGeodesicsData data; - igl::heat_geodesics_precompute(v_copy, f_copy, npe_Scalar_v(t), data); - igl::heat_geodesics_solve(data, gamma_copy, d_copy); - EigenDenseLike d = d_copy; - - return npe::move(d); - -npe_end_code() - - diff --git a/src/hessian.cpp b/src/hessian.cpp deleted file mode 100644 index f35fec5a..00000000 --- a/src/hessian.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_hessian = R"igl_Qu8mg5v7( - Constructs the finite element Hessian matrix - as described in https:arxiv.org/abs/1707.04348, - Natural Boundary Conditions for Smoothing in Geometry Processing - (Oded Stein, Eitan Grinspun, Max Wardetzky, Alec Jacobson) - The interior vertices are NOT set to zero yet. - -Parameters ----------- -V #V by dim list of mesh vertex positions -F #F by 3 list of mesh faces (must be triangles) - -Returns -------- -H #V by #V Hessian energy matrix, each column i corresponding to V(i,:) - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(hessian) -npe_doc(ds_hessian) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_23d_tri_mesh(v, f); - EigenSparseLike h; - igl::hessian(v, f, h); - return npe::move(h); -npe_end_code() - - diff --git a/src/hessian_energy.cpp b/src/hessian_energy.cpp deleted file mode 100644 index 927838d8..00000000 --- a/src/hessian_energy.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// might be sparse matrix problem - -#include -#include -#include -#include - -const char* ds_hessian_energy = R"igl_Qu8mg5v7( - -Constructs the Hessian energy matrix using mixed FEM - as described in https:arxiv.org/abs/1707.04348 - Natural Boundary Conditions for Smoothing in Geometry Processing - (Oded Stein, Eitan Grinspun, Max Wardetzky, Alec Jacobson) - -Parameters ----------- -V #V by dim list of mesh vertex positions -F #F by 3 list of mesh faces (must be triangles) - - -Returns -------- -Q #V by #V Hessian energy matrix, each row/column i - corresponding to V(i,:) - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(hessian_energy) -npe_doc(ds_hessian_energy) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - EigenSparseLike q; - igl::hessian_energy(v, f, q); - return npe::move(q); - -npe_end_code() - - diff --git a/src/in_element.cpp b/src/in_element.cpp deleted file mode 100644 index 71dfd96a..00000000 --- a/src/in_element.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -using AABB_f64_3 = igl::AABB,3>; -using AABB_f64_2 = igl::AABB,2>; -const char* ds_in_element = R"igl_Qu8mg5v7( -Determine whether each point in a list of points is in the elements of a mesh. - -Parameters: ------- -V : #V by dim list of mesh vertex positions. -Ele : #Ele by dim+1 list of mesh indices into #V. -Q : #Q by dim list of query point positions -aabb : axis-aligned bounding box tree object (see AABB.h) - -Returns: -------- -I : #Q list of indices into Ele of first containing element (-1 means no -containing element) -)igl_Qu8mg5v7"; -npe_function(in_element_3) -npe_doc( ds_in_element) -npe_arg(V, Eigen::MatrixXd) -npe_arg(Ele, Eigen::MatrixXi) -npe_arg(Q, Eigen::MatrixXd) -npe_arg(aabb, AABB_f64_3) -npe_begin_code() - - Eigen::VectorXi I; - Eigen::Map V_map(V.data(),V.rows(),V.cols()); - igl::in_element(V_map,Ele,Q,aabb,I); - return npe::move(I); - -npe_end_code() - - -npe_function(in_element_2) -npe_doc( ds_in_element) -npe_arg(V, Eigen::MatrixXd) -npe_arg(Ele, Eigen::MatrixXi) -npe_arg(Q, Eigen::MatrixXd) -npe_arg(aabb, AABB_f64_2) -npe_begin_code() - - Eigen::VectorXi I; - Eigen::Map V_map(V.data(),V.rows(),V.cols()); - igl::in_element(V_map,Ele,Q,aabb,I); - return npe::move(I); - -npe_end_code() - - - diff --git a/src/include/common.h b/src/include/common.h deleted file mode 100644 index b33061e2..00000000 --- a/src/include/common.h +++ /dev/null @@ -1,331 +0,0 @@ -#include - -#ifdef WIN32 // FIXME: at the end remove me at the end, usefull only fo appveyour debug -#ifdef _DEBUG - #undef _DEBUG - #include - #define _DEBUG -#else - #include -#endif -#endif - -template -void assert_nonzero_rows(const T& mat, std::string name) { - if (mat.rows() == 0) { - throw pybind11::value_error("Parameter " + name + " has shape 0 at dimension 0. Expected " + name + ".shape[0] > 0."); - } -} - -template -void assert_cols_equals(const T& mat, int cols, std::string name) { - if (mat.cols() != cols) { - throw pybind11::value_error("Parameter " + name + " has invalid shape at dimension 1, expected " + name + ".shape[1] = " + std::to_string(cols) + - " but got " + name + ".shape = [" + std::to_string(mat.rows()) + ", " + std::to_string(mat.cols()) + "]"); - } -} - -template -void assert_rows_equals(const T& mat, int rows, std::string name) { - if (mat.rows() != rows) { - throw pybind11::value_error("Parameter " + name + " has invalid shape at dimension 0, expected " + name + ".shape[0] = " + std::to_string(rows) + - " but got " + name + ".shape = [" + std::to_string(mat.rows()) + ", " + std::to_string(mat.cols()) + "]"); - } -} - -template -void assert_size_equals(const T& mat, int size, std::string name) { - if (mat.size() != size) { - throw pybind11::value_error("Parameter " + name + " has invalid size expected " + name + ".size() = " + std::to_string(size) + - " but got " + name + ".shape = [" + std::to_string(mat.rows()) + ", " + std::to_string(mat.cols()) + "]"); - } -} - -template -void assert_shape_equals(const T& mat, int rows, int cols, std::string name) { - if (mat.rows() != rows || mat.cols() != cols) { - throw pybind11::value_error("Parameter " + name + " has invalid shape, expected " + name + ".shape = [" + std::to_string(rows) + ", " + std::to_string(cols) + - "] but got " + name + ".shape = [" + std::to_string(mat.rows()) + ", " + std::to_string(mat.cols()) + "]"); - } -} - - -template -void assert_shapes_match(const T1& mat1, const T2& mat2, std::string name1, std::string name2) { - if (mat1.rows() != mat2.rows() || mat1.cols() != mat2.cols()) { - throw pybind11::value_error("Parameters " + name1 + " and " + name2 + " must have the same shape but got " + - name1 + ".shape = [" + std::to_string(mat1.rows()) + ", " + std::to_string(mat1.cols()) + "] and " + - name2 + ".shape = [" + std::to_string(mat2.rows()) + ", " + std::to_string(mat2.cols()) + "]"); - } -} - -template -void assert_rows_match(const T1& mat1, const T2& mat2, std::string name1, std::string name2) { - if (mat1.rows() != mat2.rows()) { - throw pybind11::value_error("Parameters " + name1 + " and " + name2 + " must have the same shape at dimension 0 (rows) but got " + - name1 + ".shape = [" + std::to_string(mat1.rows()) + ", " + std::to_string(mat1.cols()) + "] and " + - name2 + ".shape = [" + std::to_string(mat2.rows()) + ", " + std::to_string(mat2.cols()) + "]"); - } -} - -template -void assert_cols_match(const T1& mat1, const T2& mat2, std::string name1, std::string name2) { - if (mat1.cols() != mat2.cols()) { - throw pybind11::value_error("Parameters " + name1 + " and " + name2 + " must have the same shape at dimension 1 (cols) but got " + - name1 + ".shape = [" + std::to_string(mat1.rows()) + ", " + std::to_string(mat1.cols()) + "] and " + - name2 + ".shape = [" + std::to_string(mat2.rows()) + ", " + std::to_string(mat2.cols()) + "]"); - } -} - - - -template -void assert_rows_match(const T1& mat1, int mat2_rows, int mat2_cols, std::string name1, std::string name2) { - if (mat1.rows() != mat2_rows) { - throw pybind11::value_error("Parameters " + name1 + " and " + name2 + " must have the same shape at dimension 0 (rows) but got " + - name1 + ".shape = [" + std::to_string(mat1.rows()) + ", " + std::to_string(mat1.cols()) + "] and " + - name2 + ".shape = [" + std::to_string(mat2_rows) + ", " + std::to_string(mat2_cols) + "]"); - } -} - -template -void assert_cols_match(const T1& mat1, int mat2_rows, int mat2_cols, std::string name1, std::string name2) { - if (mat1.cols() != mat2_cols) { - throw pybind11::value_error("Parameters " + name1 + " and " + name2 + " must have the same shape at dimension 1 (cols) but got " + - name1 + ".shape = [" + std::to_string(mat1.rows()) + ", " + std::to_string(mat1.cols()) + "] and " + - name2 + ".shape = [" + std::to_string(mat2_rows) + ", " + std::to_string(mat2_cols) + "]"); - } -} - -template -void assert_shapes_match(const T1& mat1, int mat2_rows, int mat2_cols, std::string name1, std::string name2) { - if (mat1.rows() != mat2_rows || mat1.cols() != mat2_cols) { - throw pybind11::value_error("Parameters " + name1 + " and " + name2 + " must have the same shape but got " + - name1 + ".shape = [" + std::to_string(mat1.rows()) + ", " + std::to_string(mat1.cols()) + "] and " + - name2 + ".shape = [" + std::to_string(mat2_rows) + ", " + std::to_string(mat2_cols) + "]"); - } -} - - -template -void assert_valid_tet_or_tri_mesh_faces(const TF& f, std::string f_name="f") { - if (f.rows() <= 0) { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (f.cols() != 3 && f.cols() != 4) { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " must have shape [#faces, 3] (for a triangle mesh) or [#faces, 4] (for a tet mesh) " + - "but got " + f_name + ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } -} - -template -void assert_valid_simplex_idxs(const TF& f, std::string f_name="f") { - if (f.rows() <= 0) { - throw pybind11::value_error("Invalid simplex indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (f.cols() < 2) { - throw pybind11::value_error("Invalid simplex indices, " + f_name + " must have shape [#faces, #edges] with #edges >= 2 for a valid simplex." + - "but got " + f_name + ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } -} - -template -void assert_valid_tri_mesh_faces(const TF &f, std::string f_name = "f") -{ - if (f.rows() <= 0) - { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (f.cols() != 3) - { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " must have shape [#faces, 3] " + - "but got " + f_name + ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } -} - -template -void assert_valid_tet_or_tri_mesh(const TV& v, const TF& f, std::string v_name="v", std::string f_name="f") { - if (v.rows() <= 0) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " has zero rows (" + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]) "); - } - if (f.rows() <= 0) { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (v.cols() != 3) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " must have shape [#vertices, 3] but got " + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]"); - } - if (f.cols() != 3 && f.cols() != 4) { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " must have shape [#faces, 3] (for a triangle mesh) or [#faces, 4] (for a tet mesh) " + - "but got " + f_name + ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } -} - -template -void assert_valid_tet_or_tri_mesh_23d(const TV &v, const TF &f, std::string v_name = "v", std::string f_name = "f") -{ - if (v.rows() <= 0) - { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " has zero rows (" + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]) "); - } - if (f.rows() <= 0) - { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (v.cols() != 3 && v.cols() != 2) - { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " must have shape [#vertices, 3] or [#vertices, 2] but got " + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]"); - } - - if (f.cols() != 3 && f.cols() != 4) - { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " must have shape [#faces, 3] (for a triangle mesh) or [#faces, 4] (for a tet mesh) " + - "but got " + f_name + ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } - - if (v.cols() == 2 && f.cols() == 4) - { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " must have shape [#vertices, 3] (and not 2) to be compatible with " + f_name + " which are tets, shape [#faces, 4]"); - } -} - -template -void assert_valid_3d_tri_mesh(const TV& v, const TF& f, std::string v_name="v", std::string f_name="f") { - if (v.rows() <= 0) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " has zero rows (" + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]) "); - } - if (f.rows() <= 0) { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (v.cols() != 3) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " must have shape [#vertices, 3] but got " + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]"); - } - if (f.cols() != 3) { - throw pybind11::value_error("Invalid mesh faces, " + f_name + " must have shape [#faces, 3] but got " + f_name + - ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } -} - -template -void assert_valid_3d_quad_mesh(const TV& v, const TF& f, std::string v_name="v", std::string f_name="f") { - if (v.rows() <= 0) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " has zero rows (" + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]) "); - } - if (f.rows() <= 0) { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (v.cols() != 3) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " must have shape [#vertices, 3] but got " + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]"); - } - if (f.cols() != 4) { - throw pybind11::value_error("Invalid mesh faces, " + f_name + " must have shape [#faces, 4] but got " + f_name + - ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } -} - -template -void assert_valid_23d_tri_mesh(const TV &v, const TF &f, std::string v_name = "v", std::string f_name = "f") -{ - if (v.rows() <= 0) - { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " has zero rows (" + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]) "); - } - if (f.rows() <= 0) - { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (v.cols() != 3 && v.cols() != 2) - { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " must have shape [#vertices, 3] or [#vertices, 2] but got " + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]"); - } - if (f.cols() != 3) - { - throw pybind11::value_error("Invalid mesh faces, " + f_name + " must have shape [#faces, 3] but got " + f_name + - ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } -} - -template -void assert_valid_tet_mesh(const TV& v, const TF& t, std::string v_name="v", std::string t_name="t") { - if (v.rows() <= 0) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " has zero rows (" + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]) "); - } - if (t.rows() <= 0) { - throw pybind11::value_error("Invalid mesh indices, " + t_name + " has zero rows (" + t_name + ".shape = [" + - std::to_string(t.rows()) + ", " + std::to_string(t.cols()) + "]) "); - } - - if (v.cols() != 3) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " must have shape [#vertices, 3] but got " + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]"); - } - if (t.cols() != 4) { - throw pybind11::value_error("Invalid mesh tets, " + t_name + " must have shape [#tets, 4] but got " + t_name + - ".shape = [" + std::to_string(t.rows()) + ", " + std::to_string(t.cols()) + "]"); - } -} - -template -void assert_valid_2d_tri_mesh(const TV& v, const TF& f, std::string v_name="v", std::string f_name="f") { - if (v.rows() <= 0) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " has zero rows (" + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]) "); - } - if (f.rows() <= 0) { - throw pybind11::value_error("Invalid mesh indices, " + f_name + " has zero rows (" + f_name + ".shape = [" + - std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]) "); - } - - if (v.cols() != 2) { - throw pybind11::value_error("Invalid mesh vertices, " + v_name + " must have shape [#vertices, 2] but got " + v_name + - ".shape = [" + std::to_string(v.rows()) + ", " + std::to_string(v.cols()) + "]"); - } - if (f.cols() != 3) { - throw pybind11::value_error("Invalid mesh faces, " + f_name + " must have shape [#faces, 3] but got " + f_name + - ".shape = [" + std::to_string(f.rows()) + ", " + std::to_string(f.cols()) + "]"); - } -} - -template -void assert_valid_bone_transforms(const TV& t, std::string t_name="t") { - if (t.rows() <= 0) { - throw pybind11::value_error("Invalid number of transforms, " + t_name + " has zero rows (" + t_name + - ".shape = [" + std::to_string(t.rows()) + ", " + std::to_string(t.cols()) + "]) "); - } - - if (t.cols() != 3) { - throw pybind11::value_error("Invalid number of cols in transforms, " + t_name + " must have shape [#bones * 4, 3] but got " + t_name + - ".shape = [" + std::to_string(t.rows()) + ", " + std::to_string(t.cols()) + "]"); - } - - if (t.rows() % 4 != 0) { - throw pybind11::value_error("Invalid number of rows in transforms, " + t_name + " must have shape [#bones * 4, 3] but got " + t_name + - ".shape = [" + std::to_string(t.rows()) + ", " + std::to_string(t.cols()) + "]."); - } -} diff --git a/src/include/typedefs.h b/src/include/typedefs.h deleted file mode 100644 index 2afdc886..00000000 --- a/src/include/typedefs.h +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include - - -const int IglDefaultOptions = Eigen::RowMajor; - -constexpr bool opts_dontalign(int options) { - return (options == (Eigen::ColMajor | Eigen::DontAlign) || options == (Eigen::RowMajor | Eigen::DontAlign)); -} - -template -struct OptExtractor { - enum { - Options = Opts - }; -}; - -template -struct OptExtractor { - enum Options { - Options = Eigen::RowMajor - }; -}; - -template -using EigenSparseLike = Eigen::SparseMatrix; // FIXME: Maybe we should output CSR if LikeT is row major - -template -using EigenDenseLike = Eigen::Matrix::Options, Eigen::Dynamic, Eigen::Dynamic>; - -template -using EigenDense = Eigen::Matrix; - -typedef Eigen::Matrix EigenDenseF32; -typedef Eigen::Matrix EigenDenseF64; -typedef Eigen::Matrix EigenDenseI32; -typedef Eigen::Matrix EigenDenseI64; - -//TODO replace with fancy CMAKE -#ifdef WIN32 -typedef EigenDenseF64 EigenDenseFloat; -typedef EigenDenseI32 EigenDenseInt; -#else -typedef EigenDenseF64 EigenDenseFloat; -typedef EigenDenseI64 EigenDenseInt; -#endif - -typedef Eigen::SparseMatrix EigenSparseF32; -typedef Eigen::SparseMatrix EigenSparseF64; -typedef Eigen::SparseMatrix EigenSparseI32; -typedef Eigen::SparseMatrix EigenSparseI64; diff --git a/src/inradius.cpp b/src/inradius.cpp deleted file mode 100644 index 708c9364..00000000 --- a/src/inradius.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_inradius = R"igl_Qu8mg5v7( - Compute the inradius of each triangle in a mesh (V,F) - -Parameters ----------- - V #V by dim list of mesh vertex positions - F #F by 3 list of triangle indices into V - -Returns -------- -R #F list of inradii - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(inradius) -npe_doc(ds_inradius) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_23d_tri_mesh(v, f); - EigenDenseLike r; - igl::inradius(v, f, r); - return npe::move(r); - -npe_end_code() - - diff --git a/src/internal_angles.cpp b/src/internal_angles.cpp deleted file mode 100644 index cdfa7e99..00000000 --- a/src/internal_angles.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_internal_angles = R"igl_Qu8mg5v7( -Computes internal angles for a triangle mesh. - -Parameters ----------- -v : #v by dim array of mesh vertex nD positions -f : #f by poly-size array of face (triangle) indices - -Returns -------- -k : #f by poly-size array of internal angles. For triangles, columns correspond to edges [1,2],[2,0],[0,1]. - -See also --------- -None - -Notes ------ -If poly-size ≠ 3 then dim must equal 3. - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(internal_angles) -npe_doc(ds_internal_angles) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - -npe_begin_code() - - assert_valid_23d_tri_mesh(v, f); - EigenDenseLike k; - igl::internal_angles(v, f, k); - return npe::move(k); - -npe_end_code() -//#include - -//const char* ds_internal_angles_using_squared_edge_lengths = R"igl_Qu8mg5v7( - -//Parameters -//---------- - - -//Returns -//------- - - -//See also -//-------- - - -//Notes -//----- -//None - -//Examples -//-------- - -// Inputs: -// L_sq #F by 3 list of squared edge lengths -// Output: -// K #F by poly-size eigen Matrix of internal angles -// for triangles, columns correspond to edges [1,2],[2,0],[0,1] -// -// Note: -// Usage of internal_angles_using_squared_edge_lengths is preferred to internal_angles_using_squared_edge_lengths -//)igl_Qu8mg5v7"; - -//npe_function(internal_angles_using_squared_edge_lengths) -//npe_doc(ds_internal_angles_using_squared_edge_lengths) - -//npe_arg(l_sq, dense_float, dense_double) - - -//npe_begin_code() - -// EigenDense k; -// igl::internal_angles_using_squared_edge_lengths(l_sq, k); -// return npe::move(k); - -//npe_end_code() -//#include - -//const char* ds_internal_angles_using_edge_lengths = R"igl_Qu8mg5v7( - -//Parameters -//---------- - - -//Returns -//------- - - -//See also -//-------- - - -//Notes -//----- -//None - -//Examples -//-------- - -// Inputs: -// L #F by 3 list of edge lengths -// Output: -// K #F by poly-size eigen Matrix of internal angles -// for triangles, columns correspond to edges [1,2],[2,0],[0,1] -// -// Note: -// Usage of internal_angles_using_squared_edge_lengths is preferred to internal_angles_using_squared_edge_lengths -// This function is deprecated and probably will be removed in future versions -//)igl_Qu8mg5v7"; - -//npe_function(internal_angles_using_edge_lengths) -//npe_doc(ds_internal_angles_using_edge_lengths) - -//npe_arg(l, dense_float, dense_double) - - -//npe_begin_code() - -// EigenDense k; -// igl::internal_angles_using_edge_lengths(l, k); -// return npe::move(k); - -//npe_end_code() - - diff --git a/src/intrinsic_delaunay_cotmatrix.cpp b/src/intrinsic_delaunay_cotmatrix.cpp deleted file mode 100644 index d0edc3da..00000000 --- a/src/intrinsic_delaunay_cotmatrix.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_intrinsic_delaunay_cotmatrix = R"igl_Qu8mg5v7( - -INTRINSIC_DELAUNAY_COTMATRIX Computes the discrete cotangent Laplacian of a -mesh after converting it into its intrinsic Delaunay triangulation (see, -e.g., [Fisher et al. 2007]. - -Parameters ----------- - -V #V by dim list of mesh vertex positions -F #F by 3 list of mesh elements (triangles or tetrahedra) - -Returns -------- - -L #V by #V cotangent matrix, each row i corresponding to V(i,:) -l_intrinsic #F by 3 list of intrinsic edge-lengths used to compute L -F_intrinsic #F by 3 list of intrinsic face indices used to compute L - -See also --------- -intrinsic_delaunay_triangulation, cotmatrix, cotmatrix_intrinsic - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(intrinsic_delaunay_cotmatrix) -npe_doc(ds_intrinsic_delaunay_cotmatrix) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - Eigen::SparseMatrix l; - EigenDense l_intrinsic; - EigenDense f_intrinsic; - igl::intrinsic_delaunay_cotmatrix(v, f, l, l_intrinsic, f_intrinsic); - return std::make_tuple(npe::move(l), npe::move(l_intrinsic), npe::move(f_intrinsic)); - -npe_end_code() - diff --git a/src/intrinsic_delaunay_triangulation.cpp b/src/intrinsic_delaunay_triangulation.cpp deleted file mode 100644 index 446611a0..00000000 --- a/src/intrinsic_delaunay_triangulation.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -const char *ds_intrinsic_delaunay_triangulation = R"igl_Qu8mg5v7( - -INTRINSIC_DELAUNAY_TRIANGULATION Flip edges _intrinsically_ until all are -"intrinsic Delaunay". See "An algorithm for the construction of intrinsic -delaunay triangulations with applications to digital geometry processing" -[Fisher et al. 2007]. - -Parameters ----------- - -l_in #F_in by 3 list of edge lengths (see edge_lengths) -F_in #F_in by 3 list of face indices into some unspecified vertex list V - -Returns -------- - -l #F by 3 list of edge lengths -F #F by 3 list of new face indices. Note: Combinatorially F may contain - non-manifold edges, duplicate faces and self-loops (e.g., an edge [1,1] - or a face [1,1,1]). However, the *intrinsic geometry* is still - well-defined and correct. See [Fisher et al. 2007] Figure 3 and 2nd to - last paragraph of 1st page. Since F may be "non-eddge-manifold" in the - usual combinatorial sense, it may be useful to call the more verbose - overload below if disentangling edges will be necessary later on. - Calling unique_edge_map on this F will give a _different_ result than - those outputs. - -See also --------- - -is_intrinsic_delaunay - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(intrinsic_delaunay_triangulation) -npe_doc(ds_intrinsic_delaunay_triangulation) - -npe_arg(l_in, dense_float, dense_double) -npe_arg(f_in, dense_int32, dense_int64) - - -npe_begin_code() - assert_rows_match(l_in, f_in, "l_in", "f_in"); - assert_cols_equals(l_in, 3, "l_in"); - assert_valid_tri_mesh_faces(f_in); - - EigenDense l; - EigenDense f; - igl::intrinsic_delaunay_triangulation(l_in, f_in, l, f); - return std::make_tuple(npe::move(l), npe::move(f)); - -npe_end_code() - - -const char *ds_intrinsic_delaunay_triangulation1 = R"igl_Qu8mg5v71( - -INTRINSIC_DELAUNAY_TRIANGULATION Flip edges _intrinsically_ until all are -"intrinsic Delaunay". See "An algorithm for the construction of intrinsic -delaunay triangulations with applications to digital geometry processing" -[Fisher et al. 2007]. - -Parameters ----------- - -l_in #F_in by 3 list of edge lengths (see edge_lengths) -F_in #F_in by 3 list of face indices into some unspecified vertex list V - -Returns -------- - -E #F*3 by 2 list of all directed edges, such that E.row(f+#F*c) is the - edge opposite F(f,c) -uE #uE by 2 list of unique undirected edges -EMAP #F*3 list of indices into uE, mapping each directed edge to unique - undirected edge -uE2E #uE list of lists of indices into E of coexisting edges - -See also --------- -unique_edge_map - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v71"; - -npe_function(intrinsic_delaunay_triangulation_edges) -npe_doc(ds_intrinsic_delaunay_triangulation1) - -npe_arg(l_in, dense_float, dense_double) -npe_arg(f_in, dense_int32, dense_int64) - - -npe_begin_code() - assert_rows_match(l_in, f_in, "l_in", "f_in"); - assert_cols_equals(l_in, 3, "l_in"); - assert_valid_tri_mesh_faces(f_in); - - EigenDense l; - EigenDense f; - EigenDense e; - EigenDense u_e; - Eigen::Matrix emap; - std::vector> u_e2_e; - igl::intrinsic_delaunay_triangulation(l_in, f_in, l, f, e, u_e, emap, u_e2_e); - std::vector> u_e2_e_copy; - - u_e2_e_copy.resize(u_e2_e.size()); - - for(size_t i = 0; i < u_e2_e_copy.size(); ++i) - { - u_e2_e_copy.resize(u_e2_e[i].size()); - for (size_t j = 0; j < u_e2_e_copy[i].size(); ++j) - u_e2_e_copy[i][j] = u_e2_e[i][j]; - } - - return std::make_tuple(npe::move(l), npe::move(f), npe::move(e), npe::move(u_e), npe::move(emap), u_e2_e_copy); - - npe_end_code() diff --git a/src/is_border_vertex.cpp b/src/is_border_vertex.cpp deleted file mode 100644 index 964fc2fe..00000000 --- a/src/is_border_vertex.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -const char *ds_is_border_vertex = R"igl_Qu8mg5v7( -Determine vertices on open boundary of a (manifold) mesh with triangle faces F -Parameters ----------- - V #V by dim list of vertex positions - F #F by 3 list of triangle indices - -Returns -------- -#V vector of bools revealing whether vertices are on boundary - -See also --------- - - -Notes ------ -Known Bugs: - - assumes mesh is edge manifold - -Examples --------- - - - -)igl_Qu8mg5v7"; - -npe_function(is_border_vertex) -npe_doc(ds_is_border_vertex) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_23d_tri_mesh(v, f); - const auto res = igl::is_border_vertex(f); - return res; - -npe_end_code() - - diff --git a/src/is_delaunay.cpp b/src/is_delaunay.cpp deleted file mode 100644 index e49c8fe4..00000000 --- a/src/is_delaunay.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_is_delaunay = R"igl_Qu8mg5v7( - -IS_DELAUNAY Determine if each edge in the mesh (V,F) is Delaunay. - - -Parameters ----------- - -V #V by dim list of vertex positions -F #F by 3 list of triangles indices - -Returns -------- - -D #F by 3 list of bools revealing whether edges corresponding 23 31 12 - are locally Delaunay. Boundary edges are by definition Delaunay. - Non-Manifold edges are by definition not Delaunay. - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(is_delaunay) -npe_doc(ds_is_delaunay) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_23d_tri_mesh(v, f); - - EigenDense d; - igl::is_delaunay(v, f, d); - return npe::move(d); - -npe_end_code() - diff --git a/src/is_edge_manifold.cpp b/src/is_edge_manifold.cpp deleted file mode 100644 index b93a9481..00000000 --- a/src/is_edge_manifold.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example, decide if to remove the first function - -#include -#include -#include -#include - - - -const char* ds_is_edge_manifold = R"igl_Qu8mg5v7( -See is_edge_manifold for the documentation. -)igl_Qu8mg5v7"; - -npe_function(is_edge_manifold) -npe_doc(ds_is_edge_manifold) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - return igl::is_edge_manifold(f); - -npe_end_code() - - diff --git a/src/is_intrinsic_delaunay.cpp b/src/is_intrinsic_delaunay.cpp deleted file mode 100644 index 3a504cc8..00000000 --- a/src/is_intrinsic_delaunay.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_is_intrinsic_delaunay = R"igl_Qu8mg5v7( - -IS_INTRINSIC_DELAUNAY Determine if each edge in the mesh (V,F) is Delaunay. - - -Parameters ----------- - l #l by dim list of edge lengths - F #F by 3 list of triangles indices - -Returns -------- - -D #F by 3 list of bools revealing whether edges corresponding 23 31 12 - are locally Delaunay. Boundary edges are by definition Delaunay. - Non-Manifold edges are by definition not Delaunay. - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(is_intrinsic_delaunay) -npe_doc(ds_is_intrinsic_delaunay) - -npe_arg(l, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_rows_match(l, f, "l", "f"); - assert_cols_equals(l, 3, "l"); - assert_valid_tri_mesh_faces(f); - - EigenDense d; - igl::is_intrinsic_delaunay(l, f, d); - return npe::move(d); - -npe_end_code() - diff --git a/src/is_irregular_vertex.cpp b/src/is_irregular_vertex.cpp deleted file mode 100644 index d29216f7..00000000 --- a/src/is_irregular_vertex.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -const char* ds_is_irregular_vertex = R"igl_Qu8mg5v7( -Determine if a vertex is irregular, i.e. it has more than 6 (triangles) or 4 (quads) incident edges. Vertices on the boundary are ignored. - -Parameters ----------- -f : #f by 3[4] array of triangle[quads] indices - -Returns -------- -s : #v list of bools revealing whether vertices are singular - -See also --------- -None - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(is_irregular_vertex) -npe_doc(ds_is_irregular_vertex) - -npe_arg(f, dense_int32, dense_int64) - -npe_begin_code() - const std::vector res = igl::is_irregular_vertex(f); - return res; - -npe_end_code() - - diff --git a/src/isolines.cpp b/src/isolines.cpp deleted file mode 100644 index 77f9983b..00000000 --- a/src/isolines.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example - -#include -#include -#include -#include - -const char *ds_isolines = R"igl_Qu8mg5v7( - Constructs isolines for a function z given on a mesh (V,F) - -Parameters ----------- - V #V by dim list of mesh vertex positions - F #F by 3 list of mesh triangle indices into V - S #S by 1 list of per-vertex scalar values - vals #vals by 1 list of values to compute isolines for - -Returns -------- - iV #iV by dim list of isoline vertex positions - iE #iE by 2 list of edge indices into iV - I #iE by 1 list of indices into vals indicating which value - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(isolines) -npe_doc(ds_isolines) - -npe_arg(V, dense_float, dense_double) -npe_arg(F, dense_int32, dense_int64) -npe_arg(S, npe_matches(V)) -npe_arg(vals, npe_matches(S)) - - -npe_begin_code() - - assert_valid_23d_tri_mesh(V, F); - assert_rows_match(V, S, "V", "S"); - assert_cols_equals(S, 1, "S"); - EigenDenseLike iV; - EigenDenseLike iE; - Eigen::Matrix I; - Eigen::Matrix vals_copy = vals; - igl::isolines(V, F, S.col(0), vals_copy, iV, iE, I); - return std::make_tuple(npe::move(iV), npe::move(iE), npe::move(I)); - -npe_end_code() - - diff --git a/src/iterative_closest_point.cpp b/src/iterative_closest_point.cpp deleted file mode 100644 index ed9e3043..00000000 --- a/src/iterative_closest_point.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_iterative_closest_point = R"igl_Qu8mg5v7( - -Solve for the rigid transformation that places mesh X onto mesh Y using the -iterative closest point method. In particular, optimize: - -min ∫_X inf ‖x*R+t - y‖² dx -R∈SO(3) y∈Y -t∈R³ - -Typically optimization strategies include using Gauss Newton -("point-to-plane" linearization) and stochastic descent (sparse random -sampling each iteration). - -Parameters ----------- -VX #VX by 3 list of mesh X vertices -FX #FX by 3 list of mesh X triangle indices into rows of VX -VY #VY by 3 list of mesh Y vertices -FY #FY by 3 list of mesh Y triangle indices into rows of VY -num_samples number of random samples to use (larger --> more accurate, - but also more suceptible to sticking to local minimum) - -Returns -------- -R 3x3 rotation matrix so that (VX*R+t,FX) ~~ (VY,FY) -t 1x3 translation row vector - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(iterative_closest_point) -npe_doc(ds_iterative_closest_point) - -npe_arg(vx, dense_float, dense_double) -npe_arg(fx, dense_int32, dense_int64) -npe_arg(vy, npe_matches(vx)) -npe_arg(fy, npe_matches(fx)) -npe_arg(num_samples, int) -npe_arg(max_iters, int) - - -npe_begin_code() - assert_valid_3d_tri_mesh(vx, fx, "vx", "fx"); - assert_valid_3d_tri_mesh(vy, fy, "vy", "fy"); - - Eigen::MatrixXd vx_copy = vx.template cast(); - Eigen::MatrixXd vy_copy = vy.template cast(); - - Eigen::MatrixXi fx_copy = fx.template cast(); - Eigen::MatrixXi fy_copy = fy.template cast(); - - Eigen::Matrix3d r_copy; - Eigen::RowVector3d t_copy; - igl::iterative_closest_point(vx_copy, fx_copy, vy_copy, fy_copy, num_samples, max_iters, r_copy, t_copy); - - EigenDense r = r_copy.template cast(); - EigenDense t = t_copy.template cast(); - - return std::make_tuple(npe::move(r), npe::move(t)); - -npe_end_code() - - - - - - -//TODO requires the abb class - -// const char* ds_iterative_closest_point = R"igl_Qu8mg5v7( - -// Parameters -// ---------- - - -// Returns -// ------- - - -// See also -// -------- - - -// Notes -// ----- -// None - -// Examples -// -------- - -// Inputs: -// Ytree precomputed AABB tree for accelerating closest point queries -// NY #FY by 3 list of precomputed unit face normals -// )igl_Qu8mg5v7"; - -// npe_function(iterative_closest_point) -// npe_doc(ds_iterative_closest_point) - -// npe_arg(vx, dense_f32, dense_f64) -// npe_arg(fx, dense_f32, dense_f64) -// npe_arg(vy, dense_f32, dense_f64) -// npe_arg(fy, dense_f32, dense_f64) -// npe_arg(ytree, igl::AABB &) -// npe_arg(ny, dense_f32, dense_f64) -// npe_arg(num_samples, int) -// npe_arg(max_iters, int) - - -// npe_begin_code() - -// EigenDense r; -// EigenDense t; -// igl::iterative_closest_point(vx, fx, vy, fy, ytree, ny, num_samples, max_iters, r, t); -// return std::make_tuple(npe::move(r), npe::move(t)); - -// npe_end_code() - - diff --git a/src/lbs_matrix.cpp b/src/lbs_matrix.cpp deleted file mode 100644 index c36421ac..00000000 --- a/src/lbs_matrix.cpp +++ /dev/null @@ -1,220 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_lbs_matrix = R"igl_Qu8mg5v7( -LBS_MATRIX Linear blend skinning can be expressed by V' = M * T where V' is - a #V by dim matrix of deformed vertex positions (one vertex per row), M is a #V by (dim+1)*#T (composed of weights and rest positions) and T is a #T*(dim+1) by dim matrix of #T stacked transposed transformation matrices. - See equations (1) and (2) in "Fast Automatic Skinning Transformations" [Jacobson et al 2012] - -Parameters ----------- - V #V by dim list of rest positions - W #V+ by #T list of weights - - -Returns -------- -M #V by #T*(dim+1) - -See also --------- - - -Notes ------ -None - -Examples --------- - In MATLAB: - kron(ones(1,size(W,2)),[V ones(size(V,1),1)]).*kron(W,ones(1,size(V,2)+1)) -)igl_Qu8mg5v7"; - -npe_function(lbs_matrix) -npe_doc(ds_lbs_matrix) - -npe_arg(v, dense_float, dense_double) -npe_arg(w, dense_float, dense_double) - - -npe_begin_code() - assert_rows_match(v, w, "V", "W"); - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXd w_copy = w.template cast(); - Eigen::MatrixXd m; - igl::lbs_matrix(v_copy, w_copy, m); - EigenDenseLike m_row_maj = m.template cast(); - return npe::move(m_row_maj); - -npe_end_code() - -//skpping because of wierd comments... - - -// #include - -// const char* ds_lbs_matrix_column = R"igl_Qu8mg5v7( - -// Parameters -// ---------- - - -// Returns -// ------- - - -// See also -// -------- - - -// Notes -// ----- -// None - -// Examples -// -------- - -// LBS_MATRIX construct a matrix that when multiplied against a column of -// affine transformation entries computes new coordinates of the vertices - -// I'm not sure it makes since that the result is stored as a sparse matrix. -// The number of non-zeros per row *is* dependent on the number of mesh -// vertices and handles. - -// Inputs: -// V #V by dim list of vertex rest positions -// W #V by #handles list of correspondence weights -// Output: -// M #V * dim by #handles * dim * (dim+1) matrix such that -// new_V(:) = LBS(V,W,A) = reshape(M * A,size(V)), where A is a column -// vectors formed by the entries in each handle's dim by dim+1 -// transformation matrix. Specifcally, A = -// reshape(permute(Astack,[3 1 2]),n*dim*(dim+1),1) -// or A = [Lxx;Lyx;Lxy;Lyy;tx;ty], and likewise for other dim -// if Astack(:,:,i) is the dim by (dim+1) transformation at handle i -// )igl_Qu8mg5v7"; - -// npe_function(lbs_matrix_column) -// npe_doc(ds_lbs_matrix_column) - -// npe_arg(v, Eigen::MatrixXd &) -// npe_arg(w, Eigen::MatrixXd &) - - -// npe_begin_code() - -// Eigen::SparseMatrix & m; -// igl::lbs_matrix_column(v, w, m); -// return npe::move(m); - -// npe_end_code() -// #include - -// const char* ds_lbs_matrix_column = R"igl_Qu8mg5v7( -// See lbs_matrix_column for the documentation. -// )igl_Qu8mg5v7"; - -// npe_function(lbs_matrix_column) -// npe_doc(ds_lbs_matrix_column) - -// npe_arg(v, Eigen::MatrixXd &) -// npe_arg(w, Eigen::MatrixXd &) - - -// npe_begin_code() - -// Eigen::MatrixXd & m; -// igl::lbs_matrix_column(v, w, m); -// return npe::move(m); - -// npe_end_code() -// #include - -// const char* ds_lbs_matrix_column = R"igl_Qu8mg5v7( - -// Parameters -// ---------- - - -// Returns -// ------- - - -// See also -// -------- - - -// Notes -// ----- -// None - -// Examples -// -------- - -// Same as LBS_MATRIX above but instead of giving W as a full matrix of weights -// (each vertex has #handles weights), a constant number of weights are given -// for each vertex. - -// Inputs: -// V #V by dim list of vertex rest positions -// W #V by k list of k correspondence weights per vertex -// WI #V by k list of k correspondence weight indices per vertex. Such that -// W(j,WI(i)) gives the ith most significant correspondence weight on vertex j -// Output: -// M #V * dim by #handles * dim * (dim+1) matrix such that -// new_V(:) = LBS(V,W,A) = reshape(M * A,size(V)), where A is a column -// vectors formed by the entries in each handle's dim by dim+1 -// transformation matrix. Specifcally, A = -// reshape(permute(Astack,[3 1 2]),n*dim*(dim+1),1) -// or A = [Lxx;Lyx;Lxy;Lyy;tx;ty], and likewise for other dim -// if Astack(:,:,i) is the dim by (dim+1) transformation at handle i - -// )igl_Qu8mg5v7"; - -// npe_function(lbs_matrix_column) -// npe_doc(ds_lbs_matrix_column) - -// npe_arg(v, Eigen::MatrixXd &) -// npe_arg(w, Eigen::MatrixXd &) -// npe_arg(wi, Eigen::MatrixXi &) - - -// npe_begin_code() - -// Eigen::SparseMatrix & m; -// igl::lbs_matrix_column(v, w, wi, m); -// return npe::move(m); - -// npe_end_code() -// #include - -// const char* ds_lbs_matrix_column = R"igl_Qu8mg5v7( -// See lbs_matrix_column for the documentation. -// )igl_Qu8mg5v7"; - -// npe_function(lbs_matrix_column) -// npe_doc(ds_lbs_matrix_column) - -// npe_arg(v, Eigen::MatrixXd &) -// npe_arg(w, Eigen::MatrixXd &) -// npe_arg(wi, Eigen::MatrixXi &) - - -// npe_begin_code() - -// Eigen::MatrixXd & m; -// igl::lbs_matrix_column(v, w, wi, m); -// return npe::move(m); - -// npe_end_code() - - diff --git a/src/line_segment_in_rectangle.cpp b/src/line_segment_in_rectangle.cpp deleted file mode 100644 index 11238d10..00000000 --- a/src/line_segment_in_rectangle.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_line_segment_in_rectangle = R"igl_Qu8mg5v7( -Determine whether a line segment overlaps with a rectangle. - -Parameters ----------- -s source point of line segment -d dest point of line segment -A first corner of rectangle -B opposite corner of rectangle - -Returns -------- -Returns true if line segment is at all inside rectangle - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(line_segment_in_rectangle) -npe_doc(ds_line_segment_in_rectangle) - -npe_arg(s, dense_float, dense_double) -npe_arg(d, npe_matches(s)) -npe_arg(a, npe_matches(s)) -npe_arg(b, npe_matches(s)) - - -npe_begin_code() - assert_size_equals(s, 2, "s"); - assert_size_equals(d, 2, "d"); - assert_size_equals(a, 2, "a"); - assert_size_equals(b, 2, "b"); - - Eigen::Vector2d s_copy = s.template cast(); - Eigen::Vector2d d_copy = d.template cast(); - Eigen::Vector2d a_copy = a.template cast(); - Eigen::Vector2d b_copy = b.template cast(); - - return igl::line_segment_in_rectangle(s_copy, d_copy, a_copy, b_copy); - -npe_end_code() diff --git a/src/local_basis.cpp b/src/local_basis.cpp deleted file mode 100644 index 87673294..00000000 --- a/src/local_basis.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_local_basis = R"igl_Qu8mg5v7( -Compute a local orthogonal reference system for each triangle in the given mesh. - -Parameters ----------- -v : #v by 3 vertex array -f : #f by 3 array of mesh faces (must be triangles) - -Returns -------- -b1 : #f by 3 array, each vector is tangent to the triangle -b2 : #f by 3 array, each vector is tangent to the triangle and perpendicular to B1 -b3 : #f by 3 array, normal of the triangle - -See also --------- -adjacency_matrix - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(local_basis) -npe_doc(ds_local_basis) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - - EigenDenseLike b1; - EigenDenseLike b2; - EigenDenseLike b3; - igl::local_basis(v_copy, f_copy, b1, b2, b3); - return std::make_tuple(npe::move(b1), npe::move(b2), npe::move(b3)); - -npe_end_code() - - diff --git a/src/look_at.cpp b/src/look_at.cpp deleted file mode 100644 index 7535a5b3..00000000 --- a/src/look_at.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_look_at = R"igl_Qu8mg5v7( -Implementation of the deprecated gluLookAt function. - - -Parameters ----------- -eye 3-vector of eye position -center 3-vector of center reference point -up 3-vector of up vector - -Returns -------- -R 4x4 rotation matrix - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(look_at) -npe_doc(ds_look_at) - -npe_arg(eye, dense_float, dense_double) -npe_arg(center, npe_matches(eye)) -npe_arg(up, npe_matches(eye)) - - -npe_begin_code() - assert_size_equals(eye, 3, "eye"); - assert_size_equals(center, 3, "center"); - assert_size_equals(up, 3, "up"); - - Eigen::Vector3d eye_copy = eye.template cast(); - Eigen::Vector3d center_copy = center.template cast(); - Eigen::Vector3d up_copy = up.template cast(); - - Eigen::MatrixXd r_copy; - igl::look_at(eye_copy, center_copy, up_copy, r_copy); - Eigen::Matrix r = r_copy.template cast(); - return npe::move(r); - -npe_end_code() - - diff --git a/src/loop.cpp b/src/loop.cpp deleted file mode 100644 index 6a8cc9b2..00000000 --- a/src/loop.cpp +++ /dev/null @@ -1,108 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -#include -#include -#include -#include - -const char *ds_loop_mat = R"igl_Qu8mg5v7( - - LOOP Given the triangle mesh [V, F], where n_verts = V.rows(), computes - newV and a sparse matrix S s.t. [newV, newF] is the subdivided mesh where - newV = S*V. - -Parameters ----------- -n_verts an integer (number of mesh vertices) -F an m by 3 matrix of integers of triangle faces - -Returns -------- - -S a sparse matrix (will become the subdivision matrix) -newF a matrix containing the new faces - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(loop_subdivision_matrix) -npe_doc(ds_loop_mat) - -npe_arg(n_verts, int) -npe_arg(f, dense_int32, dense_int64) - -npe_begin_code() - assert_valid_tri_mesh_faces(f); - Eigen::SparseMatrix S; - EigenDenseLike nf; - igl::loop(n_verts, f, S, nf); - return std::make_tuple(npe::move(S), npe::move(nf)); -npe_end_code() - - - - - -const char *ds_loop = R"igl_Qu8mg5v7( - - LOOP Given the triangle mesh [V, F], where n_verts = V.rows(), computes - newV and a sparse matrix S s.t. [newV, newF] is the subdivided mesh where - newV = S*V. - -Parameters ----------- -V an n by 3 matrix of vertices -F an m by 3 matrix of integers of triangle faces -number_of_subdivs an integer that specifies how many subdivision steps to do - -Returns -------- - -NV a matrix containing the new vertices -NF a matrix containing the new faces - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(loop) -npe_doc(ds_loop) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(number_of_subdivs, int, 1) - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - EigenDenseLike nv; - EigenDenseLike nf; - - igl::loop(v, f, nv, nf, number_of_subdivs); - return std::make_tuple(npe::move(nv), npe::move(nf)); -npe_end_code() diff --git a/src/lscm.cpp b/src/lscm.cpp deleted file mode 100644 index 9b135ecd..00000000 --- a/src/lscm.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -#include -#include -#include -#include - -const char* ds_lscm = R"igl_Qu8mg5v7( -Compute a Least-squares conformal map parametrization. - -Parameters ----------- -v : #v by 3 array of mesh vertex positions -f : #f by 3 array of mesh faces (must be triangles) -b : #b boundary indices into v -bc : #b by 2 list of boundary values - -Returns -------- -uv #v by 2 list of 2D mesh vertex positions in UV space - -See also --------- -None - -Notes ------ -Derived in "Intrinsic Parameterizations of Surface Meshes" [Desbrun et al. -2002] and "Least Squares Conformal Maps for Automatic Texture Atlas -Generation" [Lévy et al. 2002]), though this implementation follows the -derivation in: "Spectral Conformal Parameterization" [Mullen et al. 2008] -(note, this does **not** implement the Eigen-decomposition based method in -[Mullen et al. 2008], which is not equivalent. Input should be a manifold -mesh (also no unreferenced vertices) and "boundary" (fixed vertices) `b` -should contain at least two vertices per connected component. -Returns true only on solver success. - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(lscm) -npe_doc(ds_lscm) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(b, npe_matches(f)) -npe_arg(bc, npe_matches(v)) -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_cols_equals(b, 1, "b"); - assert_rows_match(b, bc, "b", "bc"); - // assert_cols_match(f, bc, "f", "bc"); - assert_cols_equals(bc, 2, "bc"); - - // TODO: remove __copy - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::VectorXi b_copy = b.template cast(); - Eigen::MatrixXd bc_copy = bc.template cast(); - Eigen::MatrixXd uv; //TODO: major - bool success = igl::lscm(v_copy, f_copy, b_copy, bc_copy, uv); - EigenDenseFloat uv_row_major = uv; - return std::make_tuple(success, npe::move(uv_row_major)); - -npe_end_code() - - diff --git a/src/map_vertices_to_circle.cpp b/src/map_vertices_to_circle.cpp deleted file mode 100644 index a9c6c0ef..00000000 --- a/src/map_vertices_to_circle.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -#include -#include -#include -#include - -const char* ds_map_vertices_to_circle = R"igl_Qu8mg5v7( -Map the vertices whose indices are in a given boundary loop (bnd) on the unit circle with spacing proportional to the original boundary edge lengths. - -Parameters ----------- -v : #v by dim array of mesh vertex positions -b : #w list of vertex ids - -Returns -------- -uv : #w by 2 list of 2D positions on the unit circle for the vertices in b - -See also --------- -None - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(map_vertices_to_circle) -npe_doc(ds_map_vertices_to_circle) -npe_arg(v, dense_float, dense_double) -npe_arg(bnd, dense_int32, dense_int64) - -npe_begin_code() - - assert_nonzero_rows(v, "v"); - assert_nonzero_rows(bnd, "bnd"); - // TODO: remove __copy - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::VectorXi bnd_copy = bnd.template cast(); - Eigen::MatrixXd uv; - igl::map_vertices_to_circle(v_copy, bnd_copy, uv); - EigenDenseFloat uv_row_major = uv; - return npe::move(uv_row_major); - -npe_end_code() - - diff --git a/src/marching_cubes.cpp b/src/marching_cubes.cpp deleted file mode 100644 index c1965213..00000000 --- a/src/marching_cubes.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Thomas -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -const char* ds_marching_cubes = R"igl_Qu8mg5v7( - -marching_cubes performs marching cubes reconstruction on a grid defined by values, and -points, and generates a mesh defined by vertices and faces - -Parameters ----------- -S nx*ny*nz list of values at each grid corner i.e. S(x + y*xres + z*xres*yres) for corner (x,y,z) -GV nx*ny*nz by 3 array of corresponding grid corner vertex locations - points, ordered in x,y,z order: - points[index] = the point at (x,y,z) where : - x = (index % (xres -1), - y = (index / (xres-1)) %(yres-1), - z = index / (xres -1) / (yres -1) ). - where x,y,z index x, y, z dimensions - i.e. index = x + y*xres + z*xres*yres -nx resolutions of the grid in x dimension -ny resolutions of the grid in y dimension -nz resolutions of the grid in z dimension -isovalue the isovalue of the surface to reconstruct - - -Returns -------- -V #V by 3 list of mesh vertex positions -F #F by 3 list of mesh triangle indices into rows of V - -See also --------- - - -Notes ------ -None - -Examples --------- ->> import numpy as np ->> K = np.linspace( -1.0, 1.0, 64) ->> pts = np.array([[x,y,z] for x in K for y in K for z in K]) ->> S = signed_distance(pts, v, f) ->> v, f = marching_cubes(S, pts, nx, ny, nz, 0.0) - -)igl_Qu8mg5v7"; - -npe_function(marching_cubes) -npe_doc(ds_marching_cubes) -npe_arg(s, dense_float, dense_double) -npe_arg(gv, dense_float, dense_double) -npe_arg(nx, int) -npe_arg(ny, int) -npe_arg(nz, int) -npe_default_arg(isovalue, double, 0.0) - -npe_begin_code() - // input checks - assert_rows_match(s, gv, "S", "GV"); - assert_cols_equals(s, 1, "S"); - assert_cols_equals(gv, 3, "GV"); - - // vertices and faces of marched iso surface - Eigen::MatrixXd SV; - Eigen::MatrixXi SF; - - Eigen::MatrixXd s_copy = s.template cast(); - Eigen::MatrixXd gv_copy = gv.template cast(); - - igl::marching_cubes(s_copy, gv_copy, nx, ny, nz, isovalue, SV, SF); - - EigenDenseLike svRowMajor = SV.template cast(); - EigenDenseLike sfRowMajor = SF.template cast(); - - return std::make_tuple(npe::move(svRowMajor), npe::move(sfRowMajor)); -npe_end_code() \ No newline at end of file diff --git a/src/marching_tets.cpp b/src/marching_tets.cpp deleted file mode 100644 index c601090a..00000000 --- a/src/marching_tets.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_marching_tets = R"igl_Qu8mg5v7( -Performs the marching tetrahedra algorithm on a tet mesh defined by TV and -TT with scalar values defined at each vertex in TV. The output is a -triangle mesh approximating the isosurface coresponding to the value -isovalue. - -Parameters ----------- -TV #tet_vertices x 3 array -- The vertices of the tetrahedral mesh -TT #tets x 4 array -- The indexes of each tet in the tetrahedral mesh -S #tet_vertices x 1 array -- The values defined on each tet vertex -isovalue scalar -- The isovalue of the level set we want to compute - -Returns -------- -SV : #SV x 3 array -- The vertices of the output level surface mesh -SF : #SF x 3 array -- The face indexes of the output level surface mesh -J : #SF list of indices into TT revealing which tet each face comes from -BC : #SV x #TV list of barycentric coordinates so that SV = BC*TV - -See also --------- - - -Notes ------ -None - -Examples --------- -sv, sf, j, bc = igl.marching_tets(tv, tt, s, isovalue) - -)igl_Qu8mg5v7"; - -npe_function(marching_tets) -npe_doc(ds_marching_tets) - -npe_arg(TV, dense_float, dense_double) -npe_arg(TT, dense_int32, dense_int64) -npe_arg(S, npe_matches(TV)) -npe_arg(isovalue, double) - -npe_begin_code() - assert_valid_tet_mesh(TV, TT, "TV", "TT"); - assert_rows_match(TV, S, "TV", "S"); - assert_cols_equals(S, 1, "S"); - - EigenDenseLike SV; - EigenDenseLike SF; - Eigen::Matrix J; - EigenSparseLike BC; - igl::marching_tets(TV, TT, S, isovalue, SV, SF, J, BC); - return std::make_tuple(npe::move(SV), npe::move(SF), npe::move(J), npe::move(BC)); - -npe_end_code() diff --git a/src/massmatrix.cpp b/src/massmatrix.cpp index 25364e5f..908fec43 100644 --- a/src/massmatrix.cpp +++ b/src/massmatrix.cpp @@ -1,68 +1,69 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include +#include "default_types.h" #include +#include +#include +#include +#include +#include -const char* ds_massmatrix = R"igl_Qu8mg5v7( -Constructs the mass (area) matrix for a given mesh (V,F). +namespace nb = nanobind; +using namespace nb::literals; -Parameters ----------- -v : #v by dim list of mesh vertex positions -f : #f by simplex_size list of mesh faces (must be triangles) -type : one of the following types: - -igl.MASSMATRIX_TYPE_BARYCENTRIC barycentric - -igl.MASSMATRIX_TYPE_VORONOI voronoi-hybrid (default) - -igl.MASSMATRIX_TYPE_FULL full (not implemented) +namespace pyigl +{ + nb::object massmatrix( + const nb::DRef &V, + const nb::DRef &F, + const std::string type) + { + Eigen::SparseMatrixN M; + igl::MassMatrixType t; + if(type == "barycentric") + { + t = igl::MASSMATRIX_TYPE_BARYCENTRIC; + }else if(type == "voronoi") + { + t = igl::MASSMATRIX_TYPE_VORONOI; + }else if(type == "full") + { + t = igl::MASSMATRIX_TYPE_FULL; + }else if(type == "default") + { + t = igl::MASSMATRIX_TYPE_DEFAULT; + }else + { + throw std::runtime_error("massmatrix: unknown type"); + } + igl::massmatrix(V,F,t,M); -Returns -------- -m : #v by #v mass matrix - -See also --------- -adjacency_matrix, cotmatrix, grad - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(massmatrix) -npe_doc(ds_massmatrix) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(type, int, 3) -npe_begin_code() - - assert_valid_tet_or_tri_mesh_23d(v, f); - static_assert(int(igl::MASSMATRIX_TYPE_BARYCENTRIC) == 0, "MASSMATRIX enum changed!"); - static_assert(int(igl::MASSMATRIX_TYPE_VORONOI) == 1, "MASSMATRIX enum changed!"); - static_assert(int(igl::MASSMATRIX_TYPE_FULL) == 2, "MASSMATRIX enum changed!"); - static_assert(int(igl::MASSMATRIX_TYPE_DEFAULT) == 3, "MASSMATRIX enum changed!"); - static_assert(int(igl::NUM_MASSMATRIX_TYPES) == 4, "MASSMATRIX enum changed!"); - - if (type >= igl::NUM_MASSMATRIX_TYPES ) { - std::string errmsg = - std::string("Invalid enum for type should be in the range 0 to ") + - std::to_string(igl::NUM_MASSMATRIX_TYPES-1); - throw pybind11::value_error(errmsg); + return nb::cast(M); } +} - EigenSparseLike m; - igl::massmatrix(v, f, igl::MassMatrixType(type), m); - return npe::move(m); - -npe_end_code() +// Bind the wrapper to the Python module +void bind_massmatrix(nb::module_ &m) +{ + m.def( + "massmatrix", + &pyigl::massmatrix, + "V"_a, + "F"_a, + "type"_a="default", +R"(Constructs the mass (area) matrix for a given mesh (V,F). +@tparam DerivedV derived type of eigen matrix for V (e.g. derived from + MatrixXd) +@tparam DerivedF derived type of eigen matrix for F (e.g. derived from + MatrixXi) +@tparam Scalar scalar type for eigen sparse matrix (e.g. double) +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by simplex_size list of mesh elements (triangles or tetrahedra) +@param[in] type one of the following ints: + MASSMATRIX_TYPE_BARYCENTRIC barycentric {default for tetrahedra} + MASSMATRIX_TYPE_VORONOI voronoi-hybrid {default for triangles} + MASSMATRIX_TYPE_FULL full +@param[out] M #V by #V mass matrix +\see cotmatrix)" + ); +} diff --git a/src/massmatrix_intrinsic.cpp b/src/massmatrix_intrinsic.cpp deleted file mode 100644 index 87f89d3d..00000000 --- a/src/massmatrix_intrinsic.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_massmatrix_intrinsic = R"igl_Qu8mg5v7( - -Constructs the mass (area) matrix for a given mesh (V,F). - - -Parameters ----------- -l #l by simplex_size list of mesh edge lengths -F #F by simplex_size list of mesh elements (triangles or tetrahedra) -type one of the following ints: - -igl.MASSMATRIX_TYPE_BARYCENTRIC barycentric - -igl.MASSMATRIX_TYPE_VORONOI voronoi-hybrid (default) - -igl.MASSMATRIX_TYPE_FULL full (not implemented) - -Returns -------- -M #V by #V mass matrix - -See also --------- -adjacency_matrix - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(massmatrix_intrinsic) -npe_doc(ds_massmatrix_intrinsic) - -npe_arg(l, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(type, int, 1) - - -npe_begin_code() - assert_rows_match(l, f, "l", "f"); - assert_cols_equals(l, 3, "l"); - assert_valid_tri_mesh_faces(f); - - static_assert(int(igl::MASSMATRIX_TYPE_BARYCENTRIC) == 0, "MASSMATRIX enum changed!"); - static_assert(int(igl::MASSMATRIX_TYPE_VORONOI) == 1, "MASSMATRIX enum changed!"); - static_assert(int(igl::MASSMATRIX_TYPE_FULL) == 2, "MASSMATRIX enum changed!"); - static_assert(int(igl::MASSMATRIX_TYPE_DEFAULT) == 3, "MASSMATRIX enum changed!"); - static_assert(int(igl::NUM_MASSMATRIX_TYPES) == 4, "MASSMATRIX enum changed!"); - - if (type >= igl::NUM_MASSMATRIX_TYPES ) - { - std::string errmsg = - std::string("Invalid enum for type should be in the range 0 to ") + - std::to_string(igl::NUM_MASSMATRIX_TYPES - 1); - throw pybind11::value_error(errmsg); - } - - Eigen::SparseMatrix m; - igl::massmatrix_intrinsic(l, f, igl::MassMatrixType(type), m); - return npe::move(m); - -npe_end_code() diff --git a/src/min_quad_with_fixed.cpp b/src/min_quad_with_fixed.cpp deleted file mode 100644 index 16c2fbda..00000000 --- a/src/min_quad_with_fixed.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example remove __copy - -#include -#include -#include - - - - - - -#include - -const char *ds_min_quad_with_fixed = R"igl_Qu8mg5v7( - -MIN_QUAD_WITH_FIXED Minimize a quadratic energy of the form -trace( 0.5*Z'*A*Z + Z'*B + constant ) -subject to -Z(known,:) = Y, and -Aeq*Z = Beq - -Parameters ----------- -A n by n matrix of quadratic coefficients -B n by 1 column of linear coefficients -known list of indices to known rows in Z -Y list of fixed values corresponding to known rows in Z -Aeq m by n list of linear equality constraint coefficients -Beq m by 1 list of linear equality constraint constant values -is_A_pd flag specifying whether A(unknown,unknown) is positive definite - - -Returns -------- -Z n by k solution - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(min_quad_with_fixed) -npe_doc(ds_min_quad_with_fixed) -//TODO missing npe_dense_like -npe_arg(A, sparse_float, sparse_double) -// npe_arg(B, npe_dense_like(A)) -npe_arg(B, dense_float, dense_double) -npe_arg(known, dense_int32, dense_int64) -npe_arg(Y, npe_matches(B)) -npe_arg(Aeq, npe_matches(A)) -npe_arg(Beq, npe_matches(B)) -npe_arg(is_A_pd, bool) - - -npe_begin_code() - - assert_nonzero_rows(A, "A"); - if(Aeq.size() > 0) - assert_cols_match(A, Aeq, "A", "Aeq"); - assert_rows_match(A, B, "A", "B"); - assert_cols_match(B, Y, "B", "Y"); - if (Beq.size() > 0) - assert_cols_match(B, Beq, "B", "Beq"); - assert_rows_match(Aeq, Beq, "Aeq", "Beq"); - - Eigen::SparseMatrix A_copy = A.template cast(); - Eigen::SparseMatrix Aeq_copy = Aeq.template cast(); - - Eigen::MatrixXd B_copy = B.template cast(); - Eigen::MatrixXd Y_copy = Y.template cast(); - Eigen::MatrixXd Beq_copy = Beq.template cast(); - - Eigen::MatrixXd sol; - - bool ok = igl::min_quad_with_fixed(A_copy, B_copy, known, Y_copy, Aeq_copy, Beq_copy, is_A_pd, sol); - // Eigen::Matrix sol_row_major = sol; - EigenDenseLike sol_row_major = sol.template cast < typename npe_Matrix_B::Scalar >(); - return std::make_pair(ok, npe::move(sol_row_major)); - -npe_end_code() diff --git a/src/module.cpp b/src/module.cpp new file mode 100644 index 00000000..47867f28 --- /dev/null +++ b/src/module.cpp @@ -0,0 +1,11 @@ +#include +namespace nb = nanobind; + +// generated by cmake +#include "BINDING_DECLARATIONS.in" + +NB_MODULE(pyigl, m) { + m.doc() = "libigl python bindings"; + // generated by cmake +#include "BINDING_INVOCATIONS.in" +} diff --git a/src/moments.cpp b/src/moments.cpp deleted file mode 100644 index 9a7d8189..00000000 --- a/src/moments.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_moments = R"igl_Qu8mg5v7( -Computes the moments of mass for a solid object bound by a triangle mesh. - -Parameters ----------- - V #V by dim list of rest domain positions - F #F by 3 list of triangle indices into V -Returns -------- - m0 zeroth moment of mass, total signed volume of solid. - m1 first moment of mass, center of mass times total mass - m2 second moment of mass, moment of inertia with center of mass as reference point - -)igl_Qu8mg5v7"; - -npe_function(moments) -npe_doc(ds_moments) - -npe_arg(V, dense_float, dense_double) -npe_arg(F, dense_int32, dense_int64) - -npe_begin_code() - assert_valid_3d_tri_mesh(V, F); - Eigen::Matrix m1; - Eigen::Matrix m2; - double m0; - igl::moments(V, F, m0, m1, m2); - return std::make_tuple(m0,npe::move(m1),npe::move(m2)); -npe_end_code() - diff --git a/src/mvc.cpp b/src/mvc.cpp deleted file mode 100644 index 77f9c236..00000000 --- a/src/mvc.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_mvc = R"igl_Qu8mg5v7( -MEAN VALUE COORDINATES - -Parameters ----------- -V #V x dim list of vertex positions (dim = 2 or dim = 3) -C #C x dim list of polygon vertex positions in counter-clockwise order (dim = 2 or dim = 3) - -Returns -------- -W weights, #V by #C matrix of weights - -See also --------- - - -Notes ------ -Known Bugs: implementation is listed as "Broken" - -Examples --------- -W = mvc(V,C) - -)igl_Qu8mg5v7"; - -npe_function(mvc) -npe_doc(ds_mvc) - -npe_arg(v, dense_float, dense_double) -npe_arg(c, npe_matches(v)) - - -npe_begin_code() - assert_nonzero_rows(v, "v"); - assert_nonzero_rows(c, "c"); - assert_cols_match(v, c, "v", "c"); - - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXd c_copy = c.template cast(); - - Eigen::MatrixXd w_copy; - igl::mvc(v_copy, c_copy, w_copy); - EigenDenseLike w = w_copy.template cast(); - return npe::move(w); -npe_end_code() - - diff --git a/src/normal_derivative.cpp b/src/normal_derivative.cpp deleted file mode 100644 index bbefd98a..00000000 --- a/src/normal_derivative.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_normal_derivative = R"igl_Qu8mg5v7( -NORMAL_DERIVATIVE Computes the directional derivative **normal** to - **all** (half-)edges of a triangle mesh (not just boundary edges). These - are integrated along the edge: they're the per-face constant gradient dot - the rotated edge vector (unit rotated edge vector for direction then - magnitude for integration). - -Parameters ----------- - V #V by dim list of mesh vertex positions - F #F by 3|4 list of triangle|tetrahedron indices into V - -Returns -------- - DD #F*3|4 by #V sparse matrix representing operator to compute - directional derivative with respect to each facet of each element. - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(normal_derivative) -npe_doc(ds_normal_derivative) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_tet_or_tri_mesh(v, f); - //inside there are plenty of MatrixXi... - Eigen::MatrixXi f_copy = f.template cast(); - - EigenSparseLike dd; - igl::normal_derivative(v, f_copy, dd); - return npe::move(dd); - -npe_end_code() - - diff --git a/src/offset_surface.cpp b/src/offset_surface.cpp deleted file mode 100644 index 55a1e1cb..00000000 --- a/src/offset_surface.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -// TODO: remove __copy -// copy is necessary since the winding number only supports matrices - -#include -#include -#include -#include - -const char *ds_offset_surface = R"igl_Qu8mg5v7( -Compute a triangulated offset surface using marching cubes on a grid of -signed distance values from the input triangle mesh. - -Parameters ----------- - V #V by 3 list of mesh vertex positions - F #F by 3 list of mesh triangle indices into V - isolevel iso level to extract (signed distance: negative inside) - s number of grid cells along longest side (controls resolution) - signed_distance_type type of signing to use one of SIGNED_DISTANCE_TYPE_PSEUDONORMAL, SIGNED_DISTANCE_TYPE_WINDING_NUMBER, SIGNED_DISTANCE_TYPE_DEFAULT, SIGNED_DISTANCE_TYPE_UNSIGNED, SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER - -Returns -------- - SV #SV by 3 list of output surface mesh vertex positions - SF #SF by 3 list of output mesh triangle indices into SV - GV #GV=side(0)*side(1)*side(2) by 3 list of grid cell centers - side list of number of grid cells in x, y, and z directions - So #GV by 3 list of signed distance values _near_ `isolevel` ("far" from `isolevel` these values are incorrect) - -See also --------- - - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(offset_surface) -npe_doc(ds_offset_surface) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(isolevel, double) -npe_arg(s, int) -npe_arg(signed_distance_type, int) -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - // Alec: I don't understand why all these copies are needed here. - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - - Eigen::MatrixXd sv_copy; - Eigen::MatrixXi sf_copy; - Eigen::MatrixXd gv_copy; - Eigen::MatrixXi side_copy; - Eigen::MatrixXd so_copy; - - igl::offset_surface(v_copy, f_copy, isolevel, s, igl::SignedDistanceType(signed_distance_type), sv_copy, sf_copy, gv_copy, side_copy, so_copy); - - EigenDenseLike sv = sv_copy.template cast(); - EigenDenseLike sf = sf_copy.template cast(); - EigenDenseLike gv = gv_copy.template cast(); - EigenDenseLike side = side_copy.template cast(); - EigenDenseLike so = so_copy.template cast(); - - return std::make_tuple(npe::move(sv), npe::move(sf), npe::move(gv), npe::move(side), npe::move(so)); - -npe_end_code() diff --git a/src/orient_halfedges.cpp b/src/orient_halfedges.cpp new file mode 100644 index 00000000..acaf94d7 --- /dev/null +++ b/src/orient_halfedges.cpp @@ -0,0 +1,40 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + nb::object orient_halfedges( + const nb::DRef &F) + { + Eigen::MatrixXI E,oE; + igl::orient_halfedges(F,E,oE); + return nb::make_tuple(E,oE); + } +} + +// Bind the wrapper to the Python module +void bind_orient_halfedges(nb::module_ &m) +{ + m.def( + "orient_halfedges", + &pyigl::orient_halfedges, + "F"_a, +R"(Orients halfedges for a triangle mesh, assigning them to a unique edge. + +@param[in] F #F by 3 input mesh connectivity +@param[out] E #F by 3 a mapping from each halfedge to each edge +@param[out] oE #F by 3 the orientation (e.g., -1 or 1) of each halfedge compared to + the orientation of the actual edge. Every edge appears positively oriented + exactly once. + +\see unique_simplices)" + ); +} + + diff --git a/src/orient_outward.cpp b/src/orient_outward.cpp deleted file mode 100644 index 3a4ca6c2..00000000 --- a/src/orient_outward.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_orient_outward = R"igl_Qu8mg5v7( - - Orient each component (identified by C) of a mesh (V,F) so the normals on - average point away from the patch's centroid. - -Parameters ----------- - V #V by 3 list of vertex positions - F #F by 3 list of triangle indices - C #F list of components (output of orientable_patches) - -Returns -------- - FF #F by 3 list of new triangle indices such that FF(~I,:) = F(~I,:) and - FF(I,:) = fliplr(F(I,:)) (OK if &FF = &F) - I max(C)+1 list of whether face has been flipped - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(orient_outward) -npe_doc(ds_orient_outward) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(c, npe_matches(f)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_rows_match(f, c, "f", "c"); - //problem with if(&FF != &F) - //problem with DerivedV as type - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - Eigen::Matrix c_copy = c; - - EigenDenseLike ff; - Eigen::Matrix i_copy; - igl::orient_outward(v_copy, f_copy, c_copy, ff, i_copy); - EigenDenseLike i = i_copy; - return std::make_tuple(npe::move(ff), npe::move(i)); - -npe_end_code() - - diff --git a/src/orientable_patches.cpp b/src/orientable_patches.cpp deleted file mode 100644 index 2db872c1..00000000 --- a/src/orientable_patches.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_orientable_patches = R"igl_Qu8mg5v7( -Compute connected components of facets connected by manifold edges. - -Parameters ----------- -f : n by dim array of face ids - -Returns -------- -A tuple (c, A) where c is an array of component ids (starting with 0) -and A is a #f x #f adjacency matri - -See also --------- -components - -Notes ------ -Known bugs: This will detect a moebius strip as a single patch (manifold, non-orientable) and also non-manfiold, yet orientable patches. - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(orientable_patches) -npe_doc(ds_orientable_patches) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - EigenDenseLike c; - EigenSparseLike A; - igl::orientable_patches(f, c, A); - return std::make_tuple(npe::move(c), npe::move(A)); - -npe_end_code() - diff --git a/src/oriented_facets.cpp b/src/oriented_facets.cpp deleted file mode 100644 index 6f74c734..00000000 --- a/src/oriented_facets.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_oriented_facets = R"igl_Qu8mg5v7( -Determines all 'directed [facets](https:en.wikipedia.org/wiki/Simplex#Elements)' of a given set -of simplicial elements. For a manifold triangle mesh, this computes all half-edges. -For a manifold tetrahedral mesh, this computes all half-faces. - -Parameters ----------- -f : #F by simplex_size list of simplices - - -Returns -------- -#E : by simplex_size-1 list of half-edges/facets - -See also --------- -edges - -Notes ------ -This is not the same as igl::edges because this includes every -directed edge including repeats (meaning interior edges on a surface will -show up once for each direction and non-manifold edges may appear more than -once for each direction). - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(oriented_facets) -npe_doc(ds_oriented_facets) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tet_or_tri_mesh_faces(f); - EigenDenseLike e; - igl::oriented_facets(f, e); - return npe::move(e); - -npe_end_code() - - diff --git a/src/outer_element.cpp b/src/outer_element.cpp deleted file mode 100644 index dadebbcf..00000000 --- a/src/outer_element.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -const char *ds_outer_vertex = R"igl_Qu8mg5v7( - -Find a vertex that is reachable from infinite without crossing any faces. -Such vertex is called "outer vertex." - -Precondition: The input mesh must have all self-intersection resolved and -no duplicated vertices. See cgal::remesh_self_intersections.h for how to -obtain such input. - -Parameters ----------- -V #V by 3 list of vertex positions -F #F by 3 list of triangle indices into V -I #I list of facets to consider - -Returns -------- -v_index index of outer vertex -A #A list of facets incident to the outer vertex - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(outer_vertex) -npe_doc(ds_outer_vertex) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(i, npe_matches(f)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_cols_equals(i, 1, "i"); - - int v_index; - EigenDenseLike a; - igl::outer_vertex(v, f, i, v_index, a); - return std::make_tuple(v_index, npe::move(a)); - -npe_end_code() - -const char *ds_outer_edge = R"igl_Qu8mg5v7( - -Find an edge that is reachable from infinity without crossing any faces. - Such edge is called "outer edge." - - Precondition: The input mesh must have all self-intersection resolved and - no duplicated vertices. The correctness of the output depends on the fact - that there is no edge overlap. See cgal::remesh_self_intersections.h for - how to obtain such input. - -Parameters ----------- -V #V by 3 list of vertex positions -F #F by 3 list of triangle indices into V -I #I list of facets to consider - -Returns -------- - -v1 index of the first end point of outer edge -v2 index of the second end point of outer edge -A #A list of facets incident to the outer edge - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(outer_edge) -npe_doc(ds_outer_edge) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(i, npe_matches(f)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_cols_equals(i, 1, "i"); - - int v1; - int v2; - EigenDenseLike a; - igl::outer_edge(v, f, i, v1, v2, a); - return std::make_tuple(v1, v2, npe::move(a)); - -npe_end_code() - - -const char *ds_outer_facet = R"igl_Qu8mg5v7( - -Find a facet that is reachable from infinity without crossing any faces. -Such facet is called "outer facet." - -Precondition: The input mesh must have all self-intersection resolved. I.e -there is no duplicated vertices, no overlapping edge and no intersecting -faces (the only exception is there could be topologically duplicated faces). -See cgal::remesh_self_intersections.h for how to obtain such input. - -Parameters ----------- -V #V by 3 list of vertex positions -F #F by 3 list of triangle indices into V -N #N by 3 list of face normals -I #I list of facets to consider - -Returns -------- -f Index of the outer facet. -flipped true iff the normal of f points inwards. - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(outer_facet) -npe_doc(ds_outer_facet) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(n, npe_matches(v)) -npe_arg(i, npe_matches(f)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_cols_equals(n, 3, "n"); - assert_rows_match(f, n, "f", "n"); - assert_cols_equals(i, 1, "i"); - - int face; - bool flipped; - igl::outer_facet(v, f, n, i, face, flipped); - return std::make_tuple(face, flipped); - -npe_end_code() - - diff --git a/src/partition.cpp b/src/partition.cpp deleted file mode 100644 index 8088083a..00000000 --- a/src/partition.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_partition = R"igl_Qu8mg5v7( - -PARTITION partition vertices into groups based on each -vertex's vector: vertices with similar coordinates (close in -space) will be put in the same group. - -Parameters ----------- -W #W by dim coordinate matrix -k desired number of groups default is dim - -Returns -------- -G #W list of group indices (1 to k) for each vertex, such that vertex i is assigned to group G(i) -S k list of seed vertices -D #W list of squared distances for each vertex to it's corresponding closest seed - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(partition) -npe_doc(ds_partition) - -npe_arg(w, dense_float, dense_double) -npe_arg(k, int) - - -npe_begin_code() - assert_nonzero_rows(w, "w"); - - Eigen::MatrixXd w_copy = w.template cast(); - Eigen::Matrix g_copy; - Eigen::Matrix s_copy; - Eigen::Matrix d_copy; - igl::partition(w_copy, k, g_copy, s_copy, d_copy); - - EigenDenseInt g = g_copy.template cast(); - EigenDenseInt s = s_copy.template cast(); - EigenDenseLike d = d_copy.template cast(); - - return std::make_tuple(npe::move(g), npe::move(s), npe::move(d)); - -npe_end_code() - - diff --git a/src/path_to_edges.cpp b/src/path_to_edges.cpp deleted file mode 100644 index 9be57637..00000000 --- a/src/path_to_edges.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - -#include - -const char *ds_path_to_edges = R"igl_Qu8mg5v7( - - -Given a path as an ordered list of N>=2 vertex indices I[0], I[1], ..., I[N-1] - construct a list of edges [[I[0],I[1]], [I[1],I[2]], ..., [I[N-2], I[N-1]]] - connecting each sequential pair of vertices. - -Parameters ----------- -I #I list of vertex indices -make_loop bool If true, include an edge connecting I[N-1] to I[0] - -Returns -------- -E #I-1 by 2 list of edges - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(path_to_edges) -npe_doc(ds_path_to_edges) - -npe_arg(i, dense_int32, dense_int64) -npe_default_arg(make_loop, bool, false) - - -npe_begin_code() - EigenDense i_copy = i; - - EigenDense e; - igl::path_to_edges(i_copy, e, make_loop); - return npe::move(e); - -npe_end_code() diff --git a/src/per_corner_normals.cpp b/src/per_corner_normals.cpp deleted file mode 100644 index 14db23ee..00000000 --- a/src/per_corner_normals.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_per_corner_normals = R"igl_Qu8mg5v7( - -Compute vertex normals via vertex position list, face list - -Parameters ----------- -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face (triangle) indices -corner_threshold threshold in degrees on sharp angles - -Returns -------- -CN #F*3 by 3 eigen Matrix of mesh vertex 3D normals, where the normal for corner F(i,j) is at CN(i*3+j,:) - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(per_corner_normals) -npe_doc(ds_per_corner_normals) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(corner_threshold, double) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - EigenDenseLike n; - igl::per_corner_normals(v, f, corner_threshold, n); - return npe::move(n); - -npe_end_code() - diff --git a/src/per_edge_normals.cpp b/src/per_edge_normals.cpp deleted file mode 100644 index 82e0fbea..00000000 --- a/src/per_edge_normals.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __miss __example - -#include -#include -#include -#include - -const char* ds_per_edge_normals = R"igl_Qu8mg5v7( - -Compute face normals via vertex position list, face list - -Parameters ----------- -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face (triangle) indices -weight weighting type -FN #F by 3 matrix of 3D face normals per face - - -Returns -------- -N #2 by 3 matrix of mesh edge 3D normals per row -E #E by 2 matrix of edge indices per row -EMAP #E by 1 matrix of indices from all edges to E - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(per_edge_normals) -npe_doc(ds_per_edge_normals) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(weight, int , 0) -npe_arg(fn, npe_matches(v)) - - -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - EigenDenseLike n; - EigenDenseLike e; - Eigen::Matrix emap; - igl::per_edge_normals(v, f, igl::PerEdgeNormalsWeightingType(weight), fn, n, e, emap); - return std::make_tuple(npe::move(n), npe::move(e), npe::move(emap)); - -npe_end_code() - - diff --git a/src/per_face_normals.cpp b/src/per_face_normals.cpp index 4734ff50..9835b238 100644 --- a/src/per_face_normals.cpp +++ b/src/per_face_normals.cpp @@ -1,60 +1,89 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __miss - -#include -#include -#include +#include "default_types.h" #include - -const char* ds_per_face_normals = R"igl_Qu8mg5v7( - -Compute face normals via vertex position list, face list - -Parameters ----------- -V #V by 3 eigen Matrix of mesh vertex 3D positions -F #F by 3 eigen Matrix of face (triangle) indices -Z 3 vector normal given to faces with degenerate normal. - -Returns -------- -N #F by 3 eigen Matrix of mesh face (triangle) 3D normals - -See also --------- - - -Notes ------ -None - -Examples --------- -Give degenerate faces (1/3,1/3,1/3)^0.5 -per_face_normals(V,F,Vector3d(1,1,1).normalized(),N); - -)igl_Qu8mg5v7"; - -npe_function(per_face_normals) -npe_doc(ds_per_face_normals) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(z, npe_matches(v)) - - -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - EigenDenseLike n; - igl::per_face_normals(v, f, z, n); - return npe::move(n); - -npe_end_code() - +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for per_face_normals function + nb::object per_face_normals( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &I, + const nb::DRef &C, + const nb::DRef &Z, + const bool return_VV, + const bool return_FF, + const bool return_J) + { + Eigen::MatrixXN N; + if(I.size() > 0) + { + if(Z.size() > 0) + { + throw std::invalid_argument("per_face_normals: Z should not be provided for polygonal meshes"); + } + Eigen::MatrixXN VV; + Eigen::MatrixXI FF; + Eigen::VectorXI J; + igl::per_face_normals(V,I,C,N,VV,FF,J); + if(return_VV && return_FF && return_J) + { + return nb::make_tuple(N,VV,FF,J); + }else if(return_VV && return_FF) + { + return nb::make_tuple(N,VV,FF); + }else if(return_VV && return_J) + { + return nb::make_tuple(N,VV,J); + }else if(return_FF && return_J) + { + return nb::make_tuple(N,FF,J); + }else if(return_VV) + { + return nb::make_tuple(N,VV); + }else if(return_FF) + { + return nb::make_tuple(N,FF); + }else if(return_J) + { + return nb::make_tuple(N,J); + } + }else + { + if(return_VV || return_FF || return_J) + { + throw std::runtime_error("per_face_normals: VV, FF, and J only available for polygonal meshes"); + } + igl::per_face_normals(V,F,Z,N); + } + return nb::cast(N); + } +} + +// Bind the wrapper to the Python module +void bind_per_face_normals(nb::module_ &m) +{ + m.def( + "per_face_normals", + &pyigl::per_face_normals, + "V"_a, + "F"_a=Eigen::MatrixXI(), + "I"_a=Eigen::VectorXI(), + "C"_a=Eigen::VectorXI(), + "Z"_a=Eigen::RowVectorXN(), + "return_VV"_a=false, + "return_FF"_a=false, + "return_J"_a=false, +R"(Compute face normals via vertex position list, face list + +@param[in] V #V by 3 eigen Matrix of mesh vertex 3D positions +@param[in] F #F by 3 eigen Matrix of face (triangle) indices +@param[in] Z 3 vector normal given to faces with degenerate normal. +@param[out] N #F by 3 eigen Matrix of mesh face (triangle) 3D normals)" + ); +} diff --git a/src/per_vertex_attribute_smoothing.cpp b/src/per_vertex_attribute_smoothing.cpp deleted file mode 100644 index 97ab9d7a..00000000 --- a/src/per_vertex_attribute_smoothing.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include -#include - -const char* ds_per_vertex_attribute_smoothing = R"igl_Qu8mg5v7( - -Smooth vertex attributes using uniform Laplacian - -Parameters ----------- -Ain #V by #A eigen Matrix of mesh vertex attributes (each vertex has #A attributes) -F #F by 3 eigne Matrix of face (triangle) indices - -Returns -------- -Aout #V by #A eigen Matrix of mesh vertex attributes - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(per_vertex_attribute_smoothing) -npe_doc(ds_per_vertex_attribute_smoothing) - -npe_arg(ain, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_3d_tri_mesh(ain, f); - // remove __copy - Eigen::MatrixXd ain_copy = ain.template cast(); - Eigen::MatrixXd aout; - igl::per_vertex_attribute_smoothing(ain_copy, f, aout); - EigenDenseFloat aout_row_major = aout; - return npe::move(aout_row_major); - -npe_end_code() - - diff --git a/src/per_vertex_normals.cpp b/src/per_vertex_normals.cpp deleted file mode 100644 index ecc78c50..00000000 --- a/src/per_vertex_normals.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_per_vertex_normals = R"igl_Qu8mg5v7( -Compute vertex normals via vertex position list, face list. - -Parameters ----------- -v : #v by 3 array of mesh vertex 3D positions -f : #f by 3 array of face (triangle) indices -weighting : Weighting type, one of the following - -igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM uniform influence - -igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA area weighted - -igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE angle weighted - -Returns -------- -n #v by 3 array of mesh vertex 3D normals - -See also --------- -per_face_normals, per_edge_normals - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>> n = per_vertex_normals(v, f) -)igl_Qu8mg5v7"; - -npe_function(per_vertex_normals) -npe_doc(ds_per_vertex_normals) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(weighting, int, 0) - -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - static_assert(int(igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) == 0, "PerVertexNormalWeightingType enum changed!"); - static_assert(int(igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) == 1, "PerVertexNormalWeightingType enum changed!"); - static_assert(int(igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) == 2, "PerVertexNormalWeightingType enum changed!"); - - EigenDenseLike n; - igl::per_vertex_normals(v, f, igl::PerVertexNormalsWeightingType(weighting), n); - return npe::move(n); - -npe_end_code() - - diff --git a/src/piecewise_constant_winding_number.cpp b/src/piecewise_constant_winding_number.cpp deleted file mode 100644 index f5b31b2e..00000000 --- a/src/piecewise_constant_winding_number.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __miss __example - -#include -#include -#include -#include - - - - - - - -#include - -const char* ds_piecewise_constant_winding_number = R"igl_Qu8mg5v7( - -PIECEWISE_CONSTANT_WINDING_NUMBER Determine if a given mesh induces a - piecewise constant winding number field: Is this mesh valid input to solid - set operations. **Assumes** that `(V,F)` contains no self-intersections - (including degeneracies and co-incidences). If there are co-planar and - co-incident vertex placements, a mesh could _fail_ this combinatorial test - but still induce a piecewise-constant winding number _geometrically_. For - example, consider a hemisphere with boundary and then pinch the boundary - "shut" along a line segment. The **_bullet-proof_** check is to first - resolve all self-intersections in `(V,F) -> (SV,SF)` (i.e. what the - `igl::copyleft::cgal::piecewise_constant_winding_number` overload does). - -Parameters ----------- -F #F by 3 list of triangle indices into some (abstract) list of - vertices V - -Returns -------- -Returns true if the mesh _combinatorially_ induces a piecewise constant - winding number field. - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(piecewise_constant_winding_number) -npe_doc(ds_piecewise_constant_winding_number) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - return igl::piecewise_constant_winding_number(f); - -npe_end_code() - - diff --git a/src/planarize_quad_mesh.cpp b/src/planarize_quad_mesh.cpp deleted file mode 100644 index 9c57cc92..00000000 --- a/src/planarize_quad_mesh.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_planarize_quad_mesh = R"igl_Qu8mg5v7( -Planarize a quad mesh. - -Parameters ----------- -v : #v by 3 array of mesh vertex 3D positions -f : #f by 4 array of face (quad) indices -max_iter : maximum numbers of iterations -threshold : minimum allowed threshold for non-planarity - -Returns -------- -out : #v by 3 array of planar mesh vertex 3D positions - -See also --------- -None - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(planarize_quad_mesh) -npe_doc(ds_planarize_quad_mesh) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(max_iter, int) -npe_arg(threshold, double) - - -npe_begin_code() - assert_valid_3d_quad_mesh(v, f); - EigenDenseLike v_copy = v; - EigenDenseLike f_copy = f; - - EigenDenseLike out; - igl::planarize_quad_mesh(v_copy, f_copy, max_iter, threshold, out); - return npe::move(out); - -npe_end_code() - - diff --git a/src/point_in_circle.cpp b/src/point_in_circle.cpp deleted file mode 100644 index 573e7b80..00000000 --- a/src/point_in_circle.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -const char *ds_point_in_circle = R"igl_Qu8mg5v7( - Determine if 2d point is in a circle - -Parameters ----------- -qx x-coordinate of query point -qy y-coordinate of query point -cx x-coordinate of circle center -cy y-coordinate of circle center -r radius of circle - -Returns -------- -Returns true if query point is in circle, false otherwise - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(point_in_circle) -npe_doc(ds_point_in_circle) - -npe_arg(qx, double) -npe_arg(qy, double) -npe_arg(cx, double) -npe_arg(cy, double) -npe_arg(r, double) - - -npe_begin_code() - - bool in = igl::point_in_circle(qx, qy, cx, cy, r); - return in; - -npe_end_code() - - diff --git a/src/point_mesh_squared_distance.cpp b/src/point_mesh_squared_distance.cpp index c54dd959..208bfa9e 100644 --- a/src/point_mesh_squared_distance.cpp +++ b/src/point_mesh_squared_distance.cpp @@ -1,89 +1,66 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - +#include "default_types.h" #include - -const char *ds_point_mesh_squared_distance = R"igl_Qu8mg5v7( - Compute distances from a set of points P to a triangle mesh (V,F) - -Parameters ----------- -P #P by 3 list of query point positions -V #V by 3 list of vertex positions -Ele #Ele by (3|2|1) list of (triangle|edge|point) indices - -Returns -------- -sqrD #P list of smallest squared distances -I #P list of primitive indices corresponding to smallest distances -C #P by 3 list of closest points - -See also --------- - - -Notes ------ -Known bugs: This only computes distances to given primitivess. So +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for point_mesh_squared_distance function + nb::object point_mesh_squared_distance( + const nb::DRef &P, + const nb::DRef &V, + const nb::DRef &Ele, + const bool return_I, + const bool return_C) + { + Eigen::VectorXN sqrD; + Eigen::VectorXI I; + Eigen::MatrixXN C; + igl::point_mesh_squared_distance(P, V, Ele, sqrD, I, C); + if(return_I && return_C) + { + return nb::make_tuple(sqrD, I, C); + }else if(return_I) + { + return nb::make_tuple(sqrD, I); + }else if(return_C) + { + return nb::make_tuple(sqrD, C); + } + return nb::cast(sqrD); + } +} + +// Bind the wrapper to the Python module +void bind_point_mesh_squared_distance(nb::module_ &m) +{ + m.def( + "point_mesh_squared_distance", + &pyigl::point_mesh_squared_distance, + "P"_a, + "V"_a, + "Ele"_a, + "return_I"_a=false, + "return_C"_a=false, +R"(Compute distances from a set of points P to a triangle mesh (V,F) + +@param[in] P #P by 3 list of query point positions +@param[in] V #V by 3 list of vertex positions +@param[in] Ele #Ele by (3|2|1) list of (triangle|edge|point) indices +@param[out] sqrD #P list of smallest squared distances +@param[out] I #P list of primitive indices corresponding to smallest distances +@param[out] C #P by 3 list of closest points + +\bug This only computes distances to given primitives. So unreferenced vertices are ignored. However, degenerate primitives are handled correctly: triangle [1 2 2] is treated as a segment [1 2], and triangle [1 1 1] is treated as a point. So one _could_ add extra combinatorially degenerate rows to Ele for all unreferenced vertices to also get distances to points. - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(point_mesh_squared_distance) -npe_doc(ds_point_mesh_squared_distance) - -npe_arg(p, dense_float, dense_double) -npe_arg(v, dense_float, dense_double) -npe_arg(ele, dense_int32, dense_int64) - - -npe_begin_code() - assert_nonzero_rows(p, "P"); - assert_nonzero_rows(v, "V"); - assert_nonzero_rows(ele, "Ele"); - - Eigen::MatrixXd p_copy; - if(p.size() == 2 || p.size() == 3) - { - p_copy.resize(1, p.size()); - for(int i = 0; i < p.size(); ++i) - p_copy(0, i) = p(i, 0); - } - else - p_copy = p.template cast(); - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi ele_copy = ele.template cast(); - - Eigen::MatrixXd sqr_d; - Eigen::MatrixXi i; - Eigen::MatrixXd c; - - igl::point_mesh_squared_distance(p_copy, v_copy, ele_copy, sqr_d, i, c); - EigenDenseLike sqr_d_row_maj = sqr_d.template cast(); - EigenDenseLike i_row_maj = i.template cast(); - EigenDenseLike c_row_maj = c.template cast(); -return std::make_tuple(npe::move(sqr_d_row_maj), npe::move(i_row_maj), npe::move(c_row_maj)); - -npe_end_code() - +)"); +} diff --git a/src/point_simplex_squared_distance.cpp b/src/point_simplex_squared_distance.cpp deleted file mode 100644 index 8a6b24e2..00000000 --- a/src/point_simplex_squared_distance.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_point_simplex_squared_distance = R"igl_Qu8mg5v7( - - Determine squared distance from a point to linear simplex. - Also return barycentric coordinate of closest point. - -Parameters ----------- - -p d-long query point -V #V by d list of vertices -Ele #Ele by ss<=d+1 list of simplex indices into V -i index into Ele of simplex - -Returns -------- -sqr_d squared distance of Ele(i) to p -c closest point on Ele(i) -b barycentric coordinates of closest point on Ele(i) - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(point_simplex_squared_distance) -npe_doc(ds_point_simplex_squared_distance) - -npe_arg(p, dense_float, dense_double) -npe_arg(v, npe_matches(p)) -npe_arg(ele, dense_int32, dense_int64) -npe_arg(i, int) - - -npe_begin_code() - assert_valid_tet_or_tri_mesh(v, ele); - assert_size_equals(p, v.cols(), "p"); - - double sqr_d; - Eigen::Matrix c; - Eigen::Matrix b; - Eigen::Matrix p_copy = p; - if(p.size() == 2) - igl::point_simplex_squared_distance<2>(p_copy, v, ele, i, sqr_d, c, b); - else - igl::point_simplex_squared_distance<3>(p_copy, v, ele, i, sqr_d, c, b); - return std::make_tuple(sqr_d, npe::move(c), npe::move(b)); - -npe_end_code() - - diff --git a/src/polar_dec.cpp b/src/polar_dec.cpp deleted file mode 100644 index c8cdc3e0..00000000 --- a/src/polar_dec.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_polar_dec = R"igl_Qu8mg5v7( - -Computes the polar decomposition (R,T) of a matrix A - - -Parameters ----------- -A 3 by 3 matrix to be decomposed - -Returns -------- - -R 3 by 3 orthonormal matrix part of decomposition -T 3 by 3 stretch matrix part of decomposition - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(polar_dec) -npe_doc(ds_polar_dec) - -npe_arg(a, dense_float, dense_double) - -npe_begin_code() - assert_rows_equals(a, 3, "a"); - assert_cols_equals(a, 3, "a"); - - EigenDense a_copy = a; - EigenDense r; - EigenDense t; - igl::polar_dec(a_copy, r, t); - return std::make_tuple(npe::move(r), npe::move(t)); - -npe_end_code() - - diff --git a/src/principal_curvature.cpp b/src/principal_curvature.cpp deleted file mode 100644 index 56e3f88a..00000000 --- a/src/principal_curvature.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -const char* ds_principal_curvature = R"igl_Qu8mg5v7( -Compute the principal curvature directions and magnitude of the given triangle mesh. - -Parameters ----------- -v : vertex array of size #V by 3 -f : face index array #F by 3 list of mesh faces (must be triangles) -radius : controls the size of the neighbourhood used, 1 = average edge length (default: 5) -use_k_ring : (default: True) - -Returns -------- -pd1 : #v by 3 maximal curvature direction for each vertex -pd2 : #v by 3 minimal curvature direction for each vertex -pv1 : #v by 1 maximal curvature value for each vertex -pv2 : #v by 1 minimal curvature value for each vertex - -See also --------- -average_onto_faces, average_onto_vertices - -Notes ------ -This function has been developed by: Nikolas De Giorgis, Luigi Rocca and Enrico Puppo. -The algorithm is based on: Efficient Multi-scale Curvature and Crease Estimation -Daniele Panozzo, Enrico Puppo, Luigi Rocca GraVisMa, 2010 - -Examples --------- -# Mesh in (v, f) ->>> pd1, pd2, pv1, pv2 = principal_curvature(v, f) -)igl_Qu8mg5v7"; - -npe_function(principal_curvature) -npe_doc(ds_principal_curvature) -npe_arg(v, dense_double, dense_float) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(radius, int, 5) -npe_default_arg(use_k_ring, bool, true) -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - EigenDenseLike pd1, pd2, pv1, pv2; - - igl::principal_curvature(v, f, pd1, pd2, pv1, pv2, radius, use_k_ring); - return std::make_tuple(npe::move(pd1), npe::move(pd2), npe::move(pv1), npe::move(pv2)); - -npe_end_code() - - diff --git a/src/procrustes.cpp b/src/procrustes.cpp deleted file mode 100644 index dd25e4f2..00000000 --- a/src/procrustes.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __miss - -#include -#include -#include -#include - - - - -const char* ds_procrustes = R"igl_Qu8mg5v7( - -Solve Procrustes problem in d dimensions. Given two point sets X,Y in R^d - find best scale s, orthogonal R and translation t s.t. |s*X*R + t - Y|^2 - is minimized. - -Parameters ----------- -X #V by DIM first list of points -Y #V by DIM second list of points -includeScaling if scaling should be allowed -includeReflections if R is allowed to be a reflection - - -Returns -------- -scale scaling -R orthogonal matrix -t translation - - -See also --------- - - -Notes ------ -None - -Examples --------- -MatrixXd X, Y; (containing 3d points as rows) -double scale; -MatrixXd R; -VectorXd t; -igl::procrustes(X,Y,true,false,scale,R,t); -R *= scale; -MatrixXd Xprime = (X * R).rowwise() + t.transpose(); - -)igl_Qu8mg5v7"; - -npe_function(procrustes) -npe_doc(ds_procrustes) - -npe_arg(x, dense_float, dense_double) -npe_arg(y, npe_matches(x)) -npe_arg(include_scaling, bool) -npe_arg(include_reflections, bool) - -npe_begin_code() - - assert_cols_match(x, y, "x", "y"); - assert_nonzero_rows(x, "x"); - double scale; - EigenDenseLike r, t; - igl::procrustes(x, y, include_scaling, include_reflections, scale, r, t); - return std::make_tuple(scale, npe::move(r), npe::move(t)); - -npe_end_code() - diff --git a/src/project.cpp b/src/project.cpp deleted file mode 100644 index 48235c91..00000000 --- a/src/project.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_project = R"igl_Qu8mg5v7( -Project - -Parameters ----------- -V #V by 3 list of object points -model model matrix -proj projection matrix -viewport viewport vector - -Returns -------- -P #V by 3 list of screen space points - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(project) -npe_doc(ds_project) - -npe_arg(v, dense_float, dense_double) -npe_arg(model, npe_matches(v)) -npe_arg(proj, npe_matches(v)) -npe_arg(viewport, npe_matches(v)) - - -npe_begin_code() - assert_nonzero_rows(v, "v"); - assert_cols_equals(v, 3, "v"); - assert_rows_equals(model, 4, "model"); - assert_cols_equals(model, 4, "model"); - - assert_rows_equals(proj, 4, "proj"); - assert_cols_equals(proj, 4, "proj"); - - assert_size_equals(viewport, 4, "viewport"); - - Eigen::Matrix viewport_copy; - for(int i = 0; i < 4; ++i){ - if(viewport.cols() == 1) - viewport_copy(i) = viewport(i, 0); - else - viewport_copy(i) = viewport(0, i); - } - - EigenDense p; - igl::project(v, model, proj, viewport_copy, p); - return npe::move(p); - -npe_end_code() diff --git a/src/project_isometrically_to_plane.cpp b/src/project_isometrically_to_plane.cpp deleted file mode 100644 index 5fbb26b8..00000000 --- a/src/project_isometrically_to_plane.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_project_isometrically_to_plane = R"igl_Qu8mg5v7( - Project each triangle to the plane - -Parameters ----------- -V #V by 3 list of vertex positions -F #F by 3 list of mesh indices - -Returns -------- -U #F*3 by 2 list of triangle positions -UF #F by 3 list of mesh indices into U -I #V by #F*3 such that I(i,j) = 1 implies U(j,:) corresponds to V(i,:) - -See also --------- - - -Notes ------ -None - -Examples --------- - [U,UF,I] = project_isometrically_to_plane(V,F) - -)igl_Qu8mg5v7"; - -npe_function(project_isometrically_to_plane) -npe_doc(ds_project_isometrically_to_plane) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - EigenDense u; - EigenDense uf; - Eigen::SparseMatrix < npe_Scalar_f> i; - igl::project_isometrically_to_plane(v, f, u, uf, i); - return std::make_tuple(npe::move(u), npe::move(uf), npe::move(i)); - -npe_end_code() - - diff --git a/src/project_to_line.cpp b/src/project_to_line.cpp deleted file mode 100644 index a344bdf7..00000000 --- a/src/project_to_line.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_project_to_line = R"igl_Qu8mg5v7( -PROJECT_TO_LINE project points onto vectors, that is find the parameter - t for a point p such that proj_p = (y-x).*t, additionally compute the - squared distance from p to the line of the vector, such that - |p - proj_p|² = sqr_d - - -Parameters ----------- -P #P by dim list of points to be projected -S size dim start position of line vector -D size dim destination position of line vector - -Returns -------- -T #P by 1 list of parameters -sqrD #P by 1 list of squared distances - - -See also --------- - - -Notes ------ -None - -Examples --------- - -[T,sqrD] = project_to_line(P,S,D) - -)igl_Qu8mg5v7"; - -npe_function(project_to_line) -npe_doc(ds_project_to_line) - -npe_arg(p, dense_float, dense_double) -npe_arg(s, npe_matches(p)) -npe_arg(d, npe_matches(p)) - - -npe_begin_code() - assert_nonzero_rows(p, "p"); - assert_size_equals(s, p.cols(), "s"); - assert_size_equals(d, p.cols(), "d"); - - typedef Eigen::Matrix Mat; - Mat s_copy(s.size()); - Mat d_copy(d.size()); - - for(int i = 0; i < s.size(); ++i) - { - if(s.cols() == 1) - s_copy[i] = s(i, 0); - else - s_copy[i] = s(0, i); - - if (d.cols() == 1) - d_copy[i] = d(i, 0); - else - d_copy[i] = d(0, i); - } - - Eigen::Matrix p_copy = p; - - EigenDense t; - EigenDense sqr_d; - igl::project_to_line(p_copy, s_copy, d_copy, t, sqr_d); - return std::make_tuple(npe::move(t), npe::move(sqr_d)); - -npe_end_code() - diff --git a/src/project_to_line_segment.cpp b/src/project_to_line_segment.cpp deleted file mode 100644 index dc7e0198..00000000 --- a/src/project_to_line_segment.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_project_to_line_segment = R"igl_Qu8mg5v7( -PROJECT_TO_LINE_SEGMENT project points onto vectors, that is find the parameter - t for a point p such that proj_p = (y-x).*t, additionally compute the - squared distance from p to the line of the vector, such that - |p - proj_p|² = sqr_d - - -Parameters ----------- -P #P by dim list of points to be projected -S size dim start position of line vector -D size dim destination position of line vector - -Returns -------- -T #P by 1 list of parameters -sqrD #P by 1 list of squared distances - -See also --------- - - -Notes ------ -None - -Examples --------- - [T,sqrD] = project_to_line_segment(P,S,D) - -)igl_Qu8mg5v7"; - -npe_function(project_to_line_segment) -npe_doc(ds_project_to_line_segment) - -npe_arg(p, dense_float, dense_double) -npe_arg(s, npe_matches(p)) -npe_arg(d, npe_matches(p)) - - -npe_begin_code() - assert_nonzero_rows(p, "p"); - assert_size_equals(s, p.cols(), "s"); - assert_size_equals(d, p.cols(), "d"); - - typedef Eigen::Matrix Mat; - Mat s_copy(s.size()); - Mat d_copy(d.size()); - - for (int i = 0; i < s.size(); ++i) - { - if (s.cols() == 1) - s_copy[i] = s(i, 0); - else - s_copy[i] = s(0, i); - - if (d.cols() == 1) - d_copy[i] = d(i, 0); - else - d_copy[i] = d(0, i); - } - - Eigen::Matrix p_copy = p; - - EigenDense t; - EigenDense sqr_d; - - igl::project_to_line_segment(p_copy, s_copy, d_copy, t, sqr_d); - return std::make_tuple(npe::move(t), npe::move(sqr_d)); - -npe_end_code() - - diff --git a/src/pso.cpp b/src/pso.cpp deleted file mode 100644 index 8a6883d4..00000000 --- a/src/pso.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include -#include - -const char *ds_pso = R"igl_Qu8mg5v7ss( -Solve the problem: - - minimize f(x) - subject to lb ≤ x ≤ ub - -by particle swarm optimization (PSO). - - -Parameters ----------- -f function that evaluates the objective for a given "particle" location -LB #X vector of lower bounds -UB #X vector of upper bounds -max_iters maximum number of iterations -population number of particles in swarm - -Returns -------- -f(X) objective corresponding to best particle seen so far -X best particle seen so far - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7ss"; - -npe_function(pso) -npe_doc(ds_pso) - -npe_arg(f, const std::function< typename EigenDenseFloat::Scalar (Eigen::Matrix &) >) -npe_arg(lb, EigenDenseFloat) -npe_arg(ub, EigenDenseFloat) -npe_arg(max_iters, int) -npe_arg(population, int) - - -npe_begin_code() - Eigen::Matrix x_copy; - const auto obj = igl::pso(f, lb, ub, max_iters, population, x_copy); - EigenDenseFloat x = x_copy; - return std::make_tuple(obj, npe::move(x)); - -npe_end_code() - - - -// const char *ds_psop = R"igl_Qu8mg5v7ssp( -// Solve the problem: -// -// minimize f(x) -// subject to lb ≤ x ≤ ub -// -// by particle swarm optimization (PSO). -// -// -// Parameters -// ---------- -// f function that evaluates the objective for a given "particle" location -// LB #X vector of lower bounds -// UB #X vector of upper bounds -// P whether each DOF is periodic -// max_iters maximum number of iterations -// population number of particles in swarm -// -// Returns -// ------- -// X best particle seen so far -// Returns objective corresponding to best particle seen so far -// -// See also -// -------- -// -// -// Notes -// ----- -// None -// -// Examples -// -------- -// -// )igl_Qu8mg5v7ssp"; - -// npe_function(pso_periodic) -// npe_doc(ds_psop) -// -// npe_arg(f, const std::function< typename EigenDenseFloat::Scalar (Eigen::Matrix &) >) -// npe_arg(lb, EigenDenseFloat) -// npe_arg(ub, EigenDenseFloat) -// npe_arg(p, EigenDenseFloat) //SHOULD BE BOOLEAN, BUT IS NOT USED ANYWAYS -// npe_arg(max_iters, int) -// npe_arg(population, int) -// -// -// npe_begin_code() -// Eigen::Matrix x_copy; -// const auto obj = igl::pso(f, lb, ub, p, max_iters, population, x_copy); -// EigenDenseFloat x = x_copy; -// return std::make_tuple(obj, npe::move(x)); -// -// npe_end_code() diff --git a/src/qslim.cpp b/src/qslim.cpp deleted file mode 100644 index b684e0c0..00000000 --- a/src/qslim.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include - - - - - - -#include - -const char* ds_qslim = R"igl_Qu8mg5v7( - -Decimate (simplify) a triangle mesh in nD according to the paper - "Simplifying Surfaces with Color and Texture using Quadric Error Metrics" - by [Garland and Heckbert, 1987] (technically a followup to qslim). The - mesh can have open boundaries but should be edge-manifold. - -Parameters ----------- -V #V by dim list of vertex positions. Assumes that vertices w -F #F by 3 list of triangle indices into V -max_m desired number of output faces -block_intersections whether to block intersections - - -Returns -------- -U #U by dim list of output vertex posistions (can be same ref as V) -G #G by 3 list of output face indices into U (can be same ref as G) -J #G list of indices into F of birth face -I #U list of indices into V of birth vertices - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(qslim) -npe_doc(ds_qslim) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(max_m, size_t) -npe_arg(block_intersections, bool) - - -npe_begin_code() - - assert_valid_23d_tri_mesh(v, f); - // TODO: remove __copy - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXd u; - Eigen::MatrixXi g; - Eigen::VectorXi j; - Eigen::VectorXi i; - bool success = igl::qslim(v_copy, f_copy, max_m, block_intersections, u, g, j, i); - EigenDenseFloat u_row_major = u; - EigenDenseInt g_row_major = g.template cast(); - // FIXME: vector not allowing row major, but they should be essentially the same so i feel we can leave it as col major - Eigen::Matrix j_row_major = j.template cast(); - Eigen::Matrix i_row_major = i.template cast(); - return std::make_tuple(success, npe::move(u_row_major), npe::move(g_row_major), npe::move(j_row_major), npe::move(i_row_major)); - -npe_end_code() - - diff --git a/src/quad_grid.cpp b/src/quad_grid.cpp deleted file mode 100644 index 79e30a5e..00000000 --- a/src/quad_grid.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - -#include - -const char *ds_quad_grid = R"igl_Qu8mg5v7( - -Generate a quad mesh over a regular grid. - -Parameters ----------- -nx number of vertices in the x direction -ny number of vertices in the y direction - - -Returns -------- - -V nx*ny by 2 list of vertex positions -Q (nx-1)*(ny-1) by 4 list of quad indices into V -E (nx-1)*ny+(ny-1)*nx by 2 list of undirected quad edge indices into V - -See also --------- -grid, triangulated_grid - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(quad_grid) -npe_doc(ds_quad_grid) - -npe_arg(nx, int) -npe_arg(ny, int) - - -npe_begin_code() - - EigenDenseFloat gv; - EigenDenseInt gf; - EigenDenseInt e; - igl::quad_grid(nx, ny, gv, gf, e); - return std::make_tuple(npe::move(gv), npe::move(gf), npe::move(e)); - -npe_end_code() diff --git a/src/quad_planarity.cpp b/src/quad_planarity.cpp deleted file mode 100644 index c9e17ab9..00000000 --- a/src/quad_planarity.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: missing __example - -#include -#include -#include -#include - -const char* ds_quad_planarity = R"igl_Qu8mg5v7( -Compute planarity of the faces of a quad mesh. - -Parameters ----------- -v : #v by 3 array of mesh vertex 3D positions -f : #f by 4 array of face (quad) indices - -Returns -------- -p : #f by 1 array of mesh face (quad) planarities - -See also --------- -None - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(quad_planarity) -npe_doc(ds_quad_planarity) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tet_mesh(v, f); - EigenDenseLike p; - igl::quad_planarity(v, f, p); - return npe::move(p); - -npe_end_code() - - diff --git a/src/ramer_douglas_peucker.cpp b/src/ramer_douglas_peucker.cpp deleted file mode 100644 index 47f3d737..00000000 --- a/src/ramer_douglas_peucker.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_ramer_douglas_peucker = R"igl_Qu8mg5v7( -Run (Ramer-)Duglass-Peucker curve simplification but keep track of where -every point on the original curve maps to on the simplified curve. - - -Parameters ----------- -P #P by dim list of points, (use P([1:end 1],:) for loops) -tol DP tolerance - -Returns -------- -S #S by dim list of points along simplified curve -J #S indices into P of simplified points -Q #P by dim list of points mapping along simplified curve - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(ramer_douglas_peucker) -npe_doc(ds_ramer_douglas_peucker) - -npe_arg(p, dense_float, dense_double) -npe_arg(tol, double) - - -npe_begin_code() - assert_nonzero_rows(p, "p"); - - - EigenDense s; - Eigen::VectorXi j_copy; - EigenDense q; - igl::ramer_douglas_peucker(p, tol, s, j_copy, q); - EigenDenseInt j = j_copy.template cast(); - return std::make_tuple(npe::move(s), npe::move(j), npe::move(q)); - -npe_end_code() - - diff --git a/src/random_points_on_mesh.cpp b/src/random_points_on_mesh.cpp deleted file mode 100644 index 4940128e..00000000 --- a/src/random_points_on_mesh.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -#include -#include -#include -#include - -const char* ds_random_points_on_mesh = R"igl_Qu8mg5v7( -RANDOM_POINTS_ON_MESH Randomly sample a mesh (V,F) n times. -Parameters ----------- - n number of samples - V #V by dim list of mesh vertex positions - F #F by 3 list of mesh triangle indices - -Returns -------- - B n by 3 list of barycentric coordinates, ith row are coordinates of - ith sampled point in face FI(i) - FI n list of indices into F - X n by dim list of sampled points - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(random_points_on_mesh) -npe_doc(ds_random_points_on_mesh) - -npe_arg(n, int) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_23d_tri_mesh(v, f); - EigenDenseLike b; - Eigen::Matrix fi; - EigenDenseLike x; - igl::random_points_on_mesh(n, v, f, b, fi, x); - return std::make_tuple(npe::move(b), npe::move(fi), npe::move(x)); - -npe_end_code() diff --git a/src/random_points_on_mesh_intrinsic.cpp b/src/random_points_on_mesh_intrinsic.cpp deleted file mode 100644 index b4417754..00000000 --- a/src/random_points_on_mesh_intrinsic.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include -#include -#include - -const char* ds_random_points_on_mesh_intrinsic = R"igl_Qu8mg5v7( -RANDOM_POINTS_ON_MESH Randomly sample a mesh (V,F) n times. -Parameters ----------- - n number of samples - dblA #F list of double areas of triangles - -Returns -------- - B n by 3 list of barycentric coordinates, ith row are coordinates of - ith sampled point in face FI(i) - FI n list of indices into F - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(random_points_on_mesh_intrinsic) -npe_doc(ds_random_points_on_mesh_intrinsic) - -npe_arg(n, int) -npe_arg(dblA, dense_float, dense_double) - - -npe_begin_code() - - EigenDenseLike b; - EigenDenseInt fi; - igl::random_points_on_mesh_intrinsic(n, dblA, b, fi); - return std::make_tuple(npe::move(b), npe::move(fi)); - -npe_end_code() - diff --git a/src/random_search.cpp b/src/random_search.cpp deleted file mode 100644 index 25fb39d9..00000000 --- a/src/random_search.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - - - -#include - -const char *ds_random_search = R"igl_Qu8mg5v7( - - Solve the problem: - - minimize f(x) - subject to lb ≤ x ≤ ub - - by uniform random search. - -Parameters ----------- -f function to minimize -LB #X vector of finite lower bounds -UB #X vector of finite upper bounds -iters number of iterations - -Returns -------- -f(X) -X #X optimal parameter vector - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(random_search) -npe_doc(ds_random_search) - -npe_arg(f, const std::function< typename EigenDenseFloat::Scalar (Eigen::Matrix &) >) -npe_arg(lb, EigenDenseFloat) -npe_arg(ub, EigenDenseFloat) -npe_arg(iters, int) - - -npe_begin_code() - - Eigen::Matrix x; - const auto obj = igl::random_search(f, lb, ub, iters, x); - return std::make_tuple(obj, npe::move(x)); - -npe_end_code() diff --git a/src/ray_box_intersect.cpp b/src/ray_box_intersect.cpp deleted file mode 100644 index 8dd913f9..00000000 --- a/src/ray_box_intersect.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - -#include - -const char *ds_ray_box_intersect = R"igl_Qu8mg5v7( - Determine whether a ray origin+t*dir and box intersect within the ray's parameterized - range (t0,t1) - -Parameters ----------- -source 3-vector origin of ray -dir 3-vector direction of ray -box_min min axis aligned box -box_max max axis aligned box -t0 hit only if hit.t less than t0 -t1 hit only if hit.t greater than t1 - -Returns -------- -true if hit -tmin minimum of interval of overlap within [t0,t1] -tmax maximum of interval of overlap within [t0,t1] - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(ray_box_intersect) -npe_doc(ds_ray_box_intersect) - -npe_arg(source, dense_float, dense_double) -npe_arg(dir, npe_matches(source)) -npe_arg(box_min, npe_matches(source)) -npe_arg(box_max, npe_matches(source)) -npe_arg(t0, double) -npe_arg(t1, double) - - -npe_begin_code() - assert_size_equals(source, 3, "source"); - assert_size_equals(dir, 3, "dir"); - assert_size_equals(box_min, 3, "box_min"); - assert_size_equals(box_max, 3, "box_max"); - - Eigen::Matrix dir_copy = dir.template cast(); - Eigen::Matrix source_copy = source.template cast(); - - Eigen::AlignedBox box_copy(box_min.template cast(), box_max.template cast()); - - double tmin; - double tmax; - - double t0_copy = t0; - double t1_copy = t1; - bool hit = igl::ray_box_intersect(source_copy, dir_copy, box_copy, t0_copy, t1_copy, tmin, tmax); - return std::make_tuple(hit, tmin, tmax); - -npe_end_code() - - diff --git a/src/ray_mesh_intersect.cpp b/src/ray_mesh_intersect.cpp deleted file mode 100644 index a0e7e85c..00000000 --- a/src/ray_mesh_intersect.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include -#include -#include -#include - -const char *ds_ray_mesh_intersect = R"igl_Qu8mg5v7( -Shoot a ray against a mesh (V,F) and collect the first hit. - -Parameters ----------- -source 3-vector origin of ray -dir 3-vector direction of ray -V #V by 3 list of mesh vertex positions -F #F by 3 list of mesh face indices into V - -Returns -------- -hits **sorted** list of hits: id, gid, u, v, t - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(ray_mesh_intersect) -npe_doc(ds_ray_mesh_intersect) - -npe_arg(source, dense_float, dense_double) -npe_arg(dir, npe_matches(source)) -// This does not work and seems to be a bug in numpyeigen -//// npe_arg(v, npe_matches(source)) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_size_equals(source, 3, "source"); - assert_size_equals(dir, 3, "dir"); - assert_valid_3d_tri_mesh(v, f); - - std::vector hits; - igl::ray_mesh_intersect( - source.template cast().eval(), - dir.template cast().eval(), - v, f, hits); - std::vector> hits_res; - - for(const auto &h : hits) - hits_res.emplace_back(h.id, h.gid, h.u, h.v, h.t); - - return hits_res; - -npe_end_code() diff --git a/src/ray_sphere_intersect.cpp b/src/ray_sphere_intersect.cpp deleted file mode 100644 index 13c27f18..00000000 --- a/src/ray_sphere_intersect.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_ray_sphere_intersect = R"igl_Qu8mg5v7( -Compute the intersection between a ray from O in direction D and a sphere centered at C with radius r - -Parameters ----------- -source origin of ray -dir direction of ray -center center of sphere -r radius of sphere - -Returns -------- -Returns the number of hits -t0 parameterization of first hit (set only if exists) so that hit position = o + t0*d -t1 parameterization of second hit (set only if exists) - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(ray_sphere_intersect) -npe_doc(ds_ray_sphere_intersect) - -npe_arg(source, dense_float, dense_double) -npe_arg(dir, npe_matches(source)) -npe_arg(center, npe_matches(source)) -npe_arg(r, double) - - -npe_begin_code() - assert_size_equals(source, 3, "source"); - assert_size_equals(dir, 3, "dir"); - assert_size_equals(center, 3, "center"); - - Eigen::Matrix dir_copy = dir.template cast(); - Eigen::Matrix source_copy = source.template cast(); - Eigen::Matrix center_copy = center.template cast(); - - double t0; - double t1; - int inters = igl::ray_sphere_intersect(source_copy, dir_copy, center_copy, r, t0, t1); - return std::make_tuple(inters, t0, t1); - -npe_end_code() - - diff --git a/src/readDMAT.cpp b/src/readDMAT.cpp deleted file mode 100644 index 28862c7b..00000000 --- a/src/readDMAT.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include - - - - - - -#include - -const char* ds_read_dmat = R"igl_Qu8mg5v7( -Read a matrix from an ascii/binary dmat file, a simple ascii matrix file type, defined as follows. The first line is always: -<#columns> <#rows> -Then the coefficients of the matrix are given separated by whitespace with columns running fastest. - -Parameters ----------- -filename : string, path to .dmat file -dtype : data-type of the returned matrix. Default is `float64`. - (returned faces always have type int32.) - -Returns -------- -w : array containing read-in coefficients - -See also --------- -read_triangle_mesh, read_off - -Notes ------ -None - -Examples --------- ->>> w = read_dmat("my_model.dmat") -)igl_Qu8mg5v7"; - -npe_function(read_dmat) -npe_doc(ds_read_dmat) -npe_arg(filename, std::string) -npe_default_arg(dtype, npe::dtype, "float64") -npe_begin_code() - - if (dtype.type() == npe::type_f32) { - EigenDenseF32 w; - bool ret = igl::readDMAT(filename, w); - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found."); - } - return npe::move(w); - } else if (dtype.type() == npe::type_f64) { - EigenDenseF64 w; - bool ret = igl::readDMAT(filename, w); - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found."); - } - return npe::move(w); - } else { - throw pybind11::type_error("Only float32 and float64 dtypes are supported."); - } - -npe_end_code() - - - diff --git a/src/readMESH.cpp b/src/readMESH.cpp deleted file mode 100644 index 66e210db..00000000 --- a/src/readMESH.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_read_mesh = R"igl_Qu8mg5v7( -Load a tetrahedral volume mesh from a .mesh file. - -Parameters ----------- -filename : path of .mesh file -dtype : data-type of the returned vertices, optional. Default is `float64`. - (returned faces always have type int32.) - -Returns -------- -v : array of vertex positions #v by 3 -t : #t by 4 array of tet indices into vertex positions -f : #f by 3 array of face indices into vertex positions - -See also --------- -None - -Notes ------ -Known bugs: Holes and regions are not supported - -Examples --------- ->>> v, t, f = read_mesh("my_mesh.mesh") -)igl_Qu8mg5v7"; - -npe_function(read_mesh) -npe_doc(ds_read_mesh) -npe_arg(filename, std::string) -npe_default_arg(dtypef, npe::dtype, "float") -npe_begin_code() - - if (dtypef.type() == npe::type_f32) { - EigenDenseF32 v; - EigenDenseInt f, t; - bool ret = igl::readMESH(filename, v, t, f); - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found."); - } - return std::make_tuple(npe::move(v), npe::move(t), npe::move(f)); - } else if (dtypef.type() == npe::type_f64) { - EigenDenseF64 v; - EigenDenseInt f, t; - bool ret = igl::readMESH(filename, v, t, f); - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found."); - } - return std::make_tuple(npe::move(v), npe::move(t), npe::move(f)); - } else { - throw pybind11::type_error("Only float32 and float64 dtypes are supported."); - } - -npe_end_code() diff --git a/src/readMSH.cpp b/src/readMSH.cpp deleted file mode 100644 index e51cc02a..00000000 --- a/src/readMSH.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -const char *ds_read_msh = R"igl_Qu8mg5v7( -Read a mesh (e.g., tet mesh) from a gmsh .msh file - -Parameters ----------- -filename path to .msh file -dtype : data-type of the returned vertices, optional. Default is `float64`. - (returned faces always have type int32.) - -Returns -------- - -V #V by 3 list of 3D mesh vertex positions -T #T by ss list of 3D ss-element indices into V (e.g., ss=4 for tets) - -See also --------- - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(read_msh) -npe_doc(ds_read_msh) - -npe_arg(filename, std::string) -npe_default_arg(dtypef, npe::dtype, "float") - -npe_begin_code() - Eigen::MatrixXd v; - Eigen::MatrixXi t; - - //NOTE: readMSH only support vertices as matrix of doubles. We instead cast the output accordingly. - bool ret = igl::readMSH(filename, v, t); - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found."); - } - - EigenDenseInt t_row_maj = t.template cast(); - - if (dtypef.type() == npe::type_f32) { - EigenDenseF32 v_row_maj = v.template cast (); - return std::make_tuple(npe::move(v_row_maj ), npe::move(t_row_maj)); - } else if (dtypef.type() == npe::type_f64) { - EigenDenseF64 v_row_maj = v.template cast (); - return std::make_tuple(npe::move(v_row_maj ), npe::move(t_row_maj)); - } - - throw pybind11::type_error("Only float32 and float64 dtypes are supported."); - -npe_end_code() - - diff --git a/src/readOBJ.cpp b/src/readOBJ.cpp deleted file mode 100644 index e00c9cc1..00000000 --- a/src/readOBJ.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_read_obj = R"igl_Qu8mg5v7( -Read a mesh from an ascii obj file, filling in vertex positions, normals -and texture coordinates. Only meshes with constant face degree are supported. - -Parameters ----------- -filename : string, path to .obj file -dtype : data-type of the returned faces, texture coordinates and normals, optional. Default is `float64`. - (returned faces always have type int32.) - -Returns -------- -v : array of vertex positions #v by 3 -tc : array of texture coordinats #tc by 2 -n : array of corner normals #n by 3 -f : #f array of face indices into vertex positions -ftc : #f array of face indices into vertex texture coordinates -fn : #f array of face indices into vertex normals - -See also --------- -read_triangle_mesh, read_off - -Notes ------ -None - -Examples --------- ->>> v, _, n, f, _, _ = read_obj("my_model.obj") -)igl_Qu8mg5v7"; - -npe_function(read_obj) -npe_doc(ds_read_obj) -npe_arg(filename, std::string) -npe_default_arg(dtype, npe::dtype, "float64") -npe_begin_code() - - if (dtype.type() == npe::type_f32) { - EigenDenseF32 v, tc, n; - EigenDenseInt f, ftc, fn; - bool ret = igl::readOBJ(filename, v, tc, n, f, ftc, fn); - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found or containing faces of varying degree (which is not supported yet)."); - } - return std::make_tuple(npe::move(v), npe::move(tc), npe::move(n), npe::move(f), npe::move(ftc), npe::move(fn)); - } else if (dtype.type() == npe::type_f64) { - EigenDenseF64 v, tc, n; - EigenDenseInt f, ftc, fn; // TODO weird problem, int64 gives int128 dtype in numpy - bool ret = igl::readOBJ(filename, v, tc, n, f, ftc, fn); - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found or containing faces of varying degree (which is not supported yet)."); - } - return std::make_tuple(npe::move(v), npe::move(tc), npe::move(n), npe::move(f), npe::move(ftc), npe::move(fn)); - } else { - throw pybind11::type_error("Only float32 and float64 dtypes are supported."); - } - -npe_end_code() diff --git a/src/readOFF.cpp b/src/readOFF.cpp deleted file mode 100644 index 460dee82..00000000 --- a/src/readOFF.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_read_off = R"igl_Qu8mg5v7( -Read a mesh from an ascii off file, filling in vertex positions, normals -and texture coordinates. Mesh may have faces of any number of degree. - -Parameters ----------- -filename : string, path to .off file -read_normals : bool, determines whether normals are read. If false, returns [] -dtype : data-type of the returned vertices, faces, and normals, optional. Default is `float64`. - (returned faces always have type int32.) - -Returns -------- -v : array of vertex positions #v by 3 -f : #f list of face indices into vertex positions -n : list of vertex normals #v by 3 - -See also --------- -read_triangle_mesh, read_obj - -Notes ------ -None - -Examples --------- ->>> v, f, n, c = read_off("my_model.off") -)igl_Qu8mg5v7"; - -npe_function(read_off) -npe_doc(ds_read_off) -npe_arg(filename, std::string) -npe_default_arg(read_normals, bool, true) -npe_default_arg(dtype, npe::dtype, "float64") -npe_begin_code() - - if (dtype.type() == npe::type_f32) { - EigenDenseF32 v, n; - EigenDenseInt f; - bool ret; - if (read_normals) { - ret = igl::readOFF(filename, v, f, n); - } - else { - ret = igl::readOFF(filename, v, f); - } - - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found."); - } - - return std::make_tuple(npe::move(v), npe::move(f), npe::move(n)); - } else if (dtype.type() == npe::type_f64) { - EigenDenseF64 v, n; - EigenDenseInt f; - bool ret; - if (read_normals) { - ret = igl::readOFF(filename, v, f, n); - } - else { - ret = igl::readOFF(filename, v, f); - } - - if (!ret) { - throw std::invalid_argument("File '" + filename + "' not found."); - } - - return std::make_tuple(npe::move(v), npe::move(f), npe::move(n)); - } else { - throw pybind11::type_error("Only float32 and float64 dtypes are supported."); - } - -npe_end_code() diff --git a/src/readTGF.cpp b/src/readTGF.cpp deleted file mode 100644 index 285ae553..00000000 --- a/src/readTGF.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include - - - -#include - -const char *ds_read_tgf = R"igl_Qu8mg5v7( -Read a graph from a .tgf file - -Parameters ----------- -filename .tgf file name - -Returns -------- - - V # vertices by 3 list of vertex positions - E # edges by 2 list of edge indices - P # point-handles list of point handle indices - BE # bone-edges by 2 list of bone-edge indices - CE # cage-edges by 2 list of cage-edge indices - PE # pseudo-edges by 2 list of pseudo-edge indices - -See also --------- - - -Notes ------ -Assumes that graph vertices are 3 dimensional - -Examples --------- - V,E,P,BE,CE,PE = igl.read_tgf(filename) - -)igl_Qu8mg5v7"; - -npe_function(read_tgf) -npe_doc(ds_read_tgf) - -npe_arg(tgf_filename, std::string) - - -npe_begin_code() - - Eigen::MatrixXd v; - Eigen::MatrixXi e; - Eigen::VectorXi p; - Eigen::MatrixXi be; - Eigen::MatrixXi ce; - Eigen::MatrixXi pe; - bool ok = igl::readTGF(tgf_filename, v, e, p, be, ce, pe); - if (!ok) - { - throw std::invalid_argument("File '" + tgf_filename + "' not found."); - } - - EigenDenseFloat v_row_major = v.template cast(); - EigenDenseInt e_row_major = e.template cast(); - EigenDenseInt p_row_major = p.template cast(); - EigenDenseInt be_row_major = be.template cast(); - EigenDenseInt ce_row_major = ce.template cast(); - EigenDenseInt pe_row_major = pe.template cast(); - - return std::make_tuple(npe::move(v_row_major), npe::move(e_row_major), npe::move(p_row_major), npe::move(be_row_major), npe::move(ce_row_major), npe::move(pe_row_major)); - -npe_end_code() diff --git a/src/read_triangle_mesh.cpp b/src/read_triangle_mesh.cpp index 0eb32ec2..641d35fb 100644 --- a/src/read_triangle_mesh.cpp +++ b/src/read_triangle_mesh.cpp @@ -1,74 +1,45 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include +#include "default_types.h" #include +#include +#include +#include +#include -const char *ds_read_triangle_mesh = R"igl_Qu8mg5v7( -Read mesh from an ascii file with automatic detection of file format. -Supported: obj, off, stl, wrl, ply, mesh. +namespace nb = nanobind; +using namespace nb::literals; -Parameters ----------- -filename : string, path to mesh file -dtype : data-type of the returned vertices, optional. Default is `float64`. - (returned faces always have type int32.) - -Returns -------- -v : array of vertex positions #v by 3 -f : #f list of face indices into vertex positions - -See also --------- - - -Notes ------ -None - -Examples --------- ->>> v, f = read_triangle_mesh("my_model.obj") -)igl_Qu8mg5v7"; - -npe_function(read_triangle_mesh) - npe_doc(ds_read_triangle_mesh) - npe_arg(filename, std::string) - npe_default_arg(dtypef, npe::dtype, "float") - npe_begin_code() - - if (dtypef.type() == npe::type_f32) +namespace pyigl { - EigenDenseF32 v; - EigenDenseInt f; - bool ret = igl::read_triangle_mesh(filename, v, f); - if (!ret) + nb::object read_triangle_mesh( + const std::string str) { - throw std::invalid_argument("File '" + filename + "' not found."); + Eigen::MatrixXN V; + Eigen::MatrixXI F; + if(!igl::read_triangle_mesh(str,V,F)) + { + // throw runtime exception + throw std::runtime_error("Failed to read mesh from: " + str); + } + return nb::make_tuple(V,F); } - return std::make_tuple(npe::move(v), npe::move(f)); } -else if (dtypef.type() == npe::type_f64) -{ - EigenDenseF64 v; - EigenDenseInt f; - bool ret = igl::read_triangle_mesh(filename, v, f); - if (!ret) - { - throw std::invalid_argument("File '" + filename + "' not found."); - } - return std::make_tuple(npe::move(v), npe::move(f)); -} -else + +// Bind the wrapper to the Python module +void bind_read_triangle_mesh(nb::module_ &m) { - throw pybind11::type_error("Only float32 and float64 dtypes are supported."); + m.def( + "read_triangle_mesh", + &pyigl::read_triangle_mesh, + "filename"_a, +R"(Read mesh from an ascii file with automatic detection of file format +among: mesh, msh obj, off, ply, stl, wrl. + +@param[in] filename path to file +@param[out] V double matrix #V by 3 +@param[out] F int matrix #F by 3 +@return true iff success)" + ); } -npe_end_code() + + diff --git a/src/remove_duplicate_vertices.cpp b/src/remove_duplicate_vertices.cpp deleted file mode 100644 index 339ffbaa..00000000 --- a/src/remove_duplicate_vertices.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -const char* ds_remove_duplicate_vertices = R"igl_Qu8mg5v7( - -REMOVE_DUPLICATE_VERTICES Remove duplicate vertices upto a uniqueness - tolerance (epsilon) - -Parameters ----------- -V #V by dim list of vertex positions -F #F by 3 list of triangle indices -epsilon uniqueness tolerance (significant digit), can probably think of - this as a tolerance on L1 distance - - -Returns -------- -SV #SV by dim new list of vertex positions -SVI #V by 1 list of indices so SV = V(SVI,:) -SVJ #SV by 1 list of indices so V = SV(SVJ,:) -SF #SF by dim new list of faces so SF = F(SVJ,:) - -See also --------- - - -Notes ------ -None - -Examples --------- -% Mesh in (V,F) -[SV,SVI,SVJ,SF] = remove_duplicate_vertices(V,F,1e-7); -% remap faces -SF = SVJ(F); - - -)igl_Qu8mg5v7"; - -npe_function(remove_duplicate_vertices) -npe_doc(ds_remove_duplicate_vertices) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(epsilon, double) - - -npe_begin_code() - - // TODO: remove __copy - // I believe we can prevent this. The libigl function uses "DerivedV rv" - // which calls Eigen::Map(Eigen::Matrix<>)::Map() and DNE - assert_nonzero_rows(v, "v"); - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXd sv; - Eigen::Matrix svi, svj; - EigenDenseLike sf; - igl::remove_duplicate_vertices(v_copy, f, epsilon, sv, svi, svj, sf); - EigenDenseFloat sv_row_major = sv; - return std::make_tuple(npe::move(sv_row_major), npe::move(svi), npe::move(svj), npe::move(sf)); - -npe_end_code() - - diff --git a/src/remove_unreferenced.cpp b/src/remove_unreferenced.cpp deleted file mode 100644 index 81e59509..00000000 --- a/src/remove_unreferenced.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include -#include - -const char* ds_remove_unreferenced = R"igl_Qu8mg5v7( - -Remove unreferenced vertices from V, updating F accordingly - -Parameters ----------- -V #V by dim list of mesh vertex positions -F #F by ss list of simplices (Values of -1 are quitely skipped) - - -Returns -------- -NV #NV by dim list of mesh vertex positions -NF #NF by ss list of simplices -IM #V by 1 list of indices such that: NF = IM(F) and NT = IM(T) - and V(find(IM<=size(NV,1)),:) = NV -J #RV by 1 list, such that RV = V(J,:) - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(remove_unreferenced) -npe_doc(ds_remove_unreferenced) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tet_or_tri_mesh_23d(v, f); - EigenDenseLike nv; - EigenDenseLike nf; - Eigen::Matrix i, j; - igl::remove_unreferenced(v, f, nv, nf, i, j); - return std::make_tuple(npe::move(nv), npe::move(nf), npe::move(i), npe::move(j)); - -npe_end_code() - diff --git a/src/resolve_duplicated_faces.cpp b/src/resolve_duplicated_faces.cpp deleted file mode 100644 index a5ea1234..00000000 --- a/src/resolve_duplicated_faces.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_resolve_duplicated_faces = R"igl_Qu8mg5v7( - -Resolve duplicated faces according to the following rules per unique face: - - If the number of positively oriented faces equals the number of - negatively oriented faces, remove all duplicated faces at this triangle. - - If the number of positively oriented faces equals the number of - negatively oriented faces plus 1, keeps one of the positively oriented - face. - - If the number of positively oriented faces equals the number of - negatively oriented faces minus 1, keeps one of the negatively oriented - face. - - If the number of postively oriented faces differ with the number of - negativley oriented faces by more than 1, the mesh is not orientable. - An exception will be thrown. - -Parameters ----------- -F1 #F1 by 3 array of input faces. - -Returns -------- -F2 #F2 by 3 array of output faces without duplicated faces. -J #F2 list of indices into F1. - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(resolve_duplicated_faces) -npe_doc(ds_resolve_duplicated_faces) - -npe_arg(f1, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f1, "f1"); - // TODO: remove __copy - // same problem that DerivedF1 causes problem, can be prevented - Eigen::MatrixXi f1_copy = f1.template cast(); - Eigen::MatrixXi f2_copy; - Eigen::Matrix j; - igl::resolve_duplicated_faces(f1_copy, f2_copy, j); - EigenDenseLike f2 = f2_copy.template cast(); - return std::make_tuple(npe::move(f2), npe::move(j)); - -npe_end_code() - - diff --git a/src/rigid_alignment.cpp b/src/rigid_alignment.cpp deleted file mode 100644 index 4f77b3b9..00000000 --- a/src/rigid_alignment.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_rigid_alignment = R"igl_Qu8mg5v7( - -Find the rigid transformation that best aligns the 3D points X to their -corresponding points P with associated normals N. - -min ‖(X*R+t-P)'N‖² -R∈SO(3) -t∈R³ - -Parameters ----------- - -X #X by 3 list of query points -P #X by 3 list of corresponding (e.g., closest) points -N #X by 3 list of unit normals for each row in P - -Returns -------- - -R 3 by 3 rotation matrix -t 1 by 3 translation vector - -See also --------- -icp - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(rigid_alignment) -npe_doc(ds_rigid_alignment) - -npe_arg(x, dense_float, dense_double) -npe_arg(p, npe_matches(x)) -npe_arg(n, npe_matches(x)) - - -npe_begin_code() - assert_cols_equals(x, 3, "x"); - - assert_rows_match(x, p, "x", "p"); - assert_rows_match(x, n, "x", "n"); - - assert_cols_match(x, p, "x", "p"); - assert_cols_match(x, n, "x", "n"); - - EigenDense x_copy = x; - - EigenDense r; - Eigen::Matrix t; - igl::rigid_alignment(x_copy, p, n, r, t); - return std::make_tuple(npe::move(r), npe::move(t)); - -npe_end_code() - - diff --git a/src/rotate_vectors.cpp b/src/rotate_vectors.cpp deleted file mode 100644 index c78e1693..00000000 --- a/src/rotate_vectors.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_rotate_vectors = R"igl_Qu8mg5v7( - Rotate the vectors V by A radiants on the tangent plane spanned by B1 and B2 - -Parameters ----------- -V #V by 3 eigen Matrix of vectors -A #V eigen vector of rotation angles or a single angle to be applied to all vectors -B1 #V by 3 eigen Matrix of base vector 1 -B2 #V by 3 eigen Matrix of base vector 2 -Returns -------- -Returns the rotated vectors - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(rotate_vectors) -npe_doc(ds_rotate_vectors) - -npe_arg(v, dense_float, dense_double) -npe_arg(a, npe_matches(v)) -npe_arg(b1, npe_matches(v)) -npe_arg(b2, npe_matches(v)) - - -npe_begin_code() - if(a.size() > 1) - assert_rows_match(v, a, "V", "A"); - assert_rows_match(v, b1, "V", "B1"); - assert_rows_match(v, b2, "V", "B2"); - - assert_cols_equals(v, 3, "V"); - assert_cols_equals(b1, 3, "B1"); - assert_cols_equals(b2, 3, "B2"); - assert_cols_equals(a, 1, "A"); - - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::VectorXd a_copy = a.template cast(); - Eigen::MatrixXd b1_copy = b1.template cast(); - Eigen::MatrixXd b2_copy = b2.template cast(); - - Eigen::MatrixXd res = igl::rotate_vectors(v_copy, a_copy, b1_copy, b2_copy); - EigenDenseLike res_row_maj = res.template cast(); - return npe::move(res_row_maj); - - npe_end_code() diff --git a/src/sample_edges.cpp b/src/sample_edges.cpp deleted file mode 100644 index 2ed96c45..00000000 --- a/src/sample_edges.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_sample_edges = R"igl_Qu8mg5v7( -Compute samples_per_edge extra points along each edge in E defined over - vertices of V. - -Parameters ----------- -V vertices over which edges are defined, # vertices by dim -E edge list, # edges by 2 -k number of extra samples to be computed along edge not including start and end points - -Returns -------- -S sampled vertices, size less than # edges * (2+k) by dim always begins - with V so that E is also defined over S - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(sample_edges) -npe_doc(ds_sample_edges) - -npe_arg(v, dense_float, dense_double) -npe_arg(e, dense_int32, dense_int64) -npe_arg(k, int) - - -npe_begin_code() - assert_nonzero_rows(v, "v"); - assert_nonzero_rows(e, "e"); - assert_cols_equals(e, 2, "e"); - - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi e_copy = e.template cast(); - Eigen::MatrixXd s_copy; - igl::sample_edges(v_copy, e_copy, k, s_copy); - EigenDenseLike s = s_copy.template cast(); - return npe::move(s); - -npe_end_code() - - diff --git a/src/segment_segment_intersect.cpp b/src/segment_segment_intersect.cpp deleted file mode 100644 index be7edf1d..00000000 --- a/src/segment_segment_intersect.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example -// error at line 33 and 41, saying cross is only for certain size matrices - -#include -#include -#include - - - - - - -#include - -const char* ds_segments_intersect = R"igl_Qu8mg5v7( - -Determine whether two line segments A,B intersect - A: p + t*r : t \in [0,1] - B: q + u*s : u \in [0,1] - -Parameters ----------- -p 3-vector origin of segment A -r 3-vector direction of segment A -q 3-vector origin of segment B -s 3-vector direction of segment B -eps precision - - -Returns -------- -t scalar point of intersection along segment A, t \in [0,1] -u scalar point of intersection along segment B, u \in [0,1] -Returns true if intersection - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(segments_intersect) -npe_doc(ds_segments_intersect) - -npe_arg(p, dense_float, dense_double) -npe_arg(r, npe_matches(p)) -npe_arg(q, npe_matches(p)) -npe_arg(s, npe_matches(p)) - - -npe_begin_code() - assert_shape_equals(p, 3, 1, "p"); - assert_shapes_match(p, r, "p", "r"); - assert_shapes_match(p, q, "p", "q"); - assert_shapes_match(p, s, "p", "s"); - // TODO: remove __copy - Eigen::Vector3d p_copy = p.template cast(); - Eigen::Vector3d r_copy = r.template cast(); - Eigen::Vector3d q_copy = q.template cast(); - Eigen::Vector3d s_copy = s.template cast(); - - double t; - double u; - double eps; - bool is_intersect = igl::segment_segment_intersect(p_copy, r_copy, q_copy, s_copy, t, u, eps); - return std::make_tuple(is_intersect, t, u, eps); - -npe_end_code() - - diff --git a/src/shape_diameter_function.cpp b/src/shape_diameter_function.cpp deleted file mode 100644 index 3b9806aa..00000000 --- a/src/shape_diameter_function.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example __miss - - -#include -#include -#include -#include - -const char* ds_shape_diameter_function = R"igl_Qu8mg5v7( - -Compute shape diamater function per given point. In the parlence of the - paper "Consistent Mesh Partitioning and Skeletonisation using the Shape - Diameter Function" [Shapiro et al. 2008], this implementation uses a 180° - cone and a _uniform_ average (_not_ a average weighted by inverse angles). - -Parameters ----------- -V #V by 3 list of mesh vertex positions -F #F by 3 list of mesh face indices into V -P #P by 3 list of origin points -N #P by 3 list of origin normals - - -Returns -------- -S #P list of shape diamater function values between bounding box - diagonal (perfect sphere) and 0 (perfect needle hook) - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(shape_diameter_function) -npe_doc(ds_shape_diameter_function) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(p, npe_matches(v)) -npe_arg(n, npe_matches(v)) -npe_arg(num_samples, int) - -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - assert_nonzero_rows(p, "p"); - assert_shapes_match(p, n, "p", "n"); - EigenDenseLike s; - igl::shape_diameter_function(v, f, p, n, num_samples, s); - return npe::move(s); - -npe_end_code() - - diff --git a/src/sharp_edges.cpp b/src/sharp_edges.cpp deleted file mode 100644 index d57fffcc..00000000 --- a/src/sharp_edges.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -#include - -const char *ds_sharp_edges = R"igl_Qu8mg5v7xxxx( -SHARP_EDGES Given a mesh, compute sharp edges. - -Parameters ----------- -V #V by 3 list of vertex positions -F #F by 3 list of triangle mesh indices into V -angle dihedral angle considered to sharp (e.g., igl::PI * 0.11) - -Returns -------- -SE #SE by 2 list of edge indices into V -E #e by 2 list of edges in no particular order -uE #uE by 2 list of unique undirected edges -EMAP #F*3 list of indices into uE, mapping each directed edge to unique -undirected edge so that uE(EMAP(f+#F*c)) is the unique edge -corresponding to E.row(f+#F*c) -uE2E #uE list of lists of indices into E of coexisting edges, so that -E.row(uE2E[i][j]) corresponds to uE.row(i) for all j in -0..uE2E[i].size()-1. -sharp #SE list of indices into uE revealing sharp undirected edges - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7xxxx"; - -npe_function(sharp_edges) -npe_doc(ds_sharp_edges) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(angle, double) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - - // Eigen::MatrixXd v_copy = v.template cast(); - // Eigen::MatrixXi e_copy = e.template cast(); - // Eigen::MatrixXd s_copy; - - EigenDense se; - EigenDense e; - EigenDense ue; - Eigen::Matrix emap; - std::vector> u_e2_e; - std::vector sharp; - igl::sharp_edges(v, f, angle, se, e, ue, emap, u_e2_e, sharp); - return std::make_tuple(npe::move(se), npe::move(e), npe::move(ue), npe::move(emap), u_e2_e, sharp); - - npe_end_code() diff --git a/src/signed_angle.cpp b/src/signed_angle.cpp deleted file mode 100644 index 36b1ac40..00000000 --- a/src/signed_angle.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_signed_angle = R"igl_Qu8mg5v7( - - Compute the signed angle subtended by the oriented 3d triangle (A,B,C) at some point P - - -Parameters ----------- -A 2D position of corner -B 2D position of corner -P 2D position of query point - -Returns -------- -returns signed angle - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(signed_angle) -npe_doc(ds_signed_angle) - -npe_arg(a, dense_float, dense_double) -npe_arg(b, npe_matches(a)) -npe_arg(p, npe_matches(a)) - - -npe_begin_code() - assert_size_equals(a, 2, "a"); - assert_size_equals(b, 2, "b"); - assert_size_equals(p, 2, "p"); - - Eigen::Vector2d a_copy, b_copy, p_copy; - - if(a.cols() == 1) - { - a_copy(0) = a(0, 0); - a_copy(1) = a(1, 0); - } - else - { - a_copy(0) = a(0, 0); - a_copy(1) = a(0, 1); - } - - if (b.cols() == 1) - { - b_copy(0) = b(0, 0); - b_copy(1) = b(1, 0); - } - else - { - b_copy(0) = b(0, 0); - b_copy(1) = b(0, 1); - } - - if (p.cols() == 1) - { - p_copy(0) = p(0, 0); - p_copy(1) = p(1, 0); - } - else - { - p_copy(0) = p(0, 0); - p_copy(1) = p(0, 1); - } - - const auto angle = igl::signed_angle(a_copy, b_copy, p_copy); - return angle; - -npe_end_code() - - diff --git a/src/signed_distance.cpp b/src/signed_distance.cpp deleted file mode 100644 index 4da33ef5..00000000 --- a/src/signed_distance.cpp +++ /dev/null @@ -1,108 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Francis Williams -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - -#include - -const char *ds_signed_distance = R"igl_Qu8mg5v7( -SIGNED_DISTANCE computes signed distance to a mesh - - -Parameters ----------- - P #P by 3 list of query point positions - V #V by 3 list of vertex positions - F #F by ss list of triangle indices, ss should be 3 unless sign_type - sign_type (Optional, defaults to psuedo-normal) Sign type method for - determining sign on signed distance - return_normals (Optional, defaults to False) If set to True, will return pseudonormals of - closest points to each query point in P -Returns -------- - S #P list of smallest signed distances - I #P list of facet indices corresponding to smallest distances - C #P by 3 list of closest points - -See also --------- - - -Notes ------ - Known issue: This only computes distances to triangles. So unreferenced - vertices and degenerate triangles are ignored. - -Examples --------- ->>> S, I, C = signed_distance(P, V, F, sign_type=igl.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER, return_normals=False) ->>> S, I, C, N = signed_distance(P, V, F, return_normals=True) - -)igl_Qu8mg5v7"; - -npe_function(signed_distance) -npe_doc(ds_signed_distance) - -npe_arg(p, dense_float, dense_double) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(sign_type, int, int(igl::SIGNED_DISTANCE_TYPE_DEFAULT)) -npe_default_arg(return_normals, bool, false) - -npe_begin_code() - assert_cols_equals(p, 3, "p"); - assert_nonzero_rows(p, "p"); - assert_valid_3d_tri_mesh(v, f, "v", "f"); - - // ensure valid sign_type given - if ((sign_type < 0) || (sign_type > igl::NUM_SIGNED_DISTANCE_TYPE)) { - //NOTE: compiler concats adjacent string literals. - throw pybind11::value_error( - "Parameter sign_type invalid, must be one of:" - "\n\t0: Use fast pseudo-normal test [Bærentzen & Aanæs 2005]" - "\n\t1: Use winding number [Jacobson, Kavan Sorking-Hornug 2013]" - "\n\t2: Default (pseudo-normal)" - "\n\t3: Unsigned" - "\n\t4: Use Fast winding number [Barill, Dickson, Schmidt, Levin, Jacobson 2018]\n" - ); - } - - //Ensure if normals requested, sign type is also SIGNED_DISTANCE_TYPE_PSEUDONORMAL - if (return_normals) { - // note: if sign_type is default, we assume they don't care and switch to pseudonormal! - if (sign_type == igl::SIGNED_DISTANCE_TYPE_DEFAULT) { - sign_type = igl::SIGNED_DISTANCE_TYPE_PSEUDONORMAL; - } else if (sign_type != igl::SIGNED_DISTANCE_TYPE_PSEUDONORMAL ) { - throw pybind11::value_error("Parameter sign_type must be SIGNED_DISTANCE_TYPE_PSEUDONORMAL for normals to be returned. Or return_normals should be false."); - } - } - - Eigen::MatrixXd V = v.template cast(); - Eigen::MatrixXd P = p.template cast(); - Eigen::MatrixXi F = f.template cast(); - - EigenDenseLike S; - Eigen::Matrix I; - EigenDenseLike C; - EigenDenseLike N; - - if (return_normals) { - igl::signed_distance(P, V, F, igl::SIGNED_DISTANCE_TYPE_PSEUDONORMAL, S, I, C, N); - return pybind11::make_tuple(npe::move(S), npe::move(I), npe::move(C), npe::move(N)); - } else { - // N is only populated when sign_type == SIGNED_DISTANCE_TYPE_PSEUDONORMAL - igl::signed_distance(P, V, F, static_cast(sign_type), S, I, C, N); - return pybind11::make_tuple(npe::move(S), npe::move(I), npe::move(C)); - } - -npe_end_code() - - - diff --git a/src/simplify_polyhedron.cpp b/src/simplify_polyhedron.cpp deleted file mode 100644 index 9f642cfc..00000000 --- a/src/simplify_polyhedron.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_simplify_polyhedron = R"igl_Qu8mg5v7( - - Simplify a polyhedron represented as a triangle mesh (OV,OF) by collapsing - any edge that doesn't contribute to defining surface's pointset. This - _would_ also make sense for open and non-manifold meshes, but the current - implementation only works with closed manifold surfaces with well defined - triangle normals. - -Parameters ----------- - OV #OV by 3 list of input mesh vertex positions - OF #OF by 3 list of input mesh triangle indices into OV - -Returns -------- - V #V by 3 list of output mesh vertex positions - F #F by 3 list of input mesh triangle indices into V - J #F list of indices into OF of birth parents - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(simplify_polyhedron) -npe_doc(ds_simplify_polyhedron) - -npe_arg(ov, dense_float, dense_double) -npe_arg(of, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_3d_tri_mesh(ov, of, "ov", "of"); - Eigen::MatrixXd ov_copy = ov.template cast(); - Eigen::MatrixXi of_copy = of.template cast(); - Eigen::MatrixXd v_copy; - Eigen::MatrixXi f_copy; - Eigen::VectorXi j_copy; - igl::simplify_polyhedron(ov_copy, of_copy, v_copy, f_copy, j_copy); - - EigenDenseLike v = v_copy.template cast < typename npe_Matrix_ov::Scalar >(); - EigenDenseLike f = f_copy.template cast(); - Eigen::Matrix j = j_copy.template cast(); - - return std::make_tuple(npe::move(v), npe::move(f), npe::move(j)); - - npe_end_code() diff --git a/src/snap_points.cpp b/src/snap_points.cpp deleted file mode 100644 index 65791641..00000000 --- a/src/snap_points.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - - -const char *ds_snap_points = R"igl_Qu8mg5v7( -SNAP_POINTS snap list of points C to closest of another list of points V -[I,minD,VI] = snap_points(C,V) - -Parameters ----------- -C #C by dim list of query point positions -V #V by dim list of data point positions - -Returns -------- -I #C list of indices into V of closest points to C -minD #C list of squared (^p) distances to closest points -VI #C by dim list of new point positions, VI = V(I,:) - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(snap_points) -npe_doc(ds_snap_points) - -npe_arg(c, dense_float, dense_double) -npe_arg(v, npe_matches(c)) - - -npe_begin_code() - assert_cols_match(c, v, "c", "v"); - assert_nonzero_rows(c, "c"); - assert_nonzero_rows(v, "v"); - - EigenDenseInt i; - EigenDense min_d; - EigenDense vi; - igl::snap_points(c, v, i, min_d, vi); - return std::make_tuple(npe::move(i), npe::move(min_d), npe::move(vi)); - -npe_end_code() diff --git a/src/solid_angle.cpp b/src/solid_angle.cpp deleted file mode 100644 index 7eb0118f..00000000 --- a/src/solid_angle.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_solid_angle = R"igl_Qu8mg5v7( - Compute the signed solid angle subtended by the oriented 3d triangle (A,B,C) at some point P - -Parameters ----------- - A 3D position of corner - B 3D position of corner - C 3D position of corner - P 3D position of query point - -Returns -------- - Returns signed solid angle - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(solid_angle) -npe_doc(ds_solid_angle) - -npe_arg(a, dense_float, dense_double) -npe_arg(b, npe_matches(a)) -npe_arg(c, npe_matches(a)) -npe_arg(p, npe_matches(a)) - - -npe_begin_code() - assert_size_equals(a, 3, "a"); - assert_size_equals(b, 3, "b"); - assert_size_equals(c, 3, "c"); - assert_size_equals(p, 3, "p"); - - using Scalar = typename npe_Matrix_a::Scalar; - using MatType = Eigen::Matrix; - //fixme whit eigen flag - Eigen::Map a_tmp(&a(0, 0), a.size()); - Eigen::Map b_tmp(&b(0, 0), b.size()); - Eigen::Map c_tmp(&c(0, 0), c.size()); - Eigen::Map p_tmp(&p(0, 0), p.size()); - - double res = igl::solid_angle(a_tmp, b_tmp, c_tmp, p_tmp); - return res; - -npe_end_code() - - diff --git a/src/sort_angles.cpp b/src/sort_angles.cpp deleted file mode 100644 index 2cd96779..00000000 --- a/src/sort_angles.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include -#include - -const char* ds_sort_angles = R"igl_Qu8mg5v7( - -Sort angles in ascending order in a numerically robust way. -Instead of computing angles using atan2(y, x), sort directly on (y, x). -Parameters ---------- -M: m by n matrix of scalars. (n >= 2). Assuming the first column of M - contains values for y, and the second column is x. Using the rest - of the columns as tie-breaker. - -Returns -------- -R: an array of m indices. M.row(R[i]) contains the i-th smallest - angle. - -See also --------- - - -Notes ------ -None. - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(sort_angles) -npe_doc(ds_sort_angles) - -npe_arg(m, dense_float, dense_double) - -npe_begin_code() - - assert_nonzero_rows(m, "m"); - //TODO: need to check column >= 2 - // In the libigl code r is column major, and using row major fails assertion - // EIGEN_STATIC_ASSERT((EIGEN_IMPLIES(MaxRowsAtCompileTime==1 && - // MaxColsAtCompileTime!=1, (Options&RowMajor)==RowMajor) - // FIXME: vector not allowing row major, but they should be essentially the same so i feel we can leave it as col major - Eigen::Matrix r; - igl::sort_angles(m, r); - return npe::move(r); - -npe_end_code() - - diff --git a/src/sparse_voxel_grid.cpp b/src/sparse_voxel_grid.cpp deleted file mode 100644 index 08127abf..00000000 --- a/src/sparse_voxel_grid.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include -#include - -#include - -const char *ds_sparse_voxel_grid = R"igl_Qu8mg5v7( - -Given a point, p0, on an isosurface, construct a shell of epsilon sized cubes surrounding the surface. -These cubes can be used as the input to marching cubes. - - -Parameters ----------- -p0 A 3D point on the isosurface surface defined by scalarFunc(x) = 0 -scalarFunc A scalar function from R^3 to R -- points which map to 0 lie - on the surface, points which are negative lie inside the surface, - and points which are positive lie outside the surface -eps The edge length of the cubes surrounding the surface -expected_number_of_cubes This pre-allocates internal data structures to speed things up - -Returns -------- - -CS #cube-vertices by 1 list of scalar values at the cube vertices -CV #cube-vertices by 3 list of cube vertex positions -CI #number of cubes by 8 list of indexes into CS and CV. Each row represents a cube - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(sparse_voxel_grid) -npe_doc(ds_sparse_voxel_grid) - -npe_arg(p0, EigenDenseFloat) -npe_arg(scalar_func, const std::function< typename EigenDenseFloat::Scalar (Eigen::Matrix &) >) -npe_arg(eps, double) -npe_arg(expected_number_of_cubes, int) - - -npe_begin_code() - assert_size_equals(p0, 3, "p0"); - - EigenDenseFloat cs; - EigenDenseFloat cv; - EigenDenseInt ci; - Eigen::Matrix p0_copy; - if(p0.cols() == 1) - p0_copy = p0.transpose(); - else - p0_copy = p0; - - igl::sparse_voxel_grid(p0_copy, scalar_func, eps, expected_number_of_cubes, cs, cv, ci); - return std::make_tuple(npe::move(cs), npe::move(cv), npe::move(ci)); - -npe_end_code() - - diff --git a/src/swept_volume_bounding_box.cpp b/src/swept_volume_bounding_box.cpp deleted file mode 100644 index 980d85b3..00000000 --- a/src/swept_volume_bounding_box.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -const char *ds_swept_volume_bounding_box = R"igl_Qu8mg5v7( -Construct an axis-aligned bounding box containing a shape undergoing a - motion sampled at `steps` discrete momements. - -Parameters ----------- -n number of mesh vertices -V function handle so that V(i,t) returns the 3d position of vertex i at time t, for t∈[0,1] -steps number of time steps: steps=3 --> t∈{0,0.5,1} - -Returns -------- -min,max corners of box containing mesh under motion - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(swept_volume_bounding_box) -npe_doc(ds_swept_volume_bounding_box) - -npe_arg(n, int) -npe_arg(v, std::function &) -npe_arg(steps, int &) - - -npe_begin_code() - - Eigen::AlignedBox3d box; - igl::swept_volume_bounding_box(n, v, steps, box); - - EigenDenseFloat mmin = box.min().template cast(); - EigenDenseFloat mmax = box.max().template cast(); - - return std::make_tuple(npe::move(mmin), npe::move(mmax)); - - npe_end_code() diff --git a/src/tet_tet_adjacency.cpp b/src/tet_tet_adjacency.cpp deleted file mode 100644 index b024e75d..00000000 --- a/src/tet_tet_adjacency.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example - -#include -#include -#include -#include - -const char *ds_tet_tet_adjacency = R"igl_Qu8mg5v7( -Constructs the tet_tet adjacency matrix for a given tet mesh with tets T - -Parameters ----------- -T #T by 4 list of tets - -Returns -------- -TT #T by #4 adjacency matrix, the element i,j is the id of the tet adjacent to the j face of tet i -TTi #T by #4 adjacency matrix, the element i,j is the id of face of the tet TT(i,j) that is adjacent to tet i - -See also --------- - -Notes ------ -the first face of a tet is [0,1,2], the second [0,1,3], the third [1,2,3], and the fourth [2,0,3]. - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(tet_tet_adjacency) -npe_doc(ds_tet_tet_adjacency) -npe_arg(t, dense_int32, dense_int64) -npe_begin_code() - - assert_nonzero_rows(t, "t"); - assert_cols_equals(t, 4, "t"); - EigenDenseLike tt, tti; - igl::tet_tet_adjacency(t, tt, tti); - return std::make_tuple(npe::move(tt), npe::move(tti)); - -npe_end_code() diff --git a/src/topological_hole_fill.cpp b/src/topological_hole_fill.cpp deleted file mode 100644 index a5c7a55f..00000000 --- a/src/topological_hole_fill.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -#include - -#include - -const char *ds_topological_hole_fill = R"igl_Qu8mg5v7( - -Topological fill hole on a mesh, with one additional vertex each hole -Index of new abstract vertices will be F.maxCoeff() + (index of hole) - -Parameters ----------- - -F #F by simplex-size list of element indices -holes vector of hole loops to fill - -Returns -------- -F_filled input F stacked with filled triangles. - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(topological_hole_fill) -npe_doc(ds_topological_hole_fill) - -npe_arg(f, dense_int32, dense_int64) -npe_arg(holes, std::vector>) - - -npe_begin_code() - - EigenDense f_filled; - igl::topological_hole_fill(f, holes, f_filled); - return npe::move(f_filled); - -npe_end_code() diff --git a/src/triangle/triangulate.cpp b/src/triangle/triangulate.cpp deleted file mode 100644 index 0037a305..00000000 --- a/src/triangle/triangulate.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include - -#include -#include - -#include - -const char* ds_triangulate = R"igl_Qu8mg5v7( -Triangulate the interior of a polygon using the triangle library. - -Parameters ----------- - V : #V by 2 list of 2D vertex positions - E : #E by 2 list of vertex ids forming unoriented edges of the boundary of the polygon - H : #H by 2 coordinates of points contained inside holes of the polygon - flags : string of options pass to triangle (see triangle documentation) -Returns --------- - V2 #V2 by 2 coordinates of the vertives of the generated triangulation - F2 #F2 by 3 list of indices forming the faces of the generated triangulation - -)igl_Qu8mg5v7"; - -npe_function(triangulate) -npe_doc(ds_triangulate) - -npe_arg(V, dense_float, dense_double) -npe_arg(E, dense_int32, dense_int64) -npe_default_arg(H, npe_matches(V) ,pybind11::array()) -npe_default_arg(flags, std::string, "") -npe_default_arg(VM, npe_matches(E), pybind11::array()) -npe_default_arg(EM, npe_matches(E), pybind11::array()) -npe_begin_code() - -EigenDenseLike V2; -EigenDenseLike F2; -EigenDenseLike VM2,E2,EM2; -using V2Type = decltype(V2); -using F2Type = decltype(F2); -using VM2Type = decltype(VM2); -using E2Type = decltype(E2); -using EM2Type = decltype(EM2); -if(VM.size() == 0 && EM.size() == 0) -{ - igl::triangle::triangulate(V, E, H, flags, V2, F2); - return std::list({npe::move(V2), npe::move(F2)}); -}else -{ - igl::triangle::triangulate(V, E, H, VM.reshaped(), EM.reshaped(), flags, V2, F2, VM2, E2, EM2); - if(VM.size() && EM.size()) - { - return std::list({npe::move(V2), npe::move(F2), npe::move(VM2), npe::move(E2), npe::move(EM2)}); - }else if(VM.size()) - { - return std::list({npe::move(V2), npe::move(F2), npe::move(VM2)}); - }else - { - return std::list({npe::move(V2), npe::move(F2), npe::move(E2), npe::move(EM2)}); - } -} - -npe_end_code() - diff --git a/src/triangle_fan.cpp b/src/triangle_fan.cpp deleted file mode 100644 index 85beb604..00000000 --- a/src/triangle_fan.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - -#include - -const char *ds_triangle_fan = R"igl_Qu8mg5v7( -Given a list of faces tessellate all of the "exterior" edges forming another - list of - -Parameters ----------- -E #E by 2 list of exterior edges (see exterior_edges.h) - -Returns -------- - -cap #cap by simplex_size list of "faces" tessellating the boundary edges - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(triangle_fan) -npe_doc(ds_triangle_fan) - -npe_arg(e, dense_int32, dense_int64) - - -npe_begin_code() - assert_nonzero_rows(e, "e"); - assert_cols_equals(e, 2, "e"); - Eigen::MatrixXi e_copy = e.template cast(); - Eigen::MatrixXi cap_copy; - igl::triangle_fan(e_copy, cap_copy); - EigenDenseLike cap = cap_copy.template cast(); - return npe::move(cap); - -npe_end_code() diff --git a/src/triangle_triangle_adjacency.cpp b/src/triangle_triangle_adjacency.cpp deleted file mode 100644 index 36b8b541..00000000 --- a/src/triangle_triangle_adjacency.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __miss __example - -#include -#include -#include -#include - -const char* ds_triangle_triangle_adjacency = R"igl_Qu8mg5v7( - -Constructs the triangle-triangle adjacency matrix for a given - mesh (V,F). - -Parameters ----------- -F #F by simplex_size list of mesh faces (must be triangles) - -Returns -------- -TT #F by #3 adjacent matrix, the element i,j is the id of the triangle - adjacent to the j edge of triangle i -TTi #F by #3 adjacent matrix, the element i,j is the id of edge of the - triangle TT(i,j) that is adjacent with triangle i - - -See also --------- - - -Notes ------ -NOTE: the first edge of a triangle is [0,1] the second [1,2] and the third - [2,3]. this convention is DIFFERENT from cotmatrix_entries.h - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(triangle_triangle_adjacency) -npe_doc(ds_triangle_triangle_adjacency) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - EigenDenseLike tt, t_ti; - igl::triangle_triangle_adjacency(f, tt, t_ti); - return std::make_tuple(npe::move(tt), npe::move(t_ti)); - -npe_end_code() - - diff --git a/src/triangles_from_strip.cpp b/src/triangles_from_strip.cpp deleted file mode 100644 index 4e9adabe..00000000 --- a/src/triangles_from_strip.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -const char *ds_triangles_from_strip = R"igl_Qu8mg5v7( - -TRIANGLES_FROM_STRIP Create a list of triangles from a stream of indices - along a strip. - -Parameters ----------- -S #S list of indices - -Returns -------- -F #S-2 by 3 list of triangle indices - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(triangles_from_strip) -npe_doc(ds_triangles_from_strip) - -npe_arg(s, dense_int32, dense_int64) - - -npe_begin_code() - Eigen::Matrix s_copy(s.size()); - - for(int i = 0; i < s_copy.size(); ++i) - { - if(s.cols() == 1) - s_copy(i) = s(i, 0); - else if (s.rows() == 1) - s_copy(i) = s(0, i); - else - throw std::invalid_argument("Input s must have rows or cols equal to 1"); - } - - EigenDense f; - igl::triangles_from_strip(s_copy, f); - return npe::move(f); - -npe_end_code() - - diff --git a/src/triangulated_grid.cpp b/src/triangulated_grid.cpp deleted file mode 100644 index 0440bb0f..00000000 --- a/src/triangulated_grid.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - -#include - -const char *ds_triangulated_grid = R"igl_Qu8mg5v7( - -Create a regular grid of elements (only 2D supported, currently) - Vertex position order is compatible with `igl::grid` - -Parameters ----------- -nx number of vertices in the x direction -ny number of vertices in the y direction - -Returns -------- - -GV nx*ny by 2 list of mesh vertex positions. -GF 2*(nx-1)*(ny-1) by 3 list of triangle indices - -See also --------- -grid, quad_grid - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(triangulated_grid) -npe_doc(ds_triangulated_grid) - -npe_arg(nx, int) -npe_arg(ny, int) - - -npe_begin_code() - - EigenDenseFloat gv; - EigenDenseInt gf; - igl::triangulated_grid(nx, ny, gv, gf); - return std::make_tuple(npe::move(gv), npe::move(gf)); - -npe_end_code() diff --git a/src/two_axis_valuator_fixed_up.cpp b/src/two_axis_valuator_fixed_up.cpp deleted file mode 100644 index 58657313..00000000 --- a/src/two_axis_valuator_fixed_up.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - - - - - -#include - -const char *ds_two_axis_valuator_fixed_up = R"igl_Qu8mg5v7( - -Applies a two-axis valuator drag rotation (as seen in Maya/Studio max) to a given rotation. - -Parameters ----------- -w width of the trackball context -h height of the trackball context -speed controls how fast the trackball feels, 1 is normal -down_quat rotation at mouse down, i.e. the rotation we're applying the - trackball motion to (as quaternion). **Note:** Up-vector that is fixed - is with respect to this rotation. -down_x position of mouse down -down_y position of mouse down -mouse_x current x position of mouse -mouse_y current y position of mouse - -Returns -------- -quat the resulting rotation (as quaternion) - -See also --------- -snap_to_fixed_up - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(two_axis_valuator_fixed_up) -npe_doc(ds_two_axis_valuator_fixed_up) - -npe_arg(w, int) -npe_arg(h, int) -npe_arg(speed, double) -npe_arg(down_quat, dense_float, dense_double) -npe_arg(down_x, int) -npe_arg(down_y, int) -npe_arg(mouse_x, int) -npe_arg(mouse_y, int) - - -npe_begin_code() - assert_size_equals(down_quat, 4, "down_quat"); - Eigen::Quaternion down_quat_copy; - if (down_quat.cols() == 1) - { - down_quat_copy.x() = down_quat(0, 0); - down_quat_copy.y() = down_quat(1, 0); - down_quat_copy.z() = down_quat(2, 0); - down_quat_copy.w() = down_quat(3, 0); - } - else - { - down_quat_copy.x() = down_quat(0, 0); - down_quat_copy.y() = down_quat(0, 1); - down_quat_copy.z() = down_quat(0, 2); - down_quat_copy.w() = down_quat(0, 3); - } - - Eigen::Quaternion quat_copy; - igl::two_axis_valuator_fixed_up(w, h, speed, down_quat_copy, down_x, down_y, mouse_x, mouse_y, quat_copy); - EigenDenseLike quat(4, 1); - quat(0) = quat_copy.x(); - quat(1) = quat_copy.y(); - quat(2) = quat_copy.z(); - quat(3) = quat_copy.w(); - - return npe::move(quat); - -npe_end_code() - - diff --git a/src/uniformly_sample_two_manifold.cpp b/src/uniformly_sample_two_manifold.cpp deleted file mode 100644 index 430262b9..00000000 --- a/src/uniformly_sample_two_manifold.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __example - -#include -#include -#include - - - - - - -#include - -const char* ds_uniformly_sample_two_manifold_internal = R"igl_Qu8mg5v7( - -UNIFORMLY_SAMPLE_TWO_MANIFOLD Attempt to sample a mesh uniformly by - furthest point relaxation as described in "Fast Automatic Skinning - Transformations" - - [Jacobson et al. 12] Section 3.3. - -Parameters ----------- -W #W by dim positions of mesh in weight space -F #F by 3 indices of triangles -k number of samplse -push factor by which corners should be pushed away - - -Returns -------- -WS k by dim locations in weights space - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(uniformly_sample_two_manifold_internal) -npe_doc(ds_uniformly_sample_two_manifold_internal) - -npe_arg(w, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(k, int) -npe_arg(push, double) - - -npe_begin_code() - - assert_valid_23d_tri_mesh(w, f); - // TODO: remove __copy - Eigen::MatrixXd w_copy = w.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXd ws; - igl::uniformly_sample_two_manifold(w_copy, f_copy, k, push, ws); - EigenDenseFloat ws_row_major = ws; - return npe::move(ws_row_major); - -npe_end_code() - - - - - -const char* ds_uniformly_sample_two_manifold_at_vertices = R"igl_Qu8mg5v7( - -Find uniform sampling up to placing samples on mesh vertices - -Parameters ----------- - - -Returns -------- - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(uniformly_sample_two_manifold_at_vertices) -npe_doc(ds_uniformly_sample_two_manifold_at_vertices) - -npe_arg(ow, dense_float, dense_double) -npe_arg(k, int) -npe_arg(push, double) - - -npe_begin_code() - - // TODO: remove __copy - Eigen::MatrixXd ow_copy = ow.template cast(); - // FIXME: vector not allowing row major, but they should be essentially the same so i feel we can leave it as col major - Eigen::VectorXi s; - igl::uniformly_sample_two_manifold_at_vertices(ow_copy, k, push, s); - Eigen::Matrix s_out = s.template cast(); - return npe::move(s_out); - -npe_end_code() - - diff --git a/src/unique_edge_map.cpp b/src/unique_edge_map.cpp deleted file mode 100644 index 69996722..00000000 --- a/src/unique_edge_map.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include -#include - -const char *ds_unique_edge_map = R"igl_Qu8mg5v7xxx( -Construct relationships between facet "half"-(or rather "viewed")-edges E -to unique edges of the mesh seen as a graph. - -Parameters ----------- -F #F by 3 list of simplices - - -Returns -------- -E #F*3 by 2 list of all directed edges, such that E.row(f+#F*c) is the -edge opposite F(f,c) -uE #uE by 2 list of unique undirected edges -EMAP #F*3 list of indices into uE, mapping each directed edge to unique -undirected edge so that uE(EMAP(f+#F*c)) is the unique edge -corresponding to E.row(f+#F*c) -uE2E #uE list of lists of indices into E of coexisting edges, so that -E.row(uE2E[i][j]) corresponds to uE.row(i) for all j in -0..uE2E[i].size()-1. - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7xxx"; - -npe_function(unique_edge_map) -npe_doc(ds_unique_edge_map) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_tri_mesh_faces(f); - - EigenDenseLike E; - EigenDenseLike uE; - Eigen::Matrix EMAP; - std::vector> uE2E; - igl::unique_edge_map(f, E, uE, EMAP, uE2E); - return std::make_tuple(npe::move(E), npe::move(uE), npe::move(EMAP), uE2E); - - npe_end_code() diff --git a/src/unique_simplices.cpp b/src/unique_simplices.cpp deleted file mode 100644 index 1082f8ed..00000000 --- a/src/unique_simplices.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_unique_simplices = R"igl_Qu8mg5v7( -Find *combinatorially* unique simplices in F. **Order independent** -Parameters ----------- -F #F by simplex-size list of simplices - -Returns -------- - FF #FF by simplex-size list of unique simplices in F - IA #FF index vector so that FF == sort(F(IA,:),2); - IC #F index vector so that sort(F,2) == FF(IC,:); - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(unique_simplices) -npe_doc(ds_unique_simplices) - -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_tet_or_tri_mesh_faces(f); - - EigenDenseLike ff; - Eigen::Matrix ia; - Eigen::Matrix ic; - igl::unique_simplices(f, ff, ia, ic); - return std::make_tuple(npe::move(ff), npe::move(ia), npe::move(ic)); - -npe_end_code() - - diff --git a/src/unproject.cpp b/src/unproject.cpp deleted file mode 100644 index e8e0028a..00000000 --- a/src/unproject.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -#include -#include -#include -#include - -const char* ds_unproject = R"igl_Qu8mg5v7( - Reimplementation of gluUnproject - -Parameters ----------- - win #P by 3 or 3-vector (#P=1) of screen space x, y, and z coordinates - model 4x4 model-view matrix - proj 4x4 projection matrix - viewport 4-long viewport vector - -Returns -------- - scene #P by 3 or 3-vector (#P=1) the unprojected x, y, and z coordinates - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(unproject) -npe_doc(ds_unproject) - -npe_arg(win, dense_float, dense_double) -npe_arg(model, npe_matches(win)) -npe_arg(proj, npe_matches(win)) -npe_arg(viewport, dense_float, dense_double) - - -npe_begin_code() - - assert_cols_equals(win, 3, "win"); - assert_rows_equals(model, 4, "model"); - assert_cols_equals(model, 4, "model"); - assert_shapes_match(model, proj, "model", "proj"); - assert_rows_equals(viewport, 4, "viewport"); - EigenDenseLike scene; - igl::unproject(win, model, proj, viewport, scene); - return npe::move(scene); - -npe_end_code() diff --git a/src/unproject_in_mesh.cpp b/src/unproject_in_mesh.cpp deleted file mode 100644 index 7ab50360..00000000 --- a/src/unproject_in_mesh.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - - - - - -#include - -const char* ds_unproject_in_mesh = R"igl_Qu8mg5v7( - -Unproject a screen location (using current opengl viewport, projection, and - model view) to a 3D position _inside_ a given mesh. If the ray through the - given screen location (x,y) _hits_ the mesh more than twice then the 3D - midpoint between the first two hits is return. If it hits once, then that - point is return. If it does not hit the mesh then obj is not set. - -Parameters ----------- -pos screen space coordinates -model model matrix -proj projection matrix -viewport vieweport vector -V #V by 3 list of mesh vertex positions -F #F by 3 list of mesh triangle indices into V - -Returns -------- -obj 3d unprojected mouse point in mesh -hits vector of hits -Returns number of hits - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(unproject_in_mesh) -npe_doc(ds_unproject_in_mesh) - -npe_arg(pos, dense_float, dense_double) -npe_arg(model, npe_matches(pos)) -npe_arg(proj, npe_matches(pos)) -npe_arg(viewport, npe_matches(pos)) -npe_arg(v, npe_matches(pos)) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - assert_rows_equals(pos, 2, "pos"); - assert_rows_equals(model, 4, "model"); - assert_cols_equals(model, 4, "model"); - assert_shapes_match(model, proj, "model", "proj"); - assert_rows_equals(viewport, 4, "viewport"); - - // TODO: remove __copy - Eigen::Vector2f pos_copy = pos.template cast(); - Eigen::Matrix4f model_copy = model.template cast(); - Eigen::Matrix4f proj_copy = proj.template cast(); - Eigen::Vector4f viewport_copy = viewport.template cast(); - - EigenDenseLike obj; - std::vector > hits; - igl::unproject_in_mesh(pos_copy, model_copy, proj_copy, viewport_copy, v, f, obj, hits); - return std::make_tuple(npe::move(obj), hits); - -npe_end_code() - diff --git a/src/unproject_on_line.cpp b/src/unproject_on_line.cpp deleted file mode 100644 index 2fc701a1..00000000 --- a/src/unproject_on_line.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - - -#include - -const char *ds_unproject_on_line = R"igl_Qu8mg5v7( - - -Given a screen space point (u,v) and the current projection matrix (e.g. -gl_proj * gl_modelview) and viewport, _unproject_ the point into the scene -so that it lies on given line (origin and dir) and projects as closely as -possible to the given screen space point. - -Parameters ----------- -UV 2-long uv-coordinates of screen space point -M 4 by 4 projection matrix -VP 4-long viewport: (corner_u, corner_v, width, height) -origin point on line -dir vector parallel to line - -Returns -------- -t line parameter so that closest poin on line to viewer ray through UV - lies at origin+t*dir -Z 3d position of closest point on line to viewing ray through UV - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(unproject_on_line) -npe_doc(ds_unproject_on_line) - -npe_arg(uv, dense_float, dense_double) -npe_arg(m, npe_matches(uv)) -npe_arg(vp, npe_matches(uv)) -npe_arg(origin, npe_matches(uv)) -npe_arg(dir, npe_matches(uv)) - - -npe_begin_code() - assert_size_equals(uv, 2, "uv"); - assert_rows_equals(m, 4, "m"); - assert_cols_equals(m, 4, "m"); - assert_size_equals(vp, 4, "uv"); - - assert_size_equals(origin, 3, "origin"); - assert_size_equals(dir, 3, "dir"); - - EigenDense uv_copy = uv; - EigenDense m_copy = m; - EigenDense vp_copy = vp; - EigenDense origin_copy = origin; - EigenDense dir_copy = dir; - - npe_Scalar_uv t; - igl::unproject_on_line(uv_copy, m_copy, vp_copy, origin_copy, dir_copy, t); - npe_Matrix_uv Z = origin + dir * t; - - return std::make_tuple(double(t), npe::move(Z)); - -npe_end_code() - diff --git a/src/unproject_on_plane.cpp b/src/unproject_on_plane.cpp deleted file mode 100644 index c09f860e..00000000 --- a/src/unproject_on_plane.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_unproject_on_plane = R"igl_Qu8mg5v7( - -Given a screen space point (u,v) and the current projection matrix (e.g. -gl_proj * gl_modelview) and viewport, _unproject_ the point into the scene -so that it lies on given plane. - - -Parameters ----------- - -UV 2-long uv-coordinates of screen space point -M 4 by 4 projection matrix -VP 4-long viewport: (corner_u, corner_v, width, height) -P 4-long plane equation coefficients: P*(X 1) = 0 - -Returns -------- -Z 3-long world coordinate -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(unproject_on_plane) -npe_doc(ds_unproject_on_plane) - -npe_arg(uv, dense_float, dense_double) -npe_arg(m, npe_matches(uv)) -npe_arg(vp, npe_matches(uv)) -npe_arg(p, npe_matches(uv)) - - -npe_begin_code() - assert_size_equals(uv, 2, "uv"); - assert_rows_equals(m, 4, "m"); - assert_cols_equals(m, 4, "m"); - assert_size_equals(vp, 4, "uv"); - assert_size_equals(p, 4, "p"); - - EigenDense uv_copy = uv; - EigenDense m_copy = m; - EigenDense vp_copy = vp; - Eigen::Matrix p_copy = p; - - EigenDense z; - igl::unproject_on_plane(uv_copy, m_copy, vp_copy, p_copy, z); - return npe::move(z); - -npe_end_code() - - diff --git a/src/unproject_onto_mesh.cpp b/src/unproject_onto_mesh.cpp deleted file mode 100644 index 91ee6c1f..00000000 --- a/src/unproject_onto_mesh.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __miss __example - -#include -#include -#include - - - - - - -#include - -const char* ds_unproject_onto_mesh = R"igl_Qu8mg5v7( - -Unproject a screen location (using current opengl viewport, projection, and - model view) to a 3D position _onto_ a given mesh, if the ray through the - given screen location (x,y) _hits_ the mesh. - -Parameters ----------- -pos screen space coordinates -model model matrix -proj projection matrix -viewport vieweport vector -V #V by 3 list of mesh vertex positions -F #F by 3 list of mesh triangle indices into V - -Returns -------- -fid id of the first face hit -bc barycentric coordinates of hit -Returns true if there's a hit - - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(unproject_onto_mesh) -npe_doc(ds_unproject_onto_mesh) - -npe_arg(pos, dense_float, dense_double) -npe_arg(model, npe_matches(pos)) -npe_arg(proj, npe_matches(pos)) -npe_arg(viewport, npe_matches(pos)) -npe_arg(v, npe_matches(pos)) -npe_arg(f, dense_int32, dense_int64) - - -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - assert_rows_equals(pos, 2, "pos"); - assert_rows_equals(model, 4, "model"); - assert_cols_equals(model, 4, "model"); - assert_shapes_match(model, proj, "model", "proj"); - assert_rows_equals(viewport, 4, "viewport"); - - // TODO: remove __copy - Eigen::Vector2f pos_copy = pos.template cast(); - Eigen::Matrix4f model_copy = model.template cast(); - Eigen::Matrix4f proj_copy = proj.template cast(); - Eigen::Vector4f viewport_copy = viewport.template cast(); - int fid; - EigenDenseLike bc; - bool success = igl::unproject_onto_mesh(pos_copy, model_copy, proj_copy, viewport_copy, v, f, fid, bc); - return std::make_tuple(success, fid, npe::move(bc)); - -npe_end_code() - diff --git a/src/unproject_ray.cpp b/src/unproject_ray.cpp deleted file mode 100644 index c7c677f5..00000000 --- a/src/unproject_ray.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. - -#include -#include -#include -#include - -const char* ds_unproject_ray = R"igl_Qu8mg5v7( - -Construct a ray (source point + direction vector) given a screen space - positions (e.g. mouse) and a model-view projection constellation. - -Parameters ----------- -pos 2d screen-space position (x,y) -model 4x4 model-view matrix -proj 4x4 projection matrix -viewport 4-long viewport vector - -Returns -------- -s source of ray (pos unprojected with z=0) -dir direction of ray (d - s) where d is pos unprojected with z=1 - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(unproject_ray) -npe_doc(ds_unproject_ray) - -npe_arg(pos, dense_float, dense_double) -npe_arg(model, npe_matches(pos)) -npe_arg(proj, npe_matches(pos)) -npe_arg(viewport, npe_matches(pos)) - - -npe_begin_code() - - assert_rows_equals(pos, 2, "pos"); - assert_rows_equals(model, 4, "model"); - assert_cols_equals(model, 4, "model"); - assert_shapes_match(model, proj, "model", "proj"); - assert_rows_equals(viewport, 4, "viewport"); - - Eigen::Matrix s; - Eigen::Matrix dir; - igl::unproject_ray(pos, model, proj, viewport, s, dir); - return std::make_tuple(npe::move(s), npe::move(dir)); - -npe_end_code() - - diff --git a/src/upsample.cpp b/src/upsample.cpp deleted file mode 100644 index 59d301db..00000000 --- a/src/upsample.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -//TODO: __miss upsample that retuns a sparse matrix and inplace -#include -#include -#include - -#include - - -const char* ds_upsample = R"igl_Qu8mg5v7( - Subdivide a mesh without moving vertices: loop subdivision but odd - vertices stay put and even vertices are just edge midpoints -Parameters ----------- - V #V by dim mesh vertices - F #F by 3 mesh triangles - -Returns -------- - NV new vertex positions, V is guaranteed to be at top - NF new list of face indices - -See also --------- - - -Notes ------ -- assumes (V,F) is edge-manifold. - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(upsample) -npe_doc(ds_upsample) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(number_of_subdivs, int, 1) - - -npe_begin_code() - - assert_valid_23d_tri_mesh(v, f); - EigenDenseLike nv; - EigenDenseLike nf; - igl::upsample(v, f, nv, nf, number_of_subdivs); - return std::make_tuple(npe::move(nv), npe::move(nf)); - -npe_end_code() diff --git a/src/vector_area_matrix.cpp b/src/vector_area_matrix.cpp deleted file mode 100644 index 942263dc..00000000 --- a/src/vector_area_matrix.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_vector_area_matrix = R"igl_Qu8mg5v7( -Constructs the symmetric area matrix A, s.t. [V.col(0)' V.col(1)'] * A * - [V.col(0); V.col(1)] is the **vector area** of the mesh (V,F). -Parameters ----------- -f : #f by 3 array of mesh faces (must be triangles) - -Returns -------- -a : #vx2 by #vx2 area matrix - -See also --------- -None - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(vector_area_matrix) -npe_doc(ds_vector_area_matrix) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - Eigen::SparseMatrix a; - igl::vector_area_matrix(f, a); - return npe::move(a); - -npe_end_code() - - diff --git a/src/vertex_components.cpp b/src/vertex_components.cpp deleted file mode 100644 index a934950f..00000000 --- a/src/vertex_components.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_vertex_components = R"igl_Qu8mg5v7( -Compute connected components of the vertices of a mesh given the mesh' face indices. - -Parameters ----------- -f : #f x dim array of face indices - -Returns -------- -An array of component ids (starting with 0) - -See also --------- -vertex_components_from_adjacency_matrix -facet_components - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(vertex_components) -npe_doc(ds_vertex_components) -npe_arg(f, dense_int32, dense_int64) -npe_begin_code() - assert_valid_tri_mesh_faces(f); - EigenDenseLike c; - igl::vertex_components(f, c); - return npe::move(c); -npe_end_code() - - -const char* ds_vertex_components_from_adjacency_matrix = R"igl_Qu8mg5v7( -Compute connected components of a graph represented by a sparse adjacency -matrix. - -Parameters ----------- -a : n by n sparse adjacency matrix - -Returns -------- -A tuple (c, counts) where c is an array of component ids (starting with 0) -and counts is a #components array of counts for each component - -See also --------- -vertex_components -facet_components - -Notes ------ - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(vertex_components_from_adjacency_matrix) -npe_doc(ds_vertex_components_from_adjacency_matrix) -npe_arg(a, sparse_int32, sparse_int64) -npe_begin_code() - EigenDense c; - EigenDense counts; - igl::vertex_components(a, c, counts); - return std::make_tuple(npe::move(c), npe::move(counts)); -npe_end_code() - - - diff --git a/src/vertex_triangle_adjacency.cpp b/src/vertex_triangle_adjacency.cpp deleted file mode 100644 index e29a2f0f..00000000 --- a/src/vertex_triangle_adjacency.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 KarlLeell -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -// TODO: __miss __example - -#include -#include -#include -#include - -const char* ds_vertex_triangle_adjacency = R"igl_Qu8mg5v7( - -vertex_face_adjacency constructs the vertex-face topology of a given mesh (V,F) - -Parameters ----------- -F #F by 3 list of triangle indices into some vertex list V -n number of vertices, #V (e.g., F.maxCoeff()+1) - -Returns -------- -VF 3*#F list List of faces indice on each vertex, so that VF(NI(i)+j) = - f, means that face f is the jth face (in no particular order) incident - on vertex i. -NI #V+1 list cumulative sum of vertex-triangle degrees with a - preceeding zero. "How many faces" have been seen before visiting this - vertex and its incident faces. - - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(vertex_triangle_adjacency) -npe_doc(ds_vertex_triangle_adjacency) - -npe_arg(f, dense_int32, dense_int64) -npe_arg(n, int) - - -npe_begin_code() - - assert_valid_tri_mesh_faces(f); - Eigen::VectorXi vf; - Eigen::VectorXi ni; - igl::vertex_triangle_adjacency(f, n, vf, ni); - return std::make_tuple(npe::move(vf), npe::move(ni)); - -npe_end_code() - - - diff --git a/src/volume.cpp b/src/volume.cpp deleted file mode 100644 index 75367f52..00000000 --- a/src/volume.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char *ds_volume = R"igl_Qu8mg5v7( -Computes volume for all tets of a given tet mesh (V,T) - -Parameters ----------- -V #V by dim list of vertex positions -T #V by 4 list of tet indices - -Returns -------- -vol #T list of dihedral angles (in radians) - -See also --------- - - -Notes ------ -None - -Examples --------- - vol = volume(V,T) - -)igl_Qu8mg5v7"; - -npe_function(volume) -npe_doc(ds_volume) - -npe_arg(v, dense_float, dense_double) -npe_arg(t, dense_int32, dense_int64) - - -npe_begin_code() - assert_valid_tet_mesh(v, t); - -EigenDenseLike vol; -igl::volume(v, t, vol); -return npe::move(vol); -npe_end_code() - - - - -const char* ds_volume1 = R"igl_Qu8mg5v71( -Compute volumes of a list of tets defined by a, b, c, d - -Parameters ----------- -a,b,c,d list of vertices vertices of the tets - -Returns -------- -vol volume of the tets - -See also --------- - - -Notes ------ -None - -Examples --------- -)igl_Qu8mg5v71"; - -npe_function(volume_from_vertices) -npe_doc(ds_volume1) - -npe_arg(a, dense_float, dense_double) -npe_arg(b, npe_matches(a)) -npe_arg(c, npe_matches(a)) -npe_arg(d, npe_matches(a)) - - -npe_begin_code() - assert_cols_equals(a, 3, "a"); - // assert_cols_equals(b, 3, "b"); - // assert_cols_equals(c, 3, "c"); - // assert_cols_equals(d, 3, "d"); - - assert_nonzero_rows(a, "a"); - // assert_nonzero_rows(b, "b"); - // assert_nonzero_rows(c, "c"); - // assert_nonzero_rows(d, "d"); - - assert_shapes_match(a, b, "a", "b"); - assert_shapes_match(a, c, "a", "c"); - assert_shapes_match(a, d, "a", "d"); - - EigenDenseLike vol; - igl::volume(a, b, c, d, vol); - return npe::move(vol); -npe_end_code() - - - -const char *ds_volume2 = R"igl_Qu8mg5v72( -Computes volume for all tets from edge lengths -Parameters ----------- -L #V by 6 list of edge lengths (see edge_lengths) - -Returns -------- -vol volume of the tets - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v72"; - -npe_function(volume_from_edges) -npe_doc(ds_volume2) - -npe_arg(l, dense_float, dense_double) - -npe_begin_code() - assert_cols_equals(l, 6, "l"); - assert_nonzero_rows(l, "l"); - - EigenDenseLike vol; - igl::volume(l, vol); - return npe::move(vol); -npe_end_code() - - -const char* ds_volume_single = R"igl_Qu8mg5v73( -Volume of a single tet -Parameters ----------- -a,b,c,d vertices - -Returns -------- -volume - -See also --------- - - -Notes ------ -None - -Examples --------- - - Single tet -)igl_Qu8mg5v73"; - -npe_function(volume_single) -npe_doc(ds_volume_single) - -npe_arg(a, dense_float, dense_double) -npe_arg(b, npe_matches(a)) -npe_arg(c, npe_matches(a)) -npe_arg(d, npe_matches(a)) - - -npe_begin_code() - assert_size_equals(a, 3, "a"); - assert_size_equals(b, 3, "b"); - assert_size_equals(c, 3, "c"); - assert_size_equals(d, 3, "d"); - const bool must_transpose = a.rows() == 3; - - const Eigen::Matrix a_copy = must_transpose ? a.transpose().eval() : a; - const Eigen::Matrix b_copy = must_transpose ? b.transpose().eval() : b; - const Eigen::Matrix c_copy = must_transpose ? c.transpose().eval() : c; - const Eigen::Matrix d_copy = must_transpose ? d.transpose().eval() : d; - - double vol = igl::volume_single(a_copy, b_copy, c_copy, d_copy); - return vol; -npe_end_code() - - diff --git a/src/winding_number.cpp b/src/winding_number.cpp deleted file mode 100644 index a167799d..00000000 --- a/src/winding_number.cpp +++ /dev/null @@ -1,111 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example -// TODO: remove __copy -// copy is necessary since the winding number only supports matrices -#include -#include -#include -#include - -const char *ds_winding_number = R"igl_Qu8mg5v7( - WINDING_NUMBER Compute the sum of solid angles of a triangle/tetrahedron - described by points (vectors) V -Parameters ----------- - V n by 3 list of vertex positions - F #F by 3 list of triangle indices, minimum index is 0 - O no by 3 list of origin positions - -Returns -------- - S no by 1 list of winding numbers - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(winding_number) -npe_doc(ds_winding_number) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(o, npe_matches(v)) - - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - assert_cols_match(v, o, "v", "o"); - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXd o_copy = o.template cast(); - - Eigen::MatrixXd w; - igl::winding_number(v_copy, f_copy, o_copy, w); - return npe::move(w); - -npe_end_code() - - - - -const char* ds_winding_number1 = R"igl_Qu8mg5v7( - Compute winding number of a single point - -Parameters ----------- - V n by dim list of vertex positions - F #F by dim list of triangle indices, minimum index is 0 - p single origin position - -Returns -------- - w winding number of this point - -See also --------- - - -Notes ------ -None - -Examples --------- - - -)igl_Qu8mg5v7"; - -npe_function(winding_number_for_point) -npe_doc(ds_winding_number1) - -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(p, dense_float, dense_double) - - -npe_begin_code() - assert_valid_tet_or_tri_mesh(v, f); - assert_cols_match(v, f, "v", "f"); - Eigen::MatrixXd v_copy = v.template cast(); - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXd p_copy = p.template cast(); - return igl::winding_number(v_copy, f_copy, p_copy); - -npe_end_code() - - diff --git a/src/writeDMAT.cpp b/src/writeDMAT.cpp deleted file mode 100644 index 1f6c87c5..00000000 --- a/src/writeDMAT.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include - - - - - - -#include - -const char* ds_write_dmat = R"igl_Qu8mg5v7( -Write a matrix from an ascii/binary dmat file, a simple ascii matrix file type, defined as follows. The first line is always: -<#columns> <#rows> -Then the coefficients of the matrix are given separated by whitespace with columns running fastest. - -Parameters ----------- -filename : string, path to .dmat file -W : m × n matrix -ascii : whether to use ascii format {true} - -Returns -------- -ret : whether writing was successful - -See also --------- -read_dmat - -Notes ------ -None - -Examples --------- ->>> write_dmat("my_model.dmat",W,ascii = false) -)igl_Qu8mg5v7"; - -npe_function(write_dmat) -npe_doc(ds_write_dmat) -npe_arg(filename, std::string) -npe_arg(w, dense_double, dense_float, dense_int32, dense_int64) -npe_default_arg(ascii, bool, true) -npe_begin_code() - -return igl::writeDMAT(filename, w, ascii); - -npe_end_code() - - - diff --git a/src/writeMESH.cpp b/src/writeMESH.cpp deleted file mode 100644 index 9cf73ec4..00000000 --- a/src/writeMESH.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_writeMESH = R"igl_Qu8mg5v7( - /// save a tetrahedral volume mesh to a .mesh file - /// - /// @tparam Scalar type for positions and vectors (will be cast as double) - /// @tparam Index type for indices (will be cast to int) - /// @param[in] mesh_file_name path of .mesh file - /// @param[in] V double matrix of vertex positions #V by 3 - /// @param[in] T #T list of tet indices into vertex positions - /// @param[in] F #F list of face indices into vertex positions - /// @return true on success, false on errors - /// - /// \bug Holes and regions are not supported -)igl_Qu8mg5v7"; - -npe_function(writeMESH) -npe_doc(ds_writeMESH) -npe_arg(filename, std::string) -npe_arg(V, dense_double, dense_float) -npe_arg(T, dense_int32, dense_int64) -npe_default_arg(F, npe_matches(T), pybind11::array()) - -npe_begin_code() - - assert_valid_tet_or_tri_mesh(V,T); - return igl::writeMESH(filename, V, T, F); - -npe_end_code() - - - - - diff --git a/src/writeMSH.cpp b/src/writeMSH.cpp deleted file mode 100644 index 7f5645e1..00000000 --- a/src/writeMSH.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_writeMSH = R"igl_Qu8mg5v7( - /// write triangle surface mesh and tetrahedral volume mesh to .msh file - /// - /// @param[in] msh - file name - /// @param[in] X eigen double matrix of vertex positions #X by 3 - /// @param[in] Tri #Tri eigen integer matrix of triangular faces indices into vertex positions - /// @param[in] Tet #Tet eigen integer matrix of tetrahedral indices into vertex positions - /// @param[in] TriTag #Tri eigen integer vector of tags associated with surface faces - /// @param[in] TetTag #Tet eigen integer vector of tags associated with volume elements - /// @param[in] XFields #XFields list of strings with field names associated with nodes - /// @param[in] XF #XFields list of eigen double matrices, fields associated with nodes - /// @param[in] EFields #EFields list of strings with field names associated with elements - /// @param[in] TriF #EFields list of eigen double matrices, fields associated with surface elements - /// @param[in] TetF #EFields list of eigen double matrices, fields associated with volume elements - /// - /// \bug files are always stored in binary format - /// \bug file format is 2.2 - /// \bug only triangle surface elements and tetrahedral volumetric elements are supported - /// \bug only 3D information is supported - /// \bug the tag id is duplicated for physical (0) and elementary (1) - /// \bug same element fields are expected to be associated with surface elements and volumetric elements -)igl_Qu8mg5v7"; - -npe_function(writeMSH) -npe_doc(ds_writeMSH) -npe_arg(filename, std::string) -// float not allowed because writeMSH is not templated -npe_arg(X, dense_double) -npe_arg(Tri, dense_int32, dense_int64) -npe_default_arg(Tet, npe_matches(Tri), pybind11::array()) -npe_default_arg(TriTag, npe_matches(Tri), pybind11::array()) -npe_default_arg(TetTag, npe_matches(Tri), pybind11::array()) - -npe_begin_code() - -std::vector XFields, EFields; -std::vector XF, TriF, TetF; -Eigen::MatrixXd X_copy = X.template cast(); -Eigen::MatrixXi Tri_copy = Tri.template cast(); -Eigen::MatrixXi Tet_copy = Tet.template cast(); -Eigen::MatrixXi TriTag_copy = TriTag.template cast(); -Eigen::MatrixXi TetTag_copy = TetTag.template cast(); - -return igl::writeMSH( - filename, - X_copy, - Tri_copy, - Tet_copy, - TriTag_copy, - TetTag_copy, - XFields, - XF, - EFields, - TriF, - TetF); - -npe_end_code() - - - - - diff --git a/src/writeOBJ.cpp b/src/writeOBJ.cpp deleted file mode 100644 index 66009e2a..00000000 --- a/src/writeOBJ.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Sebastian Koch -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include -#include - -const char* ds_write_obj = R"igl_Qu8mg5v7( -Write a mesh in an ascii obj file. - -Parameters ----------- -filename : path to outputfile -v : array of vertex positions #v by 3 -f : #f list of face indices into vertex positions - -Returns -------- -ret : bool if output was successful - -See also --------- -read_obj - -Notes ------ -None - -Examples --------- -# Mesh in (v, f) ->>> success = write_obj(v, f) -)igl_Qu8mg5v7"; - -npe_function(write_obj) -npe_doc(ds_write_obj) -npe_arg(filename, std::string) -npe_arg(v, dense_double, dense_float) -npe_arg(f, dense_int32, dense_int64) -//npe_default_arg(cn, npe_matches(v), pybind11::array()) TODO: NPE Support none arrays as option -//npe_default_arg(fn, npe_matches(f), pybind11::array()) -//npe_default_arg(tc, npe_matches(v), pybind11::array()) -//npe_default_arg(ftc, npe_matches(f), pybind11::array()) -npe_begin_code() - - assert_valid_tet_or_tri_mesh(v, f); - //return igl::writeOBJ(filename, v, f, cn, fn, tc, ftc); - return igl::writeOBJ(filename, v, f); - -npe_end_code() - - - - diff --git a/src/writeOFF.cpp b/src/writeOFF.cpp deleted file mode 100644 index 300a1206..00000000 --- a/src/writeOFF.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Daniele Panozzo -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -#include -#include -#include - -// TODO None arguments - - -#include - -const char* ds_write_off = R"igl_Qu8mg5v7( -Export geometry and colors-by-vertex - Export a mesh from an ascii OFF file, filling in vertex positions. - Only triangle meshes are supported - -Parameters ----------- - str path to .off output file - V #V by 3 mesh vertex positions - F #F by 3 mesh indices into V - C double matrix of rgb values per vertex #V by 3 - - -Returns -------- - Returns true on success, false on errors - -See also --------- - - -Notes ------ -None - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(write_off) -npe_doc(ds_write_off) - -npe_arg(str, std::string) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_arg(c, npe_matches(v)) - - -npe_begin_code() - - assert_valid_3d_tri_mesh(v, f); - return igl::writeOFF(str, v, f, c); - -npe_end_code() -// #include - -// const char* ds_write_off = R"igl_Qu8mg5v7( -// See writeOFF for the documentation. -// )igl_Qu8mg5v7"; - -// npe_function(write_off) -// npe_doc(ds_write_off) - -// npe_arg(str, std::string) -// npe_arg(v, dense_float, dense_double) -// npe_arg(f, dense_int32, dense_int64) - - -// npe_begin_code() - -// igl::writeOFF(str, v, f); -// return ; - -// npe_end_code() - - diff --git a/src/write_triangle_mesh.cpp b/src/write_triangle_mesh.cpp index 2d4c2837..856b349e 100644 --- a/src/write_triangle_mesh.cpp +++ b/src/write_triangle_mesh.cpp @@ -1,70 +1,70 @@ -// This file is part of libigl, a simple c++ geometry processing library. -// -// Copyright (C) 2023 Teseo Schneider -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at http://mozilla.org/MPL/2.0/. -//TODO: __example - -#include -#include -#include - - +#include "default_types.h" #include -#include - -const char *ds_write_triangle_mesh = R"igl_Qu8mg5v7( - write mesh to a file with automatic detection of file format. supported: obj, off, stl, wrl, ply, mesh). - -Parameters ----------- - str path to file - V double matrix #V by 3 - F int matrix #F by 3 - force_ascii=True force ascii format even if binary is available -Returns -------- - Returns true iff success - -See also --------- - - -Notes ------ - - -Examples --------- - -)igl_Qu8mg5v7"; - -npe_function(write_triangle_mesh) -npe_doc(ds_write_triangle_mesh) - -npe_arg(str, std::string) -npe_arg(v, dense_float, dense_double) -npe_arg(f, dense_int32, dense_int64) -npe_default_arg(force_ascii, bool, bool(true)) - -npe_begin_code() - assert_valid_3d_tri_mesh(v, f); - // TODO: remove __copy - //copy is necessary for the ply library - Eigen::MatrixXi f_copy = f.template cast(); - Eigen::MatrixXd v_copy = v.template cast(); - - igl::FileEncoding encoding = igl::FileEncoding::Ascii; - if (!force_ascii) { - //NOTE: if not ascii, we default to binary - encoding = igl::FileEncoding::Binary; +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + nb::object write_triangle_mesh( + const std::string & filename, + const nb::DRef V, + const nb::DRef F, + const std::string encoding) + { + auto encoding_enum = igl::FileEncoding::Ascii; + if(encoding == "binary") + { + encoding_enum = igl::FileEncoding::Binary; + }else if(encoding != "ascii") + { + // throw runtime exception + throw std::runtime_error("Invalid encoding: " + encoding); + } + // Throw an error if entries in F are bigger than can be cast to int32_t + if(F.maxCoeff() > std::numeric_limits::max()) + { + // throw runtime exception + throw std::runtime_error("Entries in F are too big to be cast to int32_t (required by igl::write_triangle_mesh)"); + } + Eigen::MatrixXi F32 = F.cast(); + if(!igl::write_triangle_mesh(filename,V,F32,encoding_enum)) + { + // throw runtime exception + throw std::runtime_error("Failed to write mesh to: " + filename); + } + return nb::bool_(true); } +} + +// Bind the wrapper to the Python module +void bind_write_triangle_mesh(nb::module_ &m) +{ + m.def( + "write_triangle_mesh", + &pyigl::write_triangle_mesh, + "filename"_a, + "V"_a, + "F"_a, + "encoding"_a="ascii", +R"(write mesh to a file with automatic detection of file format. supported: +obj, off, stl, wrl, ply, mesh). + +@tparam Scalar type for positions and vectors (will be read as double and cast + to Scalar) +@tparam Index type for indices (will be read as int and cast to Index) +@param[in] str path to file +@param[in] V eigen double matrix #V by 3 +@param[in] F eigen int matrix #F by 3 +@param[in] encoding set file encoding (ascii or binary) when both are available +@return true iff success)" + ); +} - bool ok = igl::write_triangle_mesh(str, v_copy, f_copy, encoding); - return ok; - -npe_end_code() diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 00000000..0bee2e19 --- /dev/null +++ b/tests/test.py @@ -0,0 +1,68 @@ +import igl +import numpy as np + +F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) +E,oE = igl.orient_halfedges(F) +ne = E.max()+1 +uE = np.random.rand(ne).astype(np.float64) +uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) +V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) +l = igl.edge_lengths(V,F) +igl.write_triangle_mesh("out.obj",V,F) +igl.write_triangle_mesh("out.ply",V,F,encoding="binary") +V,F = igl.read_triangle_mesh("out.ply") +L = igl.cotmatrix(V,F) +I = np.array([0,1,2,3],dtype=np.int64) +C = np.array([0,4],dtype=np.int64) +L = igl.cotmatrix(V,I=I,C=C) +L,M = igl.cotmatrix(V,I=I,C=C,return_M=True) +L,P = igl.cotmatrix(V,I=I,C=C,return_P=True) +L,M,P = igl.cotmatrix(V,I=I,C=C,return_M=True,return_P=True) +M = igl.massmatrix(V,F) +M = igl.massmatrix(V,F,type="barycentric") +M = igl.massmatrix(V,F,type="voronoi") +M = igl.massmatrix(V,F,type="full") +N = igl.per_face_normals(V,F) +N = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) +N = igl.per_face_normals(V,I=I,C=C) +N,VV,FF,J = igl.per_face_normals(V,I=I,C=C,return_VV=True,return_FF=True,return_J=True) + +dblA = igl.doublearea(V,F) +dblA = igl.doublearea(l=l) +dblA = igl.doublearea(l=l,nan_replacement=0.0) +P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) +sqrD = igl.point_mesh_squared_distance(P,V,F) +sqrD,I = igl.point_mesh_squared_distance(P,V,F,return_I=True) +sqrD,C = igl.point_mesh_squared_distance(P,V,F,return_C=True) +sqrD,I,C = igl.point_mesh_squared_distance(P,V,F,return_I=True,return_C=True) +BC = igl.barycenter(V,F) +b = np.array([0,3],dtype=np.int64) +bc = np.array([[1,0],[0,1]],dtype=np.float64) +W = igl.bbw(V,F,b,bc) + +V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +T = np.array([[0,1,2,3]],dtype=np.int64) +F = igl.boundary_facets(T) +F,J = igl.boundary_facets(T,return_J=True) +F,K = igl.boundary_facets(T,return_K=True) +F,J,K = igl.boundary_facets(T,return_J=True,return_K=True) + +tree = igl.AABB() +tree.init(V,T) +# first row of P +q = P[0,:] +I = tree.find(V,T,q) +i = tree.find(V,T,q,first=True) + +tree = igl.AABB() +tree.init(V,F) +sqrD = tree.squared_distance(V,F,P) +sqrD,I = tree.squared_distance(V,F,P,return_I=True) +sqrD,C = tree.squared_distance(V,F,P,return_C=True) +sqrD,I,C = tree.squared_distance(V,F,P,return_I=True,return_C=True) + +O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); +D = V.mean(axis=0)-O +I,T,UV = tree.intersect_ray(V,F,O,D,first=True) +hits = tree.intersect_ray(V,F,O,D,first=False) + diff --git a/tests/test_basic.py b/tests/test_basic.py deleted file mode 100644 index b8f3cb2f..00000000 --- a/tests/test_basic.py +++ /dev/null @@ -1,2636 +0,0 @@ -import unittest -import os -import platform - -import igl -print("Using igl found at: ",igl.__file__) -import igl.triangle -import igl.copyleft.tetgen -import igl.copyleft.cgal -import numpy as np -import scipy as sp -import scipy.sparse as csc -import math -import sys -from git import Repo -import faulthandler -faulthandler.enable() - - - - -DOUBLE_EPS = 1.0e-14 -DOUBLE_EPS_SQ = 1.0e-28 -FLOAT_EPS = 1.0e-7 -FLOAT_EPS_SQ = 1.0e-14 - - -class TestBasic(unittest.TestCase): - - def setUp(self): - # This is called once for every sub-test. - - # Some global datastructures to use in the tests - np.random.seed(42) - # https://stackoverflow.com/a/45230996/148668 - self.test_data_path = os.path.join("./data","") - if not os.path.isdir(self.test_data_path): - Repo.clone_from("https://github.com/libigl/libigl-tests-data.git", self.test_data_path) - - self.v1, self.f1 = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "bunny_small.off")) - self.v2, self.f2 = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "fertility.off")) - - ## Alec: I don't understand why it makes sense to use junk random data - ## for the tests. Especially for f and t. These will almost never be - ## non-degenerate meshes. - #self.v = np.random.rand(10, 3).astype(self.v1.dtype) - #self.t = np.random.rand(10, 4) - #self.f = np.random.randint(0, 10, size=(20, 3), dtype=self.f1.dtype) - #self.g = np.random.randint(0, 10, size=(20, 4), dtype="int32") - # This model is a quad mesh that's been trivially triangulated - self.v3, self.f3 = igl.read_triangle_mesh(os.path.join(self.test_data_path, "face.obj")) - self.v4, self.t4, self.f4 = igl.read_mesh(os.path.join(self.test_data_path, "decimated-knight.mesh")) - self.q3 = np.concatenate((self.f3[0::2,0:3], self.f3[1::2,2:3]), axis=1) - # Use the bunny rather than random junk. Ideally we'd loop over meshes - # by category like the libigl tests. - self.v = self.v1 - self.f = self.f1 - - - self.default_int = np.array(range(2)).dtype - self.default_float = np.zeros((2,2)).dtype - - def tearDown(self): - vv1, ff1 = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "bunny_small.off")) - vv2, ff2 = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "fertility.off")) - self.assertTrue((vv1 == self.v1).all()) - self.assertTrue((ff1 == self.f1).all()) - - self.assertTrue((vv2 == self.v2).all()) - self.assertTrue((ff2 == self.f2).all()) - - def test_z_module(self): - # Extract all implemented functions from the module - funcs = [] - flist = ["helpers", "os", "print_usage", - "pyigl", "viewer", "check_dependencies"] - for att in dir(igl): - if str.istitle(att[0]) or att[:2] == "__" or att in flist: - continue - else: - funcs.append(att) - funcs = sorted(funcs) - - # Extract all tests from this file - tests = [] - flist = ["test_module"] - for att in dir(self): - if not att.startswith("test") or att in flist: - continue - else: - tests.append(att[5:]) - - # Check that there are tests for all functions - print("") - for f in funcs: - if f not in tests: - if f == "igl" or f == "np" or f == "pyigl_classes" or f == "sparse" or f == "spsolve": - continue - print("WARNING: Test for function %s missing." % f) - #self.assertTrue(f in tests) - - # sparse matrix, no flag attribute - def test_adjacency_matrix(self): - a = igl.adjacency_matrix(self.f) - b = igl.adjacency_matrix(self.f[:,:2]) # Test with edges only - self.assertTrue(a.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(b.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(a.dtype == self.f.dtype) - self.assertTrue(b.dtype == self.f.dtype) - self.assertTrue(type(a) == csc.csc_matrix) - self.assertTrue(type(b) == csc.csc_matrix) - - def test_avg_edge_length(self): - l = igl.avg_edge_length(self.v1, self.f1) - self.assertTrue(np.isclose(l, 0.004661094877063719)) - - # sparse matrix - def test_cotmatrix(self): - l = igl.cotmatrix(self.v, self.f) - self.assertTrue(l.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(l.dtype == self.v.dtype) - self.assertTrue(type(l) == csc.csc_matrix) - - def test_cotmatrix_intrinsic(self): - el = igl.edge_lengths(self.v, self.f) - l = igl.cotmatrix_intrinsic(el, self.f) - self.assertTrue(l.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(l.dtype == el.dtype) - self.assertTrue(type(l) == csc.csc_matrix) - - def test_ears(self): - ears, ears_opp = igl.ears(self.f1) - self.assertTrue(ears.shape == ears_opp.shape) - self.assertTrue(ears.dtype == self.f.dtype) - self.assertTrue(ears_opp.dtype == self.f.dtype) - self.assertTrue(ears.flags.c_contiguous) - self.assertTrue(ears_opp.flags.c_contiguous) - - def test_gaussian_curvature(self): - g = igl.gaussian_curvature(self.v, self.f) - self.assertTrue(g.shape == (self.v.shape[0],)) - self.assertTrue(g.dtype == self.v.dtype) - self.assertTrue(type(g) == np.ndarray) - self.assertTrue(g.flags.c_contiguous) - - # sparse matrix, no flag attribute - def test_grad_intrinsic(self): - el = igl.edge_lengths(self.v, self.f) - g = igl.grad_intrinsic(el, self.f) - self.assertTrue(g.shape == (self.f.shape[0] * 2, self.v.shape[0])) - self.assertTrue(type(g) == csc.csc_matrix) - - def test_grad(self): - g = igl.grad(self.v, self.f) - h = igl.grad(self.v, self.f, uniform=True) - self.assertTrue(g.shape == ( - self.f.shape[0] * self.v.shape[1], self.v.shape[0])) - self.assertTrue(h.shape == ( - self.f.shape[0] * self.v.shape[1], self.v.shape[0])) - self.assertTrue(type(g) == type(h) == csc.csc_matrix) - - # sparse matrix, no flag attribute - def test_massmatrix(self): - a = igl.massmatrix(self.v, self.f) - b = igl.massmatrix( - self.v, self.f, type=igl.MASSMATRIX_TYPE_BARYCENTRIC) - self.assertTrue(a.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(b.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(b.dtype == self.v.dtype) - self.assertTrue(a.dtype == self.v.dtype) - self.assertTrue(type(a) == type(b) == csc.csc_matrix) - - def test_massmatrix_intrinsic(self): - el = igl.edge_lengths(self.v, self.f) - a = igl.massmatrix_intrinsic(el, self.f) - b = igl.massmatrix_intrinsic( - el, self.f, type=igl.MASSMATRIX_TYPE_BARYCENTRIC) - self.assertTrue(a.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(b.shape == (self.v.shape[0], self.v.shape[0])) - self.assertTrue(b.dtype == el.dtype) - self.assertTrue(a.dtype == el.dtype) - self.assertTrue(type(a) == type(b) == csc.csc_matrix) - - def test_principal_curvature(self): - pd1, pd2, pv1, pv2 = igl.principal_curvature(self.v, self.f) - qd1, qd2, qv1, qv2 = igl.principal_curvature( - self.v, self.f, radius=7, use_k_ring=False) - self.assertTrue(pd1.shape == qd1.shape == pd2.shape == - qd2.shape == self.v.shape) - self.assertTrue(pv1.shape == qv1.shape == pv2.shape == - qv2.shape == (self.v.shape[0],)) - self.assertTrue(pd1.dtype == pd2.dtype == - pv1.dtype == pv2.dtype == self.v.dtype) - v = self.v.copy() - - pd1, pd2, pv1, pv2 = igl.principal_curvature(v, self.f) - self.assertTrue(pd1.dtype == pd2.dtype == - pv1.dtype == pv2.dtype == v.dtype) - self.assertTrue(type(pd1) == type(pd2) == type(pv1) - == type(pv2) == np.ndarray) - self.assertTrue(pd1.flags.c_contiguous) - self.assertTrue(pd2.flags.c_contiguous) - self.assertTrue(pv1.flags.c_contiguous) - self.assertTrue(pv2.flags.c_contiguous) - self.assertTrue(qd1.flags.c_contiguous) - self.assertTrue(qd2.flags.c_contiguous) - self.assertTrue(qv1.flags.c_contiguous) - self.assertTrue(qv2.flags.c_contiguous) - - def test_read_obj(self): - v, _, n, f, _, _ = igl.read_obj(self.test_data_path + "face.obj") - self.assertTrue(type(v) == type(f) == type(n) == np.ndarray) - self.assertTrue(v.shape == (25905, 3) and n.shape == - (0, 0) and f.shape == (51712, 3)) - self.assertTrue(v.dtype == self.default_float) - self.assertTrue(f.dtype == self.f.dtype) - v, _, n, f, _, _ = igl.read_obj( - self.test_data_path + "face.obj", dtype="float32") - self.assertTrue(v.shape == (25905, 3) and n.shape == - (0, 0) and f.shape == (51712, 3)) - self.assertTrue(v.dtype == np.float32) - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(f.flags.c_contiguous) - self.assertTrue(n.flags.c_contiguous) - - def test_read_off(self): - v, f, n = igl.read_off(self.test_data_path + "bunny_small.off") - self.assertTrue(type(v) == type(f) == type(n) == np.ndarray) - self.assertTrue(v.shape == (3485, 3) and n.shape == - (0, 0) and f.shape == (6966, 3)) - self.assertTrue(v.dtype == self.default_float) - v, f, n = igl.read_off( - self.test_data_path + "bunny_small.off", read_normals=False, dtype="float32") - self.assertTrue(v.shape == (3485, 3) and n.shape == - (0, 0) and f.shape == (6966, 3)) - self.assertTrue(v.dtype == np.float32) - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(f.flags.c_contiguous) - self.assertTrue(n.flags.c_contiguous) - - def test_read_mesh(self): - v, t, f = igl.read_mesh(os.path.join( - self.test_data_path, "octopus-low.mesh")) - self.assertTrue(type(v) == type(t) == type(f) == np.ndarray) - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(t.flags.c_contiguous) - self.assertTrue(f.flags.c_contiguous) - - self.assertTrue(v.dtype == self.default_float) - self.assertTrue(t.dtype == self.f1.dtype) - self.assertTrue(f.dtype == self.f1.dtype) - - def test_read_triangle_mesh(self): - v, f = igl.read_triangle_mesh(self.test_data_path + "octopus-low.mesh") - #print(v.shape, f.shape) - v, f = igl.read_triangle_mesh(self.test_data_path + "face.obj") - #print(v.shape, f.shape) - v, f = igl.read_triangle_mesh(self.test_data_path + "bunny_small.off") - #print(v.shape, f.shape) - self.assertTrue(f.dtype == self.f.dtype) - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(f.flags.c_contiguous) - - def test_read_triangle_mesh_type_issue(self): - v, f = igl.read_triangle_mesh(self.test_data_path + "face.obj") - vs = np.array([0]) - vt = np.arange(v.shape[0]) - d = igl.exact_geodesic(v, f, vs, vt) - self.assertTrue(d.dtype == v.dtype) - - # def test_triangulate(self): - # v = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]) - # e = np.array([[0, 1], [1, 2], [2, 3], [3, 0]], dtype="int32") - # h = np.array([[]]) - # #print("v.dtype = %s, h.dtype = %s" % (v.dtype, h.dtype)) - # v2, f2 = igl.triangulate(v, e, h, flags="a0.005qQ") - # self.assertTrue(v2.dtype == v.dtype) - # self.assertTrue(f2.dtype == e.dtype) - # self.assertTrue(type(v2) == type(f2) == np.ndarray) - # self.assertTrue(v2.flags.c_contiguous) - # self.assertTrue(f2.flags.c_contiguous) - - def test_write_obj(self): - suc = igl.write_obj("test.obj", self.v, self.f) - self.assertTrue(suc) - self.assertTrue(os.path.isfile("test.obj")) - - def test_write_off(self): - suc = igl.write_off("test.off", self.v, self.f, self.v) - self.assertTrue(suc) - self.assertTrue(os.path.isfile("test.off")) - - def test_adjacency_list(self): - a = igl.adjacency_list(self.f1) - self.assertEqual(len(a), self.v1.shape[0]) - - # all are sparse matrices - def test_arap_linear_block(self): - kd = igl.arap_linear_block(self.v1, self.f1, d=2, energy=0) - self.assertTrue(kd.shape[0] > 0) - - def test_arap_linear_block_elements(self): - kd = igl.arap_linear_block_elements(self.v1, self.f1, d=2) - self.assertTrue(kd.shape[0] > 0) - - def test_arap_linear_block_spokes(self): - kd = igl.arap_linear_block_spokes(self.v1, self.f1, d=2) - self.assertTrue(kd.shape[0] > 0) - - def test_arap_linear_block_spokes_and_rims(self): - kd = igl.arap_linear_block_spokes_and_rims(self.v1, self.f1, d=2) - self.assertTrue(kd.shape[0] > 0) - - def test_arap_rhs(self): - k = igl.arap_rhs(self.v1, self.f1, d=2, energy=0) - self.assertTrue(k.shape[0] > 0) - - def test_average_onto_faces(self): - s = np.random.rand(self.f1.shape[0]) - sf = igl.average_onto_faces(self.f1, s) - self.assertEqual(sf.shape[0], self.f1.shape[0]) - self.assertEqual(sf.dtype, s.dtype) - - def test_average_onto_vertices(self): - s = np.random.rand(self.f1.shape[0], self.v1.shape[1]) - sv = igl.average_onto_vertices(self.v1, self.f1, s) - self.assertEqual(sv.shape[0], self.v1.shape[0]) - self.assertEqual(sv.dtype, s.dtype) - self.assertTrue(sv.flags.c_contiguous) - - def test_barycentric_coordinates(self): - a, b, c = self.v1[self.f1[:, 0] - ], self.v1[self.f1[:, 1]], self.v1[self.f1[:, 2]] - bc = igl.barycentric_coordinates_tri(a, a, b, c) - self.assertEqual(bc.shape, a.shape) - expected_bc = np.zeros(a.shape) - expected_bc[:, 0] = np.ones(a.shape[0]) - self.assertTrue(np.linalg.norm(expected_bc-bc) < 1e-6) - self.assertTrue(bc.flags.c_contiguous) - - d = 0.5*a + 0.5*c + np.array([0.1, 0.1, 0.1]) - bc = igl.barycentric_coordinates_tet(d, a, b, c, d) - self.assertEqual(bc.shape, (a.shape[0], 4)) - self.assertTrue(bc.flags.c_contiguous) - - def test_barycentric_coordinates_tri(self): - # tested in test_barycentric_coordinates - pass - - def test_barycentric_coordinates_tet(self): - # tested in test_barycentric_coordinates - pass - - def test_vertex_components(self): - a = igl.adjacency_matrix(self.f1) - c, count = igl.vertex_components_from_adjacency_matrix(a) - self.assertEqual(c.shape[0], self.v1.shape[0]) - self.assertTrue(c.flags.c_contiguous) - - c = igl.vertex_components(self.f1) - self.assertEqual(c.shape[0], self.v1.shape[0]) - self.assertTrue(c.flags.c_contiguous) - - def test_facet_components(self): - c = igl.facet_components(self.f1) - self.assertEqual(c.shape, (self.f1.shape[0],)) - self.assertTrue(np.array_equal(c, np.zeros_like(c))) - self.assertTrue(c.flags.c_contiguous) - - def test_bfs(self): - a = igl.adjacency_matrix(self.f1) - p, d = igl.bfs(a, 0) - self.assertEqual(p.shape, (self.v1.shape[0],)) - self.assertEqual(p.shape, (self.v1.shape[0],)) - - try: - p, d, = igl.bfs(a, -1) - self.assertTrue(False) - except IndexError as e: - pass - - a = csc.csc_matrix(np.zeros([0, 0]), dtype=np.int32) - try: - p, d, = igl.bfs(a, 0) - self.assertTrue(False) - except ValueError as e: - pass - - a = csc.csc_matrix(np.zeros([10, 11]), dtype=np.int32) - try: - p, d, = igl.bfs(a, 0) - self.assertTrue(False) - except ValueError as e: - pass - - a = csc.csc_matrix(np.zeros([10, 10]), dtype=np.int32) - p, d, = igl.bfs(a, 0) - self.assertEqual(p.shape, ()) - self.assertTrue(np.array_equal(d, -np.ones(10))) - self.assertTrue(p.flags.c_contiguous) - - def test_bfs_orient(self): - ff, c = igl.bfs_orient(self.f1) - self.assertEqual(ff.shape, self.f1.shape) - self.assertEqual(c.shape, (self.f1.shape[0],)) - self.assertTrue(np.array_equal(self.f1, ff)) - self.assertTrue(ff.flags.c_contiguous) - self.assertTrue(c.flags.c_contiguous) - - def test_oriented_facets(self): - e = igl.oriented_facets(self.f1) - self.assertTrue(e.shape[0] > self.f1.shape[0]) - self.assertTrue(0 <= np.max(e) < self.v1.shape[0]) - self.assertTrue(e.flags.c_contiguous) - - # sparse matrix, no flag attribute - def test_orientable_patches(self): - c, a = igl.orientable_patches(self.f1) - - self.assertTrue(np.array_equal(c, np.zeros(self.f1.shape[0]))) - self.assertEqual(a.shape, (self.f1.shape[0], self.f1.shape[0])) - - def test_edge_topology(self): - ev, fe, ef = igl.edge_topology(self.v1, self.f1) - self.assertEqual(fe.shape, self.f1.shape) - self.assertEqual(ef.shape, (ev.shape[0], 2)) - self.assertEqual(np.max(ev), self.v1.shape[0] - 1) - self.assertEqual(np.min(ef), 0) - self.assertTrue(fe.flags.c_contiguous) - self.assertTrue(ef.flags.c_contiguous) - self.assertTrue(ev.flags.c_contiguous) - - def test_edges(self): - e = igl.edges(self.f1) - self.assertTrue(e.shape[0] > self.f1.shape[0]) - self.assertEqual(e.shape[1], 2) - self.assertTrue(e.flags.c_contiguous) - - def test_bone_parents(self): - e = igl.edges(self.f1) - res = igl.bone_parents(e) - self.assertEqual(res.shape[0], e.shape[0]) - self.assertTrue(e.flags.c_contiguous) - - def test_sort_angles(self): - r = igl.sort_angles(self.v) - self.assertTrue(r.dtype == self.f.dtype) - self.assertEqual(r.shape[0], self.v.shape[0]) - self.assertTrue(r.flags.c_contiguous) - - def test_circumradius(self): - r = igl.circumradius(self.v, self.f) - self.assertTrue(r.dtype == self.v.dtype) - self.assertEqual(r.shape[0], self.f.shape[0]) - self.assertTrue(r.flags.c_contiguous) - - def test_quad_planarity(self): - p = igl.quad_planarity(self.v3, self.q3) - self.assertTrue(p.dtype == self.v3.dtype) - self.assertEqual(p.shape[0], self.q3.shape[0]) - self.assertTrue(p.flags.c_contiguous) - - def test_collapse_small_triangles(self): - ff = igl.collapse_small_triangles(self.v, self.f, 0.5) - self.assertEqual(ff.shape[1], self.f.shape[1]) - self.assertTrue(ff.flags.c_contiguous) - - def test_bounding_box(self): - bv, bf = igl.bounding_box(self.v) - self.assertEqual(bv.shape[1], self.v.shape[1]) - self.assertEqual(bf.shape[1], self.v.shape[1]) - self.assertTrue(bv.flags.c_contiguous) - self.assertTrue(bf.flags.c_contiguous) - - def test_per_face_normals(self): - n = igl.per_face_normals(self.v2, self.f2, self.v2) - - self.assertEqual(n.dtype, self.v2.dtype) - self.assertEqual(n.shape[0], self.f2.shape[0]) - self.assertEqual(n.shape[1], 3) - self.assertTrue(n.flags.c_contiguous) - - def test_ambient_occlusion(self): - n = igl.per_vertex_normals(self.v2, self.f2) - s = igl.ambient_occlusion(self.v2, self.f2, self.v2, n, 2) - - self.assertEqual(s.dtype, self.v1.dtype) - self.assertEqual(len(s.shape), 1) - self.assertTrue(s.flags.c_contiguous) - - def test_write_triangle_mesh(self): - ok = igl.write_triangle_mesh("out.obj", self.v, self.f) - self.assertTrue(ok) - self.assertTrue(os.path.isfile("out.obj")) - - def test_barycenter(self): - bc = igl.barycenter(self.v, self.f) - self.assertEqual(bc.dtype, self.v.dtype) - self.assertEqual(bc.shape[0], self.f.shape[0]) - self.assertEqual(bc.shape[1], 3) - self.assertTrue(bc.flags.c_contiguous) - - def test_read_dmat(self): - # TODO: maybe a vector - mat = igl.read_dmat(self.test_data_path + "decimated-knight-selection.dmat") - self.assertEqual(mat.dtype, "float64") - self.assertTrue(mat.flags.c_contiguous) - - def test_write_dmat(self): - igl.write_dmat(self.test_data_path + "decimated-knight-selection.dmat",self.v) - - # sparse matrix, no flag attribute - def test_vector_area_matrix(self): - a = igl.vector_area_matrix(self.f) - self.assertEqual(a.dtype, "float64") - self.assertEqual(a.shape[0], a.shape[1]) - self.assertEqual(a.shape[0], self.v.shape[0]*2) - - # def test_tetrahedralize(self): - # status, tv, tt, tf = igl.tetrahedralize(self.v2, self.f2) - - # self.assertEqual(status, 0) - # self.assertEqual(tv.dtype, self.v1.dtype) - # self.assertEqual(tt.dtype, self.f1.dtype) - # self.assertEqual(tf.dtype, self.f1.dtype) - - # self.assertEqual(tv.shape[1], 3) - # self.assertEqual(tf.shape[1], 3) - # self.assertEqual(tt.shape[1], 4) - # self.assertTrue(tv.flags.c_contiguous) - # self.assertTrue(tt.flags.c_contiguous) - # self.assertTrue(tf.flags.c_contiguous) - - def test_hausdorff(self): - dist = igl.hausdorff(self.v, self.f, self.v1, self.f1) - # print(dist) - - def test_isolines(self): - func = np.random.rand(self.v1.shape[0], 1) - vals = np.linspace(0,1, 10) - iso_v, iso_e, I = igl.isolines(self.v1, self.f1, func, vals) - - self.assertEqual(iso_v.dtype, func.dtype) - self.assertEqual(iso_e.dtype, self.f1.dtype) - self.assertEqual(iso_e.shape[1], 2) - self.assertEqual(iso_e.shape[0], I.shape[0]) - self.assertTrue(iso_v.flags.c_contiguous) - self.assertTrue(iso_e.flags.c_contiguous) - self.assertTrue(I.flags.c_contiguous) - - def test_unproject_ray(self): - pos = np.random.rand(2, 1) - model = np.random.rand(4, 4) - proj = np.random.rand(4, 4) - viewport = np.random.rand(4, 1) - source, direction = igl.unproject_ray(pos, model, proj, viewport) - self.assertEqual(source.dtype, self.v.dtype) - self.assertEqual(direction.dtype, self.v.dtype) - self.assertEqual(len(source.shape), 1) - self.assertTrue(source.flags.c_contiguous) - - def test_winding_number(self): - s = igl.winding_number(self.v1, self.f1, self.v) - - self.assertEqual(s.shape[0], self.v.shape[0]) - - def test_winding_number_for_point(self): - p = np.zeros((1, 3)) - s = igl.winding_number_for_point(self.v1, self.f1, p) - - def test_unproject(self): - model = np.array([[1., 0, 0, 0], [0, 1, 0, 0], - [0, 0, 1, 0], [0, 0, 0, 1]]) - proj = np.array([[1., 0, 0, 0], [0, 1, 0, 0], - [0, 0, 1, 0], [0, 0, 0, 1]]) - viewport = np.array([1., 1, 1, 1]) - scene = igl.unproject(self.v, model, proj, viewport) - - self.assertEqual(scene.dtype, self.v.dtype) - self.assertEqual(scene.shape[0], self.v.shape[0]) - self.assertEqual(scene.shape[1], 3) - self.assertTrue(scene.flags.c_contiguous) - - def test_upsample(self): - nv, nf = igl.upsample(self.v1, self.f1) - - self.assertEqual(nv.dtype, self.v1.dtype) - self.assertEqual(nf.dtype, self.f1.dtype) - - self.assertEqual(nv.shape[1], self.v1.shape[1]) - self.assertEqual(nf.shape[1], self.f1.shape[1]) - self.assertTrue(nv.flags.c_contiguous) - self.assertTrue(nf.flags.c_contiguous) - - def test_random_points_on_mesh(self): - n = 10 - b, fi, x = igl.random_points_on_mesh(n, self.v1, self.f1) - - self.assertEqual(b.dtype, self.v1.dtype) - self.assertEqual(x.dtype, self.v1.dtype) - self.assertEqual(fi.dtype, self.f1.dtype) - - self.assertEqual(b.shape[0], n) - self.assertEqual(b.shape[1], 3) - self.assertEqual(x.shape[0], n) - self.assertEqual(x.shape[1], self.v1.shape[1]) - self.assertEqual(fi.shape[0], n) - self.assertTrue(b.flags.c_contiguous) - self.assertTrue(fi.flags.c_contiguous) - self.assertTrue(x.flags.c_contiguous) - - def test_boundary_loop(self): - l = igl.boundary_loop(self.f) - self.assertEqual(len(l.shape), 1) - self.assertEqual(l.dtype, self.f.dtype) - self.assertTrue(l.flags.c_contiguous) - - def test_all_boundary_loop(self): - l = igl.all_boundary_loop(self.f3) - self.assertEqual(type(l), type([])) - self.assertTrue(len(l) > 0) - - def test_bounding_box_diagonal(self): - length = igl.bounding_box_diagonal(self.v1) - self.assertEqual(type(length), float) - - def test_boundary_facets(self): - b = igl.boundary_facets(self.f1) - self.assertEqual(b.dtype, self.f1.dtype) - self.assertTrue(b.shape[1] == 3 or b.shape[1] == 2) - self.assertTrue(b.flags.c_contiguous) - - def test_connect_boundary_to_infinity(self): - fo = igl.connect_boundary_to_infinity(self.f1) - self.assertEqual(fo.dtype, self.f1.dtype) - self.assertEqual(fo.shape[1], 3) - self.assertTrue(fo.flags.c_contiguous) - - def test_connect_boundary_to_infinity_face(self): - vof, fof = igl.connect_boundary_to_infinity_face(self.v1, self.f1) - self.assertEqual(fof.dtype, self.f1.dtype) - self.assertEqual(fof.shape[1], 3) - self.assertEqual(vof.dtype, self.v1.dtype) - self.assertEqual(vof.shape[1], 3) - self.assertTrue(vof.flags.c_contiguous) - self.assertTrue(fof.flags.c_contiguous) - - def test_connect_boundary_to_infinity_index(self): - foi = igl.connect_boundary_to_infinity_index(self.f1, 0) - self.assertEqual(foi.dtype, self.f1.dtype) - self.assertEqual(foi.shape[1], 3) - self.assertTrue(foi.flags.c_contiguous) - - def test_cotmatrix_entries(self): - c = igl.cotmatrix_entries(self.v1, self.f1) - self.assertEqual(c.dtype, self.v1.dtype) - self.assertEqual(c.shape[0], self.f1.shape[0]) - self.assertTrue(c.shape[1] == 3 or c.shape[1] == 6) - self.assertTrue(c.flags.c_contiguous) - - # sparse matrix, no flag attribute - def test_crouzeix_raviart_cotmatrix(self): - l, e, emap = igl.crouzeix_raviart_cotmatrix(self.v1, self.f1) - self.assertEqual(l.dtype, self.v1.dtype) - self.assertEqual(l.shape[0], e.shape[0]) - self.assertEqual(l.shape[1], e.shape[0]) - self.assertTrue(e.shape[1] == 2 or e.shape[1] == 3) - self.assertTrue(emap.shape[0] == 3*self.f1.shape[0] - or emap.shape[0] == 4*self.f1.shape[0]) - self.assertTrue(e.flags.c_contiguous) - self.assertTrue(emap.flags.c_contiguous) - l2 = igl.crouzeix_raviart_cotmatrix_known_e(self.v1, self.f1, e, emap) - self.assertEqual(l2.dtype, l.dtype) - self.assertEqual(l2.shape, l.shape) - - def test_crouzeix_raviart_cotmatrix_known_e(self): - pass - - def test_crouzeix_raviart_massmatrix(self): - m, e, emap = igl.crouzeix_raviart_massmatrix(self.v1, self.f1) - self.assertEqual(m.dtype, self.v1.dtype) - self.assertEqual(m.shape[0], e.shape[0]) - self.assertEqual(m.shape[1], e.shape[0]) - self.assertTrue(e.shape[1] == 2 or e.shape[1] == 3) - self.assertTrue(emap.shape[0] == 3*self.f1.shape[0] - or emap.shape[0] == 4*self.f1.shape[0]) - self.assertTrue(e.flags.c_contiguous) - self.assertTrue(emap.flags.c_contiguous) - m2 = igl.crouzeix_raviart_massmatrix_known_e(self.v1, self.f1, e, emap) - self.assertEqual(m2.dtype, m.dtype) - self.assertEqual(m2.shape, m.shape) - - def test_crouzeix_raviart_massmatrix_known_e(self): - pass - - def test_cylinder(self): - v, f = igl.cylinder(100, 100) - self.assertEqual(v.dtype, self.v.dtype) - self.assertEqual(f.dtype, self.f.dtype) - self.assertEqual(v.shape[1], 3) - self.assertEqual(f.shape[1], 3) - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(f.flags.c_contiguous) - - def test_decimate(self): - success, u, g, j, i = igl.decimate(self.v1, self.f1, 100, False) - self.assertEqual(u.shape[1], self.v1.shape[1]) - self.assertEqual(g.shape[1], 3) - self.assertEqual(j.shape[0], g.shape[0]) - self.assertTrue(len(j.shape) == len(i.shape) and len(i.shape) == 1) - self.assertEqual(type(success), bool) - self.assertTrue(u.dtype == self.v.dtype) - self.assertTrue(g.dtype == j.dtype and j.dtype == - i.dtype and i.dtype == self.f.dtype) - self.assertTrue(u.flags.c_contiguous) - self.assertTrue(g.flags.c_contiguous) - self.assertTrue(j.flags.c_contiguous) - self.assertTrue(i.flags.c_contiguous) - - def test_dihedral_angles(self): - theta, cos_theta = igl.dihedral_angles(self.v4, self.t4) - self.assertEqual(theta.dtype, self.v4.dtype) - self.assertEqual(cos_theta.dtype, self.v4.dtype) - self.assertTrue( - theta.shape == cos_theta.shape and cos_theta.shape == (self.t4.shape[0], 6)) - self.assertTrue(theta.flags.c_contiguous) - self.assertTrue(cos_theta.flags.c_contiguous) - - def test_dihedral_angles_intrinsic(self): - # intrinsic function of dihedral_angles - pass - - def test_directed_edge_parents(self): - e = np.random.randint(0, 10, size=(10, 2)) - p = igl.directed_edge_parents(e) - self.assertEqual(p.dtype, e.dtype) - self.assertEqual(p.shape[0], e.shape[0]) - self.assertEqual(len(p.shape), 1) - self.assertTrue(p.flags.c_contiguous) - - def test_doublearea(self): - a = igl.doublearea(self.v1, self.f1) - self.assertEqual(a.shape[0], self.f1.shape[0]) - self.assertEqual(a.dtype, self.v1.dtype) - self.assertTrue(a.flags.c_contiguous) - - def test_euler_characteristic(self): - eu = igl.euler_characteristic(self.f1) - self.assertEqual(type(eu), int) - - def test_fit_plane(self): - n, c = igl.fit_plane(self.v1) - self.assertTrue(n.dtype == c.dtype == self.v1.dtype) - self.assertTrue(n.shape == c.shape == (3,)) - self.assertTrue(n.flags.c_contiguous) - self.assertTrue(c.flags.c_contiguous) - - def test_internal_angles(self): - k = igl.internal_angles(self.v1, self.f1) - self.assertEqual(k.dtype, self.v1.dtype) - self.assertEqual(k.shape, self.f1.shape) - self.assertTrue(k.flags.c_contiguous) - - def test_is_edge_manifold(self): - is_m = igl.is_edge_manifold(self.f1) - self.assertEqual(type(is_m), bool) - - def test_map_vertices_to_circle(self): - bnd = np.random.randint(0, self.v1.shape[0], size=(100, 1)) - uv = igl.map_vertices_to_circle(self.v1, bnd) - self.assertEqual(uv.dtype, self.v1.dtype) - self.assertEqual(uv.shape, (bnd.shape[0], 2)) - self.assertTrue(uv.flags.c_contiguous) - - def test_marching_cubes(self): - #test empty level set. - n = 50 - emptyField = np.zeros((n*n*n,1)) - K = np.linspace( -1.0, 1.0, n) - pts = np.array([[x,y,z] for x in K for y in K for z in K]) - V,F = igl.marching_cubes(emptyField, pts, n, n, n, 0.0) - self.assertEqual(V.shape, (0, 3)) - self.assertEqual(F.shape, (0, 3)) - - #test marching over a sphere - sphereField = np.linalg.norm(pts, axis=1) - 1 - V,F = igl.marching_cubes(sphereField, pts, n, n, n, 0.0) - - self.assertTrue(V.dtype == pts.dtype) - self.assertTrue(F.dtype == self.default_int) - - self.assertNotEqual(V.shape, (0,3)) - self.assertNotEqual(F.shape, (0,3)) - self.assertTrue(F.flags.c_contiguous) - self.assertTrue(F.flags.c_contiguous) - - def test_per_vertex_normals(self): - n = igl.per_vertex_normals(self.v1, self.f1, 0) - self.assertEqual(n.shape, (self.v1.shape[0], 3)) - self.assertEqual(n.dtype, self.v1.dtype) - self.assertTrue(n.flags.c_contiguous) - - def test_per_corner_normals(self): - n = igl.per_corner_normals(self.v1, self.f1, 80) - self.assertEqual(n.shape, (self.f1.shape[0]*3, 3)) - self.assertEqual(n.dtype, self.v1.dtype) - self.assertTrue(n.flags.c_contiguous) - - def test_per_vertex_attribute_smoothing(self): - aout = igl.per_vertex_attribute_smoothing(self.v1, self.f1) - self.assertEqual(aout.shape, self.v1.shape) - self.assertTrue(aout.flags.c_contiguous) - - def test_piecewise_constant_winding_number(self): - is_w = igl.piecewise_constant_winding_number(self.f1) - self.assertEqual(type(is_w), bool) - - def test_procrustes(self): - s, r, t = igl.procrustes(self.v1, self.v1, True, True) - self.assertEqual(type(s), float) - self.assertTrue(r.dtype == t.dtype == self.v1.dtype) - - def test_qslim(self): - success, u, g, j, i = igl.qslim(self.v1, self.f1, 100, False) - self.assertEqual(u.dtype, self.v1.dtype) - self.assertTrue(g.dtype == j.dtype == i.dtype == self.f1.dtype) - self.assertEqual(u.shape[1], self.v1.shape[1]) - self.assertEqual(g.shape[1], 3) - self.assertTrue(j.shape[0] > 0 and i.shape[0] > 0) - self.assertTrue(u.flags.c_contiguous) - self.assertTrue(g.flags.c_contiguous) - self.assertTrue(j.flags.c_contiguous) - self.assertTrue(i.flags.c_contiguous) - - def test_per_edge_normals(self): - fn = np.random.rand(self.f1.shape[0], 3) - n, e, emap = igl.per_edge_normals(self.v1, self.f1, 0, fn) - self.assertEqual(e.shape[1], 2) - # incorrect documentation saying emap and e have the same number of rows - #self.assertEqual(e.shape[0], emap.shape[0]) - self.assertTrue(n.flags.c_contiguous) - self.assertTrue(e.flags.c_contiguous) - self.assertTrue(emap.flags.c_contiguous) - - def test_remove_duplicate_vertices(self): - epsilon = 1e-6 - sv, svi, svj, sf = igl.remove_duplicate_vertices( - self.v1, self.f1, epsilon) - self.assertTrue(sv.dtype == self.v1.dtype) - self.assertTrue(svi.dtype == svj.dtype == sf.dtype == self.f1.dtype) - self.assertEqual(sv.shape[1], self.v1.shape[1]) - self.assertTrue(len(svi.shape) == len(svj.shape) == 1) - self.assertTrue(sv.flags.c_contiguous) - self.assertTrue(svi.flags.c_contiguous) - self.assertTrue(svj.flags.c_contiguous) - self.assertTrue(sf.flags.c_contiguous) - - def test_remove_unreferenced(self): - nv, nf, i, j = igl.remove_unreferenced(self.v1, self.f1) - self.assertEqual(nv.shape[1], self.v1.shape[1]) - self.assertEqual(nf.shape[1], self.f1.shape[1]) - self.assertEqual(i.shape[0], self.v1.shape[0]) - self.assertEqual(nv.dtype, self.v1.dtype) - self.assertTrue(nf.dtype == i.dtype == j.dtype == self.f1.dtype) - self.assertTrue(nv.flags.c_contiguous) - self.assertTrue(nf.flags.c_contiguous) - self.assertTrue(i.flags.c_contiguous) - self.assertTrue(j.flags.c_contiguous) - - def test_resolve_duplicated_faces(self): - f2, j = igl.resolve_duplicated_faces(self.f1) - self.assertTrue(f2.dtype == self.f1.dtype == j.dtype) - self.assertEqual(self.f1.shape[1], f2.shape[1]) - self.assertEqual(f2.shape[0], j.shape[0]) - self.assertTrue(f2.flags.c_contiguous) - self.assertTrue(j.flags.c_contiguous) - - def test_shape_diameter_function(self): - s = igl.shape_diameter_function( - self.v1, self.f1, self.v1, self.v1, 100) - self.assertEqual(s.shape[0], self.v1.shape[0]) - self.assertEqual(s.dtype, self.v1.dtype) - self.assertTrue(s.flags.c_contiguous) - - def test_triangle_triangle_adjacency(self): - tt, tti = igl.triangle_triangle_adjacency(self.f1) - self.assertTrue(tt.shape == tti.shape == (self.f1.shape[0], 3)) - self.assertTrue(tt.dtype == tti.dtype == self.f1.dtype) - self.assertTrue(tt.flags.c_contiguous) - self.assertTrue(tti.flags.c_contiguous) - - def test_uniformly_sample_two_manifold_at_vertices(self): - s = igl.uniformly_sample_two_manifold_at_vertices(self.v1, 100, 1.0) - self.assertEqual(s.dtype, self.f1.dtype) - self.assertTrue(s.shape[0] > 0) - self.assertTrue(s.flags.c_contiguous) - - def test_uniformly_sample_two_manifold_internal(self): - # internal function tested in test_uniformly_sample_two_manifold - pass - - def test_unproject_in_mesh(self): - pos = np.array([10., 10.]) - eye = np.eye(4) - viewport = np.array([0., 0., 100., 100.]) - obj, hits = igl.unproject_in_mesh( - pos, eye, eye, viewport, self.v1, self.f1) - - self.assertTrue(obj.flags.c_contiguous) - self.assertTrue(obj.dtype == self.v1.dtype) - - def test_unproject_onto_mesh(self): - pos = np.array([10., 10.]) - eye = np.eye(4) - viewport = np.array([0., 0., 100., 100.]) - ok, fid, bc = igl.unproject_onto_mesh( - pos, eye, eye, viewport, self.v1, self.f1) - - self.assertTrue(type(ok) == bool) - self.assertTrue(bc.flags.c_contiguous) - self.assertTrue(bc.dtype == self.v1.dtype) - - def test_vertex_components_from_adjacency_matrix(self): - # tested in test_vertex_components - pass - - def test_vertex_triangle_adjacency(self): - vf, ni = igl.vertex_triangle_adjacency(self.f1, self.v1.shape[0]) - self.assertEqual(vf.shape[0], 3*self.f1.shape[0]) - self.assertTrue(len(vf.shape) == len(ni.shape) == 1) - self.assertEqual(ni.shape[0], self.v1.shape[0]+1) - self.assertTrue(vf.flags.c_contiguous) - self.assertTrue(ni.flags.c_contiguous) - - def test_tet_tet_adjacency(self): - tet = np.array([[0, 1, 2, 3], [4, 5, 6, 7]]) - tt, tti = igl.tet_tet_adjacency(tet) - - self.assertEqual(tt.shape, tet.shape) - self.assertEqual(tti.shape, tet.shape) - self.assertEqual(tti.dtype, tet.dtype) - self.assertTrue(tt.flags.c_contiguous) - self.assertTrue(tti.flags.c_contiguous) - - def test_arap1(self): - v, f, _ = igl.read_off(os.path.join(self.test_data_path, "camelhead.off")) - b = igl.boundary_loop(f) - thetas = np.linspace(0, 2 * np.pi, len(b))[:, np.newaxis] - bc = np.concatenate([np.cos(thetas), np.sin( - thetas), np.zeros_like(thetas)], axis=1) - uv_initial_guess = igl.harmonic(v, f, b, bc, 1) - - v2d = v[:, :2].copy() - arap1 = igl.ARAP(v2d, f, 2, b) - vp1 = arap1.solve(bc[:, :2], uv_initial_guess[:, :2]) - self.assertEqual(vp1.shape[0], v.shape[0]) - self.assertTrue(vp1.flags.c_contiguous) - - arap2 = igl.ARAP(v, f, 3, b) - vp2 = arap2.solve(bc, uv_initial_guess) - self.assertEqual(vp2.shape[0], v.shape[0]) - self.assertTrue(vp2.flags.c_contiguous) - - def test_arap2(self): - num_b = 100 - - thetas = np.linspace(0, 2 * np.pi, num_b)[:, np.newaxis] - r = thetas / (2 * np.pi) - boundary = np.concatenate( - [r * np.cos(thetas), np.sin(thetas), np.zeros([num_b, 1])], axis=1) - edges = np.array([(i, (i + 1) % boundary.shape[0]) - for i in range(boundary.shape[0])]) - v = np.load(os.path.join(self.test_data_path, "test_arap2_v.npy")) - f = np.load(os.path.join(self.test_data_path, "test_arap2_f.npy")) - v = np.concatenate([v, np.zeros([v.shape[0], 1])], axis=1) - b = igl.boundary_loop(f) - - thetas = np.linspace(0, 2 * np.pi, len(b))[:, np.newaxis] - circle_b = np.concatenate( - [np.cos(thetas), np.sin(thetas), np.zeros([len(b), 1])], axis=1) - - v0 = igl.harmonic(v, f, b, circle_b, 1) - arap = igl.ARAP(v, f, 2, b) - - v2 = arap.solve(circle_b[:, :2], v0[:, :2]) - self.assertEqual(v2.shape[0], v0.shape[0]) - self.assertTrue(v2.flags.c_contiguous) - - def test_arap3(self): - v, f = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "camelhead.off")) - - # Find the open boundary - bnd = igl.boundary_loop(f) - - # Map the boundary to a circle, preserving edge proportions - bnd_uv = igl.map_vertices_to_circle(v, bnd) - - # Harmonic parametrization for the internal vertices - uv = igl.harmonic(v, f, bnd, bnd_uv, 1) - - arap = igl.ARAP(v, f, 2, np.zeros((0))) - uva = arap.solve(np.zeros((0, 0)), uv) - - def test_arap4(self): - v, f = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "camelhead.off")) - b = igl.boundary_loop(f) - thetas = np.linspace(0, 2 * np.pi, len(b))[:, np.newaxis] - bc = np.concatenate([np.cos(thetas), np.sin( - thetas), np.zeros_like(thetas)], axis=1) - uv_initial_guess = igl.harmonic(v, f, b, bc, 1) - - arap = igl.ARAP(v, f, 3, b, igl.ARAP_ENERGY_TYPE_SPOKES) - uva = arap.solve(bc, uv_initial_guess) - - def test_slim(self): - v, f, _ = igl.read_off(os.path.join(self.test_data_path, "camelhead.off")) - b = igl.boundary_loop(f) - thetas = np.linspace(0, 2 * np.pi, len(b))[:, np.newaxis] - bc = np.concatenate([np.cos(thetas), np.sin( - thetas), np.zeros_like(thetas)], axis=1) - uv_initial_guess = igl.harmonic(v, f, b, bc, 1) - - slim = igl.SLIM( - v, f, uv_initial_guess[:, :2], b, bc[:, :2], igl.SLIM_ENERGY_TYPE_ARAP, 0.0) - slim.solve(1) - v2 = slim.vertices() - self.assertEqual(v2.shape[0], v.shape[0]) - self.assertTrue(v2.flags.c_contiguous) - - def test_bbw(self): - V, T, F = igl.read_mesh(os.path.join(self.test_data_path, "hand.mesh")) - C, BE, _, _, _, _ = igl.read_tgf( - os.path.join(self.test_data_path, "hand.tgf")) - - ok, b, bc = igl.boundary_conditions( - V, T, C, np.array([], dtype=T.dtype), BE, - np.array([], dtype=T.dtype), - np.array([], dtype=T.dtype)) - - self.assertTrue(b.flags.c_contiguous) - self.assertTrue(bc.flags.c_contiguous) - self.assertTrue(b.dtype == T.dtype) - self.assertTrue(bc.dtype == V.dtype) - - bbw = igl.BBW(0, 2) - W = bbw.solve(V, T, b, bc) - self.assertTrue(W.dtype == V.dtype) - self.assertTrue(W.flags.c_contiguous) - - def test_shapeup(self): - VQC, FQC, _ = igl.read_off(os.path.join( - self.test_data_path, "halftunnel.off")) - array_of_fours = np.ones((FQC.shape[0], 1), dtype="int32")*4 - - E = np.zeros((FQC.shape[0]*FQC.shape[1], 2), dtype="int32") - E[:, 0] = np.concatenate((FQC[:, 0], FQC[:, 1], FQC[:, 2], FQC[:, 3])) - E[:, 1] = np.concatenate((FQC[:, 1], FQC[:, 2], FQC[:, 3], FQC[:, 0])) - - b = np.array([0]) - wShape = np.ones((FQC.shape[0], 1)) - wSmooth = np.ones((E.shape[0], 1)) - - shapeup = igl.shapeup(VQC, array_of_fours, FQC, E, - b, wShape, wSmooth, maxIterations=3) - - bc = VQC[0, :] - func = 'regular_face_projection' - P = shapeup.solve(bc, VQC, local_projection=func) - - self.assertTrue(P.flags.c_contiguous) - self.assertTrue(P.dtype == VQC.dtype) - - def test_boundary_conditions(self): - # tested in test bbw - pass - - def test_harmonic(self): - # tested in test_slim, test_arap2, and test_arap1 - pass - - def test_harmonic_integrated(self): - Q = igl.harmonic_integrated(self.v1, self.f1, 1) - self.assertTrue(Q.dtype == self.v1.dtype) - - def test_harmonic_uniform_laplacian(self): - b = np.array([0, 10]) - bc = np.array([ - [0, 0], [10., 10.]]) - W = igl.harmonic_uniform_laplacian(self.f1, b, bc, 1) - - self.assertTrue(W.dtype == self.v1.dtype) - self.assertTrue(W.flags.c_contiguous) - - def test_harmonic_integrated_from_laplacian_and_mass(self): - l = igl.cotmatrix(self.v1, self.f1) - m = igl.massmatrix(self.v1, self.f1, igl.MASSMATRIX_TYPE_VORONOI) - - Q = igl.harmonic_integrated_from_laplacian_and_mass(l, m, 1) - self.assertTrue(Q.dtype == self.v1.dtype) - - # deal with igl::PerEdgeNormalsWeightingType - # def test_per_edge_normals(self): - # fn = np.random.rand(self.f1.shape[0], 3) - # n, e, emap = igl.per_edge_normals(self.v1, self.f1, 0, fn) - - def test_lscm(self): - b = np.array([1, 2, 3]) - # print(b.dtype) - bc = np.array([ - [1., 0], - [1, 1], - [2, 2]]) - success, uv = igl.lscm(self.v1, self.f1, b, bc) - self.assertEqual(type(success), bool) - self.assertEqual(uv.dtype, self.v1.dtype) - self.assertEqual(uv.shape, (self.v1.shape[0], 2)) - self.assertTrue(uv.flags.c_contiguous) - - def test_is_irregular_vertex(self): - is_i = igl.is_irregular_vertex(self.f1) - self.assertEqual(type(is_i[0]), bool) - - def test_harmonic(self): - l = igl.cotmatrix(self.v1, self.f1) - m = igl.massmatrix(self.v1, self.f1, igl.MASSMATRIX_TYPE_VORONOI) - b = np.array([1, 2, 10, 7]) - bc = self.v1[b, :] - k = 1 - w = igl.harmonic_from_laplacian_and_mass(l, m, b, bc, k) - self.assertTrue(w.flags.c_contiguous) - - def test_harmonic_from_laplacian_and_mass(self): - # tested in test_harmonic - pass - - # this test is creating a matrix which is not spd and an assertion fails... - # def test_bijective_composite_harmonic_mapping(self): - # v, f = igl.read_triangle_mesh(os.path.join(self.test_data_path, "circle.obj")) - # f = np.array(f[:, [0, 2, 1]]) - # v = np.array(v[:, 0:2]) - # b = np.array([943, 1356] - # bc = np.array([ - # [0, 0], - # [10., 10.] - # ]) - - # ok, u = igl.bijective_composite_harmonic_mapping(v, f, b, bc) - - # self.assertTrue(u.flags.c_contiguous) - # self.assertTrue(u.dtype == v1.dtype) - # self.assertTrue(type(ok) == bool) - - def test_exact_geodesic(self): - vs = np.array([0]) - vt = np.arange(self.v1.shape[0]) - - d = igl.exact_geodesic(self.v1, self.f1, vs, vt) - self.assertEqual(d.dtype, self.v1.dtype) - self.assertTrue(d.flags.c_contiguous) - - def test_heat_geodesic(self): - vs = np.array([0]) - d = igl.heat_geodesic(self.v1, self.f1, 1.0, vs) - # TODO: Should this not return distances for all sources? - self.assertEqual(d.dtype, self.v1.dtype) - self.assertTrue(d.flags.c_contiguous) - - def test_cut_mesh(self): - cuts = np.random.randint(0, 2, size=self.f1.shape, dtype=self.f1.dtype) - vcut, fcut = igl.cut_mesh(self.v1, self.f1, cuts) - - self.assertTrue(vcut.flags.c_contiguous) - self.assertTrue(fcut.flags.c_contiguous) - - self.assertTrue(vcut.dtype == self.v1.dtype) - self.assertTrue(vcut.shape[1] == 3) - self.assertTrue(vcut.shape[0] >= self.v1.shape[0]) - self.assertTrue(fcut.dtype == self.f1.dtype) - self.assertTrue(fcut.shape[1] == 3) - self.assertTrue(fcut.shape[0] == self.f1.shape[0]) - - def test_cut_mesh_from_singularities(self): - mismatch = np.random.randint( - 0, 10, size=self.f1.shape, dtype=self.f1.dtype) - seams = igl.cut_mesh_from_singularities(self.v1, self.f1, mismatch) - self.assertEqual(seams.shape, (self.f1.shape[0], 3)) - self.assertEqual(seams.dtype, bool) - self.assertTrue(seams.flags.c_contiguous) - - # def test_BBW(self): - # BBW = igl.BBW() - # w = BBW.solve(self.v1, self.f1, self.bc, self.b0) - - def test_loop_subdivision_matrix(self): - S, nf = igl.loop_subdivision_matrix(len(self.v1), self.f1) - self.assertEqual(nf.dtype, self.f1.dtype) - self.assertEqual(nf.shape[1], self.f1.shape[1]) - self.assertTrue(nf.flags.c_contiguous) - - def test_loop(self): - nv, nf = igl.loop(self.v1, self.f1) - self.assertEqual(nv.dtype, self.v1.dtype) - self.assertEqual(nv.shape[1], self.v1.shape[1]) - self.assertEqual(nf.dtype, self.f1.dtype) - self.assertEqual(nf.shape[1], self.f1.shape[1]) - self.assertTrue(nv.flags.c_contiguous) - self.assertTrue(nf.flags.c_contiguous) - - def test_segments_intersect(self): - p = np.float32([0, 0, 0]) - r = np.float32([1, 2, 3]) - q = np.float32([5, 5, 5]) - s = np.float32([3, 2, 1]) - is_intersect, t, u, eps = igl.segments_intersect(p, r, q, s) - self.assertEqual(type(is_intersect), bool) - self.assertEqual(type(t), float) - self.assertEqual(type(u), float) - - def test_hessian_energy(self): - q = igl.hessian_energy(self.v1, self.f1) - self.assertEqual(q.dtype, self.v1.dtype) - self.assertEqual(q.shape, (self.v1.shape[0], self.v1.shape[0])) - - def test_signed_distance(self): - min_v = np.min(self.v1, axis=0) - max_v = np.max(self.v1, axis=0) - n = 16 - g = np.mgrid[min_v[0]:max_v[0]:complex( - n), min_v[1]:max_v[1]:complex(n), min_v[2]:max_v[2]:complex(n)] - p = np.vstack(list(map(np.ravel, g))).T - # test default type - s, i, c = igl.signed_distance(p, self.v1, self.f1) - - self.assertEqual(s.shape[0], p.shape[0]) - self.assertEqual(i.shape[0], p.shape[0]) - self.assertEqual(c.shape, p.shape) - - signTypes = [ - igl.SIGNED_DISTANCE_TYPE_PSEUDONORMAL, - igl.SIGNED_DISTANCE_TYPE_WINDING_NUMBER, - igl.SIGNED_DISTANCE_TYPE_DEFAULT, - igl.SIGNED_DISTANCE_TYPE_UNSIGNED, - igl.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER - ] - - # test each specific type. - for signType in signTypes: - s, i, c = igl.signed_distance( - p, self.v1, self.f1, sign_type=signType) - self.assertEqual(s.shape[0], p.shape[0]) - self.assertEqual(i.shape[0], p.shape[0]) - self.assertEqual(c.shape, p.shape) - self.assertTrue(s.flags.c_contiguous) - self.assertTrue(i.flags.c_contiguous) - self.assertTrue(c.flags.c_contiguous) - - self.assertTrue(s.dtype == self.v1.dtype) - self.assertTrue(c.dtype == self.v1.dtype) - self.assertTrue(i.dtype == self.f1.dtype) - - # test return_normals, default changes to psuedonormal - s, i, c, n = igl.signed_distance( - p, self.v1, self.f1, return_normals=True) - self.assertEqual(n.shape, p.shape) - self.assertTrue(n.flags.c_contiguous) - self.assertTrue(n.dtype == self.v1.dtype) - - # ensure error raised when trying param other than pseudonormal for normals - with self.assertRaises(ValueError): - igl.signed_distance( - p, self.v1, self.f1, sign_type=igl.SIGNED_DISTANCE_TYPE_WINDING_NUMBER, return_normals=True) - - # ensure error raise when invalid param given - with self.assertRaises(ValueError): - igl.signed_distance(p, self.v1, self.f1, sign_type=345) - - def test_offset_surface(self): - sv, sf, gv, side, so = igl.offset_surface( - self.v1, self.f1, 1, 10, igl.SIGNED_DISTANCE_TYPE_DEFAULT) - self.assertTrue(sv.dtype == self.v1.dtype) - self.assertTrue(sf.dtype == self.f1.dtype) - - self.assertTrue(gv.dtype == self.v1.dtype) - self.assertTrue(side.dtype == self.f1.dtype) - self.assertTrue(so.dtype == self.f1.dtype) - - self.assertTrue(sv.shape[1] == 3) - self.assertTrue(sf.shape[1] == 3) - self.assertTrue(gv.shape[1] == 3) - - self.assertTrue(gv.shape[0] == so.shape[0]) - - def test_biharmonic_coordinates(self): - w = igl.biharmonic_coordinates(self.v1, self.f1, [[0]]) - - self.assertTrue(w.flags.c_contiguous) - self.assertTrue(w.dtype == self.v1.dtype) - - # std::function in python - # def test_flip_avoid_line_search(self): - # pass - - def test_min_quad_with_fixed(self): - v, f = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "cheburashka.off")) - - # Two fixed points: Left hand, left foot should have values 1 and -1 - b = np.array([4331, 5957]) - bc = np.array([1., -1.]) - B = np.zeros((v.shape[0], 1)) - - # Construct Laplacian and mass matrix - L = igl.cotmatrix(v, f) - M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI) - Minv = sp.sparse.diags(1 / M.diagonal()) - - # Bi-Laplacian - Q = L.dot(Minv.dot(L)) - - # Solve with only equality constraints - Aeq = sp.sparse.csc_matrix((0, 0)) - Beq = np.array([]) - ok, z1 = igl.min_quad_with_fixed(Q, B, b, bc, Aeq, Beq, True) - self.assertTrue(z1.flags.c_contiguous) - self.assertTrue(z1.dtype == B.dtype) - self.assertTrue(ok) - - def test_marching_tets(self): - TV = np.array([ - [0., 0., 0.], - [1., 0., 0.], - [0., 1., 0.], - [0., 0., 1.], - [0., 0., -1.] - ]) - TT = np.array([[0, 1, 2, 3], [0, 1, 2, 4]]) - S = np.array([0., 1., 1., 1., 1.]) - - v, f, j, bc = igl.marching_tets(TV, TT, S, 0.5) - - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(f.flags.c_contiguous) - self.assertTrue(j.flags.c_contiguous) - - self.assertTrue(v.dtype == TV.dtype) - self.assertTrue(v.shape[0] >= 3) - self.assertTrue(v.shape[1] == 3) - - self.assertTrue(f.dtype == TT.dtype) - self.assertTrue(f.shape[1] == 3) - - self.assertTrue(j.dtype == TT.dtype) - self.assertTrue(j.shape[0] == f.shape[0]) - - self.assertTrue(bc.dtype == TV.dtype) - self.assertTrue(bc.shape[0] == v.shape[0]) - self.assertTrue(bc.shape[1] == TV.shape[0]) - - def test_read_tgf(self): - filename = os.path.join(self.test_data_path, "hand.tgf") - tf = np.array([1.]) - ti = np.array([1]) - - V, E, P, BE, CE, PE = igl.read_tgf(filename) - - self.assertTrue(V.flags.c_contiguous) - self.assertTrue(E.flags.c_contiguous) - self.assertTrue(P.flags.c_contiguous) - self.assertTrue(BE.flags.c_contiguous) - self.assertTrue(CE.flags.c_contiguous) - self.assertTrue(PE.flags.c_contiguous) - - self.assertTrue(V.dtype == tf.dtype) - self.assertTrue(E.dtype == ti.dtype) - self.assertTrue(P.dtype == ti.dtype) - self.assertTrue(BE.dtype == ti.dtype) - self.assertTrue(CE.dtype == ti.dtype) - self.assertTrue(PE.dtype == ti.dtype) - - def test_deform_skeleton(self): - hand_file = os.path.join(self.test_data_path, "hand.tgf") - C, BE, _, _, _, _ = igl.read_tgf(hand_file) - - T = np.zeros((BE.shape[0]*4, 3)) - I = np.eye(3) - for i in range(0, T.shape[0], 4): - T[i:i+3, :] = I - - CT, BET = igl.deform_skeleton(C, BE, T) - self.assertTrue(CT.flags.c_contiguous) - self.assertTrue(BET.flags.c_contiguous) - - self.assertTrue(CT.dtype == C.dtype) - self.assertTrue(BET.dtype == BE.dtype) - - def test_edge_lengths(self): - l = igl.edge_lengths(self.v1, self.f1) - - self.assertTrue(l.flags.c_contiguous) - self.assertTrue(l.dtype == self.v1.dtype) - self.assertTrue(l.shape == self.f1.shape) - - def test_planarize_quad_mesh(self): - v, f, _ = igl.read_off(os.path.join( - self.test_data_path, "inspired_mesh_quads_Conjugate.off")) - out = igl.planarize_quad_mesh(v, f, 1, 1e-2) - - self.assertTrue(out.dtype == v.dtype) - self.assertTrue(out.flags.c_contiguous) - - def test_local_basis(self): - b1, b2, b3 = igl.local_basis(self.v1, self.f1) - self.assertTrue(b1.flags.c_contiguous) - self.assertTrue(b2.flags.c_contiguous) - self.assertTrue(b3.flags.c_contiguous) - - self.assertTrue(b1.dtype == self.v1.dtype) - self.assertTrue(b2.dtype == self.v1.dtype) - self.assertTrue(b3.dtype == self.v1.dtype) - - self.assertTrue(b1.shape == self.f1.shape) - self.assertTrue(b2.shape == self.f1.shape) - self.assertTrue(b3.shape == self.f1.shape) - - def test_cross_fields(self): - V, F = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "3holes.off")) - - B = igl.barycenter(V, F) - b = np.array([0]) - bc = np.array([[1., 0., 0.]]) - - # if platform.system() == "Windows": - X1 = np.load(os.path.join(self.test_data_path, "X1.npy")) - S = np.load(os.path.join(self.test_data_path, "S.npy")) - - self.assertTrue(X1.flags.c_contiguous) - self.assertTrue(S.flags.c_contiguous) - self.assertTrue(X1.dtype == V.dtype) - self.assertTrue(S.dtype == V.dtype) - - B1, B2, B3 = igl.local_basis(V, F) - - X2 = igl.rotate_vectors(X1, np.array([math.pi/2]), B1, B2) - self.assertTrue(X2.flags.c_contiguous) - self.assertTrue(X2.dtype == V.dtype) - - BIS1, BIS2 = igl.compute_frame_field_bisectors_no_basis(V, F, X1, X2) - self.assertTrue(BIS1.flags.c_contiguous) - self.assertTrue(BIS1.dtype == V.dtype) - self.assertTrue(BIS2.flags.c_contiguous) - self.assertTrue(BIS2.dtype == V.dtype) - - BIS1t, BIS2t = igl.compute_frame_field_bisectors( - V, F, X1, X2, BIS1, BIS2) - self.assertTrue(BIS1t.flags.c_contiguous) - self.assertTrue(BIS1t.dtype == V.dtype) - self.assertTrue(BIS2t.flags.c_contiguous) - self.assertTrue(BIS2t.dtype == V.dtype) - - BIS1_combed, BIS2_combed = igl.comb_cross_field(V, F, BIS1, BIS2) - self.assertTrue(BIS1_combed.flags.c_contiguous) - self.assertTrue(BIS1_combed.dtype == V.dtype) - self.assertTrue(BIS2_combed.flags.c_contiguous) - self.assertTrue(BIS2_combed.dtype == V.dtype) - - BI_combed = igl.comb_line_field(V, F, BIS1) - self.assertTrue(BI_combed.flags.c_contiguous) - self.assertTrue(BI_combed.dtype == V.dtype) - - MMatch = igl.cross_field_mismatch(V, F, BIS1_combed, BIS2_combed, True) - self.assertTrue(MMatch.flags.c_contiguous) - self.assertTrue(MMatch.dtype == F.dtype) - - isSingularity, singularityIndex = igl.find_cross_field_singularities( - V, F, MMatch) - self.assertTrue(isSingularity.flags.c_contiguous) - self.assertTrue(isSingularity.dtype == F.dtype) - self.assertTrue(singularityIndex.flags.c_contiguous) - self.assertTrue(singularityIndex.dtype == F.dtype) - - isSingularityt, singularityIndext = igl.find_cross_field_singularities_from_field( - V, F, BIS1_combed, BIS2_combed) - self.assertTrue(isSingularityt.flags.c_contiguous) - self.assertTrue(isSingularityt.dtype == F.dtype) - self.assertTrue(singularityIndext.flags.c_contiguous) - self.assertTrue(singularityIndext.dtype == F.dtype) - - X1_combed, X2_combed = igl.comb_frame_field( - V, F, X1, X2, BIS1_combed, BIS2_combed) - self.assertTrue(X1_combed.flags.c_contiguous) - self.assertTrue(X1_combed.dtype == V.dtype) - self.assertTrue(X2_combed.flags.c_contiguous) - self.assertTrue(X2_combed.dtype == V.dtype) - - def test_comb_cross_field(self): - #tested in test_cross_fields - pass - - def test_comb_frame_field(self): - #tested in test_cross_fields - pass - - def test_comb_line_field(self): - #tested in test_cross_fields - pass - - def test_compute_frame_field_bisectors(self): - #tested in test_cross_fields - pass - - def test_compute_frame_field_bisectors_no_basis(self): - #tested in test_cross_fields - pass - - def test_cross_field_mismatch(self): - #tested in test_cross_fields - pass - - def test_find_cross_field_singularities(self): - #tested in test_cross_fields - pass - - def test_find_cross_field_singularities_from_field(self): - #tested in test_cross_fields - pass - - def test_rotate_vectors(self): - #tested in test_cross_fields - pass - - def test_directed_edge_orientations(self): - v = np.array([[0.0, 0.0, 0.], [1.0, 0.0, 0.], - [1.0, 1.0, 0.], [0.0, 1.0, 0.]]) - e = np.array([[0, 1], [1, 2], [2, 3], [3, 0]]) - - q = igl.directed_edge_orientations(v, e) - self.assertTrue(q.flags.c_contiguous) - self.assertTrue(q.dtype == v.dtype) - self.assertTrue(q.shape[0] == e.shape[0]) - self.assertTrue(q.shape[1] == 4) - - def test_lbs_matrix(self): - V, _ = igl.read_triangle_mesh(os.path.join(self.test_data_path, "arm.obj")) - W = igl.read_dmat(os.path.join(self.test_data_path, "arm-weights.dmat")) - M = igl.lbs_matrix(V, W) - self.assertTrue(M.flags.c_contiguous) - self.assertTrue(M.dtype == V.dtype) - self.assertTrue(M.shape[0] == V.shape[0]) - self.assertTrue(M.shape[1] == W.shape[1]*4) - - def test_direct_delta_mush(self): - V, F = igl.read_triangle_mesh(os.path.join(self.test_data_path, "arm.obj")) - W = igl.read_dmat(os.path.join(self.test_data_path, "arm-weights.dmat")) - _, BE, _, _, _, _ = igl.read_tgf(os.path.join(self.test_data_path, "arm.tgf")) - - # Use same values as tutorial - # https://github.com/libigl/libigl/blob/main/tutorial/408_DirectDeltaMush/main.cpp - p = 20 - l = 3 - k = 1 - a = 0.8 - omega = igl.direct_delta_mush_precomputation(V, F,W, p, l, k, a) - - self.assertTrue(omega.shape[0] == V.shape[0]) - self.assertTrue(omega.shape[1] == BE.shape[0] * 10) - self.assertTrue(omega.dtype == np.double) - - T = np.zeros((BE.shape[0]*4, 3)) - I = np.eye(3) - for i in range(0, T.shape[0], 4): - T[i:i+3, :] = I - - U = igl.direct_delta_mush(V, T, omega) - - self.assertTrue(U.shape[0] == V.shape[0]) - self.assertTrue(U.shape[1] == 3) - self.assertTrue(U.dtype == np.double) - self.assertFalse(np.isnan(U).any()) - - def test_direct_delta_mush_precomputation(self): - # covered in test_direct_delta_mush - pass - - - def test_point_mesh_squared_distance(self): - dist, i, c = igl.point_mesh_squared_distance( - np.array([0., 0., 0.]), self.v1, self.f1) - - self.assertTrue(dist.flags.c_contiguous) - self.assertTrue(i.flags.c_contiguous) - self.assertTrue(c.flags.c_contiguous) - - self.assertTrue(dist.dtype == self.v1.dtype) - self.assertTrue(i.dtype == self.f1.dtype) - self.assertTrue(c.dtype == self.v1.dtype) - - self.assertTrue(dist.shape == ()) - self.assertTrue(i.shape == ()) - self.assertTrue(c.shape == (3,)) - ######################### - dist, i, c = igl.point_mesh_squared_distance( - np.array([[0., 0., 0.], [0., 0., 0.]]), self.v1, self.f1) - - self.assertTrue(dist.flags.c_contiguous) - self.assertTrue(i.flags.c_contiguous) - self.assertTrue(c.flags.c_contiguous) - - self.assertTrue(dist.dtype == self.v1.dtype) - self.assertTrue(i.dtype == self.f1.dtype) - self.assertTrue(c.dtype == self.v1.dtype) - - self.assertTrue(dist.shape[0] == 2) - self.assertTrue(i.shape[0] == 2) - self.assertTrue(c.shape[0] == 2) - self.assertTrue(c.shape[1] == 3) - - def test_dual_quat_skinning(self): - V, _ = igl.read_triangle_mesh(os.path.join(self.test_data_path, "arm.obj")) - U = np.copy(V) - W = igl.read_dmat(os.path.join(self.test_data_path, "arm-weights.dmat")) - C, BE, _, _, _, _ = igl.read_tgf( - os.path.join(self.test_data_path, "arm.tgf")) - P = igl.directed_edge_parents(BE) - rest_pose = igl.directed_edge_orientations(C, BE) - - M = igl.lbs_matrix(V, W) - - vQ, vT = igl.forward_kinematics(C, BE, P, rest_pose, np.array([])) - self.assertTrue(vQ.flags.c_contiguous) - self.assertTrue(vT.flags.c_contiguous) - self.assertTrue(vQ.shape[1] == 4) - self.assertTrue(vT.shape[1] == 3) - self.assertTrue(vQ.shape[0] == BE.shape[0]) - self.assertTrue(vT.shape[0] == BE.shape[0]) - - U = igl.dqs(V, W, vQ, vT) - self.assertTrue(U.flags.c_contiguous) - self.assertTrue(U.shape == V.shape) - - def test_dqs(self): - # tested in test_dual_quat_skinning - pass - - def test_forward_kinematics(self): - # tested in test_dual_quat_skinning - pass - - def test_active_set(self): - V, F = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "cheburashka.off")) - - b = np.array([2556]) - bc = np.array([1.0]) - - L = igl.cotmatrix(V, F) - M = igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_VORONOI) - Minv = sp.sparse.csr_matrix(M) - Minv.setdiag(1./M.diagonal()) - Q = L.T * (Minv * L) - B = np.zeros((V.shape[0], 1)) - lx = np.zeros((V.shape[0], 1)) - ux = np.ones((V.shape[0], 1)) - - Beq = np.array([0.08]) - Aeq = sp.sparse.csr_matrix(M.diagonal()) - - Aieq = sp.sparse.csr_matrix((0, 0)) - Bieq = np.zeros((0, 0)) - - status, Z = igl.active_set( - Q, B, b, bc, Aeq, Beq, Aieq, Bieq, lx, ux, max_iter=5) - - self.assertTrue(status != 2) - self.assertTrue(Z.shape[0] == Q.shape[0]) - self.assertTrue(Z.dtype == V.dtype) - - def test_face_occurrences(self): - c = igl.face_occurrences(self.f) - self.assertTrue(c.flags.c_contiguous) - self.assertTrue(c.dtype == self.f.dtype) - self.assertTrue(c.shape[0] == self.f.shape[0]) - - def test_false_barycentric_subdivision(self): - vd, fd = igl.false_barycentric_subdivision(self.v, self.f) - self.assertTrue(vd.flags.c_contiguous) - self.assertTrue(fd.flags.c_contiguous) - - self.assertTrue(vd.dtype == self.v.dtype) - self.assertTrue(fd.dtype == self.f.dtype) - - self.assertTrue(vd.shape[0] == self.v.shape[0]+self.f.shape[0]) - self.assertTrue(vd.shape[1] == self.v.shape[1]) - self.assertTrue(fd.shape[1] == self.f.shape[1]) - self.assertTrue(fd.shape[0] == self.f.shape[0]*3) - - def test_flipped_triangles(self): - flipped = igl.flipped_triangles(self.v[:, :2], self.f) - self.assertTrue(flipped.flags.c_contiguous) - self.assertTrue(flipped.dtype == self.f.dtype) - self.assertTrue(len(flipped.shape) == 1) - - def test_inradius(self): - r = igl.inradius(self.v, self.f) - self.assertTrue(r.flags.c_contiguous) - self.assertTrue(r.dtype == self.v.dtype) - self.assertTrue(r.shape[0] == self.f.shape[0]) - self.assertTrue(len(r.shape) == 1) - - def test_is_border_vertex(self): - res = igl.is_border_vertex(self.v, self.f) - - self.assertTrue(len(res) == self.v.shape[0]) - self.assertTrue(type(res) == list) - self.assertTrue(type(res[0]) == bool) - - def test_extract_manifold_patches(self): - n, p = igl.extract_manifold_patches(self.f2) - self.assertTrue(p.flags.c_contiguous) - self.assertTrue(p.dtype == self.f2.dtype) - self.assertTrue(len(p.shape) == 1) - self.assertTrue(p.shape[0] == self.f2.shape[0]) - self.assertTrue(type(n) == int) - self.assertTrue(n == 1) - - def test_faces_first(self): - RV, RF, IM = igl.faces_first(self.v, self.f) - self.assertTrue(RV.flags.c_contiguous) - self.assertTrue(RF.flags.c_contiguous) - self.assertTrue(IM.flags.c_contiguous) - - self.assertTrue(RV.dtype == self.v.dtype) - self.assertTrue(RF.dtype == self.f.dtype) - self.assertTrue(IM.dtype == self.f.dtype) - - self.assertTrue(RV.shape[0] == self.v.shape[0]) - self.assertTrue(RF.shape[0] == self.f.shape[0]) - self.assertTrue(IM.shape[0] == self.v.shape[0]) - self.assertTrue(len(IM.shape) == 1) - - def test_is_intrinsic_delaunay(self): - # Tested above - pass - - def test_intrinsic_delaunay_triangulation(self): - el = igl.edge_lengths(self.v1, self.f1) - l, f = igl.intrinsic_delaunay_triangulation(el, self.f1) - # print(l, f) - self.assertTrue(f.flags.c_contiguous) - self.assertTrue(f.dtype == self.f1.dtype) - self.assertTrue(f.shape[1] == 3) - - def test_intrinsic_delaunay_triangulation_edges(self): - el = igl.edge_lengths(self.v1, self.f1) - l, f, e, u_e, emap, ue2e = igl.intrinsic_delaunay_triangulation_edges( - el, self.f1) - self.assertTrue(f.flags.c_contiguous) - self.assertTrue(f.dtype == self.f.dtype) - self.assertTrue(f.shape[1] == 3) - - def test_edges_to_path(self): - e = igl.edges(self.f1) - e = e[:1, :] - i, j, k = igl.edges_to_path(e) - - self.assertTrue(i.flags.c_contiguous) - self.assertTrue(j.flags.c_contiguous) - self.assertTrue(k.flags.c_contiguous) - - self.assertTrue(i.dtype == e.dtype) - self.assertTrue(j.dtype == e.dtype) - self.assertTrue(k.dtype == e.dtype) - - def test_data_path_to_edges(self): - v_indices = np.array(range(20)) - e1 = igl.path_to_edges(v_indices, False) - e2 = igl.path_to_edges(v_indices, True) - r2 = np.vstack([v_indices, np.array(range(1, 21))]).T - r2[19, 1] = 0 - self.assertTrue(np.allclose(e2, r2)) - self.assertTrue(e1.flags.c_contiguous) - self.assertTrue(e2.flags.c_contiguous) - self.assertTrue(e1.dtype == v_indices.dtype) - self.assertTrue(e2.dtype == v_indices.dtype) - - def test_exterior_edges(self): - e = igl.exterior_edges(self.f1) - - self.assertTrue(e.flags.c_contiguous) - self.assertTrue(e.dtype == self.f1.dtype) - self.assertTrue(e.shape[1] == 2) - - def test_normal_derivative(self): - d = igl.normal_derivative(self.v1, self.f1) - - self.assertTrue(d.shape == (self.f1.shape[0]*3, self.v1.shape[0])) - self.assertTrue(d.dtype == self.v1.dtype) - self.assertTrue(type(d) == csc.csc_matrix) - - def test_orient_outward(self): - v,f = igl.read_triangle_mesh(os.path.join(self.test_data_path, "truck.obj")) - c, _ = igl.orientable_patches(f) - ff, i = igl.orient_outward(v, f, c) - - self.assertTrue(ff.flags.c_contiguous) - self.assertTrue(i.flags.c_contiguous) - self.assertTrue(ff.dtype == f.dtype) - self.assertTrue(i.dtype == f.dtype) - self.assertTrue(ff.shape[0] == f.shape[0]) - self.assertTrue(ff.shape[1] == 3) - self.assertTrue(len(i.shape) == 1) - self.assertTrue(i.shape[0] == np.max(c)+1) - - def test_solid_angle(self): - v0 = np.array(self.v1[self.f1[0, 0], :]) - v1 = np.array(self.v1[self.f1[0, 1], :]) - v2 = np.array(self.v1[self.f1[0, 2], :]) - p = np.array(self.v1[10, :]) - d = igl.solid_angle(v0, v1, v2, p) - - def test_simplify_polyhedron(self): - v, f, j = igl.simplify_polyhedron(self.v1, self.f1) - - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(f.flags.c_contiguous) - self.assertTrue(j.flags.c_contiguous) - - self.assertTrue(v.dtype == self.v1.dtype) - self.assertTrue(f.dtype == self.f1.dtype) - self.assertTrue(j.dtype == self.f1.dtype) - - self.assertTrue(v.shape[1] == self.v1.shape[1]) - self.assertTrue(f.shape[1] == self.f1.shape[1]) - self.assertTrue(len(j.shape) == 1) - - def test_unique_simplices(self): - fa, ia, ic = igl.unique_simplices(self.f1) - - self.assertTrue(fa.flags.c_contiguous) - self.assertTrue(ia.flags.c_contiguous) - self.assertTrue(ic.flags.c_contiguous) - - self.assertTrue(fa.dtype == self.f1.dtype) - self.assertTrue(ia.dtype == self.f1.dtype) - self.assertTrue(ic.dtype == self.f1.dtype) - - self.assertTrue(fa.shape[1] == self.f1.shape[1]) - self.assertTrue(ia.shape[0] == ia.shape[0]) - self.assertTrue(ic.shape[0] == self.f1.shape[0]) - self.assertTrue(len(ic.shape) == 1) - self.assertTrue(len(ia.shape) == 1) - - def test_swept_volume_bounding_box(self): - def func(i, t): return (1-t) * \ - self.v1[self.f1[0, i], :] + t*self.v1[self.f1[1, i], :] - - bmin, bmax = igl.swept_volume_bounding_box(3, func, 3) - self.assertTrue(bmin.flags.c_contiguous) - self.assertTrue(bmax.flags.c_contiguous) - - self.assertTrue(bmin.dtype == self.v1.dtype) - self.assertTrue(bmax.dtype == self.v1.dtype) - - self.assertTrue(bmin.shape == (3,)) - self.assertTrue(bmax.shape == (3,)) - - def test_hessian(self): - H = igl.hessian(self.v1, self.f1) - self.assertTrue(H.dtype == self.v1.dtype) - - def test_snap_points(self): - I, minD, VI = igl.snap_points(self.v1, self.v) - - self.assertTrue(I.flags.c_contiguous) - self.assertTrue(minD.flags.c_contiguous) - self.assertTrue(VI.flags.c_contiguous) - - self.assertTrue(I.dtype == self.f1.dtype) - self.assertTrue(minD.dtype == self.v1.dtype) - self.assertTrue(VI.dtype == self.v1.dtype) - - self.assertTrue(I.shape == (self.v1.shape[0], )) - self.assertTrue(I.shape == (self.v1.shape[0], )) - self.assertTrue(VI.shape == (self.v1.shape[0], 3)) - - def test_ray_box_intersect(self): - bmin = np.array([0., 0., 0.]) - bmax = np.array([1., 1., 1.]) - - source = np.array([-1., -1, -1]) - dire = np.array([1., 1., 1.]) - - hit, tmin, tmax = igl.ray_box_intersect( - source, dire, bmin, bmax, 0, 100) - - self.assertTrue(hit) - self.assertTrue(tmin > 0) - self.assertTrue(tmax < 100) - - def test_ray_mesh_intersect(self): - source = np.array([-1., -1, -1]) - dire = self.v1[self.f1[0, :], :].mean(axis=0) - source - - hits = igl.ray_mesh_intersect(source, dire, self.v1, self.f1) - - self.assertTrue(len(hits) > 0) - self.assertTrue(len(hits[0]) == 5) - - def test_ray_sphere_intersect(self): - center = np.array([1., 1., 1.]) - - source = np.array([-1., -1, -1]) - dire = np.array([1., 1., 1.]) - - hits, tmin, tmax = igl.ray_sphere_intersect( - source, dire, center, 1) - - self.assertTrue(hits == 2) - - def test_volume(self): - v, t, f = igl.read_mesh(os.path.join( - self.test_data_path, "octopus-low.mesh")) - vol = igl.volume(v, t) - self.assertTrue(vol.flags.c_contiguous) - self.assertTrue(vol.dtype == v.dtype) - self.assertTrue(len(vol.shape) == 1) - self.assertTrue(vol.shape[0] == t.shape[0]) - - a = v[t[:, 0], :] - b = v[t[:, 1], :] - c = v[t[:, 2], :] - d = v[t[:, 3], :] - - vol = igl.volume_from_vertices(a, b, c, d) - self.assertTrue(vol.flags.c_contiguous) - self.assertTrue(vol.dtype == v.dtype) - self.assertTrue(len(vol.shape) == 1) - self.assertTrue(vol.shape[0] == a.shape[0]) - - vol = igl.volume_single(a[0, :], b[0, :], c[0, :], d[0, :]) - - l = igl.edge_lengths(v, t) - vol = igl.volume_from_edges(l) - self.assertTrue(vol.flags.c_contiguous) - self.assertTrue(vol.dtype == v.dtype) - self.assertTrue(len(vol.shape) == 1) - self.assertTrue(vol.shape[0] == t.shape[0]) - - def test_volume_from_edges(self): - #tested in volume - pass - - def test_volume_from_vertices(self): - #tested in volume - pass - - def test_volume_single(self): - #tested in volume - pass - - def test_mvc(self): - pts = np.random.rand(10, 2) - poly = np.array([[0., 0.], [1., 1.], [2., 2.], [0., 3.]]) - w = igl.mvc(pts, poly) - - self.assertTrue(w.flags.c_contiguous) - self.assertTrue(w.dtype == pts.dtype) - self.assertTrue(w.shape[0] == pts.shape[0]) - self.assertTrue(w.shape[1] == poly.shape[0]) - - def test_all_pairs_distances(self): - u = np.random.rand(10, 2) - v = np.random.rand(5, 2) - - d = igl.all_pairs_distances(u, v, True) - self.assertTrue(d.flags.c_contiguous) - self.assertTrue(d.dtype == u.dtype) - self.assertTrue(d.shape[0] == u.shape[0]) - self.assertTrue(d.shape[1] == v.shape[0]) - - def test_line_segment_in_rectangle(self): - s = np.array([0., 0.]) - d = np.array([10., 10.]) - - A = np.array([1., 1.]) - B = np.array([2., 2.]) - - inter = igl.line_segment_in_rectangle(s, d, A, B) - self.assertTrue(inter) - - def test_look_at(self): - eye = np.random.rand(3, 1) - center = np.random.rand(3, 1) - up = np.random.rand(3, 1) - - R = igl.look_at(eye, center, up) - - self.assertTrue(R.flags.c_contiguous) - self.assertTrue(R.dtype == eye.dtype) - self.assertTrue(R.shape[0] == 4) - self.assertTrue(R.shape[1] == 4) - - def test_outer_vertex(self): - v, a = igl.outer_vertex( - self.v1, self.f1, np.zeros((1, 1), dtype=self.f1.dtype)) - - self.assertTrue(a.flags.c_contiguous) - self.assertTrue(a.dtype == self.f1.dtype) - - def test_outer_edge(self): - v1, v2, a = igl.outer_edge( - self.v1, self.f1, np.zeros((1, 1), dtype=self.f1.dtype)) - - self.assertTrue(a.flags.c_contiguous) - self.assertTrue(a.dtype == self.f1.dtype) - - def test_outer_facet(self): - n = igl.per_face_normals(self.v1, self.f1, self.v1) - index, flipped = igl.outer_facet( - self.v1, self.f1, n, np.zeros((1, 1), dtype=self.f1.dtype)) - - def test_partition(self): - g, s, d = igl.partition(self.v1, 3) - - self.assertTrue(g.flags.c_contiguous) - self.assertTrue(s.flags.c_contiguous) - self.assertTrue(d.flags.c_contiguous) - - self.assertTrue(g.dtype == self.f1.dtype) - self.assertTrue(s.dtype == self.f1.dtype) - self.assertTrue(d.dtype == self.v1.dtype) - - self.assertTrue(g.shape[0] == self.v1.shape[0]) - self.assertTrue(s.shape[0] == 3) - self.assertTrue(d.shape[0] == self.v1.shape[0]) - - self.assertTrue(len(g.shape) == 1) - self.assertTrue(len(s.shape) == 1) - self.assertTrue(len(d.shape) == 1) - - def test_point_in_circle(self): - inside = igl.point_in_circle(1, 2, 3, 4, 1) - - def test_point_simplex_squared_distance(self): - dist, pt, bary = igl.point_simplex_squared_distance( - np.array([3., 3, 1.]), self.v1, self.f1, 0) - - self.assertTrue(pt.flags.c_contiguous) - self.assertTrue(bary.flags.c_contiguous) - - self.assertTrue(pt.dtype == self.v1.dtype) - self.assertTrue(bary.dtype == self.v1.dtype) - - self.assertTrue(pt.shape[0] == 3) - self.assertTrue(bary.shape[0] == 3) - - def test_polar_dec(self): - A = np.random.rand(3, 3) - - r, t = igl.polar_dec(A) - - self.assertTrue(r.flags.c_contiguous) - self.assertTrue(t.flags.c_contiguous) - - self.assertTrue(r.dtype == self.v1.dtype) - self.assertTrue(t.dtype == self.v1.dtype) - - self.assertTrue(r.shape[0] == 3) - self.assertTrue(t.shape[0] == 3) - self.assertTrue(r.shape[1] == 3) - self.assertTrue(t.shape[1] == 3) - - def test_project(self): - model = np.random.rand(4, 4) - proj = np.random.rand(4, 4) - viewport = np.random.rand(4, 1) - proj = igl.project(self.v, model, proj, viewport) - self.assertEqual(proj.dtype, self.v.dtype) - self.assertEqual(proj.shape, self.v.shape) - self.assertTrue(proj.flags.c_contiguous) - - def test_project_isometrically_to_plane(self): - U, UF, I = igl.project_isometrically_to_plane(self.v1, self.f1) - - self.assertEqual(U.dtype, self.v1.dtype) - self.assertEqual(U.shape, (3*self.f1.shape[0], 2)) - self.assertTrue(U.flags.c_contiguous) - - self.assertEqual(UF.dtype, self.f1.dtype) - self.assertEqual(UF.shape, self.f1.shape) - self.assertTrue(UF.flags.c_contiguous) - - self.assertEqual(I.dtype, self.f1.dtype) - self.assertEqual(I.shape, (self.v1.shape[0], 3*self.f1.shape[0])) - self.assertTrue(type(I) == csc.csc_matrix) - - def test_project_to_line(self): - pts = np.random.rand(10, 3) - s = np.random.rand(1, 3) - d = np.random.rand(1, 3) - t, sqrt_d = igl.project_to_line(pts, s, d) - - self.assertTrue(t.flags.c_contiguous) - self.assertTrue(t.dtype == pts.dtype) - self.assertTrue(t.shape[0] == pts.shape[0]) - - self.assertTrue(sqrt_d.flags.c_contiguous) - self.assertTrue(sqrt_d.dtype == pts.dtype) - self.assertTrue(sqrt_d.shape[0] == pts.shape[0]) - - def test_project_to_line_segment(self): - pts = np.random.rand(10, 3) - s = np.random.rand(1, 3) - d = np.random.rand(1, 3) - t, sqrt_d = igl.project_to_line_segment(pts, s, d) - - self.assertTrue(t.flags.c_contiguous) - self.assertTrue(t.dtype == pts.dtype) - self.assertTrue(t.shape[0] == pts.shape[0]) - - self.assertTrue(sqrt_d.flags.c_contiguous) - self.assertTrue(sqrt_d.dtype == pts.dtype) - self.assertTrue(sqrt_d.shape[0] == pts.shape[0]) - - def test_ramer_douglas_peucker(self): - pts = np.random.rand(10, 3) - s, j, q = igl.ramer_douglas_peucker(pts, 1e-1) - - self.assertTrue(s.flags.c_contiguous) - self.assertTrue(s.dtype == pts.dtype) - self.assertTrue(s.shape[1] == pts.shape[1]) - - self.assertTrue(j.flags.c_contiguous) - self.assertTrue(j.dtype == self.f1.dtype) - self.assertTrue(j.shape[0] == s.shape[0]) - - self.assertTrue(q.flags.c_contiguous) - self.assertTrue(q.dtype == pts.dtype) - self.assertTrue(q.shape == pts.shape) - - def test_sample_edges(self): - e = igl.edges(self.f1) - s = igl.sample_edges(self.v1, e, 4) - # print(e.shape, s.shape) - - self.assertTrue(s.flags.c_contiguous) - self.assertTrue(s.dtype == self.v1.dtype) - self.assertTrue(s.shape[0] < e.shape[0]*6) - self.assertTrue(s.shape[1] == self.v1.shape[1]) - - def test_read_msh(self): - v, t = igl.read_msh(os.path.join(self.test_data_path, "car.msh")) - self.assertTrue(type(v) == type(t) == np.ndarray) - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(t.flags.c_contiguous) - - self.assertTrue(v.dtype == self.default_float) - self.assertTrue(t.dtype == self.f1.dtype) - - def test_two_axis_valuator_fixed_up(self): - down_quat = np.random.rand(4, 1) - - quat = igl.two_axis_valuator_fixed_up( - 20, 20, 1, down_quat, 10, 10, 9, 9) - - self.assertTrue(quat.flags.c_contiguous) - self.assertTrue(quat.dtype == down_quat.dtype) - self.assertTrue(quat.shape[0] == 4) - - def test_triangle_fan(self): - _, f = igl.read_triangle_mesh( - os.path.join(self.test_data_path, "camelhead.off")) - e = igl.exterior_edges(f) - cap = igl.triangle_fan(e) - - self.assertTrue(cap.flags.c_contiguous) - self.assertTrue(cap.dtype == e.dtype) - self.assertTrue(cap.shape[1] == 3) - - def test_triangles_from_strip(self): - s = self.f[:, 0] - f = igl.triangles_from_strip(s) - - self.assertTrue(f.flags.c_contiguous) - self.assertTrue(f.dtype == self.f.dtype) - self.assertTrue(f.shape[0] == self.f.shape[0]-2) - self.assertTrue(f.shape[1] == 3) - - def test_signed_angle(self): - a = np.random.rand(2, 1) - b = np.random.rand(2, 1) - p = np.random.rand(2, 1) - - angle = igl.signed_angle(a, b, p) - - a = np.random.rand(1, 2) - b = np.random.rand(1, 2) - p = np.random.rand(1, 2) - - angle = igl.signed_angle(a, b, p) - - def test_unique_edge_map(self): - E, uE, EMAP, uE2E = igl.unique_edge_map(self.f1) - - self.assertTrue(E.flags.c_contiguous) - self.assertTrue(uE.flags.c_contiguous) - self.assertTrue(EMAP.flags.c_contiguous) - - self.assertTrue(E.dtype == self.f1.dtype) - self.assertTrue(uE.dtype == self.f1.dtype) - self.assertTrue(EMAP.dtype == self.f1.dtype) - - self.assertTrue(E.shape == (self.f1.shape[0]*3, 2)) - self.assertTrue(uE.shape[1] == 2) - self.assertTrue(E.shape[0] == self.f1.shape[0]*3) - - def test_connected_components(self): - a = igl.adjacency_matrix(self.f) - comps, c, k = igl.connected_components(a) - - self.assertTrue(c.flags.c_contiguous) - self.assertTrue(k.flags.c_contiguous) - - self.assertTrue(c.dtype == self.f1.dtype) - self.assertTrue(k.dtype == self.f1.dtype) - - self.assertTrue(c.shape[0] == a.shape[0]) - - def test_pso(self): - def banana(x): - x1 = x[0] - x2 = x[1] - return x1**4 - 2*x2*x1**2 + x2**2 + x1**2 - 2*x1 + 5 - - lb = np.array([-3.0, -1.0]) - ub = np.array([2.0, 6.0]) - - fopt, xopt = igl.pso(banana, lb, ub, max_iters=10, population=10) - - self.assertTrue(xopt.flags.c_contiguous) - self.assertTrue(xopt.dtype == lb.dtype) - self.assertTrue(xopt.shape == (2, )) - - def test_random_search(self): - def banana(x): - x1 = x[0] - x2 = x[1] - return x1**4 - 2*x2*x1**2 + x2**2 + x1**2 - 2*x1 + 5 - - lb = np.array([-3.0, -1.0]) - ub = np.array([2.0, 6.0]) - - fopt, xopt = igl.random_search(banana, lb, ub, iters=10) - - self.assertTrue(xopt.flags.c_contiguous) - self.assertTrue(xopt.dtype == lb.dtype) - self.assertTrue(xopt.shape == (2, )) - - def test_bijective_composite_harmonic_mapping(self): - v, f, _ = igl.read_off(os.path.join(self.test_data_path, "camelhead.off")) - b = igl.boundary_loop(f) - thetas = np.linspace(0, 2 * np.pi, len(b))[:, np.newaxis] - bc = np.concatenate([np.cos(thetas), np.sin(thetas)], axis=1) - v2d = igl.harmonic(v, f, b, bc, 1)[:, :2] - ret0, mapping0 = igl.bijective_composite_harmonic_mapping( - v2d, f, b, bc) - self.assertTrue(ret0) - self.assertTrue(mapping0.flags.c_contiguous) - self.assertTrue(mapping0.dtype == v2d.dtype) - self.assertTrue(mapping0.shape == v2d.shape) - - ret1, mapping1 = igl.bijective_composite_harmonic_mapping_with_steps( - v2d, f, b, bc, min_steps=1, max_steps=5, num_inner_iters=2, test_for_flips=True) - self.assertTrue(ret1) - self.assertTrue(mapping1.flags.c_contiguous) - self.assertTrue(mapping1.dtype == v2d.dtype) - self.assertTrue(mapping1.shape == v2d.shape) - - ret2, mapping2 = igl.bijective_composite_harmonic_mapping_with_steps( - v2d, f, b, bc, min_steps=1, max_steps=5, num_inner_iters=2, test_for_flips=False) - self.assertTrue(ret2) - self.assertTrue(mapping2.flags.c_contiguous) - self.assertTrue(mapping2.dtype == v2d.dtype) - self.assertTrue(mapping2.shape == v2d.shape) - - self.assertTrue(np.allclose(mapping0, mapping1)) - - def test_bijective_composite_harmonic_mapping_with_steps(self): - # Tested above - pass - - def test_extract_non_manifold_edge_curves(self): - _ = igl.extract_non_manifold_edge_curves(self.f1, []) - curves = igl.extract_non_manifold_edge_curves(self.f1, [range(10)]) - self.assertTrue(len(curves) == 1) - self.assertTrue(curves[0][0] == 0) - - def test_intrinsic_delaunay_cotmatrix(self): - l, l_int, f_int = igl.intrinsic_delaunay_cotmatrix(self.v1, self.f1) - # print(l.shape, l_int.shape, f_int.shape) - self.assertTrue(l.shape == (self.v1.shape[0], self.v1.shape[0])) - self.assertTrue(l.dtype == self.v1.dtype) - self.assertTrue(type(l) == csc.csc_matrix) - - def test_cut_to_disk(self): - cuts = igl.cut_to_disk(self.f2) - # This test assumes fertility.off - self.assertTrue(len(cuts) == 9) - - def test_iterative_closest_point(self): - r, t = igl.iterative_closest_point( - self.v1, self.f1, self.v1, self.f1, 3, 20) - self.assertEqual(r.shape, (3, 3)) - self.assertEqual(t.shape, (3,)) - self.assertTrue(r.flags.c_contiguous) - self.assertTrue(t.flags.c_contiguous) - self.assertTrue(r.dtype == t.dtype == self.v.dtype) - - def test_rigid_alignment(self): - n = igl.per_vertex_normals(self.v1, self.f1) - r, t = igl.rigid_alignment(self.v1, self.v1+1, n) - self.assertTrue(np.allclose(r, np.eye(3))) - self.assertTrue(np.allclose(t, np.ones(3))) - self.assertEqual(r.shape, (3, 3)) - self.assertEqual(t.shape, (3,)) - self.assertTrue(r.flags.c_contiguous) - self.assertTrue(t.flags.c_contiguous) - self.assertTrue(r.dtype == t.dtype == self.v1.dtype) - - def test_sharp_edges(self): - se, e, ue, emap, ue2e, sharp = igl.sharp_edges( - self.v1, self.f1, np.pi*0.11) - self.assertTrue(se.shape[1] == 2) - self.assertTrue(ue.shape[1] == 2) - self.assertTrue(emap.shape[0] == self.f1.shape[0]*3) - self.assertTrue(se.shape[0] == len(sharp)) - self.assertTrue(se.flags.c_contiguous) - self.assertTrue(e.flags.c_contiguous) - self.assertTrue(ue.flags.c_contiguous) - self.assertTrue(emap.flags.c_contiguous) - - def test_quad_grid(self): - v, q, e = igl.quad_grid(3, 3) - self.assertTrue(v.shape == (3*3, 2)) - self.assertTrue(q.shape == (2*2, 4)) - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(q.flags.c_contiguous) - self.assertTrue(v.dtype == self.default_float) - self.assertTrue(q.dtype == self.default_int) - self.assertTrue(e.dtype == self.default_int) - - def test_sparse_voxel_grid(self): - def sphere1(point): - return np.sqrt(point[0]**2 + point[1]**2 + point[2]**2) - 1.0 - point = np.array([1.0, 0.0, 0.0]) - cs, cv, ci = igl.sparse_voxel_grid(point, sphere1, 1.0, 100) - self.assertTrue(cv.flags.c_contiguous) - self.assertTrue(cv.dtype == self.default_float) - self.assertTrue(cv.shape == (len(cs), 3)) - self.assertTrue(ci.flags.c_contiguous) - self.assertTrue(ci.dtype == self.default_int) - self.assertTrue(ci.shape[1] == 8) - - def test_topological_hole_fill(self): - f = self.f1 - h = [range(10, 20)] - ff = igl.topological_hole_fill(f, h) - self.assertTrue(ff.flags.c_contiguous) - self.assertTrue(ff.shape[1] == 3) - self.assertTrue(ff.dtype == f.dtype) - self.assertTrue(ff.shape[0] != f.shape[0]) - - def test_triangulated_grid(self): - v, f = igl.triangulated_grid(10, 10) - self.assertTrue(v.shape == (100, 2)) - self.assertTrue(f.shape == (162, 3)) - self.assertTrue(f.flags.c_contiguous) - self.assertTrue(v.flags.c_contiguous) - self.assertTrue(v.dtype == self.default_float) - self.assertTrue(f.dtype == self.default_int) - - def test_unproject_on_line(self): - pos = np.array([10., 10.]) - eye = np.eye(4) - viewport = np.array([0., 0., 100., 100.]) - p = np.array([15.0, 20.0, 13.0]) - d = np.array([0.1, 0.2, 1.0]) - t, z = igl.unproject_on_line(pos, eye, viewport, p, d) - - self.assertTrue(z.flags.c_contiguous) - self.assertTrue(z.shape == (3, )) - self.assertTrue(z.dtype == pos.dtype) - - def test_unproject_on_plane(self): - pos = np.array([10., 10.]) - eye = np.eye(4) - viewport = np.array([0., 0., 100., 100.]) - p = np.array([1.0, 2.0, 3.0, 2.0]) - z = igl.unproject_on_plane(pos, eye, viewport, p) - - self.assertTrue(z.flags.c_contiguous) - self.assertTrue(z.shape == (3, )) - self.assertTrue(z.dtype == pos.dtype) - - def test_fast_winding_number_for_points(self): - xs = np.linspace(-5.0, 5.0, 10) - grid = np.meshgrid(xs, xs, xs, indexing='ij') - grid = np.stack(grid).reshape(3, -1, order='F').T - n = igl.per_vertex_normals(self.v1, self.f1) - a = np.ones((n.shape[0], )) / n.shape[0] - - wn = igl.fast_winding_number_for_points(self.v1, n, a, grid) - self.assertTrue(wn.flags.c_contiguous) - self.assertTrue(wn.shape == (grid.shape[0], )) - self.assertTrue(wn.dtype == self.v1.dtype) - - def test_fast_winding_number_for_meshes(self): - xs = np.linspace(-5.0, 5.0, 10) - grid = np.meshgrid(xs, xs, xs, indexing='ij') - grid = np.stack(grid).reshape(3, -1, order='F').T - - wn = igl.fast_winding_number_for_meshes(self.v1, self.f1, grid) - self.assertTrue(wn.flags.c_contiguous) - self.assertTrue(wn.shape == (grid.shape[0], )) - self.assertTrue(wn.dtype == self.v1.dtype) - - def test_flip_avoiding_line_search(self): - def fun(v): - return np.random.rand(1)[0] - - energy, vr = igl.flip_avoiding_line_search( - self.f1, self.v1[:, :2], self.v1[:, :2], fun, 10.0) - self.assertTrue(vr.shape == (self.v1.shape[0], 2)) - self.assertTrue(vr.flags.c_contiguous) - - def test_edge_flaps(self): - e, emap, ef, ei = igl.edge_flaps(self.f2) - self.assertTrue(e.shape[1] == ef.shape[1] == ei.shape[1] == 2) - self.assertTrue(e.shape[0] == ef.shape[0] == ei.shape[0]) - self.assertTrue(emap.shape[0] == self.f2.shape[0] * 3) - self.assertTrue(np.min(e) >= 0 and np.max(e) < self.v2.shape[0]) - self.assertTrue(e.flags.c_contiguous) - self.assertTrue(emap.flags.c_contiguous) - self.assertTrue(ef.flags.c_contiguous) - self.assertTrue(ei.flags.c_contiguous) - self.assertTrue(e.dtype == emap.dtype == - ef.dtype == ei.dtype == self.f2.dtype) - - def test_circulation(self): - pass - #e, emap, ef, ei = igl.edge_flaps(self.f2) - #fac = igl.circulation(667, True, emap, ef, ei) - # print(fac) - - def test_edge_collapse_is_valid(self): - pass - #e, emap, ef, ei = igl.edge_flaps(self.f2) - #emap = emap.reshape(-1, 3) - #val = igl.edge_collapse_is_valid(0, self.f2, e, emap, ef, ei) - # print(val) - - def test_flip_edge(self): - e, ue, emap, ue2e = igl.unique_edge_map(self.f1) - f, e, ue, emap, ue2e = igl.flip_edge(self.f1, e, ue, emap, ue2e, 1) - - self.assertTrue(f.shape == self.f1.shape) - self.assertTrue(e.shape[1] == ue.shape[1] == - np.array(ue2e).shape[1] == 2) - self.assertTrue(emap.shape[0] == self.f1.shape[0] * 3) - - self.assertTrue(np.min(e) >= 0 and np.max(e) < self.v2.shape[0]) - - self.assertTrue(e.flags.c_contiguous) - self.assertTrue(emap.flags.c_contiguous) - self.assertTrue(ue.flags.c_contiguous) - - self.assertTrue(f.dtype == e.dtype == ue.dtype == - emap.dtype == self.f1.dtype) - self.assertTrue(np.array(ue2e).dtype == self.f1.dtype) - - def test_AABB(self): - tree = igl.AABB_f64_3() - v1_f = np.asarray(self.v1, order='F') - f1_f = np.asarray(self.f1, order='F') - tree.init(v1_f,f1_f) - bc = igl.barycenter(v1_f,f1_f) - sqrD = tree.squared_distance(v1_f,f1_f,bc) - self.assertTrue(sqrD.shape[0] == bc.shape[0]) - self.assertTrue(np.max(sqrD) <= 1e-16) - sqrD,I,C = tree.squared_distance(v1_f,f1_f,bc,return_index=True,return_closest_point=True) - self.assertTrue(sqrD.shape[0] == bc.shape[0]) - self.assertTrue(I.shape[0] == bc.shape[0]) - self.assertTrue(C.shape == bc.shape) - - def test_in_element_3(self): - V = np.array([ [0.,0,0], [1,0,0], [0,1,0], [0,0,1], [1,1,1]],dtype='float64',order='f') - T = np.array([[0,1,2,3],[4,3,2,1]],dtype='int32',order='f') - Q = np.array([[0.1,0.1,0.1],[0.9,0.9,0.9]],dtype='float64',order='f') - tree = igl.AABB_f64_3() - tree.init(V,T) - I = igl.in_element_3(V,T,Q,tree) - self.assertTrue(I.shape[0] == Q.shape[0]) - self.assertTrue(I[0] == 0) - self.assertTrue(I[1] == 1) - - def test_in_element_2(self): - V = np.array([ [0.,0], [1,0], [0,1], [1,1]],dtype='float64',order='f') - F = np.array([[0,1,2],[2,1,3]],'int32',order='f') - Q = np.array([[0.1,0.1],[0.9,0.9]],dtype='float64',order='f') - tree = igl.AABB_f64_2() - tree.init(V,F) - I = igl.in_element_2(V,F,Q,tree) - self.assertTrue(I.shape[0] == Q.shape[0]) - self.assertTrue(I[0] == 0) - self.assertTrue(I[1] == 1) - - def test_triangulate(self): - V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype='float64') - E = np.array([[0,1],[1,2],[2,3],[3,0]]) - V2,F2 = igl.triangle.triangulate(V,E,flags='Q') - self.assertTrue(V2.shape == V.shape) - self.assertTrue(F2.shape == (2,3)) - V = np.array([[0,0],[4,0],[0,4],[1,1],[1,2],[2,1]],dtype='float64') - E = np.array([[0,1],[1,2],[2,0],[3,4],[4,5],[5,3]]) - H = np.array([[1.1,1.1]]) - # Markers can't be 0 - VM = 1+np.array(range(V.shape[0])) - EM = 1+np.array(range(E.shape[0])) - V2,F2,VM2,E2,EM2 = igl.triangle.triangulate(V,E,H,flags='Q',VM=VM,EM=EM) - self.assertTrue(V2.shape == V.shape) - self.assertTrue(F2.shape == (3*2,3)) - self.assertTrue(VM2.shape == VM.shape) - self.assertTrue(E2.shape == E.shape) - self.assertTrue(EM2.shape == EM.shape) - - def test_tetrahedralize(self): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype='float64') - F = np.array([[0,1,2],[0,1,3],[0,2,3],[1,2,3]]) - TV,TT,TF = igl.copyleft.tetgen.tetrahedralize(V,F); - self.assertTrue(TV.shape == V.shape) - self.assertTrue(TT.shape == (4,)) - self.assertTrue(TF.shape == F.shape) - V = np.array([[1,1,0],[1,-1,0],[-1,-1,0],[-1,1,0],[0,0,1]],dtype='float64') - TV,TT,TF = igl.copyleft.tetgen.tetrahedralize(V,switches="cQ"); - self.assertTrue(TV.shape == V.shape) - self.assertTrue(TT.shape == (2,4)) - self.assertTrue(TF.shape == (6,3)) - TV,TT,TF = igl.copyleft.tetgen.tetrahedralize(self.v1,self.f1,switches="pQq1.34"); - igl.writeMESH("test.mesh",TV,TT,TF) - self.assertTrue(TV.shape[0] > self.v1.shape[0]) - self.assertTrue(TT.shape[0] > self.f1.shape[0]) - - - def test_writeMESH(self): - igl.writeMESH("test.mesh",self.v4,self.t4,self.f4) - - def test_writeMSH(self): - igl.writeMSH("test.msh",self.v4,self.f4,self.t4) - - # copyleft.cgal - def test_convex_hull(self): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype="float64") - F = igl.copyleft.cgal.convex_hull(V) - F = np.sort(F) - F = F[np.lexsort(F.T[::-1],axis=0)] - F_gt = np.array([[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]],dtype="int64") - self.assertTrue((F == F_gt).all()) - - def test__version(self): - self.assertTrue(isinstance(igl.__version__, str)) - - def test_blue_noise(self): - r = igl.avg_edge_length(self.v, self.f) - b,fi,p = igl.blue_noise(self.v, self.f, r) - self.assertTrue(b.shape[0] == fi.shape[0]) - self.assertTrue(b.shape[0] == p.shape[0]) - self.assertTrue(self.v.shape[1] == p.shape[1]) - self.assertTrue(self.f.shape[1] == b.shape[1]) - - def test_fit_cubic_bezier(self): - r = igl.avg_edge_length(self.v, self.f) - cubics = igl.fit_cubic_bezier(self.v, r) - [self.assertTrue(cubic.shape[1] == self.v.shape[1]) for cubic in cubics] - - def test_is_delaunay(self): - D = igl.is_delaunay(self.v, self.f) - self.assertTrue(D.shape[0] == self.f.shape[0]) - self.assertTrue(D.shape[1] == self.f.shape[1]) - - def test_random_points_on_mesh(self): - b,fi,x = igl.random_points_on_mesh(100, self.v, self.f) - self.assertTrue(b.shape[0] == 100) - self.assertTrue(b.shape[0] == fi.shape[0]) - self.assertTrue(b.shape[0] == x.shape[0]) - self.assertTrue(self.v.shape[1] == x.shape[1]) - self.assertTrue(self.f.shape[1] == b.shape[1]) - - def test_random_points_on_mesh_intrinsic(self): - dblA = igl.doublearea(self.v, self.f) - b,fi = igl.random_points_on_mesh_intrinsic(100, dblA) - self.assertTrue(b.shape[0] == 100) - self.assertTrue(b.shape[0] == fi.shape[0]) - self.assertTrue(self.f.shape[1] == b.shape[1]) - - def test_moments(self): - m0,m1,m2 = igl.moments(self.v1, self.f1) - - def test_path_to_edges(self): - I = self.f1[:,0] - e = igl.path_to_edges(I) - self.assertTrue(e.shape[1] == 2) - self.assertTrue(e.shape[0] == I.shape[0]-1) - - - def test_copyleft(self): - # check that type is - self.assertTrue(type(igl.copyleft) == type(igl)) - - def test_triangle(self): - # check that type is - self.assertTrue(type(igl.copyleft) == type(igl)) - - def test_copyleft_tetgen(self): - # check that type is - self.assertTrue(type(igl.copyleft.tetgen) == type(igl)) - - - -if __name__ == '__main__': - unittest.main() From 80371b5e57fb452c145f325c27679da937019ab3 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Thu, 7 Nov 2024 20:08:54 -0500 Subject: [PATCH 002/102] more bindings; move --- missing.sh | 15 +++++ skip.txt | 1 - src/AABB.cpp | 13 ++-- src/average_from_edges_onto_vertices.cpp | 2 +- src/barycenter.cpp | 2 +- src/barycentric_coordinates.cpp | 2 +- src/bbw.cpp | 2 +- src/boundary_facets.cpp | 2 +- src/connected_components.cpp | 60 +++++++++++++++++ src/cotmatrix.cpp | 2 +- src/doublearea.cpp | 2 +- src/edge_lengths.cpp | 2 +- src/massmatrix.cpp | 2 +- src/min_quad_with_fixed.cpp | 64 ++++++++++++++++++ src/per_face_normals.cpp | 2 +- src/point_mesh_squared_distance.cpp | 2 +- src/triangle_triangle_adjacency.cpp | 70 +++++++++++++++++++ src/unique_edge_map.cpp | 65 ++++++++++++++++++ src/unique_simplices.cpp | 51 ++++++++++++++ src/vertex_triangle_adjacency.cpp | 85 ++++++++++++++++++++++++ tests/test.py | 46 +++++++++++++ 21 files changed, 474 insertions(+), 18 deletions(-) delete mode 100644 skip.txt create mode 100644 src/connected_components.cpp create mode 100644 src/min_quad_with_fixed.cpp create mode 100644 src/triangle_triangle_adjacency.cpp create mode 100644 src/unique_edge_map.cpp create mode 100644 src/unique_simplices.cpp create mode 100644 src/vertex_triangle_adjacency.cpp diff --git a/missing.sh b/missing.sh index b9a52326..aac5326c 100644 --- a/missing.sh +++ b/missing.sh @@ -5,6 +5,21 @@ skip="igl_inline EPS verbose colon +min_quad_with_fixed.impl +find +unique +IGL_ASSERT +cat +Hit +LinSpaced +repmat +repdiag +sum +cumsum +slice +get_seconds +matrix_to_list +decimate_callback_types sort placeholders list_to_matrix diff --git a/skip.txt b/skip.txt deleted file mode 100644 index 21ee549f..00000000 --- a/skip.txt +++ /dev/null @@ -1 +0,0 @@ -igl_inline diff --git a/src/AABB.cpp b/src/AABB.cpp index 606806e4..3862a426 100644 --- a/src/AABB.cpp +++ b/src/AABB.cpp @@ -31,13 +31,14 @@ namespace pyigl { throw std::runtime_error("find: Ele must have V.cols()+1 columns"); } - std::vector vec = tree.find(V,Ele,q,first); + std::vector vec_int = tree.find(V,Ele,q,first); + std::vector vec(vec_int.begin(),vec_int.end()); if(first) { return vec.size() ? nb::cast(vec[0]) : nb::none(); }else { - return nb::cast(vec); + return nb::cast(std::move(vec)); } } @@ -64,7 +65,7 @@ namespace pyigl { return nb::make_tuple(sqrD,C); } - return nb::cast(sqrD); + return nb::cast(std::move(sqrD)); } template @@ -93,10 +94,10 @@ namespace pyigl } std::vector>> hits; tree.intersect_ray(V,Ele,orig,dir,hits); - std::vector>> out; + std::vector>> out; for(const auto &hit : hits) { - std::vector> hit_out; + std::vector> hit_out; for(const auto &h : hit) { hit_out.push_back(std::make_tuple(h.id,h.t,h.u,h.v)); @@ -104,7 +105,7 @@ namespace pyigl out.push_back(hit_out); } - return nb::cast(out); + return nb::cast(std::move(out)); } } } diff --git a/src/average_from_edges_onto_vertices.cpp b/src/average_from_edges_onto_vertices.cpp index 2fd714e6..f0e137b3 100644 --- a/src/average_from_edges_onto_vertices.cpp +++ b/src/average_from_edges_onto_vertices.cpp @@ -17,7 +17,7 @@ namespace pyigl { Eigen::VectorXN uV; igl::average_from_edges_onto_vertices(F, E, oE, uE, uV); - return nb::cast(uV); + return nb::cast(std::move(uV)); } } diff --git a/src/barycenter.cpp b/src/barycenter.cpp index 7266f0d4..261efe0d 100644 --- a/src/barycenter.cpp +++ b/src/barycenter.cpp @@ -16,7 +16,7 @@ namespace pyigl { Eigen::MatrixXN BC; igl::barycenter(V, F, BC); - return nb::cast(BC); + return nb::cast(std::move(BC)); } } diff --git a/src/barycentric_coordinates.cpp b/src/barycentric_coordinates.cpp index e81feb80..abad198b 100644 --- a/src/barycentric_coordinates.cpp +++ b/src/barycentric_coordinates.cpp @@ -26,7 +26,7 @@ namespace pyigl { igl::barycentric_coordinates(P, A, B, C, L); } - return nb::cast(L); + return nb::cast(std::move(L)); } } diff --git a/src/bbw.cpp b/src/bbw.cpp index e8c908f3..61405a35 100644 --- a/src/bbw.cpp +++ b/src/bbw.cpp @@ -38,7 +38,7 @@ namespace pyigl { throw std::runtime_error("bbw: failed to compute weights"); } - return nb::cast(W); + return nb::cast(std::move(W)); } } diff --git a/src/boundary_facets.cpp b/src/boundary_facets.cpp index 6110eb8c..3de7767a 100644 --- a/src/boundary_facets.cpp +++ b/src/boundary_facets.cpp @@ -29,7 +29,7 @@ namespace pyigl { return nb::make_tuple(F,K); } - return nb::cast(F); + return nb::cast(std::move(F)); } } diff --git a/src/connected_components.cpp b/src/connected_components.cpp new file mode 100644 index 00000000..b91268fb --- /dev/null +++ b/src/connected_components.cpp @@ -0,0 +1,60 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for connected_components + nb::object connected_components( + const Eigen::SparseMatrix &A, + const bool return_C = false, + const bool return_K = false) + { + int num_components; + Eigen::VectorXI C; + Eigen::VectorXI K; + + if (return_C && return_K) + { + num_components = igl::connected_components(A, C, K); + return nb::make_tuple(num_components, C, K); + } + else if (return_C) + { + num_components = igl::connected_components(A, C, K); + return nb::make_tuple(num_components, C); + } + else if (return_K) + { + num_components = igl::connected_components(A, C, K); + return nb::make_tuple(num_components, K); + } + else + { + num_components = igl::connected_components(A, C, K); + return nb::cast(num_components); + } + } +} + +// Bind the wrapper to the Python module +void bind_connected_components(nb::module_ &m) +{ + m.def( + "connected_components", + &pyigl::connected_components, + "A"_a, + "return_C"_a = false, + "return_K"_a = false, +R"(Determine the connected components of a graph described by the input adjacency matrix. + +@param[in] A #A by #A adjacency matrix (treated as describing a directed graph) +@param[out] C (if return_C=True) #A list of component indices in [0,#K-1] +@param[out] K (if return_K=True) #K list of sizes of each component +@return number of connected components)"); +} diff --git a/src/cotmatrix.cpp b/src/cotmatrix.cpp index 35d4f63b..b4ff8983 100644 --- a/src/cotmatrix.cpp +++ b/src/cotmatrix.cpp @@ -46,7 +46,7 @@ namespace pyigl throw std::runtime_error("cotmatrix: M and P only available for polygonal meshes"); } } - return nb::cast(L); + return nb::cast(std::move(L)); } } diff --git a/src/doublearea.cpp b/src/doublearea.cpp index 3f15c15f..e3317e0f 100644 --- a/src/doublearea.cpp +++ b/src/doublearea.cpp @@ -40,7 +40,7 @@ namespace pyigl igl::doublearea(V, F, dblA); } } - return nb::cast(dblA); + return nb::cast(std::move(dblA)); } } diff --git a/src/edge_lengths.cpp b/src/edge_lengths.cpp index 2c8b3c05..100a39d5 100644 --- a/src/edge_lengths.cpp +++ b/src/edge_lengths.cpp @@ -16,7 +16,7 @@ namespace pyigl { Eigen::MatrixXN L; igl::edge_lengths(V, F, L); - return nb::cast(L); + return nb::cast(std::move(L)); } } diff --git a/src/massmatrix.cpp b/src/massmatrix.cpp index 908fec43..c3c3d8d5 100644 --- a/src/massmatrix.cpp +++ b/src/massmatrix.cpp @@ -36,7 +36,7 @@ namespace pyigl } igl::massmatrix(V,F,t,M); - return nb::cast(M); + return nb::cast(std::move(M)); } } diff --git a/src/min_quad_with_fixed.cpp b/src/min_quad_with_fixed.cpp new file mode 100644 index 00000000..d8cd13f5 --- /dev/null +++ b/src/min_quad_with_fixed.cpp @@ -0,0 +1,64 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for min_quad_with_fixed + nb::object min_quad_with_fixed( + const Eigen::SparseMatrixN &A, + const nb::DRef &B, + const nb::DRef &known, + const nb::DRef &Y, + const Eigen::SparseMatrixN &Aeq, + const nb::DRef &Beq, + const bool pd) + { + Eigen::MatrixXN Z; + bool success = igl::min_quad_with_fixed(A, B, known, Y, Aeq, Beq, pd, Z); + + if (!success) + { + throw std::runtime_error("min_quad_with_fixed: Optimization failed."); + } + + return nb::cast(std::move(Z)); + } +} + +// Bind the wrapper to the Python module +void bind_min_quad_with_fixed(nb::module_ &m) +{ + m.def( + "min_quad_with_fixed", + &pyigl::min_quad_with_fixed, + "A"_a = Eigen::SparseMatrixN(), + "B"_a = Eigen::MatrixXN() , + "known"_a = Eigen::VectorXI() , + "Y"_a = Eigen::MatrixXN() , + "Aeq"_a = Eigen::SparseMatrixN(), + "Beq"_a = Eigen::MatrixXN() , + "pd"_a = true , +R"(Minimize a convex quadratic energy subject to fixed values and linear equality constraints. + +The function minimizes trace(0.5 * Z' * A * Z + Z' * B) subject to: + Z(known,:) = Y, and + Aeq * Z = Beq + +@param[in] A n by n matrix of quadratic coefficients +@param[in] B n by k matrix of linear coefficients +@param[in] known list of indices to known rows in Z +@param[in] Y n by k matrix of fixed values corresponding to known rows in Z +@param[in] Aeq m by n matrix of linear equality constraint coefficients +@param[in] Beq m by k matrix of linear equality constraint target values +@param[in] pd flag specifying whether A(unknown,unknown) is positive definite +@param[out] Z solution matrix that minimizes the objective under constraints +@return Z solution matrix +)"); +} diff --git a/src/per_face_normals.cpp b/src/per_face_normals.cpp index 9835b238..1cc8c2e3 100644 --- a/src/per_face_normals.cpp +++ b/src/per_face_normals.cpp @@ -61,7 +61,7 @@ namespace pyigl } igl::per_face_normals(V,F,Z,N); } - return nb::cast(N); + return nb::cast(std::move(N)); } } diff --git a/src/point_mesh_squared_distance.cpp b/src/point_mesh_squared_distance.cpp index 208bfa9e..06d7a7fc 100644 --- a/src/point_mesh_squared_distance.cpp +++ b/src/point_mesh_squared_distance.cpp @@ -31,7 +31,7 @@ namespace pyigl { return nb::make_tuple(sqrD, C); } - return nb::cast(sqrD); + return nb::cast(std::move(sqrD)); } } diff --git a/src/triangle_triangle_adjacency.cpp b/src/triangle_triangle_adjacency.cpp new file mode 100644 index 00000000..941e6309 --- /dev/null +++ b/src/triangle_triangle_adjacency.cpp @@ -0,0 +1,70 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + nb::object triangle_triangle_adjacency( + const nb::DRef &F, + bool return_TTi = false, + bool use_lists = false) + { + if (use_lists) + { + std::vector>> TT; + + if (return_TTi) + { + std::vector>> TTi; + igl::triangle_triangle_adjacency(F, true, TT, TTi); + return nb::make_tuple(TT,TTi); + } + else + { + igl::triangle_triangle_adjacency(F, TT); + return nb::cast(std::move(TT)); + } + } + else + { + Eigen::MatrixXI TT; + + if (return_TTi) + { + Eigen::MatrixXI TTi; + igl::triangle_triangle_adjacency(F, TT, TTi); + return nb::make_tuple(TT, TTi); + } + else + { + igl::triangle_triangle_adjacency(F, TT); + return nb::cast(std::move(TT)); + } + } + } +} + +// Bind the wrapper to the Python module +void bind_triangle_triangle_adjacency(nb::module_ &m) +{ + m.def( + "triangle_triangle_adjacency", + &pyigl::triangle_triangle_adjacency, + "F"_a, + "return_TTi"_a = false, + "use_lists"_a = false, +R"(Constructs the triangle-triangle adjacency matrix for a given mesh (V,F). + +@param[in] F #F by 3 list of mesh faces (must be triangles) +@param[out] TT #F by 3 adjacent matrix, where each element represents the id of the triangle adjacent to the corresponding edge +@param[out] TTi (if return_TTi=True) #F by 3 adjacent matrix, where each element represents the id of the edge of the adjacent triangle that shares an edge with the current triangle + +- If `use_lists=True`, returns adjacency data as lists of lists for compatibility with non-manifold meshes. +)"); +} diff --git a/src/unique_edge_map.cpp b/src/unique_edge_map.cpp new file mode 100644 index 00000000..80ce32f4 --- /dev/null +++ b/src/unique_edge_map.cpp @@ -0,0 +1,65 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + nb::object unique_edge_map( + const nb::DRef &F, + bool return_uE2E = false, + bool return_uEC_uEE = false) + { + Eigen::MatrixXI E; + Eigen::MatrixXI uE; + Eigen::VectorXI EMAP; + + if(return_uE2E) + { + std::vector> uE2E; + igl::unique_edge_map(F, E, uE, EMAP, uE2E); + return nb::make_tuple(E, uE, EMAP, uE2E); + } + else if(return_uEC_uEE) + { + Eigen::VectorXI uEC; + Eigen::VectorXI uEE; + igl::unique_edge_map(F, E, uE, EMAP, uEC, uEE); + return nb::make_tuple(E, uE, EMAP, uEC, uEE); + } + else + { + igl::unique_edge_map(F, E, uE, EMAP); + return nb::make_tuple(E, uE, EMAP); + } + } +} + +// Bind the wrapper to the Python module +void bind_unique_edge_map(nb::module_ &m) +{ + m.def( + "unique_edge_map", + &pyigl::unique_edge_map, + "F"_a, + "return_uE2E"_a = false, + "return_uEC_uEE"_a = false, +R"(Construct relationships between facet "half"-(or rather "viewed")-edges E +to unique edges of the mesh seen as a graph. + +@param[in] F #F by 3 list of simplices +@param[out] E #F*3 by 2 list of all directed edges +@param[out] uE #uE by 2 list of unique undirected edges +@param[out] EMAP #F*3 list of indices into uE, mapping each directed edge to a unique + undirected edge so that uE(EMAP(f+#F*c)) is the unique edge + corresponding to E.row(f+#F*c) +@param[out] uE2E (if return_uE2E=True) #uE list of lists of indices into E of coexisting edges +@param[out] uEC, uEE (if return_uEC_uEE=True) cumulative counts of directed edges sharing each + unique edge and list of indices into E for directed edges sharing each unique edge +)"); +} diff --git a/src/unique_simplices.cpp b/src/unique_simplices.cpp new file mode 100644 index 00000000..81451534 --- /dev/null +++ b/src/unique_simplices.cpp @@ -0,0 +1,51 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for unique_simplices with overload handling + nb::object unique_simplices( + const nb::DRef &F, + const bool return_IA, + const bool return_IC) + { + Eigen::MatrixXI FF; + Eigen::VectorXI IA,IC; + igl::unique_simplices(F,FF,IA,IC); + if(return_IA && return_IC) + { + return nb::make_tuple(FF,IA,IC); + }else if(return_IA) + { + return nb::make_tuple(FF,IA); + }else if(return_IC) + { + return nb::make_tuple(FF,IC); + } + return nb::cast(std::move(FF)); + } +} + +// Bind the wrapper to the Python module +void bind_unique_simplices(nb::module_ &m) +{ + m.def( + "unique_simplices", + &pyigl::unique_simplices, + "F"_a, + "return_IA"_a = false, + "return_IC"_a = false, +R"(Find combinatorially unique simplices in F. Order independent. + +@param[in] F #F by simplex-size list of simplices +@param[out] FF #FF by simplex-size list of unique simplices in F +@param[out] IA (if return_IA=True) #FF index vector so that FF == sort(F(IA,:),2) +@param[out] IC (if return_IC=True) #F index vector so that sort(F,2) == FF(IC,:) +)"); +} diff --git a/src/vertex_triangle_adjacency.cpp b/src/vertex_triangle_adjacency.cpp new file mode 100644 index 00000000..f262f989 --- /dev/null +++ b/src/vertex_triangle_adjacency.cpp @@ -0,0 +1,85 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + nb::object vertex_triangle_adjacency( + const nb::DRef &F, + Integer n, + const nb::DRef &V, + bool return_VFi, + bool use_lists) + { + n = n ? n : (V.rows() ? V.rows() : F.maxCoeff()+1); + if(use_lists) + { + std::vector> VF,VFi; + igl::vertex_triangle_adjacency(n,F,VF,VFi); + if(return_VFi) + { + return nb::make_tuple(VF,VFi); + }else + { + return nb::cast(std::move(VF)); + } + }else + { + if(return_VFi) + { + throw std::runtime_error("vertex_triangle_adjacency: NI and VFI are mutually exclusive"); + } + Eigen::VectorXI VF,NI; + igl::vertex_triangle_adjacency(F,n,VF,NI); + return nb::make_tuple(VF,NI); + } + } +} + +// Bind the wrapper to the Python module +void bind_vertex_triangle_adjacency(nb::module_ &m) +{ + m.def( + "vertex_triangle_adjacency", + &pyigl::vertex_triangle_adjacency, + "F"_a=Eigen::MatrixXI(), + "n"_a=Integer(0), + "V"_a=Eigen::MatrixXN(), + "return_VFi"_a=false, + "use_lists"_a=false, +R"(vertex_face_adjacency constructs the vertex-face topology of a given mesh (V,F) + + @param[in] n number of vertices #V (e.g. `F.maxCoeff()+1` or `V.rows()`) + @param[in] F #F by dim list of mesh faces (must be triangles) + @param[out] VF #V list of lists of incident faces (adjacency list) + @param[out] VI #V list of lists of index of incidence within incident faces listed + in VF + + \see edges, cotmatrix, diag, vv + + \bug this should not take V as an input parameter. + \bug if a facet is combinatorially degenerate then faces will appear + multiple times in VF and correspondingly in VFI (j appears twice in + F.row(i) then i will appear twice in VF[j]) + + @param[in] F #F by 3 list of triangle indices into some vertex list V + @param[in] n number of vertices, #V (e.g., F.maxCoeff()+1) + @param[out] VF 3*#F list List of faces indice on each vertex, so that VF(NI(i)+j) = + f, means that face f is the jth face (in no particular order) incident + on vertex i. + @param[out] NI #V+1 list cumulative sum of vertex-triangle degrees with a + preceeding zero. "How many faces" have been seen before visiting this + vertex and its incident faces.)" + ); +} + + + + diff --git a/tests/test.py b/tests/test.py index 0bee2e19..c3e0825d 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,10 +1,13 @@ import igl import numpy as np +# scipy sparse matrices +import scipy.sparse F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) E,oE = igl.orient_halfedges(F) ne = E.max()+1 uE = np.random.rand(ne).astype(np.float64) + uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) l = igl.edge_lengths(V,F) @@ -66,3 +69,46 @@ I,T,UV = tree.intersect_ray(V,F,O,D,first=True) hits = tree.intersect_ray(V,F,O,D,first=False) + +E,uE,EMAP = igl.unique_edge_map(F) +E,uE,EMAP,uE2E = igl.unique_edge_map(F,return_uE2E = True) +E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F,return_uEC_uEE = True) + +TT = igl.triangle_triangle_adjacency(F) +TT,TTi = igl.triangle_triangle_adjacency(F,return_TTi=True) +TT = igl.triangle_triangle_adjacency(F,use_lists=True) +TT,TTi = igl.triangle_triangle_adjacency(F,use_lists=True,return_TTi=True) + +VF,NI = igl.vertex_triangle_adjacency(F) +VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) +VF,NI = igl.vertex_triangle_adjacency(F,V=V) +VF = igl.vertex_triangle_adjacency(F,use_lists=True) +VF,VFi = igl.vertex_triangle_adjacency(F,use_lists=True,return_VFi=True) + +F012 = F; +F120 = np.roll(F012,1,axis=1) +FF = np.vstack((F012,F120)) +F = igl.unique_simplices(FF) +F,IA = igl.unique_simplices(FF,return_IA=True) +F,IC = igl.unique_simplices(FF,return_IC=True) +F,IA,IC = igl.unique_simplices(FF,return_IA=True,return_IC=True) + + +L = igl.cotmatrix(V,F) +# convert nonzeros to 1s to int64 +A = (L != 0).astype(np.int64) +# subtract diagonal +A = A - scipy.sparse.diags(A.diagonal()) +n = igl.connected_components(A) +n,C = igl.connected_components(A,return_C=True) +n,K = igl.connected_components(A,return_K=True) +n,C,K = igl.connected_components(A,return_C=True,return_K=True) + + +A = -igl.cotmatrix(V,F) +B = np.zeros((V.shape[0],1),dtype=np.float64) +known = np.array([1,2],dtype=np.int64) +Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) +Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) +Beq = np.zeros((1,1),dtype=np.float64) +Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) From b5d70317aeb00cc6330a46be13387bf6aa759427 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 8 Nov 2024 12:10:29 -0500 Subject: [PATCH 003/102] noop tests; min_quad_with_fixed --- include/default_types.h | 7 +++--- src/min_quad_with_fixed.cpp | 44 +++++++++++++++++++++++++++++++++++++ src/noop.cpp | 37 +++++++++++++++++++++++++++++++ tests/test.py | 42 +++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 src/noop.cpp diff --git a/include/default_types.h b/include/default_types.h index dd7b25f3..9c84a678 100644 --- a/include/default_types.h +++ b/include/default_types.h @@ -10,16 +10,15 @@ /////////////////////////////////////////////////////////////////////////////////// using Numeric = double; using Integer = int64_t; +constexpr auto Options = Eigen::RowMajor; namespace Eigen { typedef Matrix MatrixXN; typedef Matrix MatrixXI; typedef Matrix VectorXN; typedef Matrix VectorXI; - typedef Matrix RowVectorXN; - typedef Matrix RowVectorXI; + typedef Matrix RowVectorXN; + typedef Matrix RowVectorXI; typedef SparseMatrix SparseMatrixN; typedef SparseMatrix SparseMatrixI; } - - diff --git a/src/min_quad_with_fixed.cpp b/src/min_quad_with_fixed.cpp index d8cd13f5..c0164da8 100644 --- a/src/min_quad_with_fixed.cpp +++ b/src/min_quad_with_fixed.cpp @@ -30,11 +30,55 @@ namespace pyigl return nb::cast(std::move(Z)); } + + struct MinQuadWithFixed + { + igl::min_quad_with_fixed_data data; + MinQuadWithFixed( + const Eigen::SparseMatrixN &A = Eigen::SparseMatrixN(), + const nb::DRef &known = Eigen::VectorXI(), + const Eigen::SparseMatrixN &Aeq = Eigen::SparseMatrixN(), + const bool pd = true) + { + if(!igl::min_quad_with_fixed_precompute(A, known, Aeq, pd, data)) + { + throw std::runtime_error("min_quad_with_fixed: Precomputation failed."); + } + } + + Eigen::MatrixXN solve( + const nb::DRef &B, + const nb::DRef &Y, + const nb::DRef &Beq) + { + Eigen::MatrixXN Z; + if(!igl::min_quad_with_fixed_solve(data, B, Y, Beq, Z)) + { + throw std::runtime_error("min_quad_with_fixed: Optimization failed."); + } + return std::move(Z); + } + }; + } // Bind the wrapper to the Python module void bind_min_quad_with_fixed(nb::module_ &m) { + nb::class_(m, "MinQuadWithFixed") + .def(nb::init< + const Eigen::SparseMatrixN &, + const nb::DRef &, + const Eigen::SparseMatrixN &, + const bool>()) + .def("solve", &pyigl::MinQuadWithFixed::solve, + "B"_a=Eigen::MatrixXN(), + "Y"_a=Eigen::MatrixXN(), + "Beq"_a=Eigen::MatrixXN(), + R"(Solve the precomputed quadratic optimization problem.)") + ; + + m.def( "min_quad_with_fixed", &pyigl::min_quad_with_fixed, diff --git a/src/noop.cpp b/src/noop.cpp new file mode 100644 index 00000000..8dbf270e --- /dev/null +++ b/src/noop.cpp @@ -0,0 +1,37 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + + +namespace pyigl +{ + nb::object noop( + const nb::DRef &N, + const nb::DRef &I, + const Eigen::SparseMatrixN &SN, + const Eigen::SparseMatrixI &SI) + { + return nb::none(); + } +} + +// Bind the wrapper to the Python module +void bind_noop(nb::module_ &m) +{ + m.def( + "noop", + &pyigl::noop, + "N"_a=Eigen::MatrixXN(), + "I"_a=Eigen::MatrixXI(), + "SN"_a=Eigen::SparseMatrixN(), + "SI"_a=Eigen::SparseMatrixI(), +R"(Dummy function that does nothing. Useful for timing bindings overhead.)" + ); +} + + diff --git a/tests/test.py b/tests/test.py index c3e0825d..9300982e 100644 --- a/tests/test.py +++ b/tests/test.py @@ -2,6 +2,47 @@ import numpy as np # scipy sparse matrices import scipy.sparse +# timing +import time + +#def rand_sparse(n,density): +# n_features = n +# n_samples = n +# rng1 = np.random.RandomState(42) +# rng2 = np.random.RandomState(43) +# +# nnz = int(n_samples*n_features*density) +# +# row = rng1.randint(n_samples, size=nnz) +# cols = rng2.randint(n_features, size=nnz) +# data = rng1.rand(nnz) +# +# S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) +# return S.tocsc() +# +#def time_noop(): +# def helper(N,I,SN,SI): +# igl.noop(SN=SN) +# # start timer +# runs = 100 +# start = time.time() +# for i in range(runs): +# igl.noop(SN=SN) +# # end timer +# end = time.time() +# return (end - start)/runs +# n = 10000 +# m = 10 +# N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) +# I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) +# # random sparse matrix +# SN64 = rand_sparse(n,1.0/(n)) +# # print number of nonzeros +# SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) +# print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +# +#time_noop() + F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) E,oE = igl.orient_halfedges(F) @@ -112,3 +153,4 @@ Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) Beq = np.zeros((1,1),dtype=np.float64) Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) +Z = igl.MinQuadWithFixed(A,known,Aeq,True).solve(B,Y,Beq) From 3412fbc2ef67d2b2d70835638a23d036f819dab2 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 8 Nov 2024 13:28:01 -0500 Subject: [PATCH 004/102] handle overloads more elegantly --- missing.sh | 2 + src/barycentric_coordinates.cpp | 50 ++++++++++---- src/cotmatrix.cpp | 69 ++++++++----------- src/doublearea.cpp | 69 ++++++++++--------- src/per_face_normals.cpp | 111 ++++++++++++++++++------------ src/vertex_triangle_adjacency.cpp | 42 ++++------- tests/test.py | 53 +++++++++++++- 7 files changed, 237 insertions(+), 159 deletions(-) diff --git a/missing.sh b/missing.sh index aac5326c..575f0561 100644 --- a/missing.sh +++ b/missing.sh @@ -14,6 +14,8 @@ Hit LinSpaced repmat repdiag +unique_rows +sortrows sum cumsum slice diff --git a/src/barycentric_coordinates.cpp b/src/barycentric_coordinates.cpp index abad198b..c41af591 100644 --- a/src/barycentric_coordinates.cpp +++ b/src/barycentric_coordinates.cpp @@ -11,7 +11,17 @@ using namespace nb::literals; namespace pyigl { // Wrapper for barycentric_coordinates function - nb::object barycentric_coordinates( + nb::object barycentric_coordinates_PABC( + const nb::DRef &P, + const nb::DRef &A, + const nb::DRef &B, + const nb::DRef &C) + { + Eigen::MatrixXN L; + igl::barycentric_coordinates(P, A, B, C, L); + return nb::cast(std::move(L)); + } + nb::object barycentric_coordinates_PABCD( const nb::DRef &P, const nb::DRef &A, const nb::DRef &B, @@ -19,13 +29,7 @@ namespace pyigl const nb::DRef &D) { Eigen::MatrixXN L; - if(D.size()>0) - { - igl::barycentric_coordinates(P, A, B, C, D, L); - }else - { - igl::barycentric_coordinates(P, A, B, C, L); - } + igl::barycentric_coordinates(P, A, B, C, D, L); return nb::cast(std::move(L)); } } @@ -35,20 +39,36 @@ void bind_barycentric_coordinates(nb::module_ &m) { m.def( "barycentric_coordinates", - &pyigl::barycentric_coordinates, + &pyigl::barycentric_coordinates_PABC, + "P"_a, + "A"_a, + "B"_a, + "C"_a, +R"(Compute barycentric coordinates of each point in a corresponding triangle + +@param[in] P #P by 3 Query points in 3d +@param[in] A #P by 3 Tri corners in 3d +@param[in] B #P by 3 Tri corners in 3d +@param[in] C #P by 3 Tri corners in 3d +@param[out] L #P by 3 list of barycentric coordinates + )" + ); + m.def( + "barycentric_coordinates", + &pyigl::barycentric_coordinates_PABCD, "P"_a, "A"_a, "B"_a, "C"_a, - "D"_a=Eigen::MatrixXN(), -R"(Compute barycentric coordinates of each point in a corresponding triangle/tetrahedron. + "D"_a, +R"(Compute barycentric coordinates of each point in a corresponding tetrhedron @param[in] P #P by 3 Query points in 3d -@param[in] A #P by 3 (Tri|Tet) corners in 3d -@param[in] B #P by 3 (Tri|Tet) corners in 3d -@param[in] C #P by 3 (Tri|Tet) corners in 3d +@param[in] A #P by 3 Tet corners in 3d +@param[in] B #P by 3 Tet corners in 3d +@param[in] C #P by 3 Tet corners in 3d @param[in] D #P by 3 Tet corners in 3d -@param[out] L #P by (3|4) list of barycentric coordinates +@param[out] L #P by 3 list of barycentric coordinates )" ); } diff --git a/src/cotmatrix.cpp b/src/cotmatrix.cpp index b4ff8983..4cb60122 100644 --- a/src/cotmatrix.cpp +++ b/src/cotmatrix.cpp @@ -10,41 +10,33 @@ using namespace nb::literals; namespace pyigl { - nb::object cotmatrix( + nb::object cotmatrix_VF( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::SparseMatrixN L; + igl::cotmatrix(V,F,L); + return nb::cast(std::move(L)); + } + + nb::object cotmatrix_VIC( const nb::DRef &V, - const nb::DRef &F, const nb::DRef &I, const nb::DRef &C, bool return_M, bool return_P) { - const bool poly = I.size() != 0; Eigen::SparseMatrixN L,M,P; - if(poly) + igl::cotmatrix(V,I,C,L,M,P); + if(return_M && return_P) { - igl::cotmatrix(V,I,C,L,M,P); - }else + return nb::make_tuple(L,M,P); + }else if(return_M) { - igl::cotmatrix(V,F,L); - } - if(poly) - { - if(return_M && return_P) - { - return nb::make_tuple(L,M,P); - }else if(return_M) - { - return nb::make_tuple(L,M); - }else if(return_P) - { - return nb::make_tuple(L,P); - } - }else + return nb::make_tuple(L,M); + }else if(return_P) { - if(return_M || return_P) - { - throw std::runtime_error("cotmatrix: M and P only available for polygonal meshes"); - } + return nb::make_tuple(L,P); } return nb::cast(std::move(L)); } @@ -55,13 +47,9 @@ void bind_cotmatrix(nb::module_ &m) { m.def( "cotmatrix", - &pyigl::cotmatrix, + &pyigl::cotmatrix_VF, "V"_a, - "F"_a=Eigen::MatrixXI(), - "I"_a=Eigen::VectorXI(), - "C"_a=Eigen::VectorXI(), - "return_M"_a=false, - "return_P"_a=false, + "F"_a, R"(Constructs the cotangent stiffness matrix (discrete laplacian) for a given mesh (V,F). @@ -72,16 +60,17 @@ mesh (V,F). @tparam Scalar scalar type for eigen sparse matrix (e.g. double) @param[in] V #V by dim list of mesh vertex positions @param[in] F #F by simplex_size list of mesh elements (triangles or tetrahedra) - @param[out] L #V by #V cotangent matrix, each row i corresponding to V(i,:) - -\see adjacency_matrix - -\note This Laplacian uses the convention that diagonal entries are -**minus** the sum of off-diagonal entries. The diagonal entries are -therefore in general negative and the matrix is **negative** semi-definite -(immediately, -L is **positive** semi-definite) + @param[out] L #V by #V cotangent matrix, each row i corresponding to V(i,:))"); -Cotangent Laplacian (and mass matrix) for polygon meshes according to + m.def( + "cotmatrix", + &pyigl::cotmatrix_VIC, + "V"_a, + "I"_a=Eigen::VectorXI(), + "C"_a=Eigen::VectorXI(), + "return_M"_a=false, + "return_P"_a=false, +R"(Cotangent Laplacian (and mass matrix) for polygon meshes according to "Polygon Laplacian Made Simple" [Bunge et al.\ 2020] @param[in] V #V by 3 list of mesh vertex positions diff --git a/src/doublearea.cpp b/src/doublearea.cpp index e3317e0f..d094978c 100644 --- a/src/doublearea.cpp +++ b/src/doublearea.cpp @@ -11,35 +11,29 @@ using namespace nb::literals; namespace pyigl { // Wrapper for doublearea function - nb::object doublearea( + nb::object doublearea_VF( const nb::DRef &V, - const nb::DRef &F, + const nb::DRef &F) + { + Eigen::VectorXN dblA; + igl::doublearea(V, F, dblA); + return nb::cast(std::move(dblA)); + } + nb::object doublearea_ABC( const nb::DRef &A, const nb::DRef &B, - const nb::DRef &C, + const nb::DRef &C) + { + Eigen::VectorXN dblA; + igl::doublearea(A, B, C, dblA); + return nb::cast(std::move(dblA)); + } + nb::object doublearea_l( const nb::DRef &l, const Numeric nan_replacement) { Eigen::VectorXN dblA; - if(l.size() > 0) - { - igl::doublearea(l, nan_replacement, dblA); - }else - { - // if not isnan(nan_replacement) then throw error - if(!std::isnan(nan_replacement)) - { - throw std::invalid_argument("nan_replacement must be NaN if l is not provided"); - } - if(A.size() > 0) - { - igl::doublearea(A, B, C, dblA); - } - else - { - igl::doublearea(V, F, dblA); - } - } + igl::doublearea(l, nan_replacement, dblA); return nb::cast(std::move(dblA)); } } @@ -49,19 +43,37 @@ void bind_doublearea(nb::module_ &m) { m.def( "doublearea", - &pyigl::doublearea, + &pyigl::doublearea_VF, "V"_a=Eigen::MatrixXN(), "F"_a=Eigen::MatrixXI(), +R"(Computes twice the area for each input triangle or quad. + +@param[in] V eigen matrix #V by 3 +@param[in] F #F by (3|4) list of mesh face indices into rows of V +@param[out] dblA #F list of triangle double areas + )" + ); + m.def( + "doublearea", + &pyigl::doublearea_ABC, "A"_a=Eigen::MatrixXN(), "B"_a=Eigen::MatrixXN(), "C"_a=Eigen::MatrixXN(), +R"(Computes twice the area for each input triangle or quad. + +@param[in] A #F by dim list of triangle corner positions +@param[in] B #F by dim list of triangle corner positions +@param[in] C #F by dim list of triangle corner positions +@param[out] dblA #F list of triangle double areas + )" + ); + m.def( + "doublearea", + &pyigl::doublearea_l, "l"_a=Eigen::MatrixXN(), "nan_replacement"_a=std::numeric_limits::quiet_NaN(), R"(Computes twice the area for each input triangle or quad. -@param[in] V eigen matrix #V by 3 -@param[in] F #F by (3|4) list of mesh face indices into rows of V - @param[in] l #F by dim list of edge lengths using for triangles, columns correspond to edges 23,31,12 @param[in] nan_replacement what value should be used for triangles whose given edge lengths do not obey the triangle inequality. These may be very @@ -72,11 +84,6 @@ R"(Computes twice the area for each input triangle or quad. that case, the only circumstance the triangle inequality is broken is when the triangle is nearly degenerate and floating point error dominates: hence replacing with zero is reasonable. - -@param[in] A #F by dim list of triangle corner positions -@param[in] B #F by dim list of triangle corner positions -@param[in] C #F by dim list of triangle corner positions - @param[out] dblA #F list of triangle double areas )" ); diff --git a/src/per_face_normals.cpp b/src/per_face_normals.cpp index 1cc8c2e3..23710b09 100644 --- a/src/per_face_normals.cpp +++ b/src/per_face_normals.cpp @@ -10,9 +10,18 @@ using namespace nb::literals; namespace pyigl { // Wrapper for per_face_normals function - nb::object per_face_normals( + nb::object per_face_normals_VF( const nb::DRef &V, const nb::DRef &F, + const nb::DRef &Z) + { + Eigen::MatrixXN N; + igl::per_face_normals(V,F,Z,N); + return nb::cast(std::move(N)); + } + // Wrapper for per_face_normals function + nb::object per_face_normals_VIC( + const nb::DRef &V, const nb::DRef &I, const nb::DRef &C, const nb::DRef &Z, @@ -21,46 +30,37 @@ namespace pyigl const bool return_J) { Eigen::MatrixXN N; - if(I.size() > 0) + if(Z.size() > 0) + { + throw std::invalid_argument("per_face_normals: Z should not be provided for polygonal meshes"); + } + Eigen::MatrixXN VV; + Eigen::MatrixXI FF; + Eigen::VectorXI J; + igl::per_face_normals(V,I,C,N,VV,FF,J); + if(return_VV && return_FF && return_J) + { + return nb::make_tuple(N,VV,FF,J); + }else if(return_VV && return_FF) + { + return nb::make_tuple(N,VV,FF); + }else if(return_VV && return_J) + { + return nb::make_tuple(N,VV,J); + }else if(return_FF && return_J) { - if(Z.size() > 0) - { - throw std::invalid_argument("per_face_normals: Z should not be provided for polygonal meshes"); - } - Eigen::MatrixXN VV; - Eigen::MatrixXI FF; - Eigen::VectorXI J; - igl::per_face_normals(V,I,C,N,VV,FF,J); - if(return_VV && return_FF && return_J) - { - return nb::make_tuple(N,VV,FF,J); - }else if(return_VV && return_FF) - { - return nb::make_tuple(N,VV,FF); - }else if(return_VV && return_J) - { - return nb::make_tuple(N,VV,J); - }else if(return_FF && return_J) - { - return nb::make_tuple(N,FF,J); - }else if(return_VV) - { - return nb::make_tuple(N,VV); - }else if(return_FF) - { - return nb::make_tuple(N,FF); - }else if(return_J) - { - return nb::make_tuple(N,J); - } - }else + return nb::make_tuple(N,FF,J); + }else if(return_VV) { - if(return_VV || return_FF || return_J) - { - throw std::runtime_error("per_face_normals: VV, FF, and J only available for polygonal meshes"); - } - igl::per_face_normals(V,F,Z,N); + return nb::make_tuple(N,VV); + }else if(return_FF) + { + return nb::make_tuple(N,FF); + }else if(return_J) + { + return nb::make_tuple(N,J); } + return nb::cast(std::move(N)); } } @@ -70,15 +70,10 @@ void bind_per_face_normals(nb::module_ &m) { m.def( "per_face_normals", - &pyigl::per_face_normals, + &pyigl::per_face_normals_VF, "V"_a, - "F"_a=Eigen::MatrixXI(), - "I"_a=Eigen::VectorXI(), - "C"_a=Eigen::VectorXI(), + "F"_a, "Z"_a=Eigen::RowVectorXN(), - "return_VV"_a=false, - "return_FF"_a=false, - "return_J"_a=false, R"(Compute face normals via vertex position list, face list @param[in] V #V by 3 eigen Matrix of mesh vertex 3D positions @@ -86,4 +81,30 @@ R"(Compute face normals via vertex position list, face list @param[in] Z 3 vector normal given to faces with degenerate normal. @param[out] N #F by 3 eigen Matrix of mesh face (triangle) 3D normals)" ); + m.def( + "per_face_normals", + &pyigl::per_face_normals_VIC, + "V"_a, + "I"_a, + "C"_a, + "Z"_a=Eigen::RowVectorXN(), + "return_VV"_a=false, + "return_FF"_a=false, + "return_J"_a=false, +R"(Compute face normals via vertex position list, polygon stream + +@param[in] V #V by 3 eigen Matrix of mesh vertex 3D positions +@param[in] I #I vectorized list of polygon corner indices into rows of some matrix V +@param[in] C #polygons+1 list of cumulative polygon sizes so that C(i+1)-C(i) = size of + the ith polygon, and so I(C(i)) through I(C(i+1)-1) are the indices of + the ith polygon +@param[in] Z 3 vector normal given to faces with degenerate normal. +@param[in] return_VV whether to return VV +@param[in] return_FF whether to return FF +@param[in] return_J whether to return J +@param[out] N #F by 3 eigen Matrix of mesh face (triangle) 3D normals +@param[out] VV #I+#polygons by 3 list of auxiliary triangle mesh vertex positions +@param[out] FF #I by 3 list of triangle indices into rows of VV +@param[out] J #I list of indices into original polygons)" + ); } diff --git a/src/vertex_triangle_adjacency.cpp b/src/vertex_triangle_adjacency.cpp index f262f989..b07594c1 100644 --- a/src/vertex_triangle_adjacency.cpp +++ b/src/vertex_triangle_adjacency.cpp @@ -14,11 +14,13 @@ namespace pyigl nb::object vertex_triangle_adjacency( const nb::DRef &F, Integer n, - const nb::DRef &V, - bool return_VFi, - bool use_lists) + bool use_lists, + bool return_VFi) { - n = n ? n : (V.rows() ? V.rows() : F.maxCoeff()+1); + if(n==0 && F.size()>0) + { + n = F.maxCoeff()+1; + } if(use_lists) { std::vector> VF,VFi; @@ -34,7 +36,7 @@ namespace pyigl { if(return_VFi) { - throw std::runtime_error("vertex_triangle_adjacency: NI and VFI are mutually exclusive"); + throw std::runtime_error("vertex_triangle_adjacency: use_lists must be true to return VFi"); } Eigen::VectorXI VF,NI; igl::vertex_triangle_adjacency(F,n,VF,NI); @@ -49,34 +51,20 @@ void bind_vertex_triangle_adjacency(nb::module_ &m) m.def( "vertex_triangle_adjacency", &pyigl::vertex_triangle_adjacency, - "F"_a=Eigen::MatrixXI(), - "n"_a=Integer(0), - "V"_a=Eigen::MatrixXN(), - "return_VFi"_a=false, + "F"_a, + "n"_a=0, "use_lists"_a=false, + "return_VFi"_a=false, R"(vertex_face_adjacency constructs the vertex-face topology of a given mesh (V,F) - @param[in] n number of vertices #V (e.g. `F.maxCoeff()+1` or `V.rows()`) @param[in] F #F by dim list of mesh faces (must be triangles) + @param[in] n number of vertices #V (e.g. `F.maxCoeff()+1` or `V.rows()`) + @param[in] use_lists whether to return the result as a list of lists or as a pair of Eigen matrices + @param[in] return_VFi whether to return the list of lists of index of incidence within incident faces listed in VF + if using lists @param[out] VF #V list of lists of incident faces (adjacency list) @param[out] VI #V list of lists of index of incidence within incident faces listed - in VF - - \see edges, cotmatrix, diag, vv - - \bug this should not take V as an input parameter. - \bug if a facet is combinatorially degenerate then faces will appear - multiple times in VF and correspondingly in VFI (j appears twice in - F.row(i) then i will appear twice in VF[j]) - - @param[in] F #F by 3 list of triangle indices into some vertex list V - @param[in] n number of vertices, #V (e.g., F.maxCoeff()+1) - @param[out] VF 3*#F list List of faces indice on each vertex, so that VF(NI(i)+j) = - f, means that face f is the jth face (in no particular order) incident - on vertex i. - @param[out] NI #V+1 list cumulative sum of vertex-triangle degrees with a - preceeding zero. "How many faces" have been seen before visiting this - vertex and its incident faces.)" + in VF)" ); } diff --git a/tests/test.py b/tests/test.py index 9300982e..aee2998f 100644 --- a/tests/test.py +++ b/tests/test.py @@ -62,6 +62,7 @@ L,M = igl.cotmatrix(V,I=I,C=C,return_M=True) L,P = igl.cotmatrix(V,I=I,C=C,return_P=True) L,M,P = igl.cotmatrix(V,I=I,C=C,return_M=True,return_P=True) + M = igl.massmatrix(V,F) M = igl.massmatrix(V,F,type="barycentric") M = igl.massmatrix(V,F,type="voronoi") @@ -109,6 +110,10 @@ D = V.mean(axis=0)-O I,T,UV = tree.intersect_ray(V,F,O,D,first=True) hits = tree.intersect_ray(V,F,O,D,first=False) +o = O[0,:] +d = D[0,:] +hit = igl.ray_mesh_intersect(o,d,V,F,first=True) +hits = igl.ray_mesh_intersect(o,d,V,F) E,uE,EMAP = igl.unique_edge_map(F) @@ -122,7 +127,6 @@ VF,NI = igl.vertex_triangle_adjacency(F) VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) -VF,NI = igl.vertex_triangle_adjacency(F,V=V) VF = igl.vertex_triangle_adjacency(F,use_lists=True) VF,VFi = igl.vertex_triangle_adjacency(F,use_lists=True,return_VFi=True) @@ -154,3 +158,50 @@ Beq = np.zeros((1,1),dtype=np.float64) Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) Z = igl.MinQuadWithFixed(A,known,Aeq,True).solve(B,Y,Beq) + +V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +T = np.array([[0,1,2,3]],dtype=np.int64) +L = igl.edge_lengths(V,T) +vol = igl.volume(V,T) +vol = igl.volume(L=L) +vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) + +F = igl.boundary_facets(T) +# remove last face +F = F[:-1,:] +B = igl.is_border_vertex(F) + +L = igl.squared_edge_lengths(V,F) +C = igl.cotmatrix_entries(V,F) +l = igl.edge_lengths(V,F) +C = igl.cotmatrix_entries(l=l) +E = igl.oriented_facets(F) +F = igl.oriented_facets(T) + +res = igl.is_edge_manifold(F) +# 4 choose 1 +res,BF = igl.is_edge_manifold(F,return_BF=True) +res,E = igl.is_edge_manifold(F,return_E=True) +res,EMAP = igl.is_edge_manifold(F,return_EMAP=True) +res,BE = igl.is_edge_manifold(F,return_BE=True) +# 4 choose 2 +res,BF,EF = igl.is_edge_manifold(F,return_BF=True,return_E=True) +res,BF,EMAP = igl.is_edge_manifold(F,return_BF=True,return_EMAP=True) +res,BF,BE = igl.is_edge_manifold(F,return_BF=True,return_BE=True) +res,EF,EMAP = igl.is_edge_manifold(F,return_E=True,return_EMAP=True) +res,EF,BE = igl.is_edge_manifold(F,return_E=True,return_BE=True) +res,EMAP,BE = igl.is_edge_manifold(F,return_EMAP=True,return_BE=True) +# 4 choose 3 +res,BF,EF,EMAP = igl.is_edge_manifold(F,return_BF=True,return_E=True,return_EMAP=True) +res,BF,EF,BE = igl.is_edge_manifold(F,return_BF=True,return_E=True,return_BE=True) +res,BF,EMAP,BE = igl.is_edge_manifold(F,return_BF=True,return_EMAP=True,return_BE=True) +res,EF,EMAP,BE = igl.is_edge_manifold(F,return_E=True,return_EMAP=True,return_BE=True) +# 4 choose 4 +res,BF,EF,EMAP,BE = igl.is_edge_manifold(F,return_BF=True,return_E=True,return_EMAP=True,return_BE=True) + +L = igl.cotmatrix(V,F) +s = igl.matlab_format(V,"V") +s = igl.matlab_format_index(F,"F") +s = igl.matlab_format(L,"L") + + From e4914abcef0b4cb86e877c4aeedf1b30b587c77c Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 8 Nov 2024 13:28:15 -0500 Subject: [PATCH 005/102] new --- src/cotmatrix_entries.cpp | 51 ++++++++++++++++ src/is_border_vertex.cpp | 30 +++++++++ src/is_edge_manifold.cpp | 114 +++++++++++++++++++++++++++++++++++ src/matlab_format.cpp | 71 ++++++++++++++++++++++ src/oriented_facets.cpp | 33 ++++++++++ src/ray_mesh_intersect.cpp | 62 +++++++++++++++++++ src/squared_edge_lengths.cpp | 39 ++++++++++++ src/volume.cpp | 76 +++++++++++++++++++++++ 8 files changed, 476 insertions(+) create mode 100644 src/cotmatrix_entries.cpp create mode 100644 src/is_border_vertex.cpp create mode 100644 src/is_edge_manifold.cpp create mode 100644 src/matlab_format.cpp create mode 100644 src/oriented_facets.cpp create mode 100644 src/ray_mesh_intersect.cpp create mode 100644 src/squared_edge_lengths.cpp create mode 100644 src/volume.cpp diff --git a/src/cotmatrix_entries.cpp b/src/cotmatrix_entries.cpp new file mode 100644 index 00000000..a18ff7ba --- /dev/null +++ b/src/cotmatrix_entries.cpp @@ -0,0 +1,51 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + Eigen::MatrixXN cotmatrix_entries( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &l) + { + Eigen::MatrixXN C; + + if (l.size() > 0) + { + igl::cotmatrix_entries(l, C); + } else if (V.size() > 0) + { + igl::cotmatrix_entries(V, F, C); + } else + { + throw std::invalid_argument("Either V,F or l must be provided to compute cotmatrix entries"); + } + + return std::move(C); + } +} + +void bind_cotmatrix_entries(nb::module_ &m) +{ + m.def( + "cotmatrix_entries", + &pyigl::cotmatrix_entries, + "V"_a = Eigen::MatrixXN(), + "F"_a = Eigen::MatrixXI(), + "l"_a = Eigen::MatrixXN(), +R"(Compute the cotangent contributions for each angle in a mesh. + +@param[in] V #V by dim matrix of vertex positions +@param[in] F #F by {3|4} matrix of {triangle|tetrahedra} indices into V (optional) + +@param[in] l #F by 3 matrix of triangle edge lengths (optional, alternative to F) +@return C #F by {3|6} matrix of cotangent contributions + - For triangles, columns correspond to edges [1,2], [2,0], [0,1] + - For tets, columns correspond to edges [1,2], [2,0], [0,1], [3,0], [3,1], [3,2])"); +} diff --git a/src/is_border_vertex.cpp b/src/is_border_vertex.cpp new file mode 100644 index 00000000..9e1861f4 --- /dev/null +++ b/src/is_border_vertex.cpp @@ -0,0 +1,30 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for is_border_vertex + std::vector is_border_vertex(const nb::DRef &F) + { + return std::move(igl::is_border_vertex(F)); + } +} + +// Bind the wrapper to the Python module +void bind_is_border_vertex(nb::module_ &m) +{ + m.def( + "is_border_vertex", + &pyigl::is_border_vertex, + "F"_a, +R"(Determine vertices on the open boundary of a manifold mesh with triangle faces. + +@param[in] F #F by 3 list of triangle indices +@return #V vector of bools indicating if vertices are on the boundary)"); +} diff --git a/src/is_edge_manifold.cpp b/src/is_edge_manifold.cpp new file mode 100644 index 00000000..ce411814 --- /dev/null +++ b/src/is_edge_manifold.cpp @@ -0,0 +1,114 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for is_edge_manifold with overload handling + nb::object is_edge_manifold( + const nb::DRef &F, + const bool return_BF = false, + const bool return_E = false, + const bool return_EMAP = false, + const bool return_BE = false) + { + bool result; + Eigen::MatrixXI BF; + Eigen::MatrixXI E; + Eigen::VectorXI EMAP; + Eigen::VectorX BE; + + result = igl::is_edge_manifold(F, BF, E, EMAP, BE); + // 4 choose 4 + if (return_BF && return_E && return_EMAP && return_BE) + { + return nb::make_tuple(result, BF, E, EMAP, BE); + } + // 4 choose 3 + else if (return_BF && return_E && return_EMAP) + { + return nb::make_tuple(result, BF, E, EMAP); + } + else if (return_BF && return_E && return_BE) + { + return nb::make_tuple(result, BF, E, BE); + } + else if (return_BF && return_EMAP && return_BE) + { + return nb::make_tuple(result, BF, EMAP, BE); + } + else if (return_E && return_EMAP && return_BE) + { + return nb::make_tuple(result, E, EMAP, BE); + } + // 4 choose 2 + else if (return_BF && return_E) + { + return nb::make_tuple(result, BF, E); + } + else if (return_BF && return_EMAP) + { + return nb::make_tuple(result, BF, EMAP); + } + else if (return_BF && return_BE) + { + return nb::make_tuple(result, BF, BE); + } + else if (return_E && return_EMAP) + { + return nb::make_tuple(result, E, EMAP); + } + else if (return_E && return_BE) + { + return nb::make_tuple(result, E, BE); + } + else if (return_EMAP && return_BE) + { + return nb::make_tuple(result, EMAP, BE); + } + // 4 choose 1 + else if (return_BF) + { + return nb::make_tuple(result, BF); + }else if (return_E) + { + return nb::make_tuple(result, E); + }else if (return_EMAP) + { + return nb::make_tuple(result, EMAP); + }else if (return_BE) + { + return nb::make_tuple(result, BE); + } + else + { + return nb::cast(result); + } + } +} + +// Bind the wrapper to the Python module +void bind_is_edge_manifold(nb::module_ &m) +{ + m.def( + "is_edge_manifold", + &pyigl::is_edge_manifold, + "F"_a, + "return_BF"_a = false, + "return_E"_a = false, + "return_EMAP"_a = false, + "return_BE"_a = false, +R"(Check if the mesh is edge-manifold (every edge is incident to one or two oppositely oriented faces). + +@param[in] F #F by 3 list of triangle indices +@param[out] BF (if return_BF=True) #F by 3 list of flags for non-manifold edges opposite each vertex +@param[out] E (if return_E=True) #E by 2 list of unique edges +@param[out] EMAP (if return_EMAP=True) 3*#F list of indices of opposite edges in E +@param[out] BE (if return_BE=True) #E list of flags for whether each edge is non-manifold +@return True if all edges are manifold, otherwise False)"); +} diff --git a/src/matlab_format.cpp b/src/matlab_format.cpp new file mode 100644 index 00000000..f26ff4f7 --- /dev/null +++ b/src/matlab_format.cpp @@ -0,0 +1,71 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for dense matrix formatting + std::string matlab_format_dense(const nb::DRef &M, const std::string &name = "") + { + std::ostringstream oss; + oss << igl::matlab_format(M, name); + return oss.str(); + } + + // Wrapper for sparse matrix formatting + std::string matlab_format_sparse(const Eigen::SparseMatrix &S, const std::string &name = "") + { + return igl::matlab_format(S, name); + } + + // Wrapper for scalar formatting + std::string matlab_format_scalar(Numeric v, const std::string &name = "") + { + return igl::matlab_format(v, name); + } + + // Wrapper for matlab_format_index + std::string matlab_format_index(const nb::DRef &M, const std::string &name = "") + { + return igl::matlab_format_index(M, name); + } +} + +// Bind the wrapper to the Python module +void bind_matlab_format(nb::module_ &m) +{ + m.def( + "matlab_format", + &pyigl::matlab_format_dense, + "M"_a, + "name"_a = "", + R"(Format a dense matrix for MATLAB-style output.)"); + + m.def( + "matlab_format", + &pyigl::matlab_format_sparse, + "S"_a, + "name"_a = "", + R"(Format a sparse matrix for MATLAB-style output in IJV format.)"); + + m.def( + "matlab_format", + nb::overload_cast(&pyigl::matlab_format_scalar), + "v"_a, + "name"_a = "", + R"(Format a double scalar for MATLAB-style output.)"); + + m.def( + "matlab_format_index", + &pyigl::matlab_format_index, + "M"_a, + "name"_a = "", + R"(Format a matrix for MATLAB-style output with 1-based indexing.)"); +} diff --git a/src/oriented_facets.cpp b/src/oriented_facets.cpp new file mode 100644 index 00000000..ea9ab783 --- /dev/null +++ b/src/oriented_facets.cpp @@ -0,0 +1,33 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for oriented_facets + Eigen::MatrixXI oriented_facets(const nb::DRef &F) + { + Eigen::MatrixXI E; + igl::oriented_facets(F, E); + return std::move(E); + } +} + +// Bind the wrapper to the Python module +void bind_oriented_facets(nb::module_ &m) +{ + m.def( + "oriented_facets", + &pyigl::oriented_facets, + "F"_a, +R"(Determines all directed facets of a given set of simplicial elements. + +@param[in] F #F by simplex_size matrix of simplices +@return E #E by (simplex_size-1) matrix of directed facets, such that each row in E + represents a facet opposite to a vertex in F)"); +} diff --git a/src/ray_mesh_intersect.cpp b/src/ray_mesh_intersect.cpp new file mode 100644 index 00000000..83c92b0a --- /dev/null +++ b/src/ray_mesh_intersect.cpp @@ -0,0 +1,62 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for ray_mesh_intersect with overload handling + nb::object ray_mesh_intersect( + const Eigen::VectorXN &source, + const Eigen::VectorXN &dir, + const nb::DRef &V, + const nb::DRef &F, + const bool first = false) + { + if (first) + { + // Overload for a single hit + igl::Hit hit; + bool result = igl::ray_mesh_intersect(source, dir, V, F, hit); + return nb::make_tuple(hit.id, hit.t, hit.u, hit.v); + } + else + { + // Overload for all hits + std::vector > hits; + bool result = igl::ray_mesh_intersect(source, dir, V, F, hits); + std::vector> out; + for (const auto &hit : hits) + { + out.push_back(std::make_tuple(hit.id, hit.t, hit.u, hit.v)); + } + return nb::cast(std::move(out)); + } + } +} + +// Bind the wrapper to the Python module +void bind_ray_mesh_intersect(nb::module_ &m) +{ + m.def( + "ray_mesh_intersect", + &pyigl::ray_mesh_intersect, + "source"_a, + "dir"_a, + "V"_a, + "F"_a, + "first"_a = false, +R"(Shoot a ray against a mesh (V, F) and collect hits. + +@param[in] source 3-vector origin of the ray +@param[in] dir 3-vector direction of the ray +@param[in] V #V by 3 list of mesh vertex positions +@param[in] F #F by 3 list of mesh face indices into V +@param[in] first If True, only return the first hit (if any) +@return Sorted list of hits if any exist, otherwise None)"); +} diff --git a/src/squared_edge_lengths.cpp b/src/squared_edge_lengths.cpp new file mode 100644 index 00000000..2fcc548e --- /dev/null +++ b/src/squared_edge_lengths.cpp @@ -0,0 +1,39 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for squared_edge_lengths + Eigen::MatrixXN squared_edge_lengths( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::MatrixXN L; + igl::squared_edge_lengths(V, F, L); + return std::move(L); + } +} + +// Bind the wrapper to the Python module +void bind_squared_edge_lengths(nb::module_ &m) +{ + m.def( + "squared_edge_lengths", + &pyigl::squared_edge_lengths, + "V"_a, + "F"_a, +R"(Constructs a list of squared lengths of edges opposite each index in a face (triangle/tet) list. + +@param[in] V #V by 3 eigen matrix of vertex positions +@param[in] F #F by (2|3|4) list of mesh edges, triangles, or tets +@return L #F by {1|3|6} matrix of squared edge lengths + - For edges, a single column of lengths + - For triangles, columns correspond to edges [1,2], [2,0], [0,1] + - For tets, columns correspond to edges [3 0], [3 1], [3 2], [1 2], [2 0], [0 1])"); +} diff --git a/src/volume.cpp b/src/volume.cpp new file mode 100644 index 00000000..581bd32a --- /dev/null +++ b/src/volume.cpp @@ -0,0 +1,76 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + nb::object volume_VT( + const nb::DRef &V = Eigen::MatrixXN(), + const nb::DRef &T = Eigen::MatrixXI()) + { + Eigen::VectorXN vol; + igl::volume(V, T, vol); + return nb::cast(std::move(vol)); + } + nb::object volume_L( + const nb::DRef &L = Eigen::MatrixXN()) + { + Eigen::VectorXN vol; + igl::volume(L, vol); + return nb::cast(std::move(vol)); + } + nb::object volume_ABCD( + const nb::DRef &A = Eigen::MatrixXN(), + const nb::DRef &B = Eigen::MatrixXN(), + const nb::DRef &C = Eigen::MatrixXN(), + const nb::DRef &D = Eigen::MatrixXN()) + { + Eigen::VectorXN vol; + igl::volume(A, B, C, D, vol); + return nb::cast(std::move(vol)); + } + +} + +// Bind the wrapper to the Python module +void bind_volume(nb::module_ &m) +{ + m.def( + "volume", + &pyigl::volume_VT, + "V"_a = Eigen::MatrixXN(), + "T"_a = Eigen::MatrixXI(), +R"(Compute volume for tetrahedrons in various input formats. + +@param[in] V #V by dim list of vertex positions or first corner positions +@param[in] T #T by 4 list of tet indices +@return vol #T list of tetrahedron volumes)"); + m.def( + "volume", + &pyigl::volume_L, + "L"_a = Eigen::MatrixXN(), +R"(Compute volume for tetrahedrons in various input formats. + +@param[in] L #V by 6 list of edge lengths (see edge_lengths) +@return vol #T list of tetrahedron volumes)"); + m.def( + "volume", + &pyigl::volume_ABCD, + "A"_a = Eigen::MatrixXN(), + "B"_a = Eigen::MatrixXN(), + "C"_a = Eigen::MatrixXN(), + "D"_a = Eigen::MatrixXN(), +R"(Compute volume for tetrahedrons in various input formats. + +@param[in] A #A by dim list of vertex positions or first corner positions +@param[in] B #A by dim list of second corner positions +@param[in] C #A by dim list of third corner positions +@param[in] D #A by dim list of fourth corner positions + +@return vol #T list of tetrahedron volumes)"); +} From 596ddea0f6aa0db937af4a0add66e7e3e7779781 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 9 Nov 2024 21:33:53 -0500 Subject: [PATCH 006/102] don't return different types; wrappers dont return nb types --- CMakeLists.txt | 4 +- missing.sh | 3 + src/AABB.cpp | 86 ++++++++----------- src/average_from_edges_onto_vertices.cpp | 4 +- src/barycenter.cpp | 4 +- src/barycentric_coordinates.cpp | 8 +- src/bbw.cpp | 4 +- src/boundary_facets.cpp | 20 +---- src/circulation.cpp | 50 ++++++++++++ src/connected_components.cpp | 32 +------- src/cotmatrix.cpp | 35 +++----- src/cotmatrix_entries.cpp | 41 +++++----- src/doublearea.cpp | 12 +-- src/edge_flaps.cpp | 66 +++++++++++++++ src/edge_lengths.cpp | 4 +- src/grad.cpp | 41 ++++++++++ src/is_border_vertex.cpp | 2 +- src/is_edge_manifold.cpp | 79 +----------------- src/local_basis.cpp | 40 +++++++++ src/massmatrix.cpp | 4 +- src/min_quad_with_fixed.cpp | 8 +- src/noop.cpp | 2 +- src/orient_halfedges.cpp | 5 +- src/oriented_facets.cpp | 2 +- src/per_face_normals.cpp | 50 ++---------- src/point_mesh_squared_distance.cpp | 21 +---- src/polar_svd.cpp | 39 +++++++++ src/ray_mesh_intersect.cpp | 12 +-- src/read_triangle_mesh.cpp | 5 +- src/remove_duplicate_vertices.cpp | 71 ++++++++++++++++ src/squared_edge_lengths.cpp | 2 +- src/triangle_triangle_adjacency.cpp | 63 ++++++-------- src/unique_edge_map.cpp | 55 +++++++------ src/unique_simplices.cpp | 19 +---- src/vertex_triangle_adjacency.cpp | 57 +++++++------ src/volume.cpp | 12 +-- src/winding_number.cpp | 66 +++++++++++++++ src/write_triangle_mesh.cpp | 6 +- tests/test.py | 100 ++++++++++------------- 39 files changed, 642 insertions(+), 492 deletions(-) create mode 100644 src/circulation.cpp create mode 100644 src/edge_flaps.cpp create mode 100644 src/grad.cpp create mode 100644 src/local_basis.cpp create mode 100644 src/polar_svd.cpp create mode 100644 src/remove_duplicate_vertices.cpp create mode 100644 src/winding_number.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 415c273a..1f6a476e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16.0) project(pyigl) -# use C++20 +# use C++20 (we need at least C++17 for return value optimization) set(CMAKE_CXX_STANDARD 20) if (CMAKE_VERSION VERSION_LESS 3.18) @@ -32,7 +32,7 @@ FetchContent_MakeAvailable(nanobind) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 0e504e1b331e6696b485258a9aac779f0f20c5b0 + GIT_TAG 1410ad43dff78bfaf8d8103de422fa64adfa35d5 ) FetchContent_MakeAvailable(libigl) diff --git a/missing.sh b/missing.sh index 575f0561..352b169a 100644 --- a/missing.sh +++ b/missing.sh @@ -3,6 +3,9 @@ already=$(ls src/*.cpp | sed -e "s/src\/\(.*\).cpp$/\1/g") skip="igl_inline EPS +sparse +setdiff +rotation_matrix_from_directions verbose colon min_quad_with_fixed.impl diff --git a/src/AABB.cpp b/src/AABB.cpp index 3862a426..1a467cb4 100644 --- a/src/AABB.cpp +++ b/src/AABB.cpp @@ -20,7 +20,7 @@ namespace pyigl tree.init(V,Ele); } template - nb::object find( + auto find( AABB &tree, const nb::DRef &V, const nb::DRef &Ele, @@ -33,80 +33,61 @@ namespace pyigl } std::vector vec_int = tree.find(V,Ele,q,first); std::vector vec(vec_int.begin(),vec_int.end()); - if(first) - { - return vec.size() ? nb::cast(vec[0]) : nb::none(); - }else - { - return nb::cast(std::move(vec)); - } + return vec; } template - nb::object squared_distance( + auto squared_distance( AABB &tree, const nb::DRef &V, const nb::DRef &Ele, - const nb::DRef &P, - const bool return_I, - const bool return_C) + const nb::DRef &P) { Eigen::VectorXN sqrD; Eigen::VectorXI I; Eigen::MatrixXN C; tree.squared_distance(V,Ele,P,sqrD,I,C); - if(return_I && return_C) - { - return nb::make_tuple(sqrD,I,C); - }else if(return_I) - { - return nb::make_tuple(sqrD,I); - }else if(return_C) - { - return nb::make_tuple(sqrD,C); - } - return nb::cast(std::move(sqrD)); + return std::make_tuple(sqrD,I,C); } template - nb::object intersect_ray( + auto intersect_ray_first( AABB &tree, const nb::DRef &V, const nb::DRef &Ele, const nb::DRef &orig, const nb::DRef &dir, - const Numeric min_t, - const bool first) + const Numeric min_t) { - if(first) - { + Eigen::VectorXI I; + Eigen::VectorXN T; + Eigen::MatrixXN UV; + tree.intersect_ray(V,Ele,orig,dir,min_t,I,T,UV); + return std::make_tuple(I,T,UV); + } - Eigen::VectorXI I; - Eigen::VectorXN T; - Eigen::MatrixXN UV; - tree.intersect_ray(V,Ele,orig,dir,min_t,I,T,UV); - return nb::make_tuple(I,T,UV); - }else + template + auto intersect_ray( + AABB &tree, + const nb::DRef &V, + const nb::DRef &Ele, + const nb::DRef &orig, + const nb::DRef &dir) + { + std::vector>> hits; + tree.intersect_ray(V,Ele,orig,dir,hits); + std::vector>> out; + for(const auto &hit : hits) { - if(std::isfinite(min_t)) - { - throw std::runtime_error("intersect_ray: min_t only supported for first=true"); - } - std::vector>> hits; - tree.intersect_ray(V,Ele,orig,dir,hits); - std::vector>> out; - for(const auto &hit : hits) + std::vector> hit_out; + for(const auto &h : hit) { - std::vector> hit_out; - for(const auto &h : hit) - { - hit_out.push_back(std::make_tuple(h.id,h.t,h.u,h.v)); - } - out.push_back(hit_out); + hit_out.push_back(std::make_tuple(h.id,h.t,h.u,h.v)); } - - return nb::cast(std::move(out)); + out.push_back(hit_out); } + + return out; } } @@ -118,8 +99,9 @@ void bind_AABB(nb::module_ &m) .def(nb::init<>()) .def("init", &pyigl::init, "V"_a, "Ele"_a) .def("find", &pyigl::find, "V"_a, "Ele"_a, "q"_a, "first"_a=false) - .def("squared_distance", &pyigl::squared_distance, "V"_a, "Ele"_a, "P"_a, "return_I"_a=false,"return_C"_a=false) - .def("intersect_ray",&pyigl::intersect_ray, "V"_a, "Ele"_a, "orig"_a, "dir"_a, "min_t"_a=std::numeric_limits::infinity(), "first"_a=false) + .def("squared_distance", &pyigl::squared_distance, "V"_a, "Ele"_a, "P"_a) + .def("intersect_ray_first",&pyigl::intersect_ray_first, "V"_a, "Ele"_a, "orig"_a, "dir"_a, "min_t"_a=std::numeric_limits::infinity()) + .def("intersect_ray",&pyigl::intersect_ray, "V"_a, "Ele"_a, "orig"_a, "dir"_a) ; } diff --git a/src/average_from_edges_onto_vertices.cpp b/src/average_from_edges_onto_vertices.cpp index f0e137b3..35647b14 100644 --- a/src/average_from_edges_onto_vertices.cpp +++ b/src/average_from_edges_onto_vertices.cpp @@ -9,7 +9,7 @@ using namespace nb::literals; namespace pyigl { - nb::object average_from_edges_onto_vertices( + auto average_from_edges_onto_vertices( const nb::DRef &F, const nb::DRef &E, const nb::DRef &oE, @@ -17,7 +17,7 @@ namespace pyigl { Eigen::VectorXN uV; igl::average_from_edges_onto_vertices(F, E, oE, uE, uV); - return nb::cast(std::move(uV)); + return uV; } } diff --git a/src/barycenter.cpp b/src/barycenter.cpp index 261efe0d..25dee12d 100644 --- a/src/barycenter.cpp +++ b/src/barycenter.cpp @@ -10,13 +10,13 @@ using namespace nb::literals; namespace pyigl { // Wrapper for barycenter function - nb::object barycenter( + auto barycenter( const nb::DRef &V, const nb::DRef &F) { Eigen::MatrixXN BC; igl::barycenter(V, F, BC); - return nb::cast(std::move(BC)); + return BC; } } diff --git a/src/barycentric_coordinates.cpp b/src/barycentric_coordinates.cpp index c41af591..7d86c860 100644 --- a/src/barycentric_coordinates.cpp +++ b/src/barycentric_coordinates.cpp @@ -11,7 +11,7 @@ using namespace nb::literals; namespace pyigl { // Wrapper for barycentric_coordinates function - nb::object barycentric_coordinates_PABC( + auto barycentric_coordinates_PABC( const nb::DRef &P, const nb::DRef &A, const nb::DRef &B, @@ -19,9 +19,9 @@ namespace pyigl { Eigen::MatrixXN L; igl::barycentric_coordinates(P, A, B, C, L); - return nb::cast(std::move(L)); + return L; } - nb::object barycentric_coordinates_PABCD( + auto barycentric_coordinates_PABCD( const nb::DRef &P, const nb::DRef &A, const nb::DRef &B, @@ -30,7 +30,7 @@ namespace pyigl { Eigen::MatrixXN L; igl::barycentric_coordinates(P, A, B, C, D, L); - return nb::cast(std::move(L)); + return L; } } diff --git a/src/bbw.cpp b/src/bbw.cpp index 61405a35..0bd23f14 100644 --- a/src/bbw.cpp +++ b/src/bbw.cpp @@ -11,7 +11,7 @@ using namespace nb::literals; namespace pyigl { // Wrapper for bbw function - nb::object bbw( + auto bbw( const nb::DRef &V, const nb::DRef &Ele, const nb::DRef &b, @@ -38,7 +38,7 @@ namespace pyigl { throw std::runtime_error("bbw: failed to compute weights"); } - return nb::cast(std::move(W)); + return W; } } diff --git a/src/boundary_facets.cpp b/src/boundary_facets.cpp index 3de7767a..f9ff6f44 100644 --- a/src/boundary_facets.cpp +++ b/src/boundary_facets.cpp @@ -4,32 +4,20 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; namespace pyigl { - nb::object boundary_facets( - const nb::DRef &T, - bool return_J, - bool return_K) + auto boundary_facets( const nb::DRef &T) { Eigen::MatrixXI F; Eigen::VectorXI J; Eigen::VectorXI K; igl::boundary_facets(T,F,J,K); - if(return_J && return_K) - { - return nb::make_tuple(F,J,K); - }else if(return_J) - { - return nb::make_tuple(F,J); - }else if(return_K) - { - return nb::make_tuple(F,K); - } - return nb::cast(std::move(F)); + return std::make_tuple(F,J,K); } } @@ -40,8 +28,6 @@ void bind_boundary_facets(nb::module_ &m) "boundary_facets", &pyigl::boundary_facets, "T"_a, - "return_J"_a=false, - "return_K"_a=false, R"(Determine boundary faces (edges) of tetrahedra (triangles) stored in T (analogous to qptoolbox's `outline` and `boundary_faces`). diff --git a/src/circulation.cpp b/src/circulation.cpp new file mode 100644 index 00000000..0f5bdb84 --- /dev/null +++ b/src/circulation.cpp @@ -0,0 +1,50 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto circulation( + const int e, + const bool ccw, + const nb::DRef &F, + const nb::DRef &EMAP, + const nb::DRef &EF, + const nb::DRef &EI) + { + std::vector Nv, Nf; + igl::circulation(e, ccw, F, EMAP, EF, EI, Nv, Nf); + return std::make_tuple(Nv, Nf); + } +} + +// Bind the wrapper to the Python module +void bind_circulation(nb::module_ &m) +{ + m.def( + "circulation", + &pyigl::circulation, + "e"_a, + "ccw"_a, + "F"_a, + "EMAP"_a, + "EF"_a, + "EI"_a, + R"(Return lists of "next" vertex indices (Nv) and face indices (Nf) for circulation. + + @param[in] e index of edge to circulate + @param[in] ccw circulate in ccw direction + @param[in] F #F by 3 list of mesh faces + @param[in] EMAP #F*3 list of indices mapping each directed edge to a unique edge in E + @param[in] EF #E by 2 list of edge flaps + @param[in] EI #E by 2 list of edge flap corners + @return Tuple containing Nv (next vertex indices) and Nf (face indices))" + ); +} diff --git a/src/connected_components.cpp b/src/connected_components.cpp index b91268fb..b4fe0d9c 100644 --- a/src/connected_components.cpp +++ b/src/connected_components.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; @@ -10,35 +11,12 @@ using namespace nb::literals; namespace pyigl { // Wrapper for connected_components - nb::object connected_components( - const Eigen::SparseMatrix &A, - const bool return_C = false, - const bool return_K = false) + auto connected_components( const Eigen::SparseMatrix &A) { - int num_components; Eigen::VectorXI C; Eigen::VectorXI K; - - if (return_C && return_K) - { - num_components = igl::connected_components(A, C, K); - return nb::make_tuple(num_components, C, K); - } - else if (return_C) - { - num_components = igl::connected_components(A, C, K); - return nb::make_tuple(num_components, C); - } - else if (return_K) - { - num_components = igl::connected_components(A, C, K); - return nb::make_tuple(num_components, K); - } - else - { - num_components = igl::connected_components(A, C, K); - return nb::cast(num_components); - } + Integer num_components = (Integer)igl::connected_components(A, C, K); + return std::make_tuple(num_components, C, K); } } @@ -49,8 +27,6 @@ void bind_connected_components(nb::module_ &m) "connected_components", &pyigl::connected_components, "A"_a, - "return_C"_a = false, - "return_K"_a = false, R"(Determine the connected components of a graph described by the input adjacency matrix. @param[in] A #A by #A adjacency matrix (treated as describing a directed graph) diff --git a/src/cotmatrix.cpp b/src/cotmatrix.cpp index 4cb60122..f16dfb18 100644 --- a/src/cotmatrix.cpp +++ b/src/cotmatrix.cpp @@ -4,41 +4,30 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; namespace pyigl { - nb::object cotmatrix_VF( + auto cotmatrix( const nb::DRef &V, const nb::DRef &F) { Eigen::SparseMatrixN L; igl::cotmatrix(V,F,L); - return nb::cast(std::move(L)); + return L; } - nb::object cotmatrix_VIC( + auto cotmatrix_polygon( const nb::DRef &V, const nb::DRef &I, - const nb::DRef &C, - bool return_M, - bool return_P) + const nb::DRef &C) { Eigen::SparseMatrixN L,M,P; igl::cotmatrix(V,I,C,L,M,P); - if(return_M && return_P) - { - return nb::make_tuple(L,M,P); - }else if(return_M) - { - return nb::make_tuple(L,M); - }else if(return_P) - { - return nb::make_tuple(L,P); - } - return nb::cast(std::move(L)); + return std::make_tuple(L,M,P); } } @@ -47,7 +36,7 @@ void bind_cotmatrix(nb::module_ &m) { m.def( "cotmatrix", - &pyigl::cotmatrix_VF, + &pyigl::cotmatrix, "V"_a, "F"_a, R"(Constructs the cotangent stiffness matrix (discrete laplacian) for a given @@ -63,13 +52,11 @@ mesh (V,F). @param[out] L #V by #V cotangent matrix, each row i corresponding to V(i,:))"); m.def( - "cotmatrix", - &pyigl::cotmatrix_VIC, + "cotmatrix_polygon", + &pyigl::cotmatrix_polygon, "V"_a, - "I"_a=Eigen::VectorXI(), - "C"_a=Eigen::VectorXI(), - "return_M"_a=false, - "return_P"_a=false, + "I"_a, + "C"_a, R"(Cotangent Laplacian (and mass matrix) for polygon meshes according to "Polygon Laplacian Made Simple" [Bunge et al.\ 2020] diff --git a/src/cotmatrix_entries.cpp b/src/cotmatrix_entries.cpp index a18ff7ba..ad544bc7 100644 --- a/src/cotmatrix_entries.cpp +++ b/src/cotmatrix_entries.cpp @@ -9,25 +9,20 @@ using namespace nb::literals; namespace pyigl { - Eigen::MatrixXN cotmatrix_entries( + Eigen::MatrixXN cotmatrix_entries_VF( const nb::DRef &V, - const nb::DRef &F, + const nb::DRef &F) + { + Eigen::MatrixXN C; + igl::cotmatrix_entries(V,F,C); + return C; + } + Eigen::MatrixXN cotmatrix_entries_l( const nb::DRef &l) { Eigen::MatrixXN C; - - if (l.size() > 0) - { - igl::cotmatrix_entries(l, C); - } else if (V.size() > 0) - { - igl::cotmatrix_entries(V, F, C); - } else - { - throw std::invalid_argument("Either V,F or l must be provided to compute cotmatrix entries"); - } - - return std::move(C); + igl::cotmatrix_entries(l,C); + return C; } } @@ -35,15 +30,23 @@ void bind_cotmatrix_entries(nb::module_ &m) { m.def( "cotmatrix_entries", - &pyigl::cotmatrix_entries, - "V"_a = Eigen::MatrixXN(), - "F"_a = Eigen::MatrixXI(), - "l"_a = Eigen::MatrixXN(), + &pyigl::cotmatrix_entries_VF, + "V"_a, + "F"_a, R"(Compute the cotangent contributions for each angle in a mesh. @param[in] V #V by dim matrix of vertex positions @param[in] F #F by {3|4} matrix of {triangle|tetrahedra} indices into V (optional) +@return C #F by {3|6} matrix of cotangent contributions + - For triangles, columns correspond to edges [1,2], [2,0], [0,1] + - For tets, columns correspond to edges [1,2], [2,0], [0,1], [3,0], [3,1], [3,2])"); + m.def( + "cotmatrix_entries", + &pyigl::cotmatrix_entries_l, + "l"_a, +R"(Compute the cotangent contributions for each angle in a mesh. + @param[in] l #F by 3 matrix of triangle edge lengths (optional, alternative to F) @return C #F by {3|6} matrix of cotangent contributions - For triangles, columns correspond to edges [1,2], [2,0], [0,1] diff --git a/src/doublearea.cpp b/src/doublearea.cpp index d094978c..7b26414f 100644 --- a/src/doublearea.cpp +++ b/src/doublearea.cpp @@ -11,30 +11,30 @@ using namespace nb::literals; namespace pyigl { // Wrapper for doublearea function - nb::object doublearea_VF( + auto doublearea_VF( const nb::DRef &V, const nb::DRef &F) { Eigen::VectorXN dblA; igl::doublearea(V, F, dblA); - return nb::cast(std::move(dblA)); + return dblA; } - nb::object doublearea_ABC( + auto doublearea_ABC( const nb::DRef &A, const nb::DRef &B, const nb::DRef &C) { Eigen::VectorXN dblA; igl::doublearea(A, B, C, dblA); - return nb::cast(std::move(dblA)); + return dblA; } - nb::object doublearea_l( + auto doublearea_l( const nb::DRef &l, const Numeric nan_replacement) { Eigen::VectorXN dblA; igl::doublearea(l, nan_replacement, dblA); - return nb::cast(std::move(dblA)); + return dblA; } } diff --git a/src/edge_flaps.cpp b/src/edge_flaps.cpp new file mode 100644 index 00000000..922bbfe2 --- /dev/null +++ b/src/edge_flaps.cpp @@ -0,0 +1,66 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Binding for the first overload of edge_flaps with `uE` and `EMAP` as inputs + auto edge_flaps( + const nb::DRef &F, + const nb::DRef &uE, + const nb::DRef &EMAP) + { + Eigen::MatrixXI EF, EI; + igl::edge_flaps(F, uE, EMAP, EF, EI); + return std::make_tuple(EF, EI); + } + + // Binding for the second overload of edge_flaps where `uE` and `EMAP` are outputs + auto edge_flaps_F( + const nb::DRef &F) + { + Eigen::MatrixXI uE, EF, EI; + Eigen::VectorXI EMAP; + igl::edge_flaps(F, uE, EMAP, EF, EI); + return std::make_tuple(uE, EMAP, EF, EI); + } +} + +// Bind the wrapper to the Python module +void bind_edge_flaps(nb::module_ &m) +{ + m.def( + "edge_flaps", + &pyigl::edge_flaps, + "F"_a, + "uE"_a, + "EMAP"_a, + R"(Determine edge flaps with precomputed unique edge map and edge-face adjacency. + + @param[in] F #F by 3 list of face indices + @param[in] uE #uE by 2 list of unique edge indices + @param[in] EMAP #F*3 list of indices mapping each directed edge to unique edge in uE + @return Tuple containing EF and EI matrices, where: + EF - #E by 2 list of edge flaps + EI - #E by 2 list of edge flap corners)"); + + m.def( + "edge_flaps", + &pyigl::edge_flaps_F, + "F"_a, + R"(Determine edge flaps, unique edge map, and edge-face adjacency from face list only. + + @param[in] F #F by 3 list of face indices + @return Tuple containing uE, EMAP, EF, and EI where: + uE - #uE by 2 list of unique edge indices + EMAP - #F*3 list mapping each directed edge to unique edge in uE + EF - #E by 2 list of edge flaps + EI - #E by 2 list of edge flap corners)"); +} diff --git a/src/edge_lengths.cpp b/src/edge_lengths.cpp index 100a39d5..0132ae5e 100644 --- a/src/edge_lengths.cpp +++ b/src/edge_lengths.cpp @@ -10,13 +10,13 @@ using namespace nb::literals; namespace pyigl { // Wrapper for edge_lengths function - nb::object edge_lengths( + auto edge_lengths( const nb::DRef &V, const nb::DRef &F) { Eigen::MatrixXN L; igl::edge_lengths(V, F, L); - return nb::cast(std::move(L)); + return L; } } diff --git a/src/grad.cpp b/src/grad.cpp new file mode 100644 index 00000000..a7dc9e53 --- /dev/null +++ b/src/grad.cpp @@ -0,0 +1,41 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto grad( + const nb::DRef &V, + const nb::DRef &F, + bool uniform = false) + { + Eigen::SparseMatrixN G; + igl::grad(V, F, G, uniform); + return G; + } +} + +// Bind the wrapper to the Python module +void bind_grad(nb::module_ &m) +{ + m.def( + "grad", + &pyigl::grad, + "V"_a, + "F"_a, + "uniform"_a = false, + R"(Compute the gradient operator on a triangle mesh. + + @param[in] V #V by 3 list of mesh vertex positions + @param[in] F #F by 3 (or #F by 4 for tetrahedrons) list of mesh face indices + @param[out] G #F*dim by #V Gradient operator + @param[in] uniform boolean indicating whether to use a uniform mesh instead of the vertices V + @return Sparse gradient operator matrix G)" + ); +} diff --git a/src/is_border_vertex.cpp b/src/is_border_vertex.cpp index 9e1861f4..8339b10d 100644 --- a/src/is_border_vertex.cpp +++ b/src/is_border_vertex.cpp @@ -12,7 +12,7 @@ namespace pyigl // Wrapper for is_border_vertex std::vector is_border_vertex(const nb::DRef &F) { - return std::move(igl::is_border_vertex(F)); + return igl::is_border_vertex(F); } } diff --git a/src/is_edge_manifold.cpp b/src/is_edge_manifold.cpp index ce411814..97b11035 100644 --- a/src/is_edge_manifold.cpp +++ b/src/is_edge_manifold.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; @@ -10,12 +11,8 @@ using namespace nb::literals; namespace pyigl { // Wrapper for is_edge_manifold with overload handling - nb::object is_edge_manifold( - const nb::DRef &F, - const bool return_BF = false, - const bool return_E = false, - const bool return_EMAP = false, - const bool return_BE = false) + auto is_edge_manifold( + const nb::DRef &F) { bool result; Eigen::MatrixXI BF; @@ -24,71 +21,7 @@ namespace pyigl Eigen::VectorX BE; result = igl::is_edge_manifold(F, BF, E, EMAP, BE); - // 4 choose 4 - if (return_BF && return_E && return_EMAP && return_BE) - { - return nb::make_tuple(result, BF, E, EMAP, BE); - } - // 4 choose 3 - else if (return_BF && return_E && return_EMAP) - { - return nb::make_tuple(result, BF, E, EMAP); - } - else if (return_BF && return_E && return_BE) - { - return nb::make_tuple(result, BF, E, BE); - } - else if (return_BF && return_EMAP && return_BE) - { - return nb::make_tuple(result, BF, EMAP, BE); - } - else if (return_E && return_EMAP && return_BE) - { - return nb::make_tuple(result, E, EMAP, BE); - } - // 4 choose 2 - else if (return_BF && return_E) - { - return nb::make_tuple(result, BF, E); - } - else if (return_BF && return_EMAP) - { - return nb::make_tuple(result, BF, EMAP); - } - else if (return_BF && return_BE) - { - return nb::make_tuple(result, BF, BE); - } - else if (return_E && return_EMAP) - { - return nb::make_tuple(result, E, EMAP); - } - else if (return_E && return_BE) - { - return nb::make_tuple(result, E, BE); - } - else if (return_EMAP && return_BE) - { - return nb::make_tuple(result, EMAP, BE); - } - // 4 choose 1 - else if (return_BF) - { - return nb::make_tuple(result, BF); - }else if (return_E) - { - return nb::make_tuple(result, E); - }else if (return_EMAP) - { - return nb::make_tuple(result, EMAP); - }else if (return_BE) - { - return nb::make_tuple(result, BE); - } - else - { - return nb::cast(result); - } + return std::make_tuple(result, BF, E, EMAP, BE); } } @@ -99,10 +32,6 @@ void bind_is_edge_manifold(nb::module_ &m) "is_edge_manifold", &pyigl::is_edge_manifold, "F"_a, - "return_BF"_a = false, - "return_E"_a = false, - "return_EMAP"_a = false, - "return_BE"_a = false, R"(Check if the mesh is edge-manifold (every edge is incident to one or two oppositely oriented faces). @param[in] F #F by 3 list of triangle indices diff --git a/src/local_basis.cpp b/src/local_basis.cpp new file mode 100644 index 00000000..fc40c791 --- /dev/null +++ b/src/local_basis.cpp @@ -0,0 +1,40 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto local_basis( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::MatrixXN B1, B2, B3; + igl::local_basis(V, F, B1, B2, B3); + return std::make_tuple(B1, B2, B3); + } +} + +// Bind the wrapper to the Python module +void bind_local_basis(nb::module_ &m) +{ + m.def( + "local_basis", + &pyigl::local_basis, + "V"_a, + "F"_a, + R"(Compute a local orthogonal reference system for each triangle in the given mesh. + + @param[in] V #V by 3 eigen matrix of vertex positions + @param[in] F #F by 3 list of mesh faces (must be triangles) + @param[out] B1 #F by 3 matrix of tangent vectors for each triangle + @param[out] B2 #F by 3 matrix of tangent vectors perpendicular to B1 for each triangle + @param[out] B3 #F by 3 matrix of normal vectors for each triangle + )" + ); +} diff --git a/src/massmatrix.cpp b/src/massmatrix.cpp index c3c3d8d5..63f56a38 100644 --- a/src/massmatrix.cpp +++ b/src/massmatrix.cpp @@ -11,7 +11,7 @@ using namespace nb::literals; namespace pyigl { - nb::object massmatrix( + auto massmatrix( const nb::DRef &V, const nb::DRef &F, const std::string type) @@ -36,7 +36,7 @@ namespace pyigl } igl::massmatrix(V,F,t,M); - return nb::cast(std::move(M)); + return M; } } diff --git a/src/min_quad_with_fixed.cpp b/src/min_quad_with_fixed.cpp index c0164da8..bf896a3d 100644 --- a/src/min_quad_with_fixed.cpp +++ b/src/min_quad_with_fixed.cpp @@ -11,7 +11,7 @@ using namespace nb::literals; namespace pyigl { // Wrapper for min_quad_with_fixed - nb::object min_quad_with_fixed( + auto min_quad_with_fixed( const Eigen::SparseMatrixN &A, const nb::DRef &B, const nb::DRef &known, @@ -28,7 +28,7 @@ namespace pyigl throw std::runtime_error("min_quad_with_fixed: Optimization failed."); } - return nb::cast(std::move(Z)); + return Z; } struct MinQuadWithFixed @@ -46,7 +46,7 @@ namespace pyigl } } - Eigen::MatrixXN solve( + auto solve( const nb::DRef &B, const nb::DRef &Y, const nb::DRef &Beq) @@ -56,7 +56,7 @@ namespace pyigl { throw std::runtime_error("min_quad_with_fixed: Optimization failed."); } - return std::move(Z); + return Z; } }; diff --git a/src/noop.cpp b/src/noop.cpp index 8dbf270e..ef70f252 100644 --- a/src/noop.cpp +++ b/src/noop.cpp @@ -10,7 +10,7 @@ using namespace nb::literals; namespace pyigl { - nb::object noop( + auto noop( const nb::DRef &N, const nb::DRef &I, const Eigen::SparseMatrixN &SN, diff --git a/src/orient_halfedges.cpp b/src/orient_halfedges.cpp index acaf94d7..25069c33 100644 --- a/src/orient_halfedges.cpp +++ b/src/orient_halfedges.cpp @@ -3,18 +3,19 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; namespace pyigl { - nb::object orient_halfedges( + auto orient_halfedges( const nb::DRef &F) { Eigen::MatrixXI E,oE; igl::orient_halfedges(F,E,oE); - return nb::make_tuple(E,oE); + return std::make_tuple(E,oE); } } diff --git a/src/oriented_facets.cpp b/src/oriented_facets.cpp index ea9ab783..22708160 100644 --- a/src/oriented_facets.cpp +++ b/src/oriented_facets.cpp @@ -14,7 +14,7 @@ namespace pyigl { Eigen::MatrixXI E; igl::oriented_facets(F, E); - return std::move(E); + return E; } } diff --git a/src/per_face_normals.cpp b/src/per_face_normals.cpp index 23710b09..c9908b5a 100644 --- a/src/per_face_normals.cpp +++ b/src/per_face_normals.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; @@ -10,58 +11,27 @@ using namespace nb::literals; namespace pyigl { // Wrapper for per_face_normals function - nb::object per_face_normals_VF( + auto per_face_normals_VF( const nb::DRef &V, const nb::DRef &F, const nb::DRef &Z) { Eigen::MatrixXN N; igl::per_face_normals(V,F,Z,N); - return nb::cast(std::move(N)); + return N; } // Wrapper for per_face_normals function - nb::object per_face_normals_VIC( + auto per_face_normals_VIC( const nb::DRef &V, const nb::DRef &I, - const nb::DRef &C, - const nb::DRef &Z, - const bool return_VV, - const bool return_FF, - const bool return_J) + const nb::DRef &C) { Eigen::MatrixXN N; - if(Z.size() > 0) - { - throw std::invalid_argument("per_face_normals: Z should not be provided for polygonal meshes"); - } Eigen::MatrixXN VV; Eigen::MatrixXI FF; Eigen::VectorXI J; igl::per_face_normals(V,I,C,N,VV,FF,J); - if(return_VV && return_FF && return_J) - { - return nb::make_tuple(N,VV,FF,J); - }else if(return_VV && return_FF) - { - return nb::make_tuple(N,VV,FF); - }else if(return_VV && return_J) - { - return nb::make_tuple(N,VV,J); - }else if(return_FF && return_J) - { - return nb::make_tuple(N,FF,J); - }else if(return_VV) - { - return nb::make_tuple(N,VV); - }else if(return_FF) - { - return nb::make_tuple(N,FF); - }else if(return_J) - { - return nb::make_tuple(N,J); - } - - return nb::cast(std::move(N)); + return std::make_tuple(N,VV,FF,J); } } @@ -87,10 +57,6 @@ R"(Compute face normals via vertex position list, face list "V"_a, "I"_a, "C"_a, - "Z"_a=Eigen::RowVectorXN(), - "return_VV"_a=false, - "return_FF"_a=false, - "return_J"_a=false, R"(Compute face normals via vertex position list, polygon stream @param[in] V #V by 3 eigen Matrix of mesh vertex 3D positions @@ -98,10 +64,6 @@ R"(Compute face normals via vertex position list, polygon stream @param[in] C #polygons+1 list of cumulative polygon sizes so that C(i+1)-C(i) = size of the ith polygon, and so I(C(i)) through I(C(i+1)-1) are the indices of the ith polygon -@param[in] Z 3 vector normal given to faces with degenerate normal. -@param[in] return_VV whether to return VV -@param[in] return_FF whether to return FF -@param[in] return_J whether to return J @param[out] N #F by 3 eigen Matrix of mesh face (triangle) 3D normals @param[out] VV #I+#polygons by 3 list of auxiliary triangle mesh vertex positions @param[out] FF #I by 3 list of triangle indices into rows of VV diff --git a/src/point_mesh_squared_distance.cpp b/src/point_mesh_squared_distance.cpp index 06d7a7fc..6d930682 100644 --- a/src/point_mesh_squared_distance.cpp +++ b/src/point_mesh_squared_distance.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; @@ -10,28 +11,16 @@ using namespace nb::literals; namespace pyigl { // Wrapper for point_mesh_squared_distance function - nb::object point_mesh_squared_distance( + auto point_mesh_squared_distance( const nb::DRef &P, const nb::DRef &V, - const nb::DRef &Ele, - const bool return_I, - const bool return_C) + const nb::DRef &Ele) { Eigen::VectorXN sqrD; Eigen::VectorXI I; Eigen::MatrixXN C; igl::point_mesh_squared_distance(P, V, Ele, sqrD, I, C); - if(return_I && return_C) - { - return nb::make_tuple(sqrD, I, C); - }else if(return_I) - { - return nb::make_tuple(sqrD, I); - }else if(return_C) - { - return nb::make_tuple(sqrD, C); - } - return nb::cast(std::move(sqrD)); + return std::make_tuple(sqrD, I, C); } } @@ -44,8 +33,6 @@ void bind_point_mesh_squared_distance(nb::module_ &m) "P"_a, "V"_a, "Ele"_a, - "return_I"_a=false, - "return_C"_a=false, R"(Compute distances from a set of points P to a triangle mesh (V,F) @param[in] P #P by 3 list of query point positions diff --git a/src/polar_svd.cpp b/src/polar_svd.cpp new file mode 100644 index 00000000..cdf7f1f7 --- /dev/null +++ b/src/polar_svd.cpp @@ -0,0 +1,39 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto polar_svd( + const nb::DRef &A, + bool include_reflections) + { + Eigen::MatrixXN R, T, U, V; + Eigen::VectorXN S; + igl::polar_svd(A, include_reflections, R, T, U, S, V); + return std::make_tuple(std::move(R), std::move(T), std::move(U), std::move(S), std::move(V)); + } +} + +// Bind the wrapper to the Python module +void bind_polar_svd(nb::module_ &m) +{ + m.def( + "polar_svd", + &pyigl::polar_svd, + "A"_a, + "include_reflections"_a = false, + R"(Computes the polar decomposition of a NxN matrix A using SVD. + + @param[in] A NxN matrix to be decomposed + @param[in] includeReflections Whether to allow R to be a reflection (default is False) + @param[in] return_U If true, include the left-singular vectors U in the output (default is False) + @param[in] return_S If true, include the singular values S in the output (default is False) + @param[in] return_V If true, include the right-singular vectors V in the output (default is False) + @return Tuple containing (R,T) and selected outputs in the order specified by the flags)"); +} diff --git a/src/ray_mesh_intersect.cpp b/src/ray_mesh_intersect.cpp index 83c92b0a..80998cea 100644 --- a/src/ray_mesh_intersect.cpp +++ b/src/ray_mesh_intersect.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; @@ -11,32 +12,33 @@ using namespace nb::literals; namespace pyigl { // Wrapper for ray_mesh_intersect with overload handling - nb::object ray_mesh_intersect( + std::vector> + ray_mesh_intersect( const Eigen::VectorXN &source, const Eigen::VectorXN &dir, const nb::DRef &V, const nb::DRef &F, const bool first = false) { + std::vector> out; if (first) { // Overload for a single hit igl::Hit hit; bool result = igl::ray_mesh_intersect(source, dir, V, F, hit); - return nb::make_tuple(hit.id, hit.t, hit.u, hit.v); + out.emplace_back(hit.id, hit.t, hit.u, hit.v); } else { // Overload for all hits std::vector > hits; bool result = igl::ray_mesh_intersect(source, dir, V, F, hits); - std::vector> out; for (const auto &hit : hits) { - out.push_back(std::make_tuple(hit.id, hit.t, hit.u, hit.v)); + out.emplace_back(hit.id, hit.t, hit.u, hit.v); } - return nb::cast(std::move(out)); } + return out; } } diff --git a/src/read_triangle_mesh.cpp b/src/read_triangle_mesh.cpp index 641d35fb..762d66b7 100644 --- a/src/read_triangle_mesh.cpp +++ b/src/read_triangle_mesh.cpp @@ -4,13 +4,14 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; namespace pyigl { - nb::object read_triangle_mesh( + auto read_triangle_mesh( const std::string str) { Eigen::MatrixXN V; @@ -20,7 +21,7 @@ namespace pyigl // throw runtime exception throw std::runtime_error("Failed to read mesh from: " + str); } - return nb::make_tuple(V,F); + return std::make_tuple(V,F); } } diff --git a/src/remove_duplicate_vertices.cpp b/src/remove_duplicate_vertices.cpp new file mode 100644 index 00000000..b20d77e2 --- /dev/null +++ b/src/remove_duplicate_vertices.cpp @@ -0,0 +1,71 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Binding for remove_duplicate_vertices without face remapping + auto remove_duplicate_vertices( + const nb::DRef &V, + const double epsilon) + { + Eigen::MatrixXN SV; + Eigen::VectorXI SVI, SVJ; + igl::remove_duplicate_vertices(V, epsilon, SV, SVI, SVJ); + return std::make_tuple(SV, SVI, SVJ); + } + + // Binding for remove_duplicate_vertices with face remapping + auto remove_duplicate_vertices_F( + const nb::DRef &V, + const nb::DRef &F, + const double epsilon) + { + Eigen::MatrixXN SV; + Eigen::VectorXI SVI, SVJ; + Eigen::MatrixXI SF; + igl::remove_duplicate_vertices(V, F, epsilon, SV, SVI, SVJ, SF); + return std::make_tuple(SV, SVI, SVJ, SF); + } +} + +// Bind the wrapper to the Python module +void bind_remove_duplicate_vertices(nb::module_ &m) +{ + m.def( + "remove_duplicate_vertices", + &pyigl::remove_duplicate_vertices, + "V"_a, + "epsilon"_a, + R"(Remove duplicate vertices up to a uniqueness tolerance (epsilon). + + @param[in] V #V by dim list of vertex positions + @param[in] epsilon Uniqueness tolerance used coordinate-wise + @return Tuple containing: + - SV: #SV by dim new list of unique vertex positions + - SVI: #SV list of indices so SV = V(SVI,:) + - SVJ: #V list of indices so V = SV(SVJ,:))"); + + m.def( + "remove_duplicate_vertices", + &pyigl::remove_duplicate_vertices_F, + "V"_a, + "F"_a, + "epsilon"_a, + R"(Remove duplicate vertices and remap faces to new indices. + + @param[in] V #V by dim list of vertex positions + @param[in] F #F by dim list of face indices + @param[in] return_SVJ If true, return the SVJ mapping indices + @return Tuple containing: + - SV: #SV by dim new list of unique vertex positions + - SVI: #SV list of indices so SV = V(SVI,:) + - SVJ: #V list of indices so V = SV(SVJ,:) + - SF: #F by dim list of remapped face indices into SV)"); +} diff --git a/src/squared_edge_lengths.cpp b/src/squared_edge_lengths.cpp index 2fcc548e..c2513558 100644 --- a/src/squared_edge_lengths.cpp +++ b/src/squared_edge_lengths.cpp @@ -16,7 +16,7 @@ namespace pyigl { Eigen::MatrixXN L; igl::squared_edge_lengths(V, F, L); - return std::move(L); + return L; } } diff --git a/src/triangle_triangle_adjacency.cpp b/src/triangle_triangle_adjacency.cpp index 941e6309..6a6caa52 100644 --- a/src/triangle_triangle_adjacency.cpp +++ b/src/triangle_triangle_adjacency.cpp @@ -4,49 +4,26 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; namespace pyigl { - nb::object triangle_triangle_adjacency( - const nb::DRef &F, - bool return_TTi = false, - bool use_lists = false) + auto triangle_triangle_adjacency( const nb::DRef &F) { - if (use_lists) - { - std::vector>> TT; - - if (return_TTi) - { - std::vector>> TTi; - igl::triangle_triangle_adjacency(F, true, TT, TTi); - return nb::make_tuple(TT,TTi); - } - else - { - igl::triangle_triangle_adjacency(F, TT); - return nb::cast(std::move(TT)); - } - } - else - { - Eigen::MatrixXI TT; - - if (return_TTi) - { - Eigen::MatrixXI TTi; - igl::triangle_triangle_adjacency(F, TT, TTi); - return nb::make_tuple(TT, TTi); - } - else - { - igl::triangle_triangle_adjacency(F, TT); - return nb::cast(std::move(TT)); - } - } + Eigen::MatrixXI TT; + Eigen::MatrixXI TTi; + igl::triangle_triangle_adjacency(F, TT, TTi); + return nb::make_tuple(TT, TTi); + } + auto triangle_triangle_adjacency_lists( const nb::DRef &F) + { + std::vector>> TT; + std::vector>> TTi; + igl::triangle_triangle_adjacency(F, true, TT, TTi); + return std::make_tuple(TT,TTi); } } @@ -57,8 +34,18 @@ void bind_triangle_triangle_adjacency(nb::module_ &m) "triangle_triangle_adjacency", &pyigl::triangle_triangle_adjacency, "F"_a, - "return_TTi"_a = false, - "use_lists"_a = false, +R"(Constructs the triangle-triangle adjacency matrix for a given mesh (V,F). + +@param[in] F #F by 3 list of mesh faces (must be triangles) +@param[out] TT #F by 3 adjacent matrix, where each element represents the id of the triangle adjacent to the corresponding edge +@param[out] TTi (if return_TTi=True) #F by 3 adjacent matrix, where each element represents the id of the edge of the adjacent triangle that shares an edge with the current triangle + +- If `use_lists=True`, returns adjacency data as lists of lists for compatibility with non-manifold meshes. +)"); + m.def( + "triangle_triangle_adjacency_lists", + &pyigl::triangle_triangle_adjacency_lists, + "F"_a, R"(Constructs the triangle-triangle adjacency matrix for a given mesh (V,F). @param[in] F #F by 3 list of mesh faces (must be triangles) diff --git a/src/unique_edge_map.cpp b/src/unique_edge_map.cpp index 80ce32f4..ade6eddb 100644 --- a/src/unique_edge_map.cpp +++ b/src/unique_edge_map.cpp @@ -4,39 +4,31 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; namespace pyigl { - nb::object unique_edge_map( - const nb::DRef &F, - bool return_uE2E = false, - bool return_uEC_uEE = false) + auto unique_edge_map(const nb::DRef &F) { Eigen::MatrixXI E; Eigen::MatrixXI uE; Eigen::VectorXI EMAP; + Eigen::VectorXI uEC, uEE; + igl::unique_edge_map(F, E, uE, EMAP, uEC, uEE); + return nb::make_tuple(E, uE, EMAP, uEC, uEE); + } - if(return_uE2E) - { - std::vector> uE2E; - igl::unique_edge_map(F, E, uE, EMAP, uE2E); - return nb::make_tuple(E, uE, EMAP, uE2E); - } - else if(return_uEC_uEE) - { - Eigen::VectorXI uEC; - Eigen::VectorXI uEE; - igl::unique_edge_map(F, E, uE, EMAP, uEC, uEE); - return nb::make_tuple(E, uE, EMAP, uEC, uEE); - } - else - { - igl::unique_edge_map(F, E, uE, EMAP); - return nb::make_tuple(E, uE, EMAP); - } + auto unique_edge_map_lists( const nb::DRef &F) + { + Eigen::MatrixXI E; + Eigen::MatrixXI uE; + Eigen::VectorXI EMAP; + std::vector> uE2E; + igl::unique_edge_map(F, E, uE, EMAP, uE2E); + return std::make_tuple(E, uE, EMAP, uE2E); } } @@ -47,8 +39,6 @@ void bind_unique_edge_map(nb::module_ &m) "unique_edge_map", &pyigl::unique_edge_map, "F"_a, - "return_uE2E"_a = false, - "return_uEC_uEE"_a = false, R"(Construct relationships between facet "half"-(or rather "viewed")-edges E to unique edges of the mesh seen as a graph. @@ -59,7 +49,20 @@ to unique edges of the mesh seen as a graph. undirected edge so that uE(EMAP(f+#F*c)) is the unique edge corresponding to E.row(f+#F*c) @param[out] uE2E (if return_uE2E=True) #uE list of lists of indices into E of coexisting edges -@param[out] uEC, uEE (if return_uEC_uEE=True) cumulative counts of directed edges sharing each - unique edge and list of indices into E for directed edges sharing each unique edge +)"); + m.def( + "unique_edge_map_lists", + &pyigl::unique_edge_map_lists, + "F"_a, +R"(Construct relationships between facet "half"-(or rather "viewed")-edges E +to unique edges of the mesh seen as a graph. + +@param[in] F #F by 3 list of simplices +@param[out] E #F*3 by 2 list of all directed edges +@param[out] uE #uE by 2 list of unique undirected edges +@param[out] EMAP #F*3 list of indices into uE, mapping each directed edge to a unique + undirected edge so that uE(EMAP(f+#F*c)) is the unique edge + corresponding to E.row(f+#F*c) +@param[out] uE2E (if return_uE2E=True) #uE list of lists of indices into E of coexisting edges )"); } diff --git a/src/unique_simplices.cpp b/src/unique_simplices.cpp index 81451534..22ad7cbf 100644 --- a/src/unique_simplices.cpp +++ b/src/unique_simplices.cpp @@ -10,25 +10,12 @@ using namespace nb::literals; namespace pyigl { // Wrapper for unique_simplices with overload handling - nb::object unique_simplices( - const nb::DRef &F, - const bool return_IA, - const bool return_IC) + auto unique_simplices( const nb::DRef &F) { Eigen::MatrixXI FF; Eigen::VectorXI IA,IC; igl::unique_simplices(F,FF,IA,IC); - if(return_IA && return_IC) - { - return nb::make_tuple(FF,IA,IC); - }else if(return_IA) - { - return nb::make_tuple(FF,IA); - }else if(return_IC) - { - return nb::make_tuple(FF,IC); - } - return nb::cast(std::move(FF)); + return nb::make_tuple(FF,IA,IC); } } @@ -39,8 +26,6 @@ void bind_unique_simplices(nb::module_ &m) "unique_simplices", &pyigl::unique_simplices, "F"_a, - "return_IA"_a = false, - "return_IC"_a = false, R"(Find combinatorially unique simplices in F. Order independent. @param[in] F #F by simplex-size list of simplices diff --git a/src/vertex_triangle_adjacency.cpp b/src/vertex_triangle_adjacency.cpp index b07594c1..50fed664 100644 --- a/src/vertex_triangle_adjacency.cpp +++ b/src/vertex_triangle_adjacency.cpp @@ -5,43 +5,36 @@ #include #include #include +#include namespace nb = nanobind; using namespace nb::literals; namespace pyigl { - nb::object vertex_triangle_adjacency( + auto vertex_triangle_adjacency( const nb::DRef &F, - Integer n, - bool use_lists, - bool return_VFi) + Integer n) { if(n==0 && F.size()>0) { n = F.maxCoeff()+1; } - if(use_lists) - { - std::vector> VF,VFi; - igl::vertex_triangle_adjacency(n,F,VF,VFi); - if(return_VFi) - { - return nb::make_tuple(VF,VFi); - }else - { - return nb::cast(std::move(VF)); - } - }else + Eigen::VectorXI VF,NI; + igl::vertex_triangle_adjacency(F,n,VF,NI); + return std::make_tuple(VF,NI); + } + auto vertex_triangle_adjacency_lists( + const nb::DRef &F, + Integer n) + { + if(n==0 && F.size()>0) { - if(return_VFi) - { - throw std::runtime_error("vertex_triangle_adjacency: use_lists must be true to return VFi"); - } - Eigen::VectorXI VF,NI; - igl::vertex_triangle_adjacency(F,n,VF,NI); - return nb::make_tuple(VF,NI); + n = F.maxCoeff()+1; } + std::vector> VF,VFi; + igl::vertex_triangle_adjacency(n,F,VF,VFi); + return std::make_tuple(VF,VFi); } } @@ -53,14 +46,24 @@ void bind_vertex_triangle_adjacency(nb::module_ &m) &pyigl::vertex_triangle_adjacency, "F"_a, "n"_a=0, - "use_lists"_a=false, - "return_VFi"_a=false, R"(vertex_face_adjacency constructs the vertex-face topology of a given mesh (V,F) @param[in] F #F by dim list of mesh faces (must be triangles) @param[in] n number of vertices #V (e.g. `F.maxCoeff()+1` or `V.rows()`) - @param[in] use_lists whether to return the result as a list of lists or as a pair of Eigen matrices - @param[in] return_VFi whether to return the list of lists of index of incidence within incident faces listed in VF + @param[out] VF #V list of lists of incident faces (adjacency list) + @param[out] VI #V list of lists of index of incidence within incident faces listed + in VF + );)" + ); + m.def( + "vertex_triangle_adjacency_lists", + &pyigl::vertex_triangle_adjacency_lists, + "F"_a, + "n"_a=0, +R"(vertex_face_adjacency constructs the vertex-face topology of a given mesh (V,F) + + @param[in] F #F by dim list of mesh faces (must be triangles) + @param[in] n number of vertices #V (e.g. `F.maxCoeff()+1` or `V.rows()`) if using lists @param[out] VF #V list of lists of incident faces (adjacency list) @param[out] VI #V list of lists of index of incidence within incident faces listed diff --git a/src/volume.cpp b/src/volume.cpp index 581bd32a..57999e05 100644 --- a/src/volume.cpp +++ b/src/volume.cpp @@ -9,22 +9,22 @@ using namespace nb::literals; namespace pyigl { - nb::object volume_VT( + auto volume_VT( const nb::DRef &V = Eigen::MatrixXN(), const nb::DRef &T = Eigen::MatrixXI()) { Eigen::VectorXN vol; igl::volume(V, T, vol); - return nb::cast(std::move(vol)); + return vol; } - nb::object volume_L( + auto volume_L( const nb::DRef &L = Eigen::MatrixXN()) { Eigen::VectorXN vol; igl::volume(L, vol); - return nb::cast(std::move(vol)); + return vol; } - nb::object volume_ABCD( + auto volume_ABCD( const nb::DRef &A = Eigen::MatrixXN(), const nb::DRef &B = Eigen::MatrixXN(), const nb::DRef &C = Eigen::MatrixXN(), @@ -32,7 +32,7 @@ namespace pyigl { Eigen::VectorXN vol; igl::volume(A, B, C, D, vol); - return nb::cast(std::move(vol)); + return vol; } } diff --git a/src/winding_number.cpp b/src/winding_number.cpp new file mode 100644 index 00000000..e3b7d23a --- /dev/null +++ b/src/winding_number.cpp @@ -0,0 +1,66 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Binding for winding_number over multiple query points + auto winding_number( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &O) + { + Eigen::VectorXN W; + // The DerivedF on WindingNumberAABB and WindingNumberTree is + // broken and must be MatrixXi. + // Similarly DerivedV is broken and probably has to be MatrixX*. + Eigen::MatrixXN V_cpy = V; + Eigen::MatrixXi F_cpy = F.cast(); + igl::winding_number(V_cpy, F_cpy, O, W); + return W; + } + // Binding for winding_number over multiple query points + Numeric winding_number_single( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &o) + { + return igl::winding_number(V, F, o.transpose()); + } + +} + +// Bind the wrapper to the Python module +void bind_winding_number(nb::module_ &m) +{ + m.def( + "winding_number", + &pyigl::winding_number, + "V"_a, + "F"_a, + "O"_a, + R"(Computes the generalized winding number at each query point with respect to the mesh. + + @param[in] V #V by dim list of mesh vertex positions + @param[in] F #F by dim list of mesh facets as indices into rows of V + @param[in] O #O by dim list of query points + @return Vector of winding numbers for each query point)"); + m.def( + "winding_number", + &pyigl::winding_number_single, + "V"_a, + "F"_a, + "o"_a, + R"(Computes the generalized winding number at each query point with respect to the mesh. + + @param[in] V #V by dim list of mesh vertex positions + @param[in] F #F by dim list of mesh facets as indices into rows of V + @param[in] o dim-vector of query point + @return winding number)"); +} diff --git a/src/write_triangle_mesh.cpp b/src/write_triangle_mesh.cpp index 856b349e..a285366d 100644 --- a/src/write_triangle_mesh.cpp +++ b/src/write_triangle_mesh.cpp @@ -11,7 +11,7 @@ using namespace nb::literals; namespace pyigl { - nb::object write_triangle_mesh( + void write_triangle_mesh( const std::string & filename, const nb::DRef V, const nb::DRef F, @@ -38,7 +38,6 @@ namespace pyigl // throw runtime exception throw std::runtime_error("Failed to write mesh to: " + filename); } - return nb::bool_(true); } } @@ -61,8 +60,7 @@ obj, off, stl, wrl, ply, mesh). @param[in] str path to file @param[in] V eigen double matrix #V by 3 @param[in] F eigen int matrix #F by 3 -@param[in] encoding set file encoding (ascii or binary) when both are available -@return true iff success)" +@param[in] encoding set file encoding (ascii or binary) when both are available)" ); } diff --git a/tests/test.py b/tests/test.py index aee2998f..fa0ec2f1 100644 --- a/tests/test.py +++ b/tests/test.py @@ -43,6 +43,8 @@ # #time_noop() +# seed numpy's random number generator +np.random.seed(42) F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) E,oE = igl.orient_halfedges(F) @@ -58,10 +60,7 @@ L = igl.cotmatrix(V,F) I = np.array([0,1,2,3],dtype=np.int64) C = np.array([0,4],dtype=np.int64) -L = igl.cotmatrix(V,I=I,C=C) -L,M = igl.cotmatrix(V,I=I,C=C,return_M=True) -L,P = igl.cotmatrix(V,I=I,C=C,return_P=True) -L,M,P = igl.cotmatrix(V,I=I,C=C,return_M=True,return_P=True) +L,M,P = igl.cotmatrix_polygon(V,I,C) M = igl.massmatrix(V,F) M = igl.massmatrix(V,F,type="barycentric") @@ -69,28 +68,24 @@ M = igl.massmatrix(V,F,type="full") N = igl.per_face_normals(V,F) N = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) -N = igl.per_face_normals(V,I=I,C=C) -N,VV,FF,J = igl.per_face_normals(V,I=I,C=C,return_VV=True,return_FF=True,return_J=True) +N,_,_,_ = igl.per_face_normals(V,I,C) dblA = igl.doublearea(V,F) dblA = igl.doublearea(l=l) dblA = igl.doublearea(l=l,nan_replacement=0.0) P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) -sqrD = igl.point_mesh_squared_distance(P,V,F) -sqrD,I = igl.point_mesh_squared_distance(P,V,F,return_I=True) -sqrD,C = igl.point_mesh_squared_distance(P,V,F,return_C=True) -sqrD,I,C = igl.point_mesh_squared_distance(P,V,F,return_I=True,return_C=True) +sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) +W = igl.winding_number(V,F,P) +W = igl.winding_number(V,F,P[1,:]) BC = igl.barycenter(V,F) + b = np.array([0,3],dtype=np.int64) bc = np.array([[1,0],[0,1]],dtype=np.float64) W = igl.bbw(V,F,b,bc) V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) T = np.array([[0,1,2,3]],dtype=np.int64) -F = igl.boundary_facets(T) -F,J = igl.boundary_facets(T,return_J=True) -F,K = igl.boundary_facets(T,return_K=True) -F,J,K = igl.boundary_facets(T,return_J=True,return_K=True) +F,J,K = igl.boundary_facets(T) tree = igl.AABB() tree.init(V,T) @@ -101,42 +96,36 @@ tree = igl.AABB() tree.init(V,F) -sqrD = tree.squared_distance(V,F,P) -sqrD,I = tree.squared_distance(V,F,P,return_I=True) -sqrD,C = tree.squared_distance(V,F,P,return_C=True) -sqrD,I,C = tree.squared_distance(V,F,P,return_I=True,return_C=True) +sqrD,_,_ = tree.squared_distance(V,F,P) O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); D = V.mean(axis=0)-O -I,T,UV = tree.intersect_ray(V,F,O,D,first=True) -hits = tree.intersect_ray(V,F,O,D,first=False) +I,T,UV = tree.intersect_ray_first(V,F,O,D) +hits = tree.intersect_ray(V,F,O,D) o = O[0,:] d = D[0,:] hit = igl.ray_mesh_intersect(o,d,V,F,first=True) hits = igl.ray_mesh_intersect(o,d,V,F) -E,uE,EMAP = igl.unique_edge_map(F) -E,uE,EMAP,uE2E = igl.unique_edge_map(F,return_uE2E = True) -E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F,return_uEC_uEE = True) +E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) +E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) +EF,EI = igl.edge_flaps(F,uE,EMAP) +uE,EMAP,EF,EI = igl.edge_flaps(F) +Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) + -TT = igl.triangle_triangle_adjacency(F) -TT,TTi = igl.triangle_triangle_adjacency(F,return_TTi=True) -TT = igl.triangle_triangle_adjacency(F,use_lists=True) -TT,TTi = igl.triangle_triangle_adjacency(F,use_lists=True,return_TTi=True) -VF,NI = igl.vertex_triangle_adjacency(F) +TT,TTi = igl.triangle_triangle_adjacency(F) +TT,TTi = igl.triangle_triangle_adjacency_lists(F) + VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) -VF = igl.vertex_triangle_adjacency(F,use_lists=True) -VF,VFi = igl.vertex_triangle_adjacency(F,use_lists=True,return_VFi=True) +VF,VFi = igl.vertex_triangle_adjacency(F) F012 = F; F120 = np.roll(F012,1,axis=1) FF = np.vstack((F012,F120)) -F = igl.unique_simplices(FF) -F,IA = igl.unique_simplices(FF,return_IA=True) -F,IC = igl.unique_simplices(FF,return_IC=True) -F,IA,IC = igl.unique_simplices(FF,return_IA=True,return_IC=True) +F,IA,IC = igl.unique_simplices(FF) L = igl.cotmatrix(V,F) @@ -144,10 +133,7 @@ A = (L != 0).astype(np.int64) # subtract diagonal A = A - scipy.sparse.diags(A.diagonal()) -n = igl.connected_components(A) -n,C = igl.connected_components(A,return_C=True) -n,K = igl.connected_components(A,return_K=True) -n,C,K = igl.connected_components(A,return_C=True,return_K=True) +n,C,K = igl.connected_components(A) A = -igl.cotmatrix(V,F) @@ -166,7 +152,7 @@ vol = igl.volume(L=L) vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) -F = igl.boundary_facets(T) +F,_,_ = igl.boundary_facets(T) # remove last face F = F[:-1,:] B = igl.is_border_vertex(F) @@ -178,30 +164,26 @@ E = igl.oriented_facets(F) F = igl.oriented_facets(T) -res = igl.is_edge_manifold(F) -# 4 choose 1 -res,BF = igl.is_edge_manifold(F,return_BF=True) -res,E = igl.is_edge_manifold(F,return_E=True) -res,EMAP = igl.is_edge_manifold(F,return_EMAP=True) -res,BE = igl.is_edge_manifold(F,return_BE=True) -# 4 choose 2 -res,BF,EF = igl.is_edge_manifold(F,return_BF=True,return_E=True) -res,BF,EMAP = igl.is_edge_manifold(F,return_BF=True,return_EMAP=True) -res,BF,BE = igl.is_edge_manifold(F,return_BF=True,return_BE=True) -res,EF,EMAP = igl.is_edge_manifold(F,return_E=True,return_EMAP=True) -res,EF,BE = igl.is_edge_manifold(F,return_E=True,return_BE=True) -res,EMAP,BE = igl.is_edge_manifold(F,return_EMAP=True,return_BE=True) -# 4 choose 3 -res,BF,EF,EMAP = igl.is_edge_manifold(F,return_BF=True,return_E=True,return_EMAP=True) -res,BF,EF,BE = igl.is_edge_manifold(F,return_BF=True,return_E=True,return_BE=True) -res,BF,EMAP,BE = igl.is_edge_manifold(F,return_BF=True,return_EMAP=True,return_BE=True) -res,EF,EMAP,BE = igl.is_edge_manifold(F,return_E=True,return_EMAP=True,return_BE=True) -# 4 choose 4 -res,BF,EF,EMAP,BE = igl.is_edge_manifold(F,return_BF=True,return_E=True,return_EMAP=True,return_BE=True) +res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) +res,_,_,_,_ = igl.is_edge_manifold(F) L = igl.cotmatrix(V,F) s = igl.matlab_format(V,"V") s = igl.matlab_format_index(F,"F") s = igl.matlab_format(L,"L") +B1,B2,B3 = igl.local_basis(V,F) + +G = igl.grad(V,F) + +# random randn 3x3 matrix +A = np.random.randn(3,3).astype(np.float64) +igl.polar_svd(A) +out = igl.polar_svd(A) +R,T,_,_,_ = igl.polar_svd(A) +R,T,U,S,V = igl.polar_svd(A,include_reflections=True) + +SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) +SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) + From be0e9c030e9fc47e4f335400faaab64a8792aa22 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Mon, 11 Nov 2024 10:32:58 -0500 Subject: [PATCH 007/102] more bindings --- CMakeLists.txt | 2 +- missing.sh | 5 ++ src/adjacency_matrix.cpp | 53 ++++++++++++++++++++ src/grid.cpp | 32 ++++++++++++ src/on_boundary.cpp | 38 ++++++++++++++ src/per_vertex_normals.cpp | 93 +++++++++++++++++++++++++++++++++++ src/project.cpp | 42 ++++++++++++++++ src/remove_unreferenced.cpp | 41 ++++++++++++++++ src/signed_distance.cpp | 86 ++++++++++++++++++++++++++++++++ src/unproject.cpp | 42 ++++++++++++++++ src/writePLY.cpp | 98 +++++++++++++++++++++++++++++++++++++ tests/test.py | 31 ++++++++++-- 12 files changed, 559 insertions(+), 4 deletions(-) create mode 100644 src/adjacency_matrix.cpp create mode 100644 src/grid.cpp create mode 100644 src/on_boundary.cpp create mode 100644 src/per_vertex_normals.cpp create mode 100644 src/project.cpp create mode 100644 src/remove_unreferenced.cpp create mode 100644 src/signed_distance.cpp create mode 100644 src/unproject.cpp create mode 100644 src/writePLY.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f6a476e..3f8f7f72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ FetchContent_MakeAvailable(nanobind) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 1410ad43dff78bfaf8d8103de422fa64adfa35d5 + GIT_TAG b354782fbc527e846a1933d755001dd23b79d80f ) FetchContent_MakeAvailable(libigl) diff --git a/missing.sh b/missing.sh index 352b169a..3036742d 100644 --- a/missing.sh +++ b/missing.sh @@ -5,6 +5,7 @@ skip="igl_inline EPS sparse setdiff +ARAPEnergyType rotation_matrix_from_directions verbose colon @@ -12,13 +13,17 @@ min_quad_with_fixed.impl find unique IGL_ASSERT +tinyply cat +speye +pathinfo Hit LinSpaced repmat repdiag unique_rows sortrows +redux sum cumsum slice diff --git a/src/adjacency_matrix.cpp b/src/adjacency_matrix.cpp new file mode 100644 index 00000000..361132c2 --- /dev/null +++ b/src/adjacency_matrix.cpp @@ -0,0 +1,53 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for adjacency_matrix with simplicial mesh (F) + auto adjacency_matrix(const nb::DRef &F) + { + Eigen::SparseMatrixI A; + igl::adjacency_matrix(F, A); + return A; + } + + // Wrapper for adjacency_matrix with polygonal mesh (I, C) + auto adjacency_matrix_polygon( + const nb::DRef &I, + const nb::DRef &C) + { + Eigen::SparseMatrixI A; + igl::adjacency_matrix(I, C, A); + return A; + } +} + +// Bind the wrapper to the Python module +void bind_adjacency_matrix(nb::module_ &m) +{ + m.def( + "adjacency_matrix", + &pyigl::adjacency_matrix, + "F"_a, +R"(Constructs the adjacency matrix for a simplicial mesh. + +@param[in] F #F by dim matrix of mesh simplices +@return A Sparse adjacency matrix of size max(F)+1 by max(F)+1)"); + + m.def( + "adjacency_matrix_polygon", + &pyigl::adjacency_matrix_polygon, + "I"_a, + "C"_a, +R"(Constructs the adjacency matrix for a polygon mesh. + +@param[in] I Vectorized list of polygon corner indices into rows of some matrix V +@param[in] C Cumulative polygon sizes such that C(i+1)-C(i) = size of the ith polygon +@return A Sparse adjacency matrix of size max(I)+1 by max(I)+1)"); +} diff --git a/src/grid.cpp b/src/grid.cpp new file mode 100644 index 00000000..2d5a0d1d --- /dev/null +++ b/src/grid.cpp @@ -0,0 +1,32 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for igl::grid + auto grid(const nb::DRef &res) + { + Eigen::MatrixXN GV; + igl::grid(res, GV); + return GV; + } +} + +// Bind the wrapper to the Python module +void bind_grid(nb::module_ &m) +{ + m.def( + "grid", + &pyigl::grid, + "res"_a, +R"(Construct vertices of a regular grid. + +@param[in] res Vector containing the number of vertices along each dimension +@return GV Matrix containing grid vertex positions suitable for input to igl::marching_cubes.)"); +} diff --git a/src/on_boundary.cpp b/src/on_boundary.cpp new file mode 100644 index 00000000..251cc8ff --- /dev/null +++ b/src/on_boundary.cpp @@ -0,0 +1,38 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto on_boundary( + const nb::DRef &T) + { + Eigen::VectorX I; // Output: boundary indicator for each element + Eigen::Matrix C; // Output: boundary facets per element + + igl::on_boundary(T, I, C); + + return std::make_tuple(I, C); + } +} + +// Bind the wrapper to the Python module +void bind_on_boundary(nb::module_ &m) +{ + m.def( + "on_boundary", + &pyigl::on_boundary, + "T"_a, +R"(Determine boundary facets of mesh elements stored in T. + +@param[in] T m by 3|4 list of triangle or tetrahedron indices, where m is the number of elements +@return Tuple containing: + - I: m-length vector of bools indicating if each element is on the boundary + - C: m by 3|4 matrix of bools indicating if each opposite facet is on the boundary)"); +} diff --git a/src/per_vertex_normals.cpp b/src/per_vertex_normals.cpp new file mode 100644 index 00000000..ef9a2edd --- /dev/null +++ b/src/per_vertex_normals.cpp @@ -0,0 +1,93 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto weight_enum_from_string(const std::string &weighting) + { + igl::PerVertexNormalsWeightingType weight_enum; + if (weighting == "uniform") + { + weight_enum = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM; + } + else if (weighting == "area") + { + weight_enum = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA; + } + else if (weighting == "angle") + { + weight_enum = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE; + } + else if (weighting == "default") + { + weight_enum = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT; + } + else + { + throw std::invalid_argument("Invalid weighting type: " + weighting); + } + return weight_enum; + } + + auto per_vertex_normals( + const nb::DRef &V, + const nb::DRef &F, + const std::string &weighting) + { + auto weight_enum = weight_enum_from_string(weighting); + Eigen::MatrixXN N; + igl::per_vertex_normals(V, F, weight_enum, N); + return N; + } + auto per_vertex_normals_FN( + const nb::DRef &V, + const nb::DRef &F, + const std::string &weighting, + const nb::DRef &FN) + { + auto weight_enum = weight_enum_from_string(weighting); + Eigen::MatrixXN N; + igl::per_vertex_normals(V, F, weight_enum, FN, N); + return N; + } +} + +// Bind the wrapper to the Python module +void bind_per_vertex_normals(nb::module_ &m) +{ + m.def( + "per_vertex_normals", + &pyigl::per_vertex_normals, + "V"_a, + "F"_a, + "weighting"_a = "default", +R"(Compute per-vertex normals with optional weighting and face normals. + +@param[in] V #V by 3 matrix of vertex positions +@param[in] F #F by 3 matrix of face indices +@param[in] weighting Optional string for weighting type ("uniform", "area", "angle", or "default") +@return N #V by 3 matrix of vertex normals)"); + m.def( + "per_vertex_normals", + &pyigl::per_vertex_normals_FN, + "V"_a, + "F"_a, + "weighting"_a = "default", + "FN"_a, +R"(Compute per-vertex normals with optional weighting and face normals. + +@param[in] V #V by 3 matrix of vertex positions +@param[in] F #F by 3 matrix of face indices +@param[in] weighting Optional string for weighting type ("uniform", "area", "angle", or "default") +@param[in] FN Optional #F by 3 matrix of face normals +@return N #V by 3 matrix of vertex normals)"); +} diff --git a/src/project.cpp b/src/project.cpp new file mode 100644 index 00000000..85f1a546 --- /dev/null +++ b/src/project.cpp @@ -0,0 +1,42 @@ +#include "default_types.h" +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for project with batch mode + auto project( + const nb::DRef &scene, + const nb::DRef &model, + const nb::DRef &proj, + const nb::DRef &viewport) + { + Eigen::MatrixXN win; + igl::project(scene, model, proj, viewport, win); + return win; + } +} + +// Bind the wrapper to the Python module +void bind_project(nb::module_ &m) +{ + m.def( + "project", + &pyigl::project, + "scene"_a, + "model"_a, + "proj"_a, + "viewport"_a, +R"(Eigen reimplementation of gluUnproject for batch processing. + +@param[in] scne #P by 3 matrix of screen space x, y, and z coordinates +@param[in] model 4x4 model-view matrix +@param[in] proj 4x4 projection matrix +@param[in] viewport 4-long viewport vector +@return win #P by 3 matrix of the projected x, y, and z coordinates)"); + +} diff --git a/src/remove_unreferenced.cpp b/src/remove_unreferenced.cpp new file mode 100644 index 00000000..a23d6b16 --- /dev/null +++ b/src/remove_unreferenced.cpp @@ -0,0 +1,41 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto remove_unreferenced( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::MatrixXN NV; + Eigen::MatrixXI NF; + Eigen::VectorXI I,J; + igl::remove_unreferenced(V, F, NV, NF, I, J); + return std::make_tuple(NV, NF, I, J); + } +} + +// Bind the wrapper to the Python module +void bind_remove_unreferenced(nb::module_ &m) +{ + m.def( + "remove_unreferenced", + &pyigl::remove_unreferenced, + "F"_a, + "n"_a=0, +R"(Remove unreferenced vertices from V, updating F accordingly +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by ss list of simplices (Values of -1 are quitely skipped) +@param[out] NF #NF by ss list of simplices +@param[out] I #V by 1 list of indices such that: NF = IM(F) and NT = IM(T) + and V(find(IM<=size(NV,1)),:) = NV +@param[out] J #NV by 1 list, such that NV = V(J,:))"); +} + diff --git a/src/signed_distance.cpp b/src/signed_distance.cpp new file mode 100644 index 00000000..823c9cac --- /dev/null +++ b/src/signed_distance.cpp @@ -0,0 +1,86 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto signed_distance_type_from_string(const std::string & sign_type_str) + { + igl::SignedDistanceType sign_type; + if (sign_type_str == "pseudonormal") + { + sign_type = igl::SIGNED_DISTANCE_TYPE_PSEUDONORMAL; + } + else if (sign_type_str == "winding_number") + { + sign_type = igl::SIGNED_DISTANCE_TYPE_WINDING_NUMBER; + } + else if (sign_type_str == "unsigned") + { + sign_type = igl::SIGNED_DISTANCE_TYPE_UNSIGNED; + } + else if (sign_type_str == "fast_winding_number") + { + sign_type = igl::SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER; + } + else if (sign_type_str == "default") + { + sign_type = igl::SIGNED_DISTANCE_TYPE_DEFAULT; + } + else + { + throw std::invalid_argument("Invalid sign_type: " + sign_type_str); + } + return sign_type; + }; + + auto signed_distance( + const nb::DRef &P, + const nb::DRef &V, + const nb::DRef &F, + const std::string &sign_type_str, + const Numeric lower_bound = -std::numeric_limits::infinity(), + const Numeric upper_bound = std::numeric_limits::infinity()) + { + const auto sign_type = signed_distance_type_from_string(sign_type_str); + Eigen::VectorXN S; + Eigen::VectorXI I; + Eigen::MatrixXN C,N; + igl::signed_distance(P, V, F, sign_type, lower_bound, upper_bound, S, I, C, N); + return std::make_tuple(S, I, C, N); + } +} + +void bind_signed_distance(nb::module_ &m) +{ + m.def( + "signed_distance", + &pyigl::signed_distance, + "P"_a, + "V"_a, + "F"_a, + "sign_type"_a = "default", + "lower_bound"_a = -std::numeric_limits::infinity(), + "upper_bound"_a = std::numeric_limits::infinity(), +R"(Computes signed distance to a mesh. + +@param[in] P #P by (2|3) list of query point positions +@param[in] V #V by (2|3) list of vertex positions +@param[in] F #F by ss list of triangle indices +@param[in] sign_type method for computing distance sign: "pseudonormal", "winding_number", "unsigned", "fast_winding_number", or "default" +@param[in] lower_bound lower bound of distances needed (default: -inf) +@param[in] upper_bound upper bound of distances needed (default: inf) +@return Tuple containing: + - S: #P list of smallest signed distances + - I: #P list of facet indices corresponding to smallest distances + - C: #P by (2|3) list of closest points + - N: #P by (2|3) list of closest normals (empty unless sign_type="pseudonormal"))"); +} diff --git a/src/unproject.cpp b/src/unproject.cpp new file mode 100644 index 00000000..d5a1e418 --- /dev/null +++ b/src/unproject.cpp @@ -0,0 +1,42 @@ +#include "default_types.h" +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for unproject with batch mode + auto unproject( + const nb::DRef &win, + const nb::DRef &model, + const nb::DRef &proj, + const nb::DRef &viewport) + { + Eigen::MatrixXN scene; + igl::unproject(win, model, proj, viewport, scene); + return scene; + } +} + +// Bind the wrapper to the Python module +void bind_unproject(nb::module_ &m) +{ + m.def( + "unproject", + &pyigl::unproject, + "win"_a, + "model"_a, + "proj"_a, + "viewport"_a, +R"(Eigen reimplementation of gluUnproject for batch processing. + +@param[in] win #P by 3 matrix of screen space x, y, and z coordinates +@param[in] model 4x4 model-view matrix +@param[in] proj 4x4 projection matrix +@param[in] viewport 4-long viewport vector +@return scene #P by 3 matrix of the unprojected x, y, and z coordinates)"); + +} diff --git a/src/writePLY.cpp b/src/writePLY.cpp new file mode 100644 index 00000000..39382c0a --- /dev/null +++ b/src/writePLY.cpp @@ -0,0 +1,98 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + void writePLY( + const std::string & filename, + const Eigen::MatrixXN & V, + const Eigen::MatrixXI & F_, + const Eigen::MatrixXI & E_, + const Eigen::MatrixXN & N, + const Eigen::MatrixXN & UV, + const Eigen::MatrixXN & VD, + const std::vector & VDheader, + const Eigen::MatrixXN & FD, + const std::vector & FDheader, + const Eigen::MatrixXN & ED, + const std::vector & EDheader, + const std::vector & comments, + std::string encoding_) + { + const auto encoding = encoding_ == "binary" ? igl::FileEncoding::Binary : igl::FileEncoding::Ascii; + + // tinyply doesn't support int64 + Eigen::MatrixXi F = F_.cast(); + Eigen::MatrixXi E = E_.cast(); + + if(!igl::writePLY( + filename, + V, + F, + E, + N, + UV, + VD, + VDheader, + FD, + FDheader, + ED, + EDheader, + comments, + encoding + )) + { + throw std::runtime_error("Error writing " + filename); + } + } +} + +// Bind the wrapper to the Python module +void bind_writePLY(nb::module_ &m) +{ + m.def( + "writePLY", + &pyigl::writePLY, + "filename"_a, + "V"_a = Eigen::MatrixXN(), + "F"_a = Eigen::MatrixXI(), + "E"_a = Eigen::MatrixXI(), + "N"_a = Eigen::MatrixXN(), + "UV"_a = Eigen::MatrixXN(), + "VD"_a = Eigen::MatrixXN(), + "VDheader"_a = std::vector(), + "FD"_a = Eigen::MatrixXN(), + "FDheader"_a = std::vector(), + "ED"_a = Eigen::MatrixXN(), + "EDheader"_a = std::vector(), + "comments"_a = std::vector(), + "encoding"_a = "binary", + R"(Write a mesh to a .ply file. + + @tparam Derived from Eigen matrix parameters + @param[in] ply_stream ply file output stream + @param[in] V (#V,3) matrix of vertex positions + @param[in] F (#F,3) list of face indices into vertex positions + @param[in] E (#E,2) list of edge indices into vertex positions + @param[in] N (#V,3) list of normals + @param[in] UV (#V,2) list of texture coordinates + @param[in] VD (#V,*) additional vertex data + @param[in] Vheader (#V) list of vertex data headers + @param[in] FD (#F,*) additional face data + @param[in] Fheader (#F) list of face data headers + @param[in] ED (#E,*) additional edge data + @param[in] Eheader (#E) list of edge data headers + @param[in] comments (*) file comments + @param[in] encoding - enum, to set binary or ascii file format + )"); + +} + diff --git a/tests/test.py b/tests/test.py index fa0ec2f1..992e09b6 100644 --- a/tests/test.py +++ b/tests/test.py @@ -56,19 +56,27 @@ l = igl.edge_lengths(V,F) igl.write_triangle_mesh("out.obj",V,F) igl.write_triangle_mesh("out.ply",V,F,encoding="binary") +igl.writePLY("out.ply",V,F) V,F = igl.read_triangle_mesh("out.ply") L = igl.cotmatrix(V,F) I = np.array([0,1,2,3],dtype=np.int64) C = np.array([0,4],dtype=np.int64) L,M,P = igl.cotmatrix_polygon(V,I,C) +A = igl.adjacency_matrix(F) +A = igl.adjacency_matrix_polygon(I,C) M = igl.massmatrix(V,F) M = igl.massmatrix(V,F,type="barycentric") M = igl.massmatrix(V,F,type="voronoi") M = igl.massmatrix(V,F,type="full") -N = igl.per_face_normals(V,F) -N = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) -N,_,_,_ = igl.per_face_normals(V,I,C) +FN = igl.per_face_normals(V,F) +FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) +FN,_,_,_ = igl.per_face_normals(V,I,C) +VN = igl.per_vertex_normals(V,F) +VN = igl.per_vertex_normals(V,F,weighting="uniform") +VN = igl.per_vertex_normals(V,F,weighting="area") +VN = igl.per_vertex_normals(V,F,weighting="angle") +VN = igl.per_vertex_normals(V,F,weighting="angle",FN=FN) dblA = igl.doublearea(V,F) dblA = igl.doublearea(l=l) @@ -77,6 +85,9 @@ sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) W = igl.winding_number(V,F,P) W = igl.winding_number(V,F,P[1,:]) +S,I,C,N = igl.signed_distance(P,V,F) +S,I,C,N = igl.signed_distance(P,V,F,sign_type="fast_winding_number") + BC = igl.barycenter(V,F) b = np.array([0,3],dtype=np.int64) @@ -156,6 +167,10 @@ # remove last face F = F[:-1,:] B = igl.is_border_vertex(F) +I,C = igl.on_boundary(F) +I,C = igl.on_boundary(T) +F = np.array([[2,1,3]],dtype=np.int64) +NV,NF,I,J = igl.remove_unreferenced(V,F) L = igl.squared_edge_lengths(V,F) C = igl.cotmatrix_entries(V,F) @@ -186,4 +201,14 @@ SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) +GV = igl.grid(np.array([2,2,2],dtype=np.int64)) + +model = np.eye(4).astype(np.float64) +proj = np.eye(4).astype(np.float64) +viewport = np.array([0,0,640,480],dtype=np.float64) +win = igl.project(V,model,proj,viewport) +scene = igl.unproject(win,model,proj,viewport) + + + From 30d2ade38802ac8c8be6fb30b6868f07e90c8c32 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Mon, 11 Nov 2024 14:27:41 -0500 Subject: [PATCH 008/102] bunch more bindings --- missing.sh | 91 +++++++++++++++++++++------------ src/adjacency_list.cpp | 38 ++++++++++++++ src/boundary_loop.cpp | 33 ++++++++++++ src/edges.cpp | 79 ++++++++++++++++++++++++++++ src/exact_geodesic.cpp | 50 ++++++++++++++++++ src/fast_winding_number.cpp | 38 ++++++++++++++ src/gaussian_curvature.cpp | 35 +++++++++++++ src/harmonic.cpp | 47 +++++++++++++++++ src/loop.cpp | 70 +++++++++++++++++++++++++ src/principal_curvature.cpp | 54 +++++++++++++++++++ src/project_to_line.cpp | 44 ++++++++++++++++ src/project_to_line_segment.cpp | 44 ++++++++++++++++ src/random_points_on_mesh.cpp | 49 ++++++++++++++++++ src/readDMAT.cpp | 38 ++++++++++++++ src/readMESH.cpp | 45 ++++++++++++++++ src/readOBJ.cpp | 50 ++++++++++++++++++ src/readOFF.cpp | 46 +++++++++++++++++ src/upsample.cpp | 70 +++++++++++++++++++++++++ src/vertex_components.cpp | 33 ++++++++++++ src/voxel_grid.cpp | 42 +++++++++++++++ src/writeDMAT.cpp | 42 +++++++++++++++ src/writeMESH.cpp | 44 ++++++++++++++++ src/writeOBJ.cpp | 63 +++++++++++++++++++++++ tests/test.py | 54 +++++++++++++++++-- 24 files changed, 1164 insertions(+), 35 deletions(-) create mode 100644 src/adjacency_list.cpp create mode 100644 src/boundary_loop.cpp create mode 100644 src/edges.cpp create mode 100644 src/exact_geodesic.cpp create mode 100644 src/fast_winding_number.cpp create mode 100644 src/gaussian_curvature.cpp create mode 100644 src/harmonic.cpp create mode 100644 src/loop.cpp create mode 100644 src/principal_curvature.cpp create mode 100644 src/project_to_line.cpp create mode 100644 src/project_to_line_segment.cpp create mode 100644 src/random_points_on_mesh.cpp create mode 100644 src/readDMAT.cpp create mode 100644 src/readMESH.cpp create mode 100644 src/readOBJ.cpp create mode 100644 src/readOFF.cpp create mode 100644 src/upsample.cpp create mode 100644 src/vertex_components.cpp create mode 100644 src/voxel_grid.cpp create mode 100644 src/writeDMAT.cpp create mode 100644 src/writeMESH.cpp create mode 100644 src/writeOBJ.cpp diff --git a/missing.sh b/missing.sh index 3036742d..9038122d 100644 --- a/missing.sh +++ b/missing.sh @@ -1,48 +1,75 @@ #!/bin/bash # already=$(ls src/*.cpp | sed -e "s/src\/\(.*\).cpp$/\1/g") -skip="igl_inline -EPS -sparse -setdiff +skip=" ARAPEnergyType -rotation_matrix_from_directions -verbose -colon -min_quad_with_fixed.impl -find -unique -IGL_ASSERT -tinyply -cat -speye -pathinfo +EPS Hit +IGL_ASSERT LinSpaced -repmat -repdiag -unique_rows -sortrows -redux -sum +PI +cat +colon cumsum -slice -get_seconds -matrix_to_list decimate_callback_types -sort -placeholders +find +get_seconds +igl_inline list_to_matrix +matrix_to_list +min_quad_with_fixed.impl parallel_for -PI" +pathinfo +placeholders +quadric_binary_plus_operator +redux +repdiag +repmat +rotation_matrix_from_directions +setdiff +slice +sort +sortrows +sparse +speye +sum +tinyply +unique +unique_rows +verbose +decimate_trivial_callbacks +edges +for_each +generate_default_urbg +max_size +min +min_size +collapse_edge +max +mode +main +gl +opengl/glfw/Viewer +C_STR +STR +" # combine these into exclude exclude=$(echo -e "$already\n$skip" | sort | uniq) -echo $exclude # but don't include any from exclude # Run grep and filter out excluded files -grep -ho '#include "[^"]\+"' "$1"/*.{h,cpp} | \ - sed 's/#include "\(.*\).h"/\1/' | \ -grep -v -F -w -f <(echo "$exclude") | \ -sort | uniq -c | sort -n +include_igl=$(grep -hor '#include "[^"]\+"' /Users/alecjacobson/Repos/libigl/include/igl/ | sed 's/#include "\(.*\).h"/\1/') +# remove any .cpp includes +include_igl=$(echo "$include_igl" | grep -v -F ".cpp") +# remove any number of "../" from the beginning of the line +include_igl=$(echo "$include_igl" | sed -e 's#^\(\.\./\)*##') +# stray includes +include_igl=$(echo "$include_igl" | grep -v -F "#include") + +tutorial_igl=$(grep -hor '#include <[^>]\+>' /Users/alecjacobson/Repos/libigl/tutorial/ | sed -n 's/#include /\1/p') +tests_igl=$(grep -hor '#include <[^>]\+>' /Users/alecjacobson/Repos/libigl/tests/ | sed -n 's/#include /\1/p') +# append to include_igl +all=$(echo -e "$include_igl\n$tutorial_igl\n$tests_igl") +# +echo "$all" | grep -v -F -w -f <(echo "$exclude") | sort | uniq -c | sort -n diff --git a/src/adjacency_list.cpp b/src/adjacency_list.cpp new file mode 100644 index 00000000..3c5efd1c --- /dev/null +++ b/src/adjacency_list.cpp @@ -0,0 +1,38 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for the first overload of adjacency_list for triangle meshes + auto adjacency_list( + const nb::DRef &F, + bool sorted) + { + std::vector> A; + igl::adjacency_list(F, A, sorted); + return A; + } +} + +// Bind the wrappers to the Python module +void bind_adjacency_list(nb::module_ &m) +{ + // Binding for triangle mesh adjacency_list + m.def( + "adjacency_list", + &pyigl::adjacency_list, + "F"_a, + "sorted"_a = false, + R"(Constructs the graph adjacency list for a given triangle mesh. + +@param[in] F #F by dim list of mesh faces +@param[in] sorted Boolean flag to sort adjacency counter-clockwise +@return List of adjacent vertices for each vertex)"); + +} diff --git a/src/boundary_loop.cpp b/src/boundary_loop.cpp new file mode 100644 index 00000000..916200a6 --- /dev/null +++ b/src/boundary_loop.cpp @@ -0,0 +1,33 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for boundary_loop that returns all loops as a vector of vectors + auto boundary_loop(const nb::DRef &F) + { + std::vector> loops; + igl::boundary_loop(F, loops); + return loops; + } +} + +// Bind the wrapper to the Python module +void bind_boundary_loop(nb::module_ &m) +{ + m.def( + "boundary_loop", + &pyigl::boundary_loop, + "F"_a, +R"(Compute all ordered boundary loops for a manifold mesh. + +@param[in] F #F by dim list of mesh faces +@return List of lists of boundary vertices, where each sublist represents a loop)"); +} + diff --git a/src/edges.cpp b/src/edges.cpp new file mode 100644 index 00000000..32af1e58 --- /dev/null +++ b/src/edges.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include "default_types.h" + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Binding for edges function with face-based input + Eigen::MatrixXI edges_from_faces( + const nb::DRef &F) + { + Eigen::MatrixXI E; + igl::edges(F, E); + return E; + } + + // Binding for edges function with polygon corner indices + Eigen::MatrixXI edges_from_polygons( + const nb::DRef &I, + const nb::DRef &C) + { + Eigen::MatrixXI E; + igl::edges(I, C, E); + return E; + } + + // Binding for edges function with adjacency matrix input + Eigen::MatrixXI edges_from_adjacency( + const Eigen::SparseMatrix &A) + { + Eigen::MatrixXI E; + igl::edges(A, E); + return E; + } +} + +// Define bindings for each overload of the edges function +void bind_edges(nb::module_ &m) +{ + m.def( + "edges", + &pyigl::edges_from_faces, + "F"_a, + R"(Construct a list of unique edges from a given face matrix. + +@param[in] F #F by (3|4) matrix of mesh face indices +@return #E by 2 matrix of unique edges +)" + ); + + m.def( + "edges", + &pyigl::edges_from_polygons, + "I"_a, "C"_a, + R"(Construct a list of unique edges from a given list of polygon corner indices. + +@param[in] I Vectorized list of polygon corner indices +@param[in] C #polygons+1 list of cumulative polygon sizes +@return #E by 2 matrix of unique edges +)" + ); + + m.def( + "edges", + &pyigl::edges_from_adjacency, // Using double for adjacency matrix example + "A"_a, + R"(Construct a list of unique edges from a given adjacency matrix. + +@param[in] A #V by #V symmetric adjacency matrix +@return #E by 2 matrix of unique edges +)" + ); +} diff --git a/src/exact_geodesic.cpp b/src/exact_geodesic.cpp new file mode 100644 index 00000000..9c9ab026 --- /dev/null +++ b/src/exact_geodesic.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include "default_types.h" + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for the exact_geodesic function + Eigen::VectorXN exact_geodesic( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &VS, + const nb::DRef &FS, + const nb::DRef &VT, + const nb::DRef &FT) + { + Eigen::VectorXN D; + igl::exact_geodesic(V, F, VS, FS, VT, FT, D); + return D; + } +} + +// Bind the wrapper to the Python module +void bind_exact_geodesic(nb::module_ &m) +{ + m.def( + "exact_geodesic", + &pyigl::exact_geodesic, + "V"_a, + "F"_a, + "VS"_a=Eigen::VectorXI(), + "FS"_a=Eigen::VectorXI(), + "VT"_a=Eigen::VectorXI(), + "FT"_a=Eigen::VectorXI(), + R"(Exact geodesic algorithm for computing distances on a triangular mesh. + +@param[in] V #V by 3 matrix of vertex positions +@param[in] F #F by 3 matrix of face indices +@param[in] VS #VS by 1 vector of source vertex indices +@param[in] FS #FS by 1 vector of source face indices +@param[in] VT #VT by 1 vector of target vertex indices +@param[in] FT #FT by 1 vector of target face indices +@param[out] D #VT+#FT vector of geodesic distances from each target to the nearest source +)" + ); +} diff --git a/src/fast_winding_number.cpp b/src/fast_winding_number.cpp new file mode 100644 index 00000000..421bce2a --- /dev/null +++ b/src/fast_winding_number.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include "default_types.h" + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto fast_winding_number( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &Q) + { + Eigen::VectorXN W; // Winding number values for each query point + igl::fast_winding_number(V, F, Q, W); + return W; + } +} + +// Bind the wrapper to the Python module +void bind_fast_winding_number(nb::module_ &m) +{ + m.def( + "fast_winding_number", + &pyigl::fast_winding_number, + "V"_a, + "F"_a, + "Q"_a, +R"(Compute approximate winding number for each query point based on a triangle soup mesh. + +@param[in] V #V by 3 matrix of mesh vertex positions +@param[in] F #F by 3 matrix of triangle indices +@param[in] Q #Q by 3 matrix of query positions +@return W #Q vector of winding number values for each query point)" + ); +} diff --git a/src/gaussian_curvature.cpp b/src/gaussian_curvature.cpp new file mode 100644 index 00000000..6aa3cdd1 --- /dev/null +++ b/src/gaussian_curvature.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include "default_types.h" + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto gaussian_curvature( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::VectorXN K; // Curvature values for each vertex + igl::gaussian_curvature(V, F, K); + return K; + } +} + +// Bind the wrapper to the Python module +void bind_gaussian_curvature(nb::module_ &m) +{ + m.def( + "gaussian_curvature", + &pyigl::gaussian_curvature, + "V"_a, + "F"_a, +R"(Compute discrete Gaussian curvature at each vertex of a 3D mesh. + +@param[in] V #V by 3 matrix of vertex positions +@param[in] F #F by 3 matrix of face indices +@return K #V vector of discrete Gaussian curvature values at each vertex)" + ); +} diff --git a/src/harmonic.cpp b/src/harmonic.cpp new file mode 100644 index 00000000..80ae318d --- /dev/null +++ b/src/harmonic.cpp @@ -0,0 +1,47 @@ +#include "default_types.h" +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for the harmonic function + auto harmonic( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &b, + const nb::DRef &bc, + const int k) + { + Eigen::MatrixXN W; + if(!igl::harmonic(V, F, b, bc, k, W)) + { + throw std::runtime_error("Failed to compute harmonic map"); + } + return W; + } +} + +// Bind the wrapper to the Python module +void bind_harmonic(nb::module_ &m) +{ + m.def( + "harmonic", + &pyigl::harmonic, + "V"_a, + "F"_a, + "b"_a, + "bc"_a, + "k"_a, +R"(Compute k-harmonic weight functions "coordinates". + +@param[in] V #V by dim vertex positions +@param[in] F #F by simplex-size list of element indices +@param[in] b #b boundary indices into V +@param[in] bc #b by #W list of boundary values +@param[in] k power of harmonic operation (1: harmonic, 2: biharmonic, etc) +@return W #V by #W list of weights)"); +} diff --git a/src/loop.cpp b/src/loop.cpp new file mode 100644 index 00000000..5fa02482 --- /dev/null +++ b/src/loop.cpp @@ -0,0 +1,70 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for the first overload of loop that computes S and newF + auto loop_matrix( + const nb::DRef &F, + int n_) + { + const auto n = n_? n_ : F.maxCoeff() + 1; + Eigen::SparseMatrix S; + Eigen::MatrixXI NF; + igl::loop(n, F, S, NF); + return std::make_tuple(S, NF); + } + + // Wrapper for the second overload of loop that returns NV and NF + auto loop( + const nb::DRef &V, + const nb::DRef &F, + int number_of_subdivs) + { + Eigen::MatrixXN NV; + Eigen::MatrixXI NF; + igl::loop(V, F, NV, NF, number_of_subdivs); + return std::make_tuple(NV, NF); + } + +} + +// Bind the wrapper to the Python module +void bind_loop(nb::module_ &m) +{ + m.def( + "loop_matrix", + &pyigl::loop_matrix, + "F"_a, + "n"_a = 0, + R"(Subdivide a mesh without moving vertices. Returns the subdivision matrix and new faces. + +@param[in] n_verts Number of mesh vertices +@param[in] F #F by 3 matrix of triangle faces +@return A tuple containing: + - S: Sparse subdivision matrix + - NF: Matrix of new faces)"); + + m.def( + "loop", + &pyigl::loop, + "V"_a, + "F"_a, + "number_of_subdivs"_a = 1, + R"(Subdivide a mesh without moving vertices using loop subdivision. Returns new vertices and faces. + +@param[in] V #V by dim matrix of mesh vertices +@param[in] F #F by 3 matrix of triangle faces +@param[in] number_of_subdivs Number of subdivisions (default is 1) +@return A tuple containing: + - NV: New vertex positions with original vertices at the top + - NF: Matrix of new face indices)"); + +} diff --git a/src/principal_curvature.cpp b/src/principal_curvature.cpp new file mode 100644 index 00000000..3eef0df1 --- /dev/null +++ b/src/principal_curvature.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include "default_types.h" + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto principal_curvature( + const nb::DRef &V, + const nb::DRef &F, + unsigned radius = 5, + bool useKring = true) + { + Eigen::MatrixXN PD1; // Maximal curvature directions + Eigen::MatrixXN PD2; // Minimal curvature directions + Eigen::VectorXN PV1; // Maximal curvature values + Eigen::VectorXN PV2; // Minimal curvature values + std::vector bad_vertices; // Indices of bad vertices + + igl::principal_curvature(V, F, PD1, PD2, PV1, PV2, bad_vertices, radius, useKring); + + return std::make_tuple(PD1, PD2, PV1, PV2, bad_vertices); + } +} + +// Bind the wrapper to the Python module +void bind_principal_curvature(nb::module_ &m) +{ + m.def( + "principal_curvature", + &pyigl::principal_curvature, + "V"_a, + "F"_a, + "radius"_a = 5, + "useKring"_a = true, +R"(Compute principal curvature directions and magnitudes for each vertex in a 3D mesh. + +@param[in] V #V by 3 matrix of vertex positions +@param[in] F #F by 3 matrix of face indices (triangular mesh) +@param[in] radius controls the size of the neighborhood, where 1 corresponds to average edge length +@param[in] useKring boolean to use Kring neighborhood instead of ball neighborhood +@return Tuple containing: + - PD1: #V by 3 maximal curvature direction for each vertex + - PD2: #V by 3 minimal curvature direction for each vertex + - PV1: #V vector of maximal curvature values for each vertex + - PV2: #V vector of minimal curvature values for each vertex + - bad_vertices: list of indices of bad vertices, if any)" + ); +} diff --git a/src/project_to_line.cpp b/src/project_to_line.cpp new file mode 100644 index 00000000..2748bcb1 --- /dev/null +++ b/src/project_to_line.cpp @@ -0,0 +1,44 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for the multiple points overload of project_to_line + auto project_to_line( + const nb::DRef &P, + const nb::DRef &S, + const nb::DRef &D) + { + Eigen::VectorXN t; + Eigen::VectorXN sqrD; + igl::project_to_line(P, S, D, t, sqrD); + return std::make_tuple(t, sqrD); + } +} + +// Bind the wrapper to the Python module +void bind_project_to_line(nb::module_ &m) +{ + m.def( + "project_to_line", + &pyigl::project_to_line, + "P"_a, + "S"_a, + "D"_a, + R"(Project multiple points onto a line defined by points S and D. + + @param[in] P #P by dim list of points to be projected + @param[in] S 1 by dim starting position of line + @param[in] D 1 by dim ending position of line + @return Tuple containing: + - t: #P by 1 list of parameters along the line for each point + - sqrD: #P by 1 list of squared distances from each point to the line)"); + +} diff --git a/src/project_to_line_segment.cpp b/src/project_to_line_segment.cpp new file mode 100644 index 00000000..1b05b83a --- /dev/null +++ b/src/project_to_line_segment.cpp @@ -0,0 +1,44 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for project_to_line_segment function + auto project_to_line_segment( + const nb::DRef &P, + const nb::DRef &S, + const nb::DRef &D) + { + Eigen::VectorXN t; + Eigen::VectorXN sqrD; + igl::project_to_line_segment(P, S, D, t, sqrD); + return std::make_tuple(t, sqrD); + } +} + +// Bind the wrapper to the Python module +void bind_project_to_line_segment(nb::module_ &m) +{ + m.def( + "project_to_line_segment", + &pyigl::project_to_line_segment, + "P"_a, + "S"_a, + "D"_a, + R"(Project points onto a line segment defined by points S and D. + + @param[in] P #P by dim list of points to be projected + @param[in] S 1 by dim starting position of line segment + @param[in] D 1 by dim ending position of line segment + @return Tuple containing: + - t: #P by 1 list of parameters along the line segment for each point + - sqrD: #P by 1 list of squared distances from each point to its projection)" + ); +} diff --git a/src/random_points_on_mesh.cpp b/src/random_points_on_mesh.cpp new file mode 100644 index 00000000..7fd51f2a --- /dev/null +++ b/src/random_points_on_mesh.cpp @@ -0,0 +1,49 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for vertex_components with face indices + auto random_points_on_mesh( + const Integer n, + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::VectorXI FI; + Eigen::MatrixXN B,X; + igl::random_points_on_mesh(n, V, F, B, FI, X); + return std::make_tuple(B, FI, X); + } +} + +// Bind the wrappers to the Python module +void bind_random_points_on_mesh(nb::module_ &m) +{ + // Binding for vertex_components with adjacency matrix and counts + m.def( + "random_points_on_mesh", + &pyigl::random_points_on_mesh, + "n"_a, + "V"_a, + "F"_a, + R"( + Randomly sample a mesh (V,F) n times. + + @param[in] n number of samples + @param[in] V #V by dim list of mesh vertex positions + @param[in] F #F by 3 list of mesh triangle indices + @param[out] B n by 3 list of barycentric coordinates, ith row are coordinates of + ith sampled point in face FI(i) + @param[in] urbg An instance of UnformRandomBitGenerator (e.g., + `std::minstd_rand(0)`) + @param[out] FI n list of indices into F + @param[in,out] urbg An instance of UnformRandomBitGenerator. + @param[out] X n by dim list of sample positions.)"); +} + diff --git a/src/readDMAT.cpp b/src/readDMAT.cpp new file mode 100644 index 00000000..4f244fe3 --- /dev/null +++ b/src/readDMAT.cpp @@ -0,0 +1,38 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for readDMAT with Eigen matrix output + Eigen::MatrixXN readDMAT(const std::string &file_name) + { + Eigen::MatrixXN W; + if (!igl::readDMAT(file_name, W)) + { + throw std::runtime_error("readDMAT: Failed to read DMAT file."); + } + return W; + } + +} + +// Bind the wrapper to the Python module +void bind_readDMAT(nb::module_ &m) +{ + m.def( + "readDMAT", + &pyigl::readDMAT, + "file_name"_a, + R"(Read a matrix from a .dmat file. + + @param[in] file_name path to .dmat file + @return Eigen matrix containing read-in coefficients + )"); + +} diff --git a/src/readMESH.cpp b/src/readMESH.cpp new file mode 100644 index 00000000..72f2ab60 --- /dev/null +++ b/src/readMESH.cpp @@ -0,0 +1,45 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto readMESH(const std::string& mesh_file_name) + { + Eigen::MatrixXN V; // Vertex positions + Eigen::MatrixXI T; // Tetrahedral indices + Eigen::MatrixXI F; // Face indices + + if (!igl::readMESH(mesh_file_name, V, T, F)) + { + throw std::runtime_error("Failed to read .mesh file: " + mesh_file_name); + } + + return std::make_tuple(V, T, F); + } +} + +// Bind the wrapper to the Python module +void bind_readMESH(nb::module_ &m) +{ + m.def( + "readMESH", + &pyigl::readMESH, + "mesh_file_name"_a, +R"(Load a tetrahedral volume mesh from a .mesh file. + +@param[in] mesh_file_name Path of the .mesh file to read +@return Tuple containing: + - V: #V by 3 matrix of vertex positions + - T: #T by 4 matrix of tetrahedral indices into vertices + - F: #F by 3 matrix of face indices into vertices +@throws std::runtime_error if file reading fails +)"); +} diff --git a/src/readOBJ.cpp b/src/readOBJ.cpp new file mode 100644 index 00000000..c8991681 --- /dev/null +++ b/src/readOBJ.cpp @@ -0,0 +1,50 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto readOBJ(const std::string filename) + { + Eigen::MatrixXN V,TC,CN; + Eigen::MatrixXI F,FTC,FN; + if(!igl::readOBJ(filename,V,TC,CN,F,FTC,FN)) + { + // throw runtime exception + throw std::runtime_error("Failed to read mesh from: " + filename); + } + return std::make_tuple(V,TC,CN,F,FTC,FN); + } +} + +// Bind the wrapper to the Python module +void bind_readOBJ(nb::module_ &m) +{ + m.def( + "readOBJ", + &pyigl::readOBJ, + "filename"_a, +R"( +Read a mesh from an ascii obj file, filling in vertex positions, normals +and texture coordinates. Mesh may have faces of any number of degree + +@param[in] str path to .obj file +@param[out] V double matrix of vertex positions #V by 3 +@param[out] TC double matrix of texture coordinats #TC by 2 +@param[out] N double matrix of corner normals #N by 3 +@param[out] F #F list of face indices into vertex positions +@param[out] FTC #F list of face indices into vertex texture coordinates +@param[out] FN #F list of face indices into vertex normals)" + ); +} + + + + diff --git a/src/readOFF.cpp b/src/readOFF.cpp new file mode 100644 index 00000000..619da205 --- /dev/null +++ b/src/readOFF.cpp @@ -0,0 +1,46 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto readOFF(const std::string filename) + { + Eigen::MatrixXN V,N; + Eigen::MatrixXI F; + if(!igl::readOFF(filename,V,F,N)) + { + // throw runtime exception + throw std::runtime_error("Failed to read mesh from: " + filename ); + } + return std::make_tuple(V,F,N); + } +} + +// Bind the wrapper to the Python module +void bind_readOFF(nb::module_ &m) +{ + m.def( + "readOFF", + &pyigl::readOFF, + "filename"_a, +R"(Read mesh from an ascii .off file + +@param[in] filename path to file +@param[out] V double matrix #V by 3 +@param[out] F int matrix #F by 3 +@param[out] N,C double matrix #V by 3 normals or colors +@return true iff success)" + ); +} + + + + diff --git a/src/upsample.cpp b/src/upsample.cpp new file mode 100644 index 00000000..4a4354fe --- /dev/null +++ b/src/upsample.cpp @@ -0,0 +1,70 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for the first overload of upsample that computes S and newF + auto upsample_matrix( + const nb::DRef &F, + int n_) + { + const auto n = n_? n_ : F.maxCoeff() + 1; + Eigen::SparseMatrix S; + Eigen::MatrixXI NF; + igl::upsample(n, F, S, NF); + return std::make_tuple(S, NF); + } + + // Wrapper for the second overload of upsample that returns NV and NF + auto upsample( + const nb::DRef &V, + const nb::DRef &F, + int number_of_subdivs) + { + Eigen::MatrixXN NV; + Eigen::MatrixXI NF; + igl::upsample(V, F, NV, NF, number_of_subdivs); + return std::make_tuple(NV, NF); + } + +} + +// Bind the wrapper to the Python module +void bind_upsample(nb::module_ &m) +{ + m.def( + "upsample_matrix", + &pyigl::upsample_matrix, + "F"_a, + "n"_a = 0, + R"(Subdivide a mesh without moving vertices. Returns the subdivision matrix and new faces. + +@param[in] n_verts Number of mesh vertices +@param[in] F #F by 3 matrix of triangle faces +@return A tuple containing: + - S: Sparse subdivision matrix + - NF: Matrix of new faces)"); + + m.def( + "upsample", + &pyigl::upsample, + "V"_a, + "F"_a, + "number_of_subdivs"_a = 1, + R"(Subdivide a mesh without moving vertices using loop subdivision. Returns new vertices and faces. + +@param[in] V #V by dim matrix of mesh vertices +@param[in] F #F by 3 matrix of triangle faces +@param[in] number_of_subdivs Number of subdivisions (default is 1) +@return A tuple containing: + - NV: New vertex positions with original vertices at the top + - NF: Matrix of new face indices)"); + +} diff --git a/src/vertex_components.cpp b/src/vertex_components.cpp new file mode 100644 index 00000000..40be01c5 --- /dev/null +++ b/src/vertex_components.cpp @@ -0,0 +1,33 @@ +#include "default_types.h" +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for vertex_components with face indices + auto vertex_components( + const nb::DRef &F) + { + Eigen::VectorXI C; + igl::vertex_components(F, C); + return C; + } +} + +// Bind the wrappers to the Python module +void bind_vertex_components(nb::module_ &m) +{ + // Binding for vertex_components with adjacency matrix and counts + m.def( + "vertex_components", + &pyigl::vertex_components, + "F"_a, + R"(Compute the connected components of a graph using an adjacency matrix, returning component IDs and counts. + +@param[in] F Matrix of triangle indices +@return Vector C of component IDs per vertex)"); +} diff --git a/src/voxel_grid.cpp b/src/voxel_grid.cpp new file mode 100644 index 00000000..934166aa --- /dev/null +++ b/src/voxel_grid.cpp @@ -0,0 +1,42 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto voxel_grid( + const nb::DRef &V, + const Numeric offset, + const int s, + const int pad_count) + { + Eigen::MatrixXN GV; + Eigen::VectorXI side; + igl::voxel_grid(V, offset, s, pad_count, GV, side); + return std::make_tuple(GV, side); + } +} + +// Bind the wrappers to the Python module +void bind_voxel_grid(nb::module_ &m) +{ + m.def( + "voxel_grid", + &pyigl::voxel_grid, + "V"_a, + "offset"_a =0.0, + "s"_a, + "pad_count"_a=0, + R"(Constructs a voxel grid with an offset applied to each cell center. + +@param[in] V Matrix of input vertices +@param[in] offset Offset to add to each cell center +@param[in] s Number of cell centers on the largest side +@param[in] pad_count Number of cells beyond the box +@return Tuple (GV, side) where GV contains cell center positions and side defines grid dimensions)"); +} diff --git a/src/writeDMAT.cpp b/src/writeDMAT.cpp new file mode 100644 index 00000000..bdfe2e26 --- /dev/null +++ b/src/writeDMAT.cpp @@ -0,0 +1,42 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for writeDMAT with Eigen matrix input + bool writeDMAT( + const std::string &file_name, + const nb::DRef &W, + const bool ascii) + { + if (!igl::writeDMAT(file_name, W, ascii)) + { + throw std::runtime_error("writeDMAT: Failed to write DMAT file."); + } + return true; + } +} + +// Bind the wrappers to the Python module +void bind_writeDMAT(nb::module_ &m) +{ + m.def( + "writeDMAT", + &pyigl::writeDMAT, + "file_name"_a, + "W"_a, + "ascii"_a = true, + R"(Write a matrix to a .dmat file in ASCII or binary format. + + @param[in] file_name path to .dmat file + @param[in] W Eigen matrix containing coefficients to write + @param[in] ascii flag for ASCII format (default: true) + @return True if the operation is successful)"); +} diff --git a/src/writeMESH.cpp b/src/writeMESH.cpp new file mode 100644 index 00000000..9936a4cf --- /dev/null +++ b/src/writeMESH.cpp @@ -0,0 +1,44 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + void writeMESH( + const std::string &mesh_file_name, + const nb::DRef &V, + const nb::DRef &T, + const nb::DRef &F) + { + if (!igl::writeMESH(mesh_file_name, V, T, F)) + { + throw std::runtime_error("Failed to write .mesh file: " + mesh_file_name); + } + } +} + +// Bind the wrapper to the Python module +void bind_writeMESH(nb::module_ &m) +{ + m.def( + "writeMESH", + &pyigl::writeMESH, + "mesh_file_name"_a, + "V"_a=Eigen::MatrixXN(), + "T"_a=Eigen::MatrixXI(), + "F"_a=Eigen::MatrixXI(), +R"(Save a tetrahedral volume mesh to a .mesh file. + +@param[in] mesh_file_name Path to the .mesh file to save +@param[in] V #V by 3 matrix of vertex positions +@param[in] T #T by 4 matrix of tetrahedral indices +@param[in] F #F by 3 matrix of face indices +@throws std::runtime_error if file writing fails +)"); +} diff --git a/src/writeOBJ.cpp b/src/writeOBJ.cpp new file mode 100644 index 00000000..db2412f3 --- /dev/null +++ b/src/writeOBJ.cpp @@ -0,0 +1,63 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + void writeOBJ( + const std::string & filename, + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &CN, + const nb::DRef &FN, + const nb::DRef &TC, + const nb::DRef &FTC) + { + if(!igl::writeOBJ(filename,V,F,CN,FN,TC,FTC)) + { + // throw runtime exception + throw std::runtime_error("Failed to write mesh to: " + filename); + } + } +} + +// Bind the wrapper to the Python module +void bind_writeOBJ(nb::module_ &m) +{ + m.def( + "writeOBJ", + &pyigl::writeOBJ, + "filename"_a, + "V"_a, + "F"_a, + "CN"_a = Eigen::MatrixXN(), + "FN"_a = Eigen::MatrixXI(), + "TC"_a = Eigen::MatrixXN(), + "FTC"_a = Eigen::MatrixXI(), +R"(Write a mesh in an ascii obj file + +@param[in] str path to outputfile +@param[in] V #V by 3 mesh vertex positions +@param[in] F #F by 3|4 mesh indices into V +@param[in] CN #CN by 3 normal vectors +@param[in] FN #F by 3|4 corner normal indices into CN +@param[in] TC #TC by 2|3 texture coordinates +@param[in] FTC #F by 3|4 corner texture coord indices into TC +@return true on success, false on error + +\bug Horrifyingly, this does not have the same order of parameters as +readOBJ. + +\see readOBJ)" + ); +} + + + diff --git a/tests/test.py b/tests/test.py index 992e09b6..6d8b7e5b 100644 --- a/tests/test.py +++ b/tests/test.py @@ -55,8 +55,13 @@ V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) l = igl.edge_lengths(V,F) igl.write_triangle_mesh("out.obj",V,F) +igl.write_triangle_mesh("out.off",V,F) igl.write_triangle_mesh("out.ply",V,F,encoding="binary") igl.writePLY("out.ply",V,F) +igl.writeOBJ("out.obj",V,F) +igl.writeOBJ("out.obj",V,F,V,F,V,F) +V,F,_ = igl.readOFF("out.off") +V,_,_,F,_,_ = igl.readOBJ("out.obj") V,F = igl.read_triangle_mesh("out.ply") L = igl.cotmatrix(V,F) I = np.array([0,1,2,3],dtype=np.int64) @@ -64,6 +69,9 @@ L,M,P = igl.cotmatrix_polygon(V,I,C) A = igl.adjacency_matrix(F) A = igl.adjacency_matrix_polygon(I,C) +E = igl.edges(F) +E = igl.edges(I,C) +E = igl.edges(A) M = igl.massmatrix(V,F) M = igl.massmatrix(V,F,type="barycentric") @@ -85,6 +93,7 @@ sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) W = igl.winding_number(V,F,P) W = igl.winding_number(V,F,P[1,:]) +W = igl.fast_winding_number(V,F,P) S,I,C,N = igl.signed_distance(P,V,F) S,I,C,N = igl.signed_distance(P,V,F,sign_type="fast_winding_number") @@ -93,10 +102,16 @@ b = np.array([0,3],dtype=np.int64) bc = np.array([[1,0],[0,1]],dtype=np.float64) W = igl.bbw(V,F,b,bc) +W = igl.harmonic(V,F,b,bc,k=1) +W = igl.harmonic(V,F,b,bc,k=2) + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) T = np.array([[0,1,2,3]],dtype=np.int64) F,J,K = igl.boundary_facets(T) +igl.writeMESH("out.mesh",V,T) +igl.writeMESH("out.mesh",V,T,F=F) +V,T,F = igl.readMESH("out.mesh") tree = igl.AABB() tree.init(V,T) @@ -196,7 +211,7 @@ igl.polar_svd(A) out = igl.polar_svd(A) R,T,_,_,_ = igl.polar_svd(A) -R,T,U,S,V = igl.polar_svd(A,include_reflections=True) +R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) @@ -209,6 +224,39 @@ win = igl.project(V,model,proj,viewport) scene = igl.unproject(win,model,proj,viewport) - - +S = np.array([0,0,0],dtype=np.float64) +D = np.array([0.2,0.2,0.2],dtype=np.float64) +t,sqrD = igl.project_to_line(V,S,D) +t,sqrD = igl.project_to_line_segment(V,S,D) + +S,_ = igl.upsample_matrix(F,n=V.shape[0]) +S,_ = igl.loop_matrix(F,n=V.shape[0]) +V,F = igl.upsample(V,F,number_of_subdivs=1) +V,F = igl.loop(V,F,number_of_subdivs=1) +# remove first and last +F = F[1:-1,:] +L = igl.boundary_loop(F) + +A = igl.adjacency_list(F) +A = igl.adjacency_list(F,sorted=True) + +GV,side = igl.voxel_grid(V,s=10) +GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) + +C = igl.vertex_components(F) +B,I,X = igl.random_points_on_mesh(1000,V,F) + +igl.writeDMAT("out.dmat",V) +igl.writeDMAT("out.dmat",V,ascii=True) +V = igl.readDMAT("out.dmat") +igl.writeDMAT("out.dmat",V,ascii=False) +V = igl.readDMAT("out.dmat") + +K = igl.gaussian_curvature(V,F) +PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) +PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) + +VS = np.array([0],dtype=np.int64) +VT = np.array([1],dtype=np.int64) +D = igl.exact_geodesic(V,F,VS=VS,VT=VT) From ddbab020c871ff3bc1c1fe76a8600682ccfe7d6d Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Mon, 11 Nov 2024 16:32:44 -0500 Subject: [PATCH 009/102] cgal, tetgen, triangle modules --- .gitignore | 1 + CMakeLists.txt | 115 ++++++++++++++++++------- igl/__init__.py | 10 --- src/copyleft/cgal/mesh_boolean.cpp | 59 +++++++++++++ src/copyleft/cgal/module.cpp | 12 +++ src/copyleft/tetgen/module.cpp | 12 +++ src/copyleft/tetgen/tetrahedralize.cpp | 73 ++++++++++++++++ src/matlab_format.cpp | 30 ++++++- src/module.cpp | 2 +- src/triangle/module.cpp | 12 +++ src/triangle/triangulate.cpp | 60 +++++++++++++ tests/test.py | 37 ++++++++ 12 files changed, 378 insertions(+), 45 deletions(-) delete mode 100644 igl/__init__.py create mode 100644 src/copyleft/cgal/mesh_boolean.cpp create mode 100644 src/copyleft/cgal/module.cpp create mode 100644 src/copyleft/tetgen/module.cpp create mode 100644 src/copyleft/tetgen/tetrahedralize.cpp create mode 100644 src/triangle/module.cpp create mode 100644 src/triangle/triangulate.cpp diff --git a/.gitignore b/.gitignore index 4f8d0da4..a50ba9a6 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ junit .*.swo *~ dist/ +igl/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f8f7f72..5900b711 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,10 +29,13 @@ FetchContent_Declare( FetchContent_MakeAvailable(nanobind) # Download and set up libigl +option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) +option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) +option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG b354782fbc527e846a1933d755001dd23b79d80f + GIT_TAG 00eb7032a8944666c5c1d9941768f4b35b2f4971 ) FetchContent_MakeAvailable(libigl) @@ -58,32 +61,84 @@ option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" OFF) option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" OFF) option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" OFF) -# A module for writing bindings with our framework -file(GLOB PYIGL_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") -set(BINDING_SOURCES ${PYIGL_SOURCES}) -list(REMOVE_ITEM BINDING_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/module.cpp") # Exclude module.cpp - -# Generate the function calls based on filenames -set(BINDING_DECLARATIONS "") -foreach(source_file ${BINDING_SOURCES}) - get_filename_component(filename ${source_file} NAME_WE) - set(BINDING_DECLARATIONS "${BINDING_DECLARATIONS}extern void bind_${filename}(nb::module_ &m);\n") - set(BINDING_INVOCATIONS "${BINDING_INVOCATIONS} bind_${filename}(m);\n") -endforeach() -# make a temporary folder in the build directory to store the generated files -file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include") -# write contents into BINDING_DECLARATIONS.h and BINDING_INVOCATIONS.h -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/include/BINDING_DECLARATIONS.in" "${BINDING_DECLARATIONS}") -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/include/BINDING_INVOCATIONS.in" "${BINDING_INVOCATIONS}") - -nanobind_add_module(pyigl ${PYIGL_SOURCES}) - -target_link_libraries(pyigl PRIVATE igl::core) -target_include_directories(pyigl PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/include") -target_include_directories(pyigl PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") -set_target_properties(pyigl PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" - RUNTIME_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" - RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" - ) + +# don't need to worry about nested modules (opengl/** are the only ones and +# those probably aren't ever getting python bindings) +# +# prefix is either "", "copyleft", or "restricted" +function(pyigl_include prefix name) + string(TOUPPER "${prefix}" prefix_uc) + string(TOUPPER "${name}" name_uc) + if(prefix_uc) + string(PREPEND prefix_uc _) + endif() + string(TOLOWER "${prefix_uc}" prefix_lc) + # if(LIBIGL${prefix_uc}_${name_uc}) or name == "core" + if(LIBIGL${prefix_uc}_${name_uc} OR name STREQUAL "core") + if("${prefix}" STREQUAL "copyleft") + set(subpath "copyleft/${name}") + elseif("${name}" STREQUAL "core") + set(subpath "") + else() # "" or "restricted" + set(subpath "${name}") + endif() + file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/*.cpp") + set(BINDING_SOURCES ${sources}) + list(FILTER BINDING_SOURCES EXCLUDE REGEX ".*/module\\.cpp$") + # Generate the function calls based on filenames + set(BINDING_DECLARATIONS "") + foreach(source_file ${BINDING_SOURCES}) + get_filename_component(filename ${source_file} NAME_WE) + set(BINDING_DECLARATIONS "${BINDING_DECLARATIONS}extern void bind_${filename}(nb::module_ &m);\n") + set(BINDING_INVOCATIONS "${BINDING_INVOCATIONS} bind_${filename}(m);\n") + endforeach() + # make a temporary folder in the build directory to store the generated files + set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/include/${subpath}") + file(MAKE_DIRECTORY "${generated_dir}") + # write contents into BINDING_DECLARATIONS.h and BINDING_INVOCATIONS.h + file(WRITE "${generated_dir}/BINDING_DECLARATIONS.in" "${BINDING_DECLARATIONS}") + file(WRITE "${generated_dir}/BINDING_INVOCATIONS.in" "${BINDING_INVOCATIONS}") + + + set(target_name "pyigl${prefix_lc}_${name}") + nanobind_add_module(${target_name} ${sources}) + + if("${name}" STREQUAL "core") + target_link_libraries(${target_name} PRIVATE igl::core) + else() + target_link_libraries(${target_name} PRIVATE igl::core igl${prefix_lc}::${name}) + endif() + target_include_directories(${target_name} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/include") + target_include_directories(${target_name} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") + set(output_dir "${PYIGL_OUTPUT_DIRECTORY}/${subpath}") + file(MAKE_DIRECTORY ${output_dir}) + file(WRITE "${output_dir}/__init__.py" "from .${target_name} import *\n") + if("${name}" STREQUAL "core") + file(APPEND "${output_dir}/__init__.py" "from ._version import __version__\n") + endif() + set_target_properties(${target_name} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${output_dir}" + RUNTIME_OUTPUT_DIRECTORY "${output_dir}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${output_dir}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${output_dir}" + ) + # just to add dependency? + + if("${name}" STREQUAL "core") + else() + target_link_libraries(pyigl_core INTERFACE ${target_name}) + endif() + endif() +endfunction() + +pyigl_include("" "core") +if(LIBIGL_RESTRICTED_TRIANGLE) + pyigl_include("restricted" "triangle") +endif() +if(LIBIGL_COPYLEFT_CGAL) + pyigl_include("copyleft" "cgal") +endif() +if(LIBIGL_COPYLEFT_TETGEN) + pyigl_include("copyleft" "tetgen") +endif() + diff --git a/igl/__init__.py b/igl/__init__.py deleted file mode 100644 index ebb03af8..00000000 --- a/igl/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file is part of libigl, a simple c++ geometry processing library. -# -# Copyright (C) 2023 Alec Jacobson -# -# This Source Code Form is subject to the terms of the Mozilla Public License -# v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at http://mozilla.org/MPL/2.0/. -from .pyigl import * -from .helpers import * -from ._version import __version__ diff --git a/src/copyleft/cgal/mesh_boolean.cpp b/src/copyleft/cgal/mesh_boolean.cpp new file mode 100644 index 00000000..6f2f428f --- /dev/null +++ b/src/copyleft/cgal/mesh_boolean.cpp @@ -0,0 +1,59 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto mesh_boolean( + const nb::DRef &VA, + const nb::DRef &FA, + const nb::DRef &VB, + const nb::DRef &FB, + const std::string &type_str) + { + Eigen::MatrixXN VC; + Eigen::MatrixXI FC; + Eigen::VectorXI J; + bool success = igl::copyleft::cgal::mesh_boolean(VA, FA, VB, FB, type_str, VC, FC, J); + if(!success) + { + throw std::runtime_error("Mesh boolean failed"); + } + return std::make_tuple( VC, FC, J); + } +} + +// Bind the wrapper to the Python module +void bind_mesh_boolean(nb::module_ &m) +{ + m.def( + "mesh_boolean", + &pyigl::mesh_boolean, + "VA"_a, + "FA"_a, + "VB"_a=Eigen::MatrixXN(), + "FB"_a=Eigen::MatrixXI(), + "type_str"_a, + R"(Compute the boolean operation (union, intersection, difference, etc.) between two meshes. + +@param[in] VA #VA by dim matrix of mesh A vertices +@param[in] FA #FA by simplex_size matrix of mesh A faces +@param[in] VB #VB by dim matrix of mesh B vertices +@param[in] FB #FB by simplex_size matrix of mesh B faces +@param[in] type_str Type of boolean operation: "union", "intersection", "difference", etc. +@param[out] VC #VC by dim matrix of result vertices +@param[out] FC #FC by simplex_size matrix of result faces +@param[out] J #FC list of indices indicating which input face contributed to each result face +@return Tuple containing: + - VC: Result vertices + - FC: Result faces + - J: Face origin indices)"); +} diff --git a/src/copyleft/cgal/module.cpp b/src/copyleft/cgal/module.cpp new file mode 100644 index 00000000..5224622f --- /dev/null +++ b/src/copyleft/cgal/module.cpp @@ -0,0 +1,12 @@ +#include +namespace nb = nanobind; + +// generated by cmake +#include "copyleft/cgal/BINDING_DECLARATIONS.in" + +NB_MODULE(pyigl_copyleft_cgal, m) { + m.doc() = "libigl cgal module python bindings"; + // generated by cmake +#include "copyleft/cgal/BINDING_INVOCATIONS.in" +} + diff --git a/src/copyleft/tetgen/module.cpp b/src/copyleft/tetgen/module.cpp new file mode 100644 index 00000000..1428ef24 --- /dev/null +++ b/src/copyleft/tetgen/module.cpp @@ -0,0 +1,12 @@ +#include +namespace nb = nanobind; + +// generated by cmake +#include "copyleft/tetgen/BINDING_DECLARATIONS.in" + +NB_MODULE(pyigl_copyleft_tetgen, m) { + m.doc() = "libigl tetgen module python bindings"; + // generated by cmake +#include "copyleft/tetgen/BINDING_INVOCATIONS.in" +} + diff --git a/src/copyleft/tetgen/tetrahedralize.cpp b/src/copyleft/tetgen/tetrahedralize.cpp new file mode 100644 index 00000000..d1d38d02 --- /dev/null +++ b/src/copyleft/tetgen/tetrahedralize.cpp @@ -0,0 +1,73 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Full version with all parameters + auto tetrahedralize( + const nb::DRef& V, + const nb::DRef& F, + const nb::DRef& H, + const nb::DRef& VM, + const nb::DRef& FM, + const nb::DRef& R, + const std::string& flags) + { + Eigen::MatrixXN TV; + Eigen::MatrixXI TT, TF, TN, FT; + Eigen::VectorXI TM, TR, PT; + int num_regions = 0; + + int status = igl::copyleft::tetgen::tetrahedralize(V, F, H, VM, FM, R, flags, TV, TT, TF, TM, TR, TN, PT, FT, num_regions); + if(status != 0) + { + // throw error including status code + throw std::runtime_error("Tetrahedralization failed with status " + std::to_string(status)); + } + + return std::make_tuple(TV, TT, TF, TM, TR, TN, PT, FT, num_regions); + } +} + +void bind_tetrahedralize(nb::module_ &m) +{ + m.def("tetrahedralize", &pyigl::tetrahedralize, + "V"_a, + "F"_a=Eigen::MatrixXI(), + "H"_a=Eigen::MatrixXN(), + "VM"_a=Eigen::VectorXI(), + "FM"_a=Eigen::VectorXI(), + "R"_a=Eigen::MatrixXN(), + "flags"_a="", + R"(Mesh the interior of a surface mesh (V,F) using tetgen + +@param[in] V #V by 3 vertex position list +@param[in] F #F list of polygon face indices into V (0-indexed) +@param[in] H #H by 3 list of seed points inside holes +@param[in] VM #VM list of vertex markers +@param[in] FM #FM list of face markers +@param[in] R #R by 5 list of region attributes +@param[in] flags string of tetgen options (See tetgen documentation) e.g. + "pq1.414a0.01" tries to mesh the interior of a given surface with + quality and area constraints + "" will mesh the convex hull constrained to pass through V (ignores F) +@param[out] TV #TV by 3 vertex position list +@param[out] TT #TT by 4 list of tet face indices +@param[out] TF #TF by 3 list of triangle face indices ('f', else + `boundary_facets` is called on TT) +@param[out] TR #TT list of region ID for each tetrahedron +@param[out] TN #TT by 4 list of indices neighbors for each tetrahedron ('n') +@param[out] PT #TV list of incident tetrahedron for a vertex ('m') +@param[out] FT #TF by 2 list of tetrahedrons sharing a triface ('nn') +@param[out] num_regions Number of regions in output mesh)"); + +} diff --git a/src/matlab_format.cpp b/src/matlab_format.cpp index f26ff4f7..b4dfbed8 100644 --- a/src/matlab_format.cpp +++ b/src/matlab_format.cpp @@ -11,7 +11,6 @@ using namespace nb::literals; namespace pyigl { - // Wrapper for dense matrix formatting std::string matlab_format_dense(const nb::DRef &M, const std::string &name = "") { std::ostringstream oss; @@ -19,23 +18,32 @@ namespace pyigl return oss.str(); } - // Wrapper for sparse matrix formatting std::string matlab_format_sparse(const Eigen::SparseMatrix &S, const std::string &name = "") { return igl::matlab_format(S, name); } - // Wrapper for scalar formatting std::string matlab_format_scalar(Numeric v, const std::string &name = "") { return igl::matlab_format(v, name); } - // Wrapper for matlab_format_index std::string matlab_format_index(const nb::DRef &M, const std::string &name = "") { return igl::matlab_format_index(M, name); } + + std::string matlab_format_index_vector(const nb::DRef &M, const std::string &name = "") + { + return igl::matlab_format_index(M, name); + } + + std::string matlab_format_dense_vector(const nb::DRef &M, const std::string &name = "") + { + std::ostringstream oss; + oss << igl::matlab_format(M, name); + return oss.str(); + } } // Bind the wrapper to the Python module @@ -68,4 +76,18 @@ void bind_matlab_format(nb::module_ &m) "M"_a, "name"_a = "", R"(Format a matrix for MATLAB-style output with 1-based indexing.)"); + + m.def( + "matlab_format", + &pyigl::matlab_format_dense_vector, + "M"_a, + "name"_a = "", + R"(Format a dense matrix for MATLAB-style output.)"); + + m.def( + "matlab_format_index", + &pyigl::matlab_format_index_vector, + "M"_a, + "name"_a = "", + R"(Format a matrix for MATLAB-style output with 1-based indexing.)"); } diff --git a/src/module.cpp b/src/module.cpp index 47867f28..8d24c79f 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -4,7 +4,7 @@ namespace nb = nanobind; // generated by cmake #include "BINDING_DECLARATIONS.in" -NB_MODULE(pyigl, m) { +NB_MODULE(pyigl_core, m) { m.doc() = "libigl python bindings"; // generated by cmake #include "BINDING_INVOCATIONS.in" diff --git a/src/triangle/module.cpp b/src/triangle/module.cpp new file mode 100644 index 00000000..c9dd02d2 --- /dev/null +++ b/src/triangle/module.cpp @@ -0,0 +1,12 @@ +#include +namespace nb = nanobind; + +// generated by cmake +#include "triangle/BINDING_DECLARATIONS.in" + +NB_MODULE(pyigl_restricted_triangle, m) { + m.doc() = "libigl triangle module python bindings"; + // generated by cmake +#include "triangle/BINDING_INVOCATIONS.in" +} + diff --git a/src/triangle/triangulate.cpp b/src/triangle/triangulate.cpp new file mode 100644 index 00000000..cd08212a --- /dev/null +++ b/src/triangle/triangulate.cpp @@ -0,0 +1,60 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto triangulate( + const nb::DRef &V, + const nb::DRef &E, + const nb::DRef &H, + const nb::DRef &VM, + const nb::DRef &EM, + const std::string &flags) + { + Eigen::MatrixXN V2; + Eigen::MatrixXI F2,E2; + Eigen::VectorXI VM2,EM2; + igl::triangle::triangulate(V,E,H,VM,EM,flags,V2,F2,VM2,E2,EM2); + return std::make_tuple(V2,F2,VM2,E2,EM2); + } +} + +// Bind the wrapper to the Python module +void bind_triangulate(nb::module_ &m) +{ + m.def( + "triangulate", + &pyigl::triangulate, + "V"_a, + "E"_a = Eigen::MatrixXI(), + "H"_a = Eigen::MatrixXN(), + "VM"_a = Eigen::VectorXI(), + "EM"_a = Eigen::VectorXI(), + "flags"_a = "", +R"(Triangulate the interior of a polygon using the triangle library. + +@param[in] V #V by 2 list of 2D vertex positions +@param[in] E #E by 2 list of vertex ids forming unoriented edges of the boundary of the polygon +@param[in] H #H by 2 coordinates of points contained inside holes of the polygon +@param[in] VM #V list of markers for input vertices +@param[in] EM #E list of markers for input edges +@param[in] flags string of options pass to triangle (see triangle documentation) +@param[out] V2 #V2 by 2 coordinates of the vertives of the generated triangulation +@param[out] F2 #F2 by 3 list of indices forming the faces of the generated triangulation +@param[out] VM2 #V2 list of markers for output vertices +@param[out] E2 #E2 by 2 list of output edges +@param[out] EM2 #E2 list of markers for output edges)"); +} + + + + diff --git a/tests/test.py b/tests/test.py index 6d8b7e5b..0704f1b6 100644 --- a/tests/test.py +++ b/tests/test.py @@ -4,6 +4,8 @@ import scipy.sparse # timing import time +import warnings + #def rand_sparse(n,density): # n_features = n @@ -260,3 +262,38 @@ VT = np.array([1],dtype=np.int64) D = igl.exact_geodesic(V,F,VS=VS,VT=VT) + +try: + import igl.triangle + V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) + E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) + V,T,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") + V,T,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") + V,T,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") +except ImportError: + warnings.warn("igl.triangle not available") + pass + +try: + import igl.copyleft.tetgen + # octahedron + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) + V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") +except ImportError: + warnings.warn("igl.copyleft.tetgen not available") + pass + +try: + import igl.copyleft.cgal + # tetrahedron + VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + FA,_,_ = igl.boundary_facets(T) + # flip z + VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) + FB = FA[:,::-1] + VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") +except ImportError: + warnings.warn("igl.copyleft.tetgen not available") + pass From f8a1e1fcd3b724f01c3906298844984efd9761f0 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Mon, 11 Nov 2024 23:35:14 -0500 Subject: [PATCH 010/102] more bindings --- include/default_types.h | 10 ++-- src/decimate.cpp | 64 ++++++++++++++++++++++ src/marching_cubes.cpp | 90 +++++++++++++++++++++++++++++++ src/quad_grid.cpp | 46 ++++++++++++++++ src/slim.cpp | 111 ++++++++++++++++++++++++++++++++++++++ src/triangulated_grid.cpp | 45 ++++++++++++++++ tests/test.py | 20 ++++++- 7 files changed, 380 insertions(+), 6 deletions(-) create mode 100644 src/decimate.cpp create mode 100644 src/marching_cubes.cpp create mode 100644 src/quad_grid.cpp create mode 100644 src/slim.cpp create mode 100644 src/triangulated_grid.cpp diff --git a/include/default_types.h b/include/default_types.h index 9c84a678..2b917050 100644 --- a/include/default_types.h +++ b/include/default_types.h @@ -10,15 +10,15 @@ /////////////////////////////////////////////////////////////////////////////////// using Numeric = double; using Integer = int64_t; -constexpr auto Options = Eigen::RowMajor; +//constexpr auto Options = Eigen::RowMajor; namespace Eigen { typedef Matrix MatrixXN; typedef Matrix MatrixXI; - typedef Matrix VectorXN; - typedef Matrix VectorXI; - typedef Matrix RowVectorXN; - typedef Matrix RowVectorXI; + typedef Matrix VectorXN; + typedef Matrix VectorXI; + typedef Matrix RowVectorXN; + typedef Matrix RowVectorXI; typedef SparseMatrix SparseMatrixN; typedef SparseMatrix SparseMatrixI; } diff --git a/src/decimate.cpp b/src/decimate.cpp new file mode 100644 index 00000000..939fddf6 --- /dev/null +++ b/src/decimate.cpp @@ -0,0 +1,64 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // igl::decimate is a nightmare of poor templating. Suffer copies. + auto decimate( + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + const int max_m, + const bool block_intersections) + { + Eigen::MatrixXd U; + Eigen::MatrixXi G; + Eigen::VectorXi J,I; + igl::decimate(V,F,max_m,block_intersections,U,G,J,I); + return std::make_tuple( + U.cast().eval(), // If Numeric==double will this incur a copy? + G.cast().eval(), + J.cast().eval(), + I.cast().eval()); + } +} + +// Bind the wrapper to the Python module +void bind_decimate(nb::module_ &m) +{ + m.def( + "decimate", + &pyigl::decimate, + "V"_a, + "F"_a, + "max_m"_a = 0, + "block_intersections"_a = false, + R"(Assumes (V,F) is a manifold mesh (possibly with boundary) collapses edges +until desired number of faces is achieved. This uses default edge cost and +merged vertex placement functions {edge length, edge midpoint}. + +See \fileinfo for more details. + +@param[in] V #V by dim list of vertex positions +@param[in] F #F by 3 list of face indices into V. +@param[in] max_m desired number of output faces +@param[in] block_intersections whether to block intersections (see + intersection_blocking_collapse_edge_callbacks) +@param[out] U #U by dim list of output vertex posistions (can be same ref as V) +@param[out] G #G by 3 list of output face indices into U (can be same ref as G) +@param[out] J #G list of indices into F of birth face +@param[out] I #U list of indices into V of birth vertices +@return true if m was reached (otherwise #G > m))"); + +} + + + + diff --git a/src/marching_cubes.cpp b/src/marching_cubes.cpp new file mode 100644 index 00000000..87330d70 --- /dev/null +++ b/src/marching_cubes.cpp @@ -0,0 +1,90 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto marching_cubes( + const nb::DRef &S, + const nb::DRef &GV, + const unsigned nx, + const unsigned ny, + const unsigned nz, + const Numeric isovalue) + { + Eigen::MatrixXN V; + Eigen::MatrixXI F; + std::unordered_map E2V; + igl::marching_cubes(S,GV,nx,ny,nz,isovalue,V,F,E2V); + return std::make_tuple(V,F,E2V); + } + + auto marching_cubes_sparse( + const nb::DRef &S, + const nb::DRef &GV, + const nb::DRef &GI, + const Numeric isovalue) + { + Eigen::MatrixXN V; + Eigen::MatrixXI F; + igl::marching_cubes(S,GV,nx,ny,nz,isovalue,V,F); + return std::make_tuple(V,F); + } +} + +// Bind the wrapper to the Python module +void bind_marching_cubes(nb::module_ &m) +{ + + m.def( + "marching_cubes", + &pyigl::marching_cubes, + "S"_a, + "GV"_a, + "nx"_a, + "ny"_a, + "nz"_a, + "isovalue"_a=0, +R"(Performs marching cubes reconstruction on a grid defined by values, and +points, and generates a mesh defined by vertices and faces + +@param[in] S nx*ny*nz list of values at each grid corner + i.e. S(x + y*xres + z*xres*yres) for corner (x,y,z) +@param[in] GV nx*ny*nz by 3 array of corresponding grid corner vertex locations +@param[in] nx resolutions of the grid in x dimension +@param[in] ny resolutions of the grid in y dimension +@param[in] nz resolutions of the grid in z dimension +@param[in] isovalue the isovalue of the surface to reconstruct +@param[out] V #V by 3 list of mesh vertex positions +@param[out] F #F by 3 list of mesh triangle indices into rows of V +@param[out] E2V map from edge key to index into rows of V + +# unpack keys into (i,j,v) index triplets +EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) +)"); + + m.def( + "marching_cubes", + &pyigl::marching_cubes, + "S"_a, + "GV"_a, + "GI"_a, + "isovalue"_a=0, +R"(Performs marching cubes reconstruction on a grid defined by values, and +points, and generates a mesh defined by vertices and faces + +@param[in] S #S list of scalar field values +@param[in] GV #S by 3 list of referenced grid vertex positions +@param[in] GI #GI by 8 list of grid corner indices into rows of GV +@param[in] isovalue the isovalue of the surface to reconstruct +@param[out] V #V by 3 list of mesh vertex positions +@param[out] F #F by 3 list of mesh triangle indices into rows of V)"); +} diff --git a/src/quad_grid.cpp b/src/quad_grid.cpp new file mode 100644 index 00000000..4b67d493 --- /dev/null +++ b/src/quad_grid.cpp @@ -0,0 +1,46 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto quad_grid( + const int nx, + const int ny) + { + Eigen::MatrixXN V; + Eigen::MatrixXI Q,E; + igl::quad_grid(nx,ny, V, Q, E); + return std::make_tuple(V,Q,E); + } +} + +// Bind the wrapper to the Python module +void bind_quad_grid(nb::module_ &m) +{ + m.def( + "quad_grid", + &pyigl::quad_grid, + "nx"_a, + "ny"_a, +R"(Create a regular quad quad_grid of elements (only 2D supported, currently) Vertex +position order is compatible with `igl::quad_grid` + +@param[in] nx number of vertices in the x direction +@param[in] ny number of vertices in the y direction +@param[out] V nx*ny by 2 list of vertex positions +@param[out] Q (nx-1)*(ny-1) by 4 list of quad indices into V +@param[out] E (nx-1)*ny+(ny-1)*nx by 2 list of undirected quad edge indices into V + +\see quad_grid, triangulated_quad_grid)" + ); +} + + + diff --git a/src/slim.cpp b/src/slim.cpp new file mode 100644 index 00000000..b341bdaf --- /dev/null +++ b/src/slim.cpp @@ -0,0 +1,111 @@ +#include "default_types.h" +// Not templated at all +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto mapping_energy(const std::string str) + { + if (str == "arap") + { + return igl::MappingEnergyType::ARAP; + } + else if (str == "log_arap") + { + return igl::MappingEnergyType::LOG_ARAP; + } + else if (str == "symmetric_dirichlet") + { + return igl::MappingEnergyType::SYMMETRIC_DIRICHLET; + } + else if (str == "conformal") + { + return igl::MappingEnergyType::CONFORMAL; + } + else if (str == "exp_conformal") + { + return igl::MappingEnergyType::EXP_CONFORMAL; + } + else if (str == "exp_symmetric_dirichlet") + { + return igl::MappingEnergyType::EXP_SYMMETRIC_DIRICHLET; + } + else + { + throw std::runtime_error("Unknown energy type"); + } + } + + auto slim_precompute( + const Eigen::MatrixXd &V, // maybe copy here to avoid yet another copy in slim + const Eigen::MatrixXi &F, + const Eigen::MatrixXd &V_init, + const std::string & slim_energy_str, + const Eigen::VectorXi &b, + const Eigen::MatrixXd &bc, + const double soft_p) + { + igl::SLIMData data; + auto slim_energy = mapping_energy(slim_energy_str); + igl::slim_precompute(V, F, V_init, data, slim_energy, b, bc, soft_p); + return data; + } + + auto slim_solve( + igl::SLIMData &data, + int iter_num) + { + return igl::slim_solve(data, iter_num); + } + +} + +// Bind the wrapper to the Python module +void bind_slim(nb::module_ &m) +{ + nb::class_(m, "SLIMData") + .def(nb::init()); + m.def( + "slim_precompute", + &pyigl::slim_precompute, + "V"_a, + "F"_a, + "V_init"_a, + "slim_energy"_a, + "b"_a, + "bc"_a, + "soft_p"_a=1e5, + R"(Precompute data for SLIM optimization. + + @param[in] V #V by 3 list of mesh vertex positions + @param[in] F #F by (3|4) list of mesh elements (triangles or tetrahedra) + @param[in] V_init #V by 3 list of initial mesh vertex positions + @param[in] slim_energy Energy to minimize + @param[in] b list of boundary indices into V + @param[in] bc #b by 3 list of boundary conditions + @param[in] soft_p Soft penalty factor (can be zero)"); + m.def( + "slim_solve", + &pyigl::slim_solve, + "data"_a, + "iter_num"_a=1, + R"(Run iterations of SLIM optimization. + + @param[in] data Precomputation data structure + @param[in] iter_num Number of iterations to run + @return #V by 3 list of mesh vertex positions)"); +} + + + + + diff --git a/src/triangulated_grid.cpp b/src/triangulated_grid.cpp new file mode 100644 index 00000000..b62e9458 --- /dev/null +++ b/src/triangulated_grid.cpp @@ -0,0 +1,45 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto triangulated_grid( + const int nx, + const int ny) + { + Eigen::MatrixXN GV; + Eigen::MatrixXI GF; + igl::triangulated_grid(nx,ny, GV,GF); + return std::make_tuple(GV,GF); + } +} + +// Bind the wrapper to the Python module +void bind_triangulated_grid(nb::module_ &m) +{ + m.def( + "triangulated_grid", + &pyigl::triangulated_grid, + "nx"_a, + "ny"_a, +R"(Create a regular grid of elements (only 2D supported, currently) Vertex +position order is compatible with `igl::grid` + +@param[in] nx number of vertices in the x direction +@param[in] ny number of vertices in the y direction +@param[out] GV nx*ny by 2 list of mesh vertex positions. +@param[out] GF 2*(nx-1)*(ny-1) by 3 list of triangle indices + +\see grid, quad_grid)" + ); +} + + + diff --git a/tests/test.py b/tests/test.py index 0704f1b6..cd65ed42 100644 --- a/tests/test.py +++ b/tests/test.py @@ -218,7 +218,7 @@ SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) -GV = igl.grid(np.array([2,2,2],dtype=np.int64)) + model = np.eye(4).astype(np.float64) proj = np.eye(4).astype(np.float64) @@ -262,6 +262,24 @@ VT = np.array([1],dtype=np.int64) D = igl.exact_geodesic(V,F,VS=VS,VT=VT) +dV,dF,J,I = igl.decimate(V,F) + +V,Q,E = igl.quad_grid(3,3); +V,F = igl.triangulated_grid(3,3); +V_init = V.copy() +V_init[:,0] = V_init[:,0] * 2 +b = np.array([0,1],dtype=np.int64) +bc = V[b,:] +data = igl.slim_precompute(V,F,V_init,"symmetric_dirichlet",b,bc,soft_p=1e10) +U = igl.slim_solve(data,iter_num=1) + + +res =np.array([3,3,3],dtype=np.int64) +GV = igl.grid(res) +S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; +V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) +# unpack keys into (i,j,v) index triplets +EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) try: import igl.triangle From 6d4854a6324f8e11a091377a23e50eb885ed178e Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Tue, 12 Nov 2024 09:35:26 -0500 Subject: [PATCH 011/102] arap --- CMakeLists.txt | 2 +- include/default_types.h | 9 ++-- src/arap.cpp | 93 +++++++++++++++++++++++++++++++++ src/marching_cubes.cpp | 4 +- src/project_to_line.cpp | 4 +- src/project_to_line_segment.cpp | 4 +- tests/test.py | 15 ++++-- 7 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 src/arap.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5900b711..4153e339 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 00eb7032a8944666c5c1d9941768f4b35b2f4971 + GIT_TAG 0971aecc2349feed0dc010ed85dcece607cb0b14 ) FetchContent_MakeAvailable(libigl) diff --git a/include/default_types.h b/include/default_types.h index 2b917050..dc81cf2d 100644 --- a/include/default_types.h +++ b/include/default_types.h @@ -10,11 +10,14 @@ /////////////////////////////////////////////////////////////////////////////////// using Numeric = double; using Integer = int64_t; -//constexpr auto Options = Eigen::RowMajor; +// When using `const nb::DRef` this `Options` does not +// affect the input types (either order should result in no copy if `dtype`s +// match). It does affect return types, `Eigen::MatrixXN X; … ; return X;` +constexpr auto Options = Eigen::RowMajor; namespace Eigen { - typedef Matrix MatrixXN; - typedef Matrix MatrixXI; + typedef Matrix MatrixXN; + typedef Matrix MatrixXI; typedef Matrix VectorXN; typedef Matrix VectorXI; typedef Matrix RowVectorXN; diff --git a/src/arap.cpp b/src/arap.cpp new file mode 100644 index 00000000..322df927 --- /dev/null +++ b/src/arap.cpp @@ -0,0 +1,93 @@ +#include "default_types.h" +// Not templated at all +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto arap_precomputation( + const nb::DRef &V, + const nb::DRef &F, + const int dim, + // Will be cast to int32_t in arap, so avoid potential double copy if passed + // as int32_t + const nb::DRef &b, + igl::ARAPData &data) + { + if(!igl::arap_precomputation(V,F,dim,b,data)) + { + throw std::runtime_error("arap_precomputation failed"); + } + } + + auto arap_solve( + const nb::DRef & bc, + igl::ARAPData &data, + const nb::DRef & U0) + { + printf("pyigl::arap_solve\n"); + // igl::arap_solve expects U to be both the initial guess and the output + // not sure how to avoid this copy + Eigen::MatrixXN U = U0; + if(!igl::arap_solve(bc,data,U)) + { + throw std::runtime_error("arap_solve failed"); + } + return U; + } + +} + +// Bind the wrapper to the Python module +void bind_arap(nb::module_ &m) +{ + nb::enum_(m, "ARAPEnergyType") + .value("ARAP_ENERGY_TYPE_DEFAULT", igl::ARAPEnergyType::ARAP_ENERGY_TYPE_DEFAULT) + .value("ARAP_ENERGY_TYPE_SPOKES", igl::ARAPEnergyType::ARAP_ENERGY_TYPE_SPOKES) + .value("ARAP_ENERGY_TYPE_SPOKES_AND_RIMS", igl::ARAPEnergyType::ARAP_ENERGY_TYPE_SPOKES_AND_RIMS) + .value("ARAP_ENERGY_TYPE_ELEMENTS", igl::ARAPEnergyType::ARAP_ENERGY_TYPE_ELEMENTS) + .value("NUM_ARAP_ENERGY_TYPES", igl::ARAPEnergyType::NUM_ARAP_ENERGY_TYPES) + ; + nb::class_(m, "ARAPData") + .def(nb::init<>()) + .def_ro("n", &igl::ARAPData::n) + .def_rw("G", &igl::ARAPData::G) + .def_rw("energy", &igl::ARAPData::energy) + .def_rw("with_dynamics", &igl::ARAPData::with_dynamics) + .def_rw("f_ext", &igl::ARAPData::f_ext) + .def_rw("vel", &igl::ARAPData::vel) + .def_rw("h", &igl::ARAPData::h) + .def_rw("ym", &igl::ARAPData::ym) + .def_rw("max_iter", &igl::ARAPData::max_iter) + ; + + m.def( + "arap_precomputation", + &pyigl::arap_precomputation, + "V"_a, + "F"_a, + "dim"_a, + "b"_a, + "data"_a, + R"()"); + m.def( + "arap_solve", + &pyigl::arap_solve, + "bc"_a, + "data"_a, + "U"_a, + R"()"); +} + + + + + diff --git a/src/marching_cubes.cpp b/src/marching_cubes.cpp index 87330d70..a196c612 100644 --- a/src/marching_cubes.cpp +++ b/src/marching_cubes.cpp @@ -35,7 +35,7 @@ namespace pyigl { Eigen::MatrixXN V; Eigen::MatrixXI F; - igl::marching_cubes(S,GV,nx,ny,nz,isovalue,V,F); + igl::marching_cubes(S,GV,GI,isovalue,V,F); return std::make_tuple(V,F); } } @@ -73,7 +73,7 @@ EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.i m.def( "marching_cubes", - &pyigl::marching_cubes, + &pyigl::marching_cubes_sparse, "S"_a, "GV"_a, "GI"_a, diff --git a/src/project_to_line.cpp b/src/project_to_line.cpp index 2748bcb1..b7f0f95e 100644 --- a/src/project_to_line.cpp +++ b/src/project_to_line.cpp @@ -13,8 +13,8 @@ namespace pyigl // Wrapper for the multiple points overload of project_to_line auto project_to_line( const nb::DRef &P, - const nb::DRef &S, - const nb::DRef &D) + const nb::DRef &S, + const nb::DRef &D) { Eigen::VectorXN t; Eigen::VectorXN sqrD; diff --git a/src/project_to_line_segment.cpp b/src/project_to_line_segment.cpp index 1b05b83a..e01f3b1e 100644 --- a/src/project_to_line_segment.cpp +++ b/src/project_to_line_segment.cpp @@ -13,8 +13,8 @@ namespace pyigl // Wrapper for project_to_line_segment function auto project_to_line_segment( const nb::DRef &P, - const nb::DRef &S, - const nb::DRef &D) + const nb::DRef &S, + const nb::DRef &D) { Eigen::VectorXN t; Eigen::VectorXN sqrD; diff --git a/tests/test.py b/tests/test.py index cd65ed42..d94521e0 100644 --- a/tests/test.py +++ b/tests/test.py @@ -79,9 +79,9 @@ M = igl.massmatrix(V,F,type="barycentric") M = igl.massmatrix(V,F,type="voronoi") M = igl.massmatrix(V,F,type="full") +FN,_,_,_ = igl.per_face_normals(V,I,C) FN = igl.per_face_normals(V,F) FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) -FN,_,_,_ = igl.per_face_normals(V,I,C) VN = igl.per_vertex_normals(V,F) VN = igl.per_vertex_normals(V,F,weighting="uniform") VN = igl.per_vertex_normals(V,F,weighting="area") @@ -266,15 +266,22 @@ V,Q,E = igl.quad_grid(3,3); V,F = igl.triangulated_grid(3,3); -V_init = V.copy() +# slim needs 3D data even if the problem is 2D +V = np.c_[V, np.zeros(V.shape[0])] +V_init = V[:,:2] V_init[:,0] = V_init[:,0] * 2 b = np.array([0,1],dtype=np.int64) -bc = V[b,:] +bc = V[b,:2] data = igl.slim_precompute(V,F,V_init,"symmetric_dirichlet",b,bc,soft_p=1e10) U = igl.slim_solve(data,iter_num=1) +data = igl.ARAPData() +data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS +V = V[:,:2] +igl.arap_precomputation(V,F,V.shape[1],b,data) +U = igl.arap_solve(bc,data,U) -res =np.array([3,3,3],dtype=np.int64) +res = np.array([3,3,3],dtype=np.int64) GV = igl.grid(res) S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) From 4c1372827f67cedbb675ca053fbc9be8229e4b66 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Tue, 12 Nov 2024 11:04:13 -0500 Subject: [PATCH 012/102] rm prints --- CMakeLists.txt | 23 +++-- include/default_types.h | 5 ++ src/arap.cpp | 1 - src/copyleft/cgal/convex_hull.cpp | 32 +++++++ src/copyleft/cgal/fast_winding_number.cpp | 46 ++++++++++ src/copyleft/cgal/intersect_other.cpp | 75 ++++++++++++++++ .../cgal/intersect_with_half_space.cpp | 87 +++++++++++++++++++ .../cgal/remesh_self_intersections.cpp | 67 ++++++++++++++ src/copyleft/cgal/trim_with_solid.cpp | 44 ++++++++++ src/copyleft/module.cpp | 12 +++ src/copyleft/progressive_hulls.cpp | 42 +++++++++ src/lscm.cpp | 52 +++++++++++ src/qslim.cpp | 60 +++++++++++++ tests/test.py | 44 +++++++++- 14 files changed, 581 insertions(+), 9 deletions(-) create mode 100644 src/copyleft/cgal/convex_hull.cpp create mode 100644 src/copyleft/cgal/fast_winding_number.cpp create mode 100644 src/copyleft/cgal/intersect_other.cpp create mode 100644 src/copyleft/cgal/intersect_with_half_space.cpp create mode 100644 src/copyleft/cgal/remesh_self_intersections.cpp create mode 100644 src/copyleft/cgal/trim_with_solid.cpp create mode 100644 src/copyleft/module.cpp create mode 100644 src/copyleft/progressive_hulls.cpp create mode 100644 src/lscm.cpp create mode 100644 src/qslim.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4153e339..defac6dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,13 +29,14 @@ FetchContent_Declare( FetchContent_MakeAvailable(nanobind) # Download and set up libigl -option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) -option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) -option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) +option(LIBIGL_COPYLEFT_CORE "Build target igl_copyleft::core" ON) +option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) +option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) +option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 0971aecc2349feed0dc010ed85dcece607cb0b14 + GIT_TAG e60c377f953ae70e66ceefed42cec73aec044f92 ) FetchContent_MakeAvailable(libigl) @@ -76,13 +77,18 @@ function(pyigl_include prefix name) # if(LIBIGL${prefix_uc}_${name_uc}) or name == "core" if(LIBIGL${prefix_uc}_${name_uc} OR name STREQUAL "core") if("${prefix}" STREQUAL "copyleft") - set(subpath "copyleft/${name}") + if("${name}" STREQUAL "core") + set(subpath "copyleft") + else() + set(subpath "copyleft/${name}") + endif() elseif("${name}" STREQUAL "core") set(subpath "") else() # "" or "restricted" set(subpath "${name}") endif() file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/*.cpp") + set(BINDING_SOURCES ${sources}) list(FILTER BINDING_SOURCES EXCLUDE REGEX ".*/module\\.cpp$") # Generate the function calls based on filenames @@ -99,7 +105,6 @@ function(pyigl_include prefix name) file(WRITE "${generated_dir}/BINDING_DECLARATIONS.in" "${BINDING_DECLARATIONS}") file(WRITE "${generated_dir}/BINDING_INVOCATIONS.in" "${BINDING_INVOCATIONS}") - set(target_name "pyigl${prefix_lc}_${name}") nanobind_add_module(${target_name} ${sources}) @@ -113,7 +118,7 @@ function(pyigl_include prefix name) set(output_dir "${PYIGL_OUTPUT_DIRECTORY}/${subpath}") file(MAKE_DIRECTORY ${output_dir}) file(WRITE "${output_dir}/__init__.py" "from .${target_name} import *\n") - if("${name}" STREQUAL "core") + if("${name}" STREQUAL "core" AND "${prefix}" STREQUAL "") file(APPEND "${output_dir}/__init__.py" "from ._version import __version__\n") endif() set_target_properties(${target_name} PROPERTIES @@ -132,6 +137,9 @@ function(pyigl_include prefix name) endfunction() pyigl_include("" "core") +if(LIBIGL_COPYLEFT_CORE) + pyigl_include("copyleft" "core") +endif() if(LIBIGL_RESTRICTED_TRIANGLE) pyigl_include("restricted" "triangle") endif() @@ -142,3 +150,4 @@ if(LIBIGL_COPYLEFT_TETGEN) pyigl_include("copyleft" "tetgen") endif() + diff --git a/include/default_types.h b/include/default_types.h index dc81cf2d..85de9318 100644 --- a/include/default_types.h +++ b/include/default_types.h @@ -10,6 +10,7 @@ /////////////////////////////////////////////////////////////////////////////////// using Numeric = double; using Integer = int64_t; +using Boolean = bool; // When using `const nb::DRef` this `Options` does not // affect the input types (either order should result in no copy if `dtype`s // match). It does affect return types, `Eigen::MatrixXN X; … ; return X;` @@ -18,10 +19,14 @@ namespace Eigen { typedef Matrix MatrixXN; typedef Matrix MatrixXI; + typedef Matrix MatrixXB; typedef Matrix VectorXN; typedef Matrix VectorXI; + typedef Matrix VectorXB; typedef Matrix RowVectorXN; typedef Matrix RowVectorXI; + typedef Matrix RowVectorXB; typedef SparseMatrix SparseMatrixN; typedef SparseMatrix SparseMatrixI; + typedef SparseMatrix SparseMatrixB; } diff --git a/src/arap.cpp b/src/arap.cpp index 322df927..6dbb8c7d 100644 --- a/src/arap.cpp +++ b/src/arap.cpp @@ -33,7 +33,6 @@ namespace pyigl igl::ARAPData &data, const nb::DRef & U0) { - printf("pyigl::arap_solve\n"); // igl::arap_solve expects U to be both the initial guess and the output // not sure how to avoid this copy Eigen::MatrixXN U = U0; diff --git a/src/copyleft/cgal/convex_hull.cpp b/src/copyleft/cgal/convex_hull.cpp new file mode 100644 index 00000000..a3c3de52 --- /dev/null +++ b/src/copyleft/cgal/convex_hull.cpp @@ -0,0 +1,32 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Second overload: convex_hull with only output F + auto convex_hull(const nb::DRef &V) + { + Eigen::MatrixXI F; + igl::copyleft::cgal::convex_hull(V, F); + return F; + } +} + +// Bind the wrapper to the Python module +void bind_convex_hull(nb::module_ &m) +{ + m.def( + "convex_hull", + &pyigl::convex_hull, + "V"_a, + R"(Compute the convex hull of a set of points, returning only the triangular faces of the hull. + + @param[in] V #V by 3 matrix of input points + @return F: #F by 3 matrix of triangle indices into V)"); +} diff --git a/src/copyleft/cgal/fast_winding_number.cpp b/src/copyleft/cgal/fast_winding_number.cpp new file mode 100644 index 00000000..c2a34eb0 --- /dev/null +++ b/src/copyleft/cgal/fast_winding_number.cpp @@ -0,0 +1,46 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Overload with `expansion_order` and `beta` + auto fast_winding_number( + const nb::DRef &P, + const nb::DRef &N, + const nb::DRef &Q, + int expansion_order, + double beta) + { + Eigen::VectorXd WN; + igl::copyleft::cgal::fast_winding_number(P, N, Q, expansion_order, beta, WN); + return WN; + } +} + +// Bind the wrapper to the Python module +void bind_fast_winding_number(nb::module_ &m) +{ + m.def( + "fast_winding_number", + &pyigl::fast_winding_number, + "P"_a, + "N"_a, + "Q"_a, + "expansion_order"_a = 2, + "beta"_a = 2.0, + R"(Evaluate the fast winding number for point data with adjustable accuracy. + + @param[in] P #P by 3 list of point locations + @param[in] N #P by 3 list of point normals + @param[in] Q #Q by 3 list of query points for the winding number + @param[in] expansion_order Order of the Taylor expansion (0, 1, or 2) + @param[in] beta Barnes-Hut style accuracy parameter (recommended: 2) + @return Vector of winding number values for each query point)"); + +} diff --git a/src/copyleft/cgal/intersect_other.cpp b/src/copyleft/cgal/intersect_other.cpp new file mode 100644 index 00000000..407a4162 --- /dev/null +++ b/src/copyleft/cgal/intersect_other.cpp @@ -0,0 +1,75 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // First overload: intersect_other with detailed outputs + auto intersect_other( + const nb::DRef &VA, + const nb::DRef &FA, + const nb::DRef &VB, + const nb::DRef &FB, + const bool detect_only, + const bool first_only, + const bool stitch_all, + const bool slow_and_more_precise_rounding, + const int cutoff) + { + Eigen::MatrixXI IF; + Eigen::MatrixXN VVAB; + Eigen::MatrixXI FFAB; + Eigen::VectorXI JAB; + Eigen::VectorXI IMAB; + + igl::copyleft::cgal::RemeshSelfIntersectionsParam params( + detect_only, first_only, stitch_all, slow_and_more_precise_rounding, cutoff); + const bool success = igl::copyleft::cgal::intersect_other( + VA, FA, VB, FB, params, IF, VVAB, FFAB, JAB, IMAB); + + return std::make_tuple(IF, VVAB, FFAB, JAB, IMAB); + } + +} + +// Bind the wrapper to the Python module +void bind_intersect_other(nb::module_ &m) +{ + // First overload + m.def( + "intersect_other", + &pyigl::intersect_other, + "VA"_a, + "FA"_a, + "VB"_a, + "FB"_a, + "detect_only"_a=false, + "first_only"_a=false, + "stitch_all"_a=false, + "slow_and_more_precise_rounding"_a=false, + "cutoff"_a=1000, + R"(Detect intersecting faces between two triangle meshes, providing detailed output. + + @param[in] VA #V by 3 list of vertices for first mesh + @param[in] FA #F by 3 list of faces for first mesh + @param[in] VB #V by 3 list of vertices for second mesh + @param[in] FB #F by 3 list of faces for second mesh + @param[in] detect_only only detect intersections, do not resolve + @param[in] first_only only return first intersection + @param[in] stitch_all stitch all intersections + @param[in] slow_and_more_precise_rounding use slow and more precise rounding + @param[in] cutoff maximum number of intersections to resolve + @return Tuple containing: + - success: bool indicating if the operation succeeded + - IF: # intersecting face pairs + - VVAB: list of intersection vertex positions + - FFAB: list of triangle indices into VVAB + - JAB: list of indices into [FA;FB] denoting the birth triangle + - IMAB: indices stitching duplicates from intersections)"); + +} diff --git a/src/copyleft/cgal/intersect_with_half_space.cpp b/src/copyleft/cgal/intersect_with_half_space.cpp new file mode 100644 index 00000000..dab5f233 --- /dev/null +++ b/src/copyleft/cgal/intersect_with_half_space.cpp @@ -0,0 +1,87 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Overload with point and normal + auto intersect_with_half_space_point_normal( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &p, + const nb::DRef &n) + { + Eigen::MatrixXN VC; + Eigen::MatrixXI FC; + Eigen::VectorXI J; + + if(!igl::copyleft::cgal::intersect_with_half_space(V, F, p, n, VC, FC, J)) + { + throw std::runtime_error("Failed to intersect with half space"); + } + return std::make_tuple(VC, FC, J); + } + + // Overload with plane equation + auto intersect_with_half_space_equation( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &equ) + { + Eigen::MatrixXN VC; + Eigen::MatrixXI FC; + Eigen::VectorXI J; + + if(!igl::copyleft::cgal::intersect_with_half_space(V, F, equ, VC, FC, J)) + { + throw std::runtime_error("Failed to intersect with half space"); + } + return std::make_tuple( VC, FC, J); + } + +} + +// Bind the wrapper to the Python module +void bind_intersect_with_half_space(nb::module_ &m) +{ + m.def( + "intersect_with_half_space", + &pyigl::intersect_with_half_space_point_normal, + "V"_a, + "F"_a, + "p"_a, + "n"_a, + R"(Intersect a PWN mesh with a half-space using a point and normal. + + @param[in] V #V by 3 list of mesh vertex positions + @param[in] F #F by 3 list of triangle indices + @param[in] p 3D point on plane + @param[in] n 3D normal vector + @return Tuple containing: + - success: bool, true if successful + - VC: vertices of resulting mesh + - FC: face indices of resulting mesh + - J: birth facet indices)"); + + m.def( + "intersect_with_half_space", + &pyigl::intersect_with_half_space_equation, + "V"_a, + "F"_a, + "equ"_a, + R"(Intersect a PWN mesh with a half-space using the plane equation. + + @param[in] V #V by 3 list of mesh vertex positions + @param[in] F #F by 3 list of triangle indices + @param[in] equ Plane equation coefficients (a, b, c, d) + @return Tuple containing: + - success: bool, true if successful + - VC: vertices of resulting mesh + - FC: face indices of resulting mesh + - J: birth facet indices)"); +} diff --git a/src/copyleft/cgal/remesh_self_intersections.cpp b/src/copyleft/cgal/remesh_self_intersections.cpp new file mode 100644 index 00000000..b85c8d4b --- /dev/null +++ b/src/copyleft/cgal/remesh_self_intersections.cpp @@ -0,0 +1,67 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // First overload: remesh_self_intersections with detailed outputs + auto remesh_self_intersections( + const nb::DRef &V, + const nb::DRef &F, + const bool detect_only, + const bool first_only, + const bool stitch_all, + const bool slow_and_more_precise_rounding, + const int cutoff) + { + Eigen::MatrixXN VV; + Eigen::MatrixXI FF; + Eigen::MatrixXI IF; + Eigen::VectorXI J; + Eigen::VectorXI IM; + + igl::copyleft::cgal::RemeshSelfIntersectionsParam params( + detect_only, first_only, stitch_all, slow_and_more_precise_rounding, cutoff); + igl::copyleft::cgal::remesh_self_intersections(V, F, params, VV, FF, IF, J, IM); + return std::make_tuple(VV, FF, IF, J, IM); + } + +} + +// Bind the wrapper to the Python module +void bind_remesh_self_intersections(nb::module_ &m) +{ + // First overload + m.def( + "remesh_self_intersections", + &pyigl::remesh_self_intersections, + "V"_a, + "F"_a, + "detect_only"_a=false, + "first_only"_a=false, + "stitch_all"_a=false, + "slow_and_more_precise_rounding"_a=false, + "cutoff"_a=1000, + // Simple overload binding + R"(Resolve self-intersections in a mesh, without returning unique vertex indices (IM). + + @param[in] V #V by 3 list of vertex positions + @param[in] F #F by 3 list of face indices + @param[in] detect_only only detect intersections, do not resolve + @param[in] first_only only return first intersection + @param[in] stitch_all stitch all intersections + @param[in] slow_and_more_precise_rounding use slow and more precise rounding + @param[in] cutoff maximum number of intersections to resolve + @return Tuple containing: + - VV: remeshed vertex positions + - FF: remeshed face indices + - IF: intersecting face pairs + - J: birth triangle indices)"); + +} + diff --git a/src/copyleft/cgal/trim_with_solid.cpp b/src/copyleft/cgal/trim_with_solid.cpp new file mode 100644 index 00000000..8745c343 --- /dev/null +++ b/src/copyleft/cgal/trim_with_solid.cpp @@ -0,0 +1,44 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto trim_with_solid( + const nb::DRef & VA, + const nb::DRef & FA, + const nb::DRef & VB, + const nb::DRef & FB) + { + Eigen::MatrixXN V; + Eigen::MatrixXI F; + Eigen::VectorXB D; + Eigen::VectorXI J; + igl::copyleft::cgal::trim_with_solid(VA, FA, VB, FB, V, F, D, J); + return std::make_tuple(V, F, D, J); + } +} + +// Binding function for the Python module +void bind_trim_with_solid(nb::module_ &m) +{ + m.def( + "trim_with_solid", + &pyigl::trim_with_solid, + "VA"_a, "FA"_a, "VB"_a, "FB"_a, + R"(Trim a mesh with another solid mesh, determining which faces lie inside or outside. + + @param[in] VA Vertex positions of mesh A + @param[in] FA Triangle indices of mesh A + @param[in] VB Vertex positions of mesh B (solid) + @param[in] FB Triangle indices of mesh B + @param[out] V Output vertex positions + @param[out] F Output triangle indices + @param[out] D Boolean vector indicating if each face is inside B + @param[out] J Indices into FA showing parent triangle)"); +} diff --git a/src/copyleft/module.cpp b/src/copyleft/module.cpp new file mode 100644 index 00000000..b6312cc9 --- /dev/null +++ b/src/copyleft/module.cpp @@ -0,0 +1,12 @@ +#include +namespace nb = nanobind; + +// generated by cmake +#include "copyleft/BINDING_DECLARATIONS.in" + +NB_MODULE(pyigl_copyleft_core, m) { + m.doc() = "libigl copyleft module python bindings"; + // generated by cmake +#include "copyleft/BINDING_INVOCATIONS.in" +} + diff --git a/src/copyleft/progressive_hulls.cpp b/src/copyleft/progressive_hulls.cpp new file mode 100644 index 00000000..fd96b877 --- /dev/null +++ b/src/copyleft/progressive_hulls.cpp @@ -0,0 +1,42 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // igl::decimate is a nightmare of poor templating. Suffer copies. + auto progressive_hulls( + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + const int max_m) + { + Eigen::MatrixXd U; + Eigen::MatrixXi G; + Eigen::VectorXi J; + igl::copyleft::progressive_hulls(V,F,max_m,U,G,J); + return std::make_tuple( + U.cast().eval(), // If Numeric==double will this incur a copy? + G.cast().eval(), + J.cast().eval()); + } +} +void bind_progressive_hulls(nb::module_ &m) +{ + m.def( + "progressive_hulls", + &pyigl::progressive_hulls, + "V"_a, "F"_a, "max_m"_a=0, + R"(Performs progressive hull simplification on a mesh, collapsing edges until a target number of faces is reached. + + @param[in] V #V by dim list of vertex positions + @param[in] F #F by 3 list of face indices into V + @param[in] max_m Target number of output faces + @param[out] U Output vertex positions + @param[out] G Output face indices into U + @param[out] J Indices into F indicating the birth face for each face in G)"); +} diff --git a/src/lscm.cpp b/src/lscm.cpp new file mode 100644 index 00000000..51e21556 --- /dev/null +++ b/src/lscm.cpp @@ -0,0 +1,52 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto lscm( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &b, + const nb::DRef &bc) + { + Eigen::MatrixXN V_uv; + Eigen::SparseMatrix Q; + if(!igl::lscm(V, F, b, bc, V_uv, Q)) + { + throw std::runtime_error("igl::lscm failed"); + } + return std::make_tuple(V_uv, Q); + } +} + +// Bind the wrapper to the Python module +void bind_lscm(nb::module_ &m) +{ + m.def( + "lscm", + &pyigl::lscm, + "V"_a, + "F"_a, + "b"_a, + "bc"_a, + R"(Compute a Least-squares conformal map parametrization. + + @param[in] V #V by 3 list of mesh vertex positions + @param[in] F #F by 3 list of mesh faces (must be triangles) + @param[in] b #b list of boundary indices into V + @param[in] bc #b by 2 list of boundary values + @param[out] V_uv #V by 2 list of 2D mesh vertex positions in UV space + @param[out] Q #Vx2 by #Vx2 symmetric positive semi-definite matrix for computing LSCM energy + @return Tuple containing: + - V_uv: UV coordinates of vertices + - Q: Symmetric positive semi-definite matrix for LSCM energy)" + ); +} + diff --git a/src/qslim.cpp b/src/qslim.cpp new file mode 100644 index 00000000..dc66d20d --- /dev/null +++ b/src/qslim.cpp @@ -0,0 +1,60 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // igl::qslim is a nightmare of poor templating. Suffer copies. + auto qslim( + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + const int max_m, + const bool block_intersections) + { + Eigen::MatrixXd U; + Eigen::MatrixXi G; + Eigen::VectorXi J,I; + igl::qslim(V,F,max_m,block_intersections,U,G,J,I); + return std::make_tuple( + U.cast().eval(), // If Numeric==double will this incur a copy? + G.cast().eval(), + J.cast().eval(), + I.cast().eval()); + } +} + +// Bind the wrapper to the Python module +void bind_qslim(nb::module_ &m) +{ + m.def( + "qslim", + &pyigl::qslim, + "V"_a, + "F"_a, + "max_m"_a = 0, + "block_intersections"_a = false, + R"(Assumes (V,F) is a manifold mesh (possibly with boundary) collapses edges +until desired number of faces is achieved. This uses default edge cost and +merged vertex placement functions {edge length, edge midpoint}. + +See \fileinfo for more details. + +@param[in] V #V by dim list of vertex positions +@param[in] F #F by 3 list of face indices into V. +@param[in] max_m desired number of output faces +@param[in] block_intersections whether to block intersections (see + intersection_blocking_collapse_edge_callbacks) +@param[out] U #U by dim list of output vertex posistions (can be same ref as V) +@param[out] G #G by 3 list of output face indices into U (can be same ref as G) +@param[out] J #G list of indices into F of birth face +@param[out] I #U list of indices into V of birth vertices +@return true if m was reached (otherwise #G > m))"); + +} diff --git a/tests/test.py b/tests/test.py index d94521e0..ba29ba5e 100644 --- a/tests/test.py +++ b/tests/test.py @@ -45,6 +45,11 @@ # #time_noop() +# print(igl.matlab_format(V,"V")) +# print(igl.matlab_format_index(F,"F")) +# print(igl.matlab_format(dV,"dV")) +# print(igl.matlab_format_index(dF,"dF")) + # seed numpy's random number generator np.random.seed(42) @@ -263,6 +268,7 @@ D = igl.exact_geodesic(V,F,VS=VS,VT=VT) dV,dF,J,I = igl.decimate(V,F) +dV,dF,J,I = igl.qslim(V,F) V,Q,E = igl.quad_grid(3,3); V,F = igl.triangulated_grid(3,3); @@ -281,6 +287,8 @@ igl.arap_precomputation(V,F,V.shape[1],b,data) U = igl.arap_solve(bc,data,U) +U,Q = igl.lscm(V,F,b,bc) + res = np.array([3,3,3],dtype=np.int64) GV = igl.grid(res) S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; @@ -299,6 +307,19 @@ warnings.warn("igl.triangle not available") pass +try: + import igl.copyleft + V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + F,_,_ = igl.boundary_facets(T) + V,F = igl.loop(V,F) + + dV,dF,J = igl.copyleft.progressive_hulls(V,F) + +except ImportError: + warnings.warn("igl.copyleft not available") + pass + try: import igl.copyleft.tetgen # octahedron @@ -318,7 +339,28 @@ # flip z VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) FB = FA[:,::-1] + IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) + IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") + H = igl.copyleft.cgal.convex_hull(VC) + # concatenate A and B meshes + VC = np.vstack((VA,VB)) + FC = np.vstack((FA,FB+VA.shape[0])) + VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) + _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) + + p = np.array([0,0,0],dtype=np.float64) + n = np.array([1,1,1],dtype=np.float64) + VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) + equ = np.hstack((n,-n.dot(p))) + VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) + + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) + W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) + + VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) + except ImportError: - warnings.warn("igl.copyleft.tetgen not available") + warnings.warn("igl.copyleft.cgal not available") pass From 62183ed28098b433b3a0f690668185da560bc602 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Mon, 18 Nov 2024 15:23:51 -0500 Subject: [PATCH 013/102] bunch more bindings; embree; enums; no mqwf class --- CMakeLists.txt | 40 +++++-- setup.py | 11 +- src/AABB.cpp | 4 +- src/FileEncoding.cpp | 15 +++ src/MappingEnergyType.cpp | 21 ++++ src/arap.cpp | 1 + src/average_onto_faces.cpp | 39 ++++++ src/average_onto_vertices.cpp | 41 +++++++ src/avg_edge_length.cpp | 41 +++++++ src/boundary_loop.cpp | 17 ++- src/collapse_edge.cpp | 88 ++++++++++++++ src/copyleft/cgal/point_areas.cpp | 58 +++++++++ src/cut_mesh.cpp | 56 +++++++++ src/dual_contouring.cpp | 75 ++++++++++++ src/embree/EmbreeIntersector.cpp | 58 +++++++++ src/embree/ambient_occlusion.cpp | 71 +++++++++++ src/embree/module.cpp | 12 ++ src/embree/reorient_facets_raycast.cpp | 60 ++++++++++ src/embree/shape_diameter_function.cpp | 72 +++++++++++ src/facet_adjacency_matrix.cpp | 34 ++++++ src/facet_components.cpp | 36 ++++++ src/in_element.cpp | 47 ++++++++ src/ismember_rows.cpp | 44 +++++++ src/isolines.cpp | 60 ++++++++++ src/isolines_intrinsic.cpp | 112 +++++++++++++++++ src/kelvinlets.cpp | 69 +++++++++++ src/knn.cpp | 63 ++++++++++ src/massmatrix.cpp | 31 ++--- src/min_quad_with_fixed.cpp | 69 ++++++----- src/module.cpp | 1 + src/moments.cpp | 44 +++++++ src/octree.cpp | 61 ++++++++++ src/per_vertex_normals.cpp | 43 ++----- src/polygon_corners.cpp | 66 ++++++++++ src/polygons_to_triangles.cpp | 49 ++++++++ src/readDMAT.cpp | 4 +- src/readMESH.cpp | 6 +- src/readMSH.cpp | 66 ++++++++++ src/readOBJ.cpp | 6 +- src/readOFF.cpp | 6 +- src/read_triangle_mesh.cpp | 7 +- src/signed_distance.cpp | 43 ++----- src/slim.cpp | 35 +----- src/triangle/scaf.cpp | 99 +++++++++++++++ src/writeDMAT.cpp | 4 +- src/writeMESH.cpp | 6 +- src/writeMSH.cpp | 58 +++++++++ src/writeOBJ.cpp | 6 +- src/writePLY.cpp | 11 +- src/write_triangle_mesh.cpp | 19 +-- tests/test.py | 160 ++++++++++++++++++++----- 51 files changed, 1904 insertions(+), 241 deletions(-) create mode 100644 src/FileEncoding.cpp create mode 100644 src/MappingEnergyType.cpp create mode 100644 src/average_onto_faces.cpp create mode 100644 src/average_onto_vertices.cpp create mode 100644 src/avg_edge_length.cpp create mode 100644 src/collapse_edge.cpp create mode 100644 src/copyleft/cgal/point_areas.cpp create mode 100644 src/cut_mesh.cpp create mode 100644 src/dual_contouring.cpp create mode 100644 src/embree/EmbreeIntersector.cpp create mode 100644 src/embree/ambient_occlusion.cpp create mode 100644 src/embree/module.cpp create mode 100644 src/embree/reorient_facets_raycast.cpp create mode 100644 src/embree/shape_diameter_function.cpp create mode 100644 src/facet_adjacency_matrix.cpp create mode 100644 src/facet_components.cpp create mode 100644 src/in_element.cpp create mode 100644 src/ismember_rows.cpp create mode 100644 src/isolines.cpp create mode 100644 src/isolines_intrinsic.cpp create mode 100644 src/kelvinlets.cpp create mode 100644 src/knn.cpp create mode 100644 src/moments.cpp create mode 100644 src/octree.cpp create mode 100644 src/polygon_corners.cpp create mode 100644 src/polygons_to_triangles.cpp create mode 100644 src/readMSH.cpp create mode 100644 src/triangle/scaf.cpp create mode 100644 src/writeMSH.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index defac6dd..bf6762db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,16 @@ -cmake_minimum_required(VERSION 3.16.0) +# Refactor the cmake/setup.py build to use scikit-build +# https://github.com/wjakob/nanobind_example +# Rather than creating multiple modules and dealing with multiple __init__.py +# files, we can create a single module and use __init__.py to import the +# submodules, create one extension module with submodules +# https://stackoverflow.com/a/77020918/148668 + +cmake_minimum_required(VERSION 3.15...3.27) project(pyigl) -# use C++20 (we need at least C++17 for return value optimization) -set(CMAKE_CXX_STANDARD 20) +# ≥17 for return value optimization +# <20 for embree +set(CMAKE_CXX_STANDARD 17) if (CMAKE_VERSION VERSION_LESS 3.18) set(DEV_MODULE Development) @@ -30,13 +38,14 @@ FetchContent_MakeAvailable(nanobind) # Download and set up libigl option(LIBIGL_COPYLEFT_CORE "Build target igl_copyleft::core" ON) -option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) +option(LIBIGL_EMBREE "Build target igl::embree" ON) option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) +option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG e60c377f953ae70e66ceefed42cec73aec044f92 + GIT_TAG 7326483d8dcea0169054599ae7e09917929b4b46 ) FetchContent_MakeAvailable(libigl) @@ -58,9 +67,6 @@ include(CXXFeatures) # Generate position independent code by default set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE INTERNAL "") -option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" OFF) -option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" OFF) -option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" OFF) # don't need to worry about nested modules (opengl/** are the only ones and @@ -127,8 +133,15 @@ function(pyigl_include prefix name) LIBRARY_OUTPUT_DIRECTORY_RELEASE "${output_dir}" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${output_dir}" ) - # just to add dependency? + nanobind_add_stub( + ${target_name}_stub + MODULE ${target_name} + OUTPUT "${output_dir}/${target_name}.pyi" + PYTHON_PATH $ + DEPENDS ${target_name} + ) + # just to add dependency? if("${name}" STREQUAL "core") else() target_link_libraries(pyigl_core INTERFACE ${target_name}) @@ -140,14 +153,17 @@ pyigl_include("" "core") if(LIBIGL_COPYLEFT_CORE) pyigl_include("copyleft" "core") endif() -if(LIBIGL_RESTRICTED_TRIANGLE) - pyigl_include("restricted" "triangle") -endif() if(LIBIGL_COPYLEFT_CGAL) pyigl_include("copyleft" "cgal") endif() +if(LIBIGL_EMBREE) + pyigl_include("" "embree") +endif() if(LIBIGL_COPYLEFT_TETGEN) pyigl_include("copyleft" "tetgen") endif() +if(LIBIGL_RESTRICTED_TRIANGLE) + pyigl_include("restricted" "triangle") +endif() diff --git a/setup.py b/setup.py index 6799e6e2..245507f2 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,13 @@ import sys import platform import subprocess +import warnings +# Define a custom format warning function +def custom_formatwarning(msg, category, filename, lineno, line=None): + return f"\033[91m{category.__name__}: {msg}\033[0m\n" + +# Apply the custom warning format +warnings.formatwarning = custom_formatwarning from packaging.version import Version from setuptools import setup, Extension, find_packages @@ -24,7 +31,8 @@ def run(self): raise RuntimeError( "CMake must be installed to build the following extensions: , ".join(e.name for e in self.extensions)) - # self.debug = True + warnings.warn("Debug mode is on") + self.debug = True cmake_version = Version(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) if cmake_version < Version('3.2.0'): @@ -45,6 +53,7 @@ def build_extension(self, ext): build_args = ['--config', cfg] cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] # cmake_args += ['-DDEBUG_TRACE=ON'] + # build_args += ['-v'] if platform.system() == "Windows": cmake_generator = os.environ.get('CMAKE_GENERATOR', '') diff --git a/src/AABB.cpp b/src/AABB.cpp index 1a467cb4..93ed1579 100644 --- a/src/AABB.cpp +++ b/src/AABB.cpp @@ -12,7 +12,7 @@ using namespace nb::literals; namespace pyigl { template - void init( + void aabb_init( AABB &tree, const nb::DRef &V, const nb::DRef &Ele) @@ -97,7 +97,7 @@ void bind_AABB(nb::module_ &m) typedef igl::AABB,3> AABBN3; nb::class_(m, "AABB") .def(nb::init<>()) - .def("init", &pyigl::init, "V"_a, "Ele"_a) + .def("init", &pyigl::aabb_init, "V"_a, "Ele"_a) .def("find", &pyigl::find, "V"_a, "Ele"_a, "q"_a, "first"_a=false) .def("squared_distance", &pyigl::squared_distance, "V"_a, "Ele"_a, "P"_a) .def("intersect_ray_first",&pyigl::intersect_ray_first, "V"_a, "Ele"_a, "orig"_a, "dir"_a, "min_t"_a=std::numeric_limits::infinity()) diff --git a/src/FileEncoding.cpp b/src/FileEncoding.cpp new file mode 100644 index 00000000..5add4652 --- /dev/null +++ b/src/FileEncoding.cpp @@ -0,0 +1,15 @@ +#include "default_types.h" +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +void bind_FileEncoding(nb::module_ &m) +{ + nb::enum_(m,"FileEncoding") + .value("Ascii", igl::FileEncoding::Ascii) + .value("Binary", igl::FileEncoding::Binary) + .export_values(); +} diff --git a/src/MappingEnergyType.cpp b/src/MappingEnergyType.cpp new file mode 100644 index 00000000..a6ecd0df --- /dev/null +++ b/src/MappingEnergyType.cpp @@ -0,0 +1,21 @@ +#include "default_types.h" +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +void bind_MappingEnergyType(nb::module_ &m) +{ + nb::enum_(m, "MappingEnergyType") + .value("ARAP", igl::MappingEnergyType::ARAP) + .value("LOG_ARAP", igl::MappingEnergyType::LOG_ARAP) + .value("SYMMETRIC_DIRICHLET", igl::MappingEnergyType::SYMMETRIC_DIRICHLET) + .value("CONFORMAL", igl::MappingEnergyType::CONFORMAL) + .value("EXP_CONFORMAL", igl::MappingEnergyType::EXP_CONFORMAL) + .value("EXP_SYMMETRIC_DIRICHLET", igl::MappingEnergyType::EXP_SYMMETRIC_DIRICHLET) + .export_values() + ; +} + diff --git a/src/arap.cpp b/src/arap.cpp index 6dbb8c7d..f1db78b1 100644 --- a/src/arap.cpp +++ b/src/arap.cpp @@ -54,6 +54,7 @@ void bind_arap(nb::module_ &m) .value("ARAP_ENERGY_TYPE_SPOKES_AND_RIMS", igl::ARAPEnergyType::ARAP_ENERGY_TYPE_SPOKES_AND_RIMS) .value("ARAP_ENERGY_TYPE_ELEMENTS", igl::ARAPEnergyType::ARAP_ENERGY_TYPE_ELEMENTS) .value("NUM_ARAP_ENERGY_TYPES", igl::ARAPEnergyType::NUM_ARAP_ENERGY_TYPES) + .export_values() ; nb::class_(m, "ARAPData") .def(nb::init<>()) diff --git a/src/average_onto_faces.cpp b/src/average_onto_faces.cpp new file mode 100644 index 00000000..b7c6ccc8 --- /dev/null +++ b/src/average_onto_faces.cpp @@ -0,0 +1,39 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto average_onto_faces( + const nb::DRef &F, + const nb::DRef &S) + { + Eigen::VectorXN SF; + igl::average_onto_faces(F,S,SF); + return SF; + } +} + +// Bind the wrapper to the Python module +void bind_average_onto_faces(nb::module_ &m) +{ + m.def( + "average_onto_faces", + &pyigl::average_onto_faces, + "F"_a, + "S"_a, +R"(Move a scalar field defined on faces to faces by averaging + +@param[in] F #F by 3 triangle mesh connectivity +@param[in] S #F by 1 scalar field defined on faces +@param[out] SF #F by 1 scalar field defined on faces +)" + ); +} + + diff --git a/src/average_onto_vertices.cpp b/src/average_onto_vertices.cpp new file mode 100644 index 00000000..393dca5f --- /dev/null +++ b/src/average_onto_vertices.cpp @@ -0,0 +1,41 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto average_onto_vertices( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &S) + { + Eigen::VectorXN SV; + igl::average_onto_vertices(V,F,S,SV); + return SV; + } +} + +// Bind the wrapper to the Python module +void bind_average_onto_vertices(nb::module_ &m) +{ + m.def( + "average_onto_vertices", + &pyigl::average_onto_vertices, + "V"_a, + "F"_a, + "S"_a, +R"(Move a scalar field defined on faces to vertices by averaging + +@param[in] S #V by dim triangle mesh connectivity +@param[in] F #F by 3 triangle mesh connectivity +@param[in] S #F by 1 scalar field defined on faces +@param[out] SV #V by 1 scalar field defined on vertices +)" + ); +} + diff --git a/src/avg_edge_length.cpp b/src/avg_edge_length.cpp new file mode 100644 index 00000000..3b902833 --- /dev/null +++ b/src/avg_edge_length.cpp @@ -0,0 +1,41 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto avg_edge_length( + const nb::DRef &V, + const nb::DRef &F) + { + return (Numeric)igl::avg_edge_length(V,F); + } +} + +void bind_avg_edge_length(nb::module_ &m) +{ + m.def( + "avg_edge_length", + &pyigl::avg_edge_length, + "V"_a, + "F"_a, +R"(Constructs the cotangent stiffness matrix (discrete laplacian) for a given +mesh (V,F). + + @tparam DerivedV derived type of eigen matrix for V (e.g. derived from + MatrixXd) + @tparam DerivedF derived type of eigen matrix for F (e.g. derived from + MatrixXi) + @tparam Scalar scalar type for eigen sparse matrix (e.g. double) + @param[in] V #V by dim list of mesh vertex positions + @param[in] F #F by simplex_size list of mesh elements (triangles or tetrahedra) + @param[out] L #V by #V cotangent matrix, each row i corresponding to V(i,:))"); + +} diff --git a/src/boundary_loop.cpp b/src/boundary_loop.cpp index 916200a6..d08a854c 100644 --- a/src/boundary_loop.cpp +++ b/src/boundary_loop.cpp @@ -10,12 +10,19 @@ using namespace nb::literals; namespace pyigl { // Wrapper for boundary_loop that returns all loops as a vector of vectors - auto boundary_loop(const nb::DRef &F) + auto boundary_loop_all(const nb::DRef &F) { std::vector> loops; igl::boundary_loop(F, loops); return loops; } + // Wrapper for boundary_loop that returns all loops as a vector of vectors + auto boundary_loop(const nb::DRef &F) + { + Eigen::VectorXI longest; + igl::boundary_loop(F, longest); + return longest; + } } // Bind the wrapper to the Python module @@ -25,6 +32,14 @@ void bind_boundary_loop(nb::module_ &m) "boundary_loop", &pyigl::boundary_loop, "F"_a, +R"(Compute the ordered boundary loop with the most vertices for a manifold mesh. + +@param[in] F #F by dim list of mesh faces +@param[out] L ordered list of boundary vertices of longest boundary loop)"); + m.def( + "boundary_loop_all", + &pyigl::boundary_loop_all, + "F"_a, R"(Compute all ordered boundary loops for a manifold mesh. @param[in] F #F by dim list of mesh faces diff --git a/src/collapse_edge.cpp b/src/collapse_edge.cpp new file mode 100644 index 00000000..acc5b395 --- /dev/null +++ b/src/collapse_edge.cpp @@ -0,0 +1,88 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto collapse_edge( + const Integer e, + const nb::DRef &p, + Eigen::Ref V, + Eigen::Ref F, + Eigen::Ref E, + Eigen::Ref EMAP, + Eigen::Ref EF, + Eigen::Ref EI) + { + int e1_,e2_,f1_,f2_; + if(!igl::collapse_edge(e,p,V,F,E,EMAP,EF,EI,e1_,e2_,f1_,f2_)) + { + throw std::runtime_error("collapse_edge failed"); + } + + Integer e1 = e1_,e2 = e2_,f1 = f1_,f2 = f2_; + return std::make_tuple(e1,e2,f1,f2); + } + +} + +// Bind the wrapper to the Python module +void bind_collapse_edge(nb::module_ &m) +{ + m.def( + "collapse_edge", + &pyigl::collapse_edge, + "e"_a, + "p"_a, + nb::arg("V").noconvert(), + nb::arg("F").noconvert(), + nb::arg("E").noconvert(), + nb::arg("EMAP").noconvert(), + nb::arg("EF").noconvert(), + nb::arg("EI").noconvert(), +R"(Attempt to collapse a given edge of a mesh. Assumes (V,F) is a closed +manifold mesh (except for previously collapsed faces which should be set +to: [IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL +IGL_COLLAPSE_EDGE_NULL]. Collapses exactly two faces and exactly 3 edges +from E (e and one side of each face gets collapsed to the other). This is +implemented in a way that it can be repeatedly called until satisfaction +and then the garbage in F can be collected by removing NULL faces. + +@param[in] e index into E of edge to try to collapse. E(e,:) = [s d] or [d s] so + that sj) is the edge of + F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " + e=(j->i) +@param[in,out] EI #E by 2 list of edge flap corners (see above). +[mesh inputs] +@param[out] e1 index into E of edge collpased on left +@param[out] e2 index into E of edge collpased on right +@param[out] f1 index into F of face collpased on left +@param[out] f2 index into F of face collpased on right +@return true if edge was collapsed + +Because there are side-effects on V,F,E,EMAP,EF,EI, this function will not +accept all numpy variations and will refuse to copy inputs that don't match +expected ordering and dtype. +)"); +} + + + + diff --git a/src/copyleft/cgal/point_areas.cpp b/src/copyleft/cgal/point_areas.cpp new file mode 100644 index 00000000..b8162d10 --- /dev/null +++ b/src/copyleft/cgal/point_areas.cpp @@ -0,0 +1,58 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto point_areas( + const nb::DRef &P, + const nb::DRef &I, + const nb::DRef &N) + { + Eigen::VectorXN A; + Eigen::MatrixXN T; + igl::copyleft::cgal::point_areas(P,I,N,A,T); + return std::make_tuple(A,T); + } +} + +// Bind the wrapper to the Python module +void bind_point_areas(nb::module_ &m) +{ + m.def( + "point_areas", + &pyigl::point_areas, + "P"_a, + "I"_a, + "N"_a, + R"(Given a 3D set of points P, each with a list of k-nearest-neighbours, +estimate the geodesic voronoi area associated with each point. + +The k nearest neighbours may be known from running igl::knn_octree on +the output data from igl::octree. We reccomend using a k value +between 15 and 20 inclusive for accurate area estimation. + +N is used filter the neighbours, to ensure area estimation only occurs +using neighbors that are on the same side of the surface (ie for thin +sheets), as well as to solve the orientation ambiguity of the tangent +plane normal. + +\note This function *should* be implemented by pre-filtering I, rather +than filtering in this function using N. In this case, the function +would only take P and I as input. + +@param[in] P #P by 3 list of point locations +@param[in] I #P by k list of k-nearest-neighbor indices into P +@param[in] N #P by 3 list of point normals +@param[out] A #P list of estimated areas +@param[out] T #P by 3 list of tangent plane normals for each point + +\see igl::knn)"); +} + diff --git a/src/cut_mesh.cpp b/src/cut_mesh.cpp new file mode 100644 index 00000000..e2b55540 --- /dev/null +++ b/src/cut_mesh.cpp @@ -0,0 +1,56 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto cut_mesh( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &C) + { + Eigen::MatrixXN Vn; + Eigen::MatrixXI Fn; + Eigen::VectorXI I; + igl::cut_mesh(V,F,C,Vn,Fn,I); + return std::make_tuple(Vn,Fn,I); + } + +} + +// Bind the wrapper to the Python module +void bind_cut_mesh(nb::module_ &m) +{ + m.def( + "cut_mesh", + &pyigl::cut_mesh, + "V"_a, + "F"_a, + "C"_a, +R"(Given a mesh and a list of edges that are to be cut, the function +generates a new disk-topology mesh that has the cuts at its boundary. + + +\note Assumes mesh is edge-manifold. +@param[in,out] V #V by 3 list of the vertex positions +@param[in,out] F #F by 3 list of the faces +@param[in] cuts #F by 3 list of boolean flags, indicating the edges that need to + be cut (has 1 at the face edges that are to be cut, 0 otherwise) +@param[out] Vn #V by 3 list of the vertex positions of the cut mesh. This matrix + will be similar to the original vertices except some rows will be + duplicated. +@param[out] Fn #F by 3 list of the faces of the cut mesh(must be triangles). This + matrix will be similar to the original face matrix except some indices + will be redirected to point to the newly duplicated vertices. +@param[out] I #V by 1 list of the map between Vn to original V index. +)"); +} + + + diff --git a/src/dual_contouring.cpp b/src/dual_contouring.cpp new file mode 100644 index 00000000..0888cf79 --- /dev/null +++ b/src/dual_contouring.cpp @@ -0,0 +1,75 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto dual_contouring( + const std::function &)> &f, + const std::function(const Eigen::Matrix &)> &f_grad, + /* Let these get copied if needed */ + const Eigen::Matrix &min_corner, + const Eigen::Matrix &max_corner, + const Integer nx, + const Integer ny, + const Integer nz, + const bool constrained, + const bool triangles, + const bool root_finding) + { + Eigen::MatrixXN V; + Eigen::MatrixXI Q; + igl::dual_contouring(f,f_grad,min_corner,max_corner,nx,ny,nz,constrained,triangles,root_finding,V,Q); + return std::make_tuple(V,Q); + } +} + +// Bind the wrapper to the Python module +void bind_dual_contouring(nb::module_ &m) +{ +// See https://github.com/libigl/libigl-python-bindings/issues/194 +// m.def( +// "dual_contouring", +// &pyigl::dual_contouring, +// "f"_a, +// "f_grad"_a, +// "min_corner"_a, +// "max_corner"_a, +// "nx"_a, +// "ny"_a, +// "nz"_a, +// "constrained"_a = false, +// "triangles"_a = false, +// "root_finding"_a = false, +// R"(Dual contouring to extract a pure quad mesh from differentiable implicit +//function using a dense grid. +// +//@param[in] f function returning >0 outside, <0 inside and =0 on the surface +//@param[in] f_grad function returning ∇f/‖∇f‖ +//@param[in] min_corner position of primal grid vertex at minimum corner +//@param[in] max_corner position of primal grid vertex at maximum corner +//@param[in] nx number of vertices on x side of primal grid +//@param[in] ny number of vertices on y side of primal grid +//@param[in] nz number of vertices on z side of primal grid +//@param[in] constrained whether to force dual vertices to lie strictly inside +// corresponding primal cell (prevents self-intersections at cost of +// surface quality; marginally slower) +//@param[in] triangles whether to output four triangles instead of one quad per +// crossing edge (quad mesh usually looks fine) +//@param[in] root_finding whether to use root finding to identify crossing point on +// each edge (improves quality a lot at cost of performance). If false, +// use linear interpolation. +//@param[out] V #V by 3 list of outputs vertex positions +//@param[out] Q #Q by 4 (or 3 if triangles=true) face indices into rows of V +// )"); +} + + + diff --git a/src/embree/EmbreeIntersector.cpp b/src/embree/EmbreeIntersector.cpp new file mode 100644 index 00000000..763c169f --- /dev/null +++ b/src/embree/EmbreeIntersector.cpp @@ -0,0 +1,58 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto embree_intersect_ray_first( + igl::embree::EmbreeIntersector &ei, + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + const float tnear, + const float tfar) + { + igl::Hit h; + bool ret = ei.intersectRay(origin,direction,h,tnear,tfar); + if(!ret){ h.id = -1; } + return std::make_tuple( + (Integer)h.id, + (Numeric)h.t, + (Numeric)h.u, + (Numeric)h.v); + } + + auto embree_intersect_ray( + igl::embree::EmbreeIntersector &ei, + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + const float tnear, + const float tfar) + { + std::vector> hits; + int num_rays; + ei.intersectRay(origin,direction,hits,num_rays,tnear,tfar); + std::vector> out; + for(const auto &h : hits) + { + out.emplace_back(h.id,h.t,h.u,h.v); + } + return std::make_tuple(out,(Integer)num_rays); + } +} + +void bind_EmbreeIntersector(nb::module_ &m) +{ + using igl::embree::EmbreeIntersector; + nb::class_(m,"EmbreeIntersector") + .def(nb::init<>()) + .def("init", nb::overload_cast(&EmbreeIntersector::init), "V"_a, "F"_a, "isStatic"_a=false) + .def("intersectRay_first", &pyigl::embree_intersect_ray_first, "origin"_a, "direction"_a, "tnear"_a=0, "tfar"_a=std::numeric_limits::infinity()) + .def("intersectRay", &pyigl::embree_intersect_ray, "origin"_a, "direction"_a, "tnear"_a=0, "tfar"_a=std::numeric_limits::infinity()) + ; +} diff --git a/src/embree/ambient_occlusion.cpp b/src/embree/ambient_occlusion.cpp new file mode 100644 index 00000000..611ce081 --- /dev/null +++ b/src/embree/ambient_occlusion.cpp @@ -0,0 +1,71 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto ambient_occlusion( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &P, + const nb::DRef &N, + const int num_samples) + { + Eigen::VectorXN S; + igl::embree::ambient_occlusion(V,F,P,N,num_samples,S); + return S; + } + auto ambient_occlusion_ei( + const igl::embree::EmbreeIntersector & ei, + const nb::DRef &P, + const nb::DRef &N, + const int num_samples) + { + Eigen::VectorXN S; + igl::embree::ambient_occlusion(ei,P,N,num_samples,S); + return S; + } +} + +// Bind the wrapper to the Python module +void bind_ambient_occlusion(nb::module_ &m) +{ + m.def( + "ambient_occlusion", + &pyigl::ambient_occlusion, + "V"_a, + "F"_a, + "P"_a, + "N"_a, + "num_samples"_a, +R"(Compute ambient occlusion per given point + +@param[in] V #V by 3 list of mesh vertex positiosn +@param[in] F #F by 3 list of mesh triangle indices into rows of V +@param[in] P #P by 3 list of origin points +@param[in] N #P by 3 list of origin normals +@param[out] S #P list of ambient occlusion values between 1 (fully occluded) and + 0 (not occluded) +)"); + m.def( + "ambient_occlusion", + &pyigl::ambient_occlusion_ei, + "ei"_a, + "P"_a, + "N"_a, + "num_samples"_a, +R"(Compute ambient occlusion per given point + +@param[in] ei EmbreeIntersector containing (V,F) +@param[in] P #P by 3 list of origin points +@param[in] N #P by 3 list of origin normals +@param[out] S #P list of ambient occlusion values between 1 (fully occluded) and + 0 (not occluded) +)"); +} diff --git a/src/embree/module.cpp b/src/embree/module.cpp new file mode 100644 index 00000000..077a4237 --- /dev/null +++ b/src/embree/module.cpp @@ -0,0 +1,12 @@ +#include +namespace nb = nanobind; + +// generated by cmake +#include "embree/BINDING_DECLARATIONS.in" + +NB_MODULE(pyigl_embree, m) { + m.doc() = "libigl embree module python bindings"; + // generated by cmake +#include "embree/BINDING_INVOCATIONS.in" +} + diff --git a/src/embree/reorient_facets_raycast.cpp b/src/embree/reorient_facets_raycast.cpp new file mode 100644 index 00000000..430286b8 --- /dev/null +++ b/src/embree/reorient_facets_raycast.cpp @@ -0,0 +1,60 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto reorient_facets_raycast( + const nb::DRef &V, + const nb::DRef &F, + const int rays_total_, + const int rays_minimum, + const bool facet_wise, + const bool use_parity, + const bool is_verbose) + { + const int rays_total = rays_total_==-1?F.rows()*100:rays_total_; + Eigen::VectorXB I; + Eigen::VectorXI C; + igl::embree::reorient_facets_raycast( + V,F,rays_total,rays_minimum,facet_wise,use_parity,is_verbose,I,C); + return std::make_tuple(I,C); + } +} + +// Bind the wrapper to the Python module +void bind_reorient_facets_raycast(nb::module_ &m) +{ + m.def( + "reorient_facets_raycast", + &pyigl::reorient_facets_raycast, + "V"_a, + "F"_a, + "rays_total"_a=-1, + "rays_minimum"_a=10, + "facet_wise"_a=false, + "use_parity"_a=false, + "is_verbose"_a=false, +R"(Orient each component (identified by C) of a mesh (V,F) using ambient +occlusion such that the front side is less occluded than back side, as +described in "A Simple Method for Correcting Facet Orientations in +Polygon Meshes Based on Ray Casting" [Takayama et al. 2014]. + +@param[in] V #V by 3 list of vertex positions +@param[in] F #F by 3 list of triangle indices +@param[in] rays_total Total number of rays that will be shot +@param[in] rays_minimum Minimum number of rays that each patch should receive +@param[in] facet_wise Decision made for each face independently, no use of patches + (i.e., each face is treated as a patch) +@param[in] use_parity Use parity mode +@param[in] is_verbose Verbose output to cout +@param[out] I #F list of whether face has been flipped +@param[out] C #F list of patch ID (output of bfs_orient > manifold patches) +)"); +} diff --git a/src/embree/shape_diameter_function.cpp b/src/embree/shape_diameter_function.cpp new file mode 100644 index 00000000..5ce04453 --- /dev/null +++ b/src/embree/shape_diameter_function.cpp @@ -0,0 +1,72 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto shape_diameter_function( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &P, + const nb::DRef &N, + const int num_samples) + { + Eigen::VectorXN S; + igl::embree::shape_diameter_function(V,F,P,N,num_samples,S); + return S; + } + auto shape_diameter_function_ei( + const igl::embree::EmbreeIntersector & ei, + const nb::DRef &P, + const nb::DRef &N, + const int num_samples) + { + Eigen::VectorXN S; + igl::embree::shape_diameter_function(ei,P,N,num_samples,S); + return S; + } +} + +// Bind the wrapper to the Python module +void bind_shape_diameter_function(nb::module_ &m) +{ + m.def( + "shape_diameter_function", + &pyigl::shape_diameter_function, + "V"_a, + "F"_a, + "P"_a, + "N"_a, + "num_samples"_a, +R"(Compute shape diameter function per given point + +@param[in] V #V by 3 list of mesh vertex positiosn +@param[in] F #F by 3 list of mesh triangle indices into rows of V +@param[in] P #P by 3 list of origin points +@param[in] N #P by 3 list of origin normals +@param[out] S #P list of ambient occlusion values between 1 (fully occluded) and + 0 (not occluded) +)"); + m.def( + "shape_diameter_function", + &pyigl::shape_diameter_function_ei, + "ei"_a, + "P"_a, + "N"_a, + "num_samples"_a, +R"(Compute shape diameter function per given point + +@param[in] ei EmbreeIntersector containing (V,F) +@param[in] P #P by 3 list of origin points +@param[in] N #P by 3 list of origin normals +@param[out] S #P list of ambient occlusion values between 1 (fully occluded) and + 0 (not occluded) +)"); +} + diff --git a/src/facet_adjacency_matrix.cpp b/src/facet_adjacency_matrix.cpp new file mode 100644 index 00000000..25cc8075 --- /dev/null +++ b/src/facet_adjacency_matrix.cpp @@ -0,0 +1,34 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for facet_adjacency_matrix with simplicial mesh (F) + auto facet_adjacency_matrix(const nb::DRef &F) + { + Eigen::SparseMatrixI A; + igl::facet_adjacency_matrix(F, A); + return A; + } + +} + +// Bind the wrapper to the Python module +void bind_facet_adjacency_matrix(nb::module_ &m) +{ + m.def( + "facet_adjacency_matrix", + &pyigl::facet_adjacency_matrix, + "F"_a, +R"(Construct a #F×#F adjacency matrix with A(i,j)>0 indicating that faces i and j +share an edge. + +@param[in] F #F by 3 list of facets +@param[out] A #F by #F adjacency matrix)"); +} diff --git a/src/facet_components.cpp b/src/facet_components.cpp new file mode 100644 index 00000000..4409ad66 --- /dev/null +++ b/src/facet_components.cpp @@ -0,0 +1,36 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for facet_components with simplicial mesh (F) + auto facet_components(const nb::DRef &F) + { + Eigen::VectorXI C; + const int nc = igl::facet_components(F, C); + return std::make_tuple(nc, C); + } + +} + +// Bind the wrapper to the Python module +void bind_facet_components(nb::module_ &m) +{ + m.def( + "facet_components", + &pyigl::facet_components, + "F"_a, + R"(Compute connected components of facets based on edge-edge adjacency. + +For connected components on vertices see igl::vertex_components + +@param[in] F #F by 3 list of triangle indices +@param[out] C #F list of connected component ids +@return number of connected components)"); +} diff --git a/src/in_element.cpp b/src/in_element.cpp new file mode 100644 index 00000000..672a9fe4 --- /dev/null +++ b/src/in_element.cpp @@ -0,0 +1,47 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for in_element with simplicial mesh (F) + auto in_element( + const nb::DRef &V, + const nb::DRef &Ele, + const nb::DRef &Q, + const igl::AABB,3> &aabb) + { + Eigen::VectorXI I; + igl::in_element(V,Ele,Q,aabb,I); + return I; + } + +} + +// Bind the wrapper to the Python module +void bind_in_element(nb::module_ &m) +{ + m.def( + "in_element", + &pyigl::in_element, + "V"_a, + "Ele"_a, + "Q"_a, + "aabb"_a, +R"(Determine whether each point in a list of points is in the elements of a +mesh. + +@tparam DIM dimension of vertices in V (# of columns) +@param[in] V #V by dim list of mesh vertex positions. +@param[in] Ele #Ele by dim+1 list of mesh indices into #V. +@param[in] Q #Q by dim list of query point positions +@param[in] aabb axis-aligned bounding box tree object (see AABB.h) +@param[out] I #Q list of indices into Ele of first containing element (-1 means no + containing element) +)"); +} diff --git a/src/ismember_rows.cpp b/src/ismember_rows.cpp new file mode 100644 index 00000000..b94ecdfa --- /dev/null +++ b/src/ismember_rows.cpp @@ -0,0 +1,44 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for ismember_rows with simplicial mesh (F) + auto ismember_rows( + const nb::DRef &A, + const nb::DRef &B) + { + Eigen::Array IA; + Eigen::VectorXI LOCB; + igl::ismember_rows(A,B,IA,LOCB); + return std::make_tuple(IA,LOCB); + } + +} + +// Bind the wrapper to the Python module +void bind_ismember_rows(nb::module_ &m) +{ + m.def( + "ismember_rows", + &pyigl::ismember_rows, + "A"_a, + "B"_a, +R"( +Determine if row of A exist in rows of B + +@param[in] A ma by na matrix of Integers +@param[in] B mb by nb matrix of Integers +@param[out] IA ma by 1 lest of flags whether corresponding element of A + exists in B +@param[out] LOCB ma by 1 list matrix of indices in B locating matching + element (-1 if not found), indices assume column major ordering +)"); +} + diff --git a/src/isolines.cpp b/src/isolines.cpp new file mode 100644 index 00000000..205f57ad --- /dev/null +++ b/src/isolines.cpp @@ -0,0 +1,60 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto isolines( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &S, + const nb::DRef &vals) + { + Eigen::MatrixXN iV; + Eigen::MatrixXI iE; + Eigen::VectorXI I; + igl::isolines(V,F,S,vals,iV,iE,I); + return std::make_tuple(iV,iE,I); + } +} + +// Bind the wrapper to the Python module +void bind_isolines(nb::module_ &m) +{ + m.def( + "isolines", + &pyigl::isolines, + "V"_a, + "F"_a, + "S"_a, + "vals"_a, +R"(Compute isolines of a scalar field on a triangle mesh. + +Isolines may cross perfectly at vertices. The output should not contain +degenerate segments (so long as the input does not contain degenerate +faces). The output segments are *oriented* so that isolines curl +counter-clockwise around local maxima (i.e., for 2D scalar fields). Unless +an isoline hits a boundary, it should be a closed loop. Isolines may run +perfectly along boundaries. Isolines should appear just "above" constants +regions. + +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of mesh triangle indices into V +@param[in] S #S by 1 list of per-vertex scalar values +@param[in] vals #vals by 1 list of values to compute isolines for +@param[out] iV #iV by dim list of isoline vertex positions +@param[out] iE #iE by 2 list of edge indices into iV +@param[out] I #iE by 1 list of indices into vals indicating which value + each segment belongs to + +\see isolines_intrinsic, edge_crossings)"); +} + + + diff --git a/src/isolines_intrinsic.cpp b/src/isolines_intrinsic.cpp new file mode 100644 index 00000000..c1dd12d6 --- /dev/null +++ b/src/isolines_intrinsic.cpp @@ -0,0 +1,112 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto isolines_intrinsic( + const nb::DRef &F, + const nb::DRef &S, + const nb::DRef &vals) + { + Eigen::MatrixXN iB; + Eigen::VectorXI iF; + Eigen::MatrixXI iE; + Eigen::VectorXI I; + igl::isolines_intrinsic(F,S,vals,iB,iF,iE,I); + return std::make_tuple(iB,iF,iE,I); + } + auto isolines_intrinsic_edges( + const nb::DRef &F, + const nb::DRef &S, + const Numeric val, + const nb::DRef &uE, + const nb::DRef &EMAP, + const nb::DRef &uEC, + const nb::DRef &uEE) + { + Eigen::MatrixXN iB; + Eigen::VectorXI iF; + Eigen::MatrixXI iE; + igl::isolines_intrinsic(F,S,uE,EMAP,uEC,uEE,val,iB,iF,iE); + return std::make_tuple(iB,iF,iE); + } +} + +// Bind the wrapper to the Python module +void bind_isolines_intrinsic(nb::module_ &m) +{ + m.def( + "isolines_intrinsic", + &pyigl::isolines_intrinsic, + "F"_a, + "S"_a, + "vals"_a, +R"(Compute isolines of a scalar field on a triangle mesh intrinsically. + +See isolines.h for details. + +@param[in] F #F by 3 list of mesh triangle indices into some V +@param[in] S #S by 1 list of per-vertex scalar values +@param[in] vals #vals by 1 list of values to compute isolines for +@param[out] iB #iB by 3 list of barycentric coordinates so that + iV.row(i) = iB(i,0)*V.row(F(iFI(i,0)) + + iB(i,1)*V.row(F(iFI(i,1)) + + iB(i,2)*V.row(F(iFI(i,2)) +@param[out] iF #iB list of triangle indices for each row of iB (all + points will either lie on an edge or vertex: an arbitrary incident face + will be given). +@param[out] iE #iE by 2 list of edge indices into iB +@param[out] I #iE by 1 list of indices into vals indicating which value + each segment belongs to + +\see isolines, edge_crossings +)"); + m.def( + "isolines_intrinsic", + &pyigl::isolines_intrinsic_edges, + "F"_a, + "S"_a, + "val"_a, + "uE"_a, + "EMAP"_a, + "uEC"_a, + "uEE"_a, +R"(Compute isolines of a scalar field on a triangle mesh intrinsically. + +See isolines.h for details. + +@param[in] F #F by 3 list of mesh triangle indices into some V +@param[in] S #S by 1 list of per-vertex scalar values +@param[in] val scalar value to compute isoline at +@param[in] uE #uE by 2 list of unique undirected edges +@param[in] EMAP #F*3 list of indices into uE, mapping each directed edge to unique + undirected edge so that uE(EMAP(f+#F*c)) is the unique edge + corresponding to E.row(f+#F*c) +@param[in] uEC #uE+1 list of cumulative counts of directed edges sharing each + unique edge so the uEC(i+1)-uEC(i) is the number of directed edges + sharing the ith unique edge. +@param[in] uEE #E list of indices into E, so that the consecutive segment of + indices uEE.segment(uEC(i),uEC(i+1)-uEC(i)) lists all directed edges + sharing the ith unique edge. +@param[out] iB #iB by 3 list of barycentric coordinates so that + iV.row(i) = iB(i,0)*V.row(F(iFI(i,0)) + + iB(i,1)*V.row(F(iFI(i,1)) + + iB(i,2)*V.row(F(iFI(i,2)) +@param[out] iF #iB list of triangle indices for each row of iB (all + points will either lie on an edge or vertex: an arbitrary incident face + will be given). +@param[out] iE #iE by 2 list of edge indices into iB + +\see unique_edge_map +)"); +} + + + diff --git a/src/kelvinlets.cpp b/src/kelvinlets.cpp new file mode 100644 index 00000000..0382ff9d --- /dev/null +++ b/src/kelvinlets.cpp @@ -0,0 +1,69 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto kelvinlets( + const nb::DRef &V, + const nb::DRef &x0, + const nb::DRef &f, + const nb::DRef &F, + const Numeric epsilon, + const Numeric falloff, + const igl::BrushType brushType) + { + Eigen::MatrixXN result; + igl::kelvinlets( + V, + x0, + f, + F, + igl::KelvinletParams(epsilon, falloff, brushType), + result); + return result; + } +} + +// Bind the wrapper to the Python module +void bind_kelvinlets(nb::module_ &m) +{ + nb::enum_(m, "BrushType") + .value("GRAB", igl::BrushType::GRAB) + .value("SCALE", igl::BrushType::SCALE) + .value("TWIST", igl::BrushType::TWIST) + .value("PINCH", igl::BrushType::PINCH) + .export_values(); + m.def( + "kelvinlets", + &pyigl::kelvinlets, + "V"_a, + "x0"_a, + "f"_a, + "F"_a, + "epsilon"_a =1.0, + "falloff"_a =1.0, + "brushType"_a=igl::BrushType::GRAB, +R"(Implements Pixar's Regularized Kelvinlets (Pixar Technical Memo #17-03): +Sculpting Brushes based on Fundamental Solutions of Elasticity, a technique +for real-time physically based volume sculpting of virtual elastic materials + +@param[in] V #V by dim list of input points in space +@param[in] x0 dim-vector of brush tip +@param[in] f dim-vector of brush force (translation) +@param[in] F dim by dim matrix of brush force matrix (linear) +@param[in] params parameters for the kelvinlet brush like brush radius, scale etc +@param[out] X #V by dim list of output points in space +)"); + +} + + + diff --git a/src/knn.cpp b/src/knn.cpp new file mode 100644 index 00000000..13817808 --- /dev/null +++ b/src/knn.cpp @@ -0,0 +1,63 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto knn( + const nb::DRef &P, + const nb::DRef &V, + const Integer k, + const std::vector> &point_indices, + const nb::DRef &CH, + const nb::DRef &CN, + const nb::DRef &W) + { + Eigen::MatrixXI I; + igl::knn(P,V,k,point_indices,CH,CN,W,I); + return I; + } +} + +// Bind the wrapper to the Python module +void bind_knn(nb::module_ &m) +{ + m.def( + "knn", + &pyigl::knn, + "P"_a, + "V"_a, + "k"_a, + "point_indices"_a, + "CH"_a, + "CN"_a, + "W"_a, + R"( +Given a 3D set of points P, an whole number k, and an octree +find the indicies of the k nearest neighbors for each point in P. +Note that each point is its own neighbor. + +The octree data structures used in this function are intended to be the +same ones output from igl::octree + +@param[in] P #P by 3 list of point locations +@param[in] V #V by 3 list of point locations for which may be neighbors +@param[in] k number of neighbors to find +@param[in] point_indices a vector of vectors, where the ith entry is a vector of + the indices into P that are the ith octree cell's points +@param[in] CH #OctreeCells by 8, where the ith row is the indices of + the ith octree cell's children +@param[in] CN #OctreeCells by 3, where the ith row is a 3d row vector + representing the position of the ith cell's center +@param[in] W #OctreeCells, a vector where the ith entry is the width + of the ith octree cell +@param[out] I #P by k list of k-nearest-neighbor indices into V +)"); +} diff --git a/src/massmatrix.cpp b/src/massmatrix.cpp index 63f56a38..07ac7519 100644 --- a/src/massmatrix.cpp +++ b/src/massmatrix.cpp @@ -14,28 +14,10 @@ namespace pyigl auto massmatrix( const nb::DRef &V, const nb::DRef &F, - const std::string type) + const igl::MassMatrixType type) { Eigen::SparseMatrixN M; - igl::MassMatrixType t; - if(type == "barycentric") - { - t = igl::MASSMATRIX_TYPE_BARYCENTRIC; - }else if(type == "voronoi") - { - t = igl::MASSMATRIX_TYPE_VORONOI; - }else if(type == "full") - { - t = igl::MASSMATRIX_TYPE_FULL; - }else if(type == "default") - { - t = igl::MASSMATRIX_TYPE_DEFAULT; - }else - { - throw std::runtime_error("massmatrix: unknown type"); - } - igl::massmatrix(V,F,t,M); - + igl::massmatrix(V,F,type,M); return M; } } @@ -43,12 +25,19 @@ namespace pyigl // Bind the wrapper to the Python module void bind_massmatrix(nb::module_ &m) { + nb::enum_(m, "MassMatrixType") + .value("MASSMATRIX_TYPE_BARYCENTRIC", igl::MASSMATRIX_TYPE_BARYCENTRIC) + .value("MASSMATRIX_TYPE_VORONOI", igl::MASSMATRIX_TYPE_VORONOI) + .value("MASSMATRIX_TYPE_FULL", igl::MASSMATRIX_TYPE_FULL) + .value("MASSMATRIX_TYPE_DEFAULT", igl::MASSMATRIX_TYPE_DEFAULT) + .export_values() + ; m.def( "massmatrix", &pyigl::massmatrix, "V"_a, "F"_a, - "type"_a="default", + "type"_a=igl::MASSMATRIX_TYPE_DEFAULT, R"(Constructs the mass (area) matrix for a given mesh (V,F). @tparam DerivedV derived type of eigen matrix for V (e.g. derived from diff --git a/src/min_quad_with_fixed.cpp b/src/min_quad_with_fixed.cpp index bf896a3d..bfd40ecd 100644 --- a/src/min_quad_with_fixed.cpp +++ b/src/min_quad_with_fixed.cpp @@ -10,7 +10,6 @@ using namespace nb::literals; namespace pyigl { - // Wrapper for min_quad_with_fixed auto min_quad_with_fixed( const Eigen::SparseMatrixN &A, const nb::DRef &B, @@ -31,53 +30,59 @@ namespace pyigl return Z; } - struct MinQuadWithFixed + void min_quad_with_fixed_precompute( + const Eigen::SparseMatrixN &A, + const nb::DRef &known, + const Eigen::SparseMatrixN &Aeq, + const bool pd, + igl::min_quad_with_fixed_data &data) { - igl::min_quad_with_fixed_data data; - MinQuadWithFixed( - const Eigen::SparseMatrixN &A = Eigen::SparseMatrixN(), - const nb::DRef &known = Eigen::VectorXI(), - const Eigen::SparseMatrixN &Aeq = Eigen::SparseMatrixN(), - const bool pd = true) + if(!igl::min_quad_with_fixed_precompute(A, known, Aeq, pd, data)) { - if(!igl::min_quad_with_fixed_precompute(A, known, Aeq, pd, data)) - { - throw std::runtime_error("min_quad_with_fixed: Precomputation failed."); - } + throw std::runtime_error("min_quad_with_fixed: Precomputation failed."); } + } - auto solve( - const nb::DRef &B, - const nb::DRef &Y, - const nb::DRef &Beq) + auto min_quad_with_fixed_solve( + const igl::min_quad_with_fixed_data &data, + const nb::DRef &B, + const nb::DRef &Y, + const nb::DRef &Beq) + { + Eigen::MatrixXN Z; + if(!igl::min_quad_with_fixed_solve(data, B, Y, Beq, Z)) { - Eigen::MatrixXN Z; - if(!igl::min_quad_with_fixed_solve(data, B, Y, Beq, Z)) - { - throw std::runtime_error("min_quad_with_fixed: Optimization failed."); - } - return Z; + throw std::runtime_error("min_quad_with_fixed: Optimization failed."); } - }; + return Z; + } } // Bind the wrapper to the Python module void bind_min_quad_with_fixed(nb::module_ &m) { - nb::class_(m, "MinQuadWithFixed") - .def(nb::init< - const Eigen::SparseMatrixN &, - const nb::DRef &, - const Eigen::SparseMatrixN &, - const bool>()) - .def("solve", &pyigl::MinQuadWithFixed::solve, + nb::class_>(m, "min_quad_with_fixed_data") + .def(nb::init<>()) + ; + + m.def("min_quad_with_fixed_solve", + &pyigl::min_quad_with_fixed_solve, + "data"_a, "B"_a=Eigen::MatrixXN(), "Y"_a=Eigen::MatrixXN(), "Beq"_a=Eigen::MatrixXN(), - R"(Solve the precomputed quadratic optimization problem.)") + R"(Solve the precomputed convex quadratic optimization problem.)") + ; + m.def("min_quad_with_fixed_precompute", + &pyigl::min_quad_with_fixed_precompute, + "A"_a = Eigen::SparseMatrixN(), + "known"_a = Eigen::VectorXI() , + "Aeq"_a = Eigen::SparseMatrixN(), + "pd"_a = true , + "data"_a, + R"(Precompute convex quadratic optimization problem.)") ; - m.def( "min_quad_with_fixed", diff --git a/src/module.cpp b/src/module.cpp index 8d24c79f..8fe7fb66 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -9,3 +9,4 @@ NB_MODULE(pyigl_core, m) { // generated by cmake #include "BINDING_INVOCATIONS.in" } + diff --git a/src/moments.cpp b/src/moments.cpp new file mode 100644 index 00000000..e81e0987 --- /dev/null +++ b/src/moments.cpp @@ -0,0 +1,44 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto moments( + const nb::DRef &V, + const nb::DRef &F) + { + Numeric m0; + Eigen::VectorXN m1; + Eigen::MatrixXN m2; + igl::moments(V,F,m0,m1,m2); + return std::make_tuple(m0,m1,m2); + } + +} + +// Bind the wrapper to the Python module +void bind_moments(nb::module_ &m) +{ + m.def( + "moments", + &pyigl::moments, + "V"_a, + "F"_a, +R"(Computes the moments of mass for a solid object bound by a triangle mesh. + +@param[in] V #V by 3 list of rest domain positions +@param[in] F #F by 3 list of triangle indices into V +@param[out] m0 zeroth moment of mass, total signed volume of solid. +@param[out] m1 first moment of mass, center of mass (centroid) times total mass +@param[out] m2 second moment of mass, moment of inertia with center of mass as reference point + +\see centroid +)"); +} diff --git a/src/octree.cpp b/src/octree.cpp new file mode 100644 index 00000000..98cea257 --- /dev/null +++ b/src/octree.cpp @@ -0,0 +1,61 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto octree(const nb::DRef &P) + { + Eigen::MatrixXI CH; + Eigen::MatrixXN CN; + Eigen::VectorXN W; + std::vector> point_indices; + igl::octree(P,point_indices,CH,CN,W); + return std::make_tuple(point_indices,CH,CN,W); + } +} + +// Bind the wrapper to the Python module +void bind_octree(nb::module_ &m) +{ + m.def( + "octree", + &pyigl::octree, + "P"_a, + R"(Given a set of 3D points P, generate data structures for a pointerless +octree. Each cell stores its points, children, center location and width. +Our octree is not dense. We use the following rule: if the current cell +has any number of points, it will have all 8 children. A leaf cell will +have -1's as its list of child indices. + +We use a binary numbering of children. Treating the parent cell's center +as the origin, we number the octants in the following manner: +The first bit is 1 iff the octant's x coordinate is positive +The second bit is 1 iff the octant's y coordinate is positive +The third bit is 1 iff the octant's z coordinate is positive + +For example, the octant with negative x, positive y, positive z is: +110 binary = 6 decimal + +@param[in] P #P by 3 list of point locations +@param[out] point_indices a vector of vectors, where the ith entry is a + vector of the indices into P that are the ith octree cell's points +@param[out] CH #OctreeCells by 8, where the ith row is the indices of the + ith octree cell's children +@param[out] CN #OctreeCells by 3, where the ith row is a 3d row vector + representing the position of the ith cell's center +@param[out] W #OctreeCells, a vector where the ith entry is the width of + the ith octree cell)" +); +} + + + + diff --git a/src/per_vertex_normals.cpp b/src/per_vertex_normals.cpp index ef9a2edd..99352b24 100644 --- a/src/per_vertex_normals.cpp +++ b/src/per_vertex_normals.cpp @@ -12,38 +12,11 @@ using namespace nb::literals; namespace pyigl { - auto weight_enum_from_string(const std::string &weighting) - { - igl::PerVertexNormalsWeightingType weight_enum; - if (weighting == "uniform") - { - weight_enum = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM; - } - else if (weighting == "area") - { - weight_enum = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA; - } - else if (weighting == "angle") - { - weight_enum = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE; - } - else if (weighting == "default") - { - weight_enum = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT; - } - else - { - throw std::invalid_argument("Invalid weighting type: " + weighting); - } - return weight_enum; - } - auto per_vertex_normals( const nb::DRef &V, const nb::DRef &F, - const std::string &weighting) + const igl::PerVertexNormalsWeightingType weight_enum) { - auto weight_enum = weight_enum_from_string(weighting); Eigen::MatrixXN N; igl::per_vertex_normals(V, F, weight_enum, N); return N; @@ -51,10 +24,9 @@ namespace pyigl auto per_vertex_normals_FN( const nb::DRef &V, const nb::DRef &F, - const std::string &weighting, + const igl::PerVertexNormalsWeightingType weight_enum, const nb::DRef &FN) { - auto weight_enum = weight_enum_from_string(weighting); Eigen::MatrixXN N; igl::per_vertex_normals(V, F, weight_enum, FN, N); return N; @@ -64,12 +36,19 @@ namespace pyigl // Bind the wrapper to the Python module void bind_per_vertex_normals(nb::module_ &m) { + nb::enum_(m, "PerVertexNormalsWeightingType") + .value("PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM", igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) + .value("PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA", igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) + .value("PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE", igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) + .value("PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT", igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT) + .export_values() + ; m.def( "per_vertex_normals", &pyigl::per_vertex_normals, "V"_a, "F"_a, - "weighting"_a = "default", + "weighting"_a = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT, R"(Compute per-vertex normals with optional weighting and face normals. @param[in] V #V by 3 matrix of vertex positions @@ -81,7 +60,7 @@ R"(Compute per-vertex normals with optional weighting and face normals. &pyigl::per_vertex_normals_FN, "V"_a, "F"_a, - "weighting"_a = "default", + "weighting"_a = igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT, "FN"_a, R"(Compute per-vertex normals with optional weighting and face normals. diff --git a/src/polygon_corners.cpp b/src/polygon_corners.cpp new file mode 100644 index 00000000..ad620577 --- /dev/null +++ b/src/polygon_corners.cpp @@ -0,0 +1,66 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto polygon_corners(const std::vector > & P) + { + Eigen::VectorXI I, C; + igl::polygon_corners(P,I,C); + return std::make_tuple(I,C); + } + + auto polygon_corners_kgon( + const nb::DRef &Q) + { + Eigen::VectorXI I, C; + igl::polygon_corners(Q,I,C); + return std::make_tuple(I,C); + } +} + +// Bind the wrapper to the Python module +void bind_polygon_corners(nb::module_ &m) +{ + m.def( + "polygon_corners", + &pyigl::polygon_corners, + "P"_a, +R"(Convert a list-of-lists polygon mesh faces representation to list of +polygon corners and sizes + +@param[in] P #P list of lists of vertex indices into rows of some matrix V +@param[out] I #I vectorized list of polygon corner indices into rows of some matrix V +@param[out] C #P+1 list of cumulative polygon sizes so that C(i+1)-C(i) = size of + the ith polygon, and so I(C(i)) through I(C(i+1)-1) are the indices of + the ith polygon + +)"); + m.def( + "polygon_corners", + &pyigl::polygon_corners_kgon, + "Q"_a, +R"( +\brief Convert a pure k-gon list of polygon mesh indices to list of +polygon corners and sizes +@param[in] Q #Q by k list of polygon indices (ith row is a k-gon, unless Q(i,j) = + -1 then it's a j-gon) +@param[out] I #I vectorized list of polygon corner indices into rows of some matrix V +@param[out] C #P+1 list of cumulative polygon sizes so that C(i+1)-C(i) = size of + the ith polygon, and so I(C(i)) through I(C(i+1)-1) are the indices of + the ith polygon + +)"); +} + + + + diff --git a/src/polygons_to_triangles.cpp b/src/polygons_to_triangles.cpp new file mode 100644 index 00000000..436c024d --- /dev/null +++ b/src/polygons_to_triangles.cpp @@ -0,0 +1,49 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto polygons_to_triangles( + const nb::DRef &I, + const nb::DRef &C) + { + Eigen::MatrixXI F; + Eigen::VectorXI J; + igl::polygons_to_triangles(I,C,F,J); + return std::make_tuple(F,J); + } + +} + +// Bind the wrapper to the Python module +void bind_polygons_to_triangles(nb::module_ &m) +{ + m.def( + "polygons_to_triangles", + &pyigl::polygons_to_triangles, + "I"_a, + "C"_a, +R"(Given a polygon mesh, trivially triangulate each polygon with a fan. This +purely combinatorial triangulation will work well for convex/flat polygons +and degrade otherwise. + +@param[in] I #I vectorized list of polygon corner indices into rows of some matrix V +@param[in] C #polygons+1 list of cumulative polygon sizes so that C(i+1)-C(i) = + size of the ith polygon, and so I(C(i)) through I(C(i+1)-1) are the + indices of the ith polygon +@param[out] F #F by 3 list of triangle indices into rows of V +@param[out] J #F list of indices into 0:#P-1 of corresponding polygon + +)"); +} + + + + diff --git a/src/readDMAT.cpp b/src/readDMAT.cpp index 4f244fe3..706e35eb 100644 --- a/src/readDMAT.cpp +++ b/src/readDMAT.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace nb = nanobind; using namespace nb::literals; @@ -10,7 +10,7 @@ using namespace nb::literals; namespace pyigl { // Wrapper for readDMAT with Eigen matrix output - Eigen::MatrixXN readDMAT(const std::string &file_name) + Eigen::MatrixXN readDMAT(const std::filesystem::path &file_name) { Eigen::MatrixXN W; if (!igl::readDMAT(file_name, W)) diff --git a/src/readMESH.cpp b/src/readMESH.cpp index 72f2ab60..a794c77b 100644 --- a/src/readMESH.cpp +++ b/src/readMESH.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include @@ -11,7 +11,7 @@ using namespace nb::literals; namespace pyigl { - auto readMESH(const std::string& mesh_file_name) + auto readMESH(const std::filesystem::path& mesh_file_name) { Eigen::MatrixXN V; // Vertex positions Eigen::MatrixXI T; // Tetrahedral indices @@ -19,7 +19,7 @@ namespace pyigl if (!igl::readMESH(mesh_file_name, V, T, F)) { - throw std::runtime_error("Failed to read .mesh file: " + mesh_file_name); + throw std::runtime_error("Failed to read .mesh file: " + mesh_file_name.generic_string()); } return std::make_tuple(V, T, F); diff --git a/src/readMSH.cpp b/src/readMSH.cpp new file mode 100644 index 00000000..c3a13550 --- /dev/null +++ b/src/readMSH.cpp @@ -0,0 +1,66 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto readMSH(const std::filesystem::path& msh_file_name) + { + Eigen::MatrixXN V; + Eigen::MatrixXI Tri; + Eigen::MatrixXI Tet; + Eigen::VectorXI TriTag; + Eigen::VectorXI TetTag; + std::vector XFields; + std::vector XF; + std::vector EFields; + std::vector TriF; + std::vector TetF; + + if (!igl::readMSH(msh_file_name, V, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF)) + { + throw std::runtime_error("Failed to read .msh file: " + msh_file_name.generic_string()); + } + + return std::make_tuple(V, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF); + } +} + +// Bind the wrapper to the Python module +void bind_readMSH(nb::module_ &m) +{ + m.def( + "readMSH", + &pyigl::readMSH, + "msh_file_name"_a, +R"(read triangle surface mesh and tetrahedral volume mesh from .msh file + +@param[in] msh - file name +@param[out] X eigen double matrix of vertex positions #X by 3 +@param[out] Tri #Tri eigen integer matrix of triangular faces indices into vertex positions +@param[out] Tet #Tet eigen integer matrix of tetrahedral indices into vertex positions +@param[out] TriTag #Tri eigen integer vector of tags associated with surface faces +@param[out] TetTag #Tet eigen integer vector of tags associated with volume elements +@param[out] XFields #XFields list of strings with field names associated with nodes +@param[out] XF #XFields list of eigen double matrices, fields associated with nodes +@param[out] EFields #EFields list of strings with field names associated with elements +@param[out] TriF #EFields list of eigen double matrices, fields associated with surface elements +@param[out] TetF #EFields list of eigen double matrices, fields associated with volume elements +@return true on success +\bug only version 2.2 of .msh file is supported (gmsh 3.X) +\bug only triangle surface elements and tetrahedral volumetric elements are supported +\bug only 3D information is supported +\bug only the 1st tag per element is returned (physical) +\bug same element fields are expected to be associated with surface elements and volumetric elements +)"); +} + diff --git a/src/readOBJ.cpp b/src/readOBJ.cpp index c8991681..6990592f 100644 --- a/src/readOBJ.cpp +++ b/src/readOBJ.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include namespace nb = nanobind; @@ -11,14 +11,14 @@ using namespace nb::literals; namespace pyigl { - auto readOBJ(const std::string filename) + auto readOBJ(const std::filesystem::path filename) { Eigen::MatrixXN V,TC,CN; Eigen::MatrixXI F,FTC,FN; if(!igl::readOBJ(filename,V,TC,CN,F,FTC,FN)) { // throw runtime exception - throw std::runtime_error("Failed to read mesh from: " + filename); + throw std::runtime_error("Failed to read mesh from: " + filename.generic_string()); } return std::make_tuple(V,TC,CN,F,FTC,FN); } diff --git a/src/readOFF.cpp b/src/readOFF.cpp index 619da205..cf01b551 100644 --- a/src/readOFF.cpp +++ b/src/readOFF.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include namespace nb = nanobind; @@ -11,14 +11,14 @@ using namespace nb::literals; namespace pyigl { - auto readOFF(const std::string filename) + auto readOFF(const std::filesystem::path filename) { Eigen::MatrixXN V,N; Eigen::MatrixXI F; if(!igl::readOFF(filename,V,F,N)) { // throw runtime exception - throw std::runtime_error("Failed to read mesh from: " + filename ); + throw std::runtime_error("Failed to read mesh from: " + filename.generic_string() ); } return std::make_tuple(V,F,N); } diff --git a/src/read_triangle_mesh.cpp b/src/read_triangle_mesh.cpp index 762d66b7..e535407f 100644 --- a/src/read_triangle_mesh.cpp +++ b/src/read_triangle_mesh.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace nb = nanobind; @@ -12,14 +13,14 @@ using namespace nb::literals; namespace pyigl { auto read_triangle_mesh( - const std::string str) + const std::filesystem::path & path) { Eigen::MatrixXN V; Eigen::MatrixXI F; - if(!igl::read_triangle_mesh(str,V,F)) + if(!igl::read_triangle_mesh(path,V,F)) { // throw runtime exception - throw std::runtime_error("Failed to read mesh from: " + str); + throw std::runtime_error("Failed to read mesh from: " + path.generic_string()); } return std::make_tuple(V,F); } diff --git a/src/signed_distance.cpp b/src/signed_distance.cpp index 823c9cac..6b55e09f 100644 --- a/src/signed_distance.cpp +++ b/src/signed_distance.cpp @@ -12,45 +12,14 @@ using namespace nb::literals; namespace pyigl { - auto signed_distance_type_from_string(const std::string & sign_type_str) - { - igl::SignedDistanceType sign_type; - if (sign_type_str == "pseudonormal") - { - sign_type = igl::SIGNED_DISTANCE_TYPE_PSEUDONORMAL; - } - else if (sign_type_str == "winding_number") - { - sign_type = igl::SIGNED_DISTANCE_TYPE_WINDING_NUMBER; - } - else if (sign_type_str == "unsigned") - { - sign_type = igl::SIGNED_DISTANCE_TYPE_UNSIGNED; - } - else if (sign_type_str == "fast_winding_number") - { - sign_type = igl::SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER; - } - else if (sign_type_str == "default") - { - sign_type = igl::SIGNED_DISTANCE_TYPE_DEFAULT; - } - else - { - throw std::invalid_argument("Invalid sign_type: " + sign_type_str); - } - return sign_type; - }; - auto signed_distance( const nb::DRef &P, const nb::DRef &V, const nb::DRef &F, - const std::string &sign_type_str, + const igl::SignedDistanceType sign_type, const Numeric lower_bound = -std::numeric_limits::infinity(), const Numeric upper_bound = std::numeric_limits::infinity()) { - const auto sign_type = signed_distance_type_from_string(sign_type_str); Eigen::VectorXN S; Eigen::VectorXI I; Eigen::MatrixXN C,N; @@ -61,13 +30,21 @@ namespace pyigl void bind_signed_distance(nb::module_ &m) { + nb::enum_(m, "SignedDistanceType") + .value("SIGNED_DISTANCE_TYPE_PSEUDONORMAL", igl::SIGNED_DISTANCE_TYPE_PSEUDONORMAL) + .value("SIGNED_DISTANCE_TYPE_WINDING_NUMBER", igl::SIGNED_DISTANCE_TYPE_WINDING_NUMBER) + .value("SIGNED_DISTANCE_TYPE_UNSIGNED", igl::SIGNED_DISTANCE_TYPE_UNSIGNED) + .value("SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER", igl::SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) + .value("SIGNED_DISTANCE_TYPE_DEFAULT", igl::SIGNED_DISTANCE_TYPE_DEFAULT) + .export_values() + ; m.def( "signed_distance", &pyigl::signed_distance, "P"_a, "V"_a, "F"_a, - "sign_type"_a = "default", + "sign_type"_a = igl::SIGNED_DISTANCE_TYPE_DEFAULT, "lower_bound"_a = -std::numeric_limits::infinity(), "upper_bound"_a = std::numeric_limits::infinity(), R"(Computes signed distance to a mesh. diff --git a/src/slim.cpp b/src/slim.cpp index b341bdaf..4948fa37 100644 --- a/src/slim.cpp +++ b/src/slim.cpp @@ -13,49 +13,16 @@ using namespace nb::literals; namespace pyigl { - auto mapping_energy(const std::string str) - { - if (str == "arap") - { - return igl::MappingEnergyType::ARAP; - } - else if (str == "log_arap") - { - return igl::MappingEnergyType::LOG_ARAP; - } - else if (str == "symmetric_dirichlet") - { - return igl::MappingEnergyType::SYMMETRIC_DIRICHLET; - } - else if (str == "conformal") - { - return igl::MappingEnergyType::CONFORMAL; - } - else if (str == "exp_conformal") - { - return igl::MappingEnergyType::EXP_CONFORMAL; - } - else if (str == "exp_symmetric_dirichlet") - { - return igl::MappingEnergyType::EXP_SYMMETRIC_DIRICHLET; - } - else - { - throw std::runtime_error("Unknown energy type"); - } - } - auto slim_precompute( const Eigen::MatrixXd &V, // maybe copy here to avoid yet another copy in slim const Eigen::MatrixXi &F, const Eigen::MatrixXd &V_init, - const std::string & slim_energy_str, + const igl::MappingEnergyType slim_energy, const Eigen::VectorXi &b, const Eigen::MatrixXd &bc, const double soft_p) { igl::SLIMData data; - auto slim_energy = mapping_energy(slim_energy_str); igl::slim_precompute(V, F, V_init, data, slim_energy, b, bc, soft_p); return data; } diff --git a/src/triangle/scaf.cpp b/src/triangle/scaf.cpp new file mode 100644 index 00000000..c3ef5749 --- /dev/null +++ b/src/triangle/scaf.cpp @@ -0,0 +1,99 @@ +#include "default_types.h" +// Not templated at all +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto scaf_precompute( + const Eigen::MatrixXd &V, // maybe copy here to avoid yet another copy in scaf + const Eigen::MatrixXi &F, + const Eigen::MatrixXd &V_init, + const igl::MappingEnergyType scaf_energy, + const Eigen::VectorXi &b, + const Eigen::MatrixXd &bc, + const double soft_p, + igl::triangle::SCAFData & data) + { + igl::triangle::scaf_precompute(V, F, V_init, scaf_energy, b, bc, soft_p, data); + } + auto scaf_solve( + const int iter_num, + igl::triangle::SCAFData &data) + { + return igl::triangle::scaf_solve(iter_num, data); + } + auto scaf_system( igl::triangle::SCAFData &s) + { + Eigen::SparseMatrix L; + Eigen::VectorXd rhs; + igl::triangle::scaf_system(s, L, rhs); + return std::make_tuple(L, rhs); + } + +} + +// Bind the wrapper to the Python module +void bind_scaf(nb::module_ &m) +{ + nb::class_(m, "SCAFData") + .def(nb::init<>()); + m.def( + "scaf_precompute", + &pyigl::scaf_precompute, + "V"_a, + "F"_a, + "V_init"_a, + "scaf_energy"_a, + "b"_a, + "bc"_a, + "soft_p"_a, + "data"_a, + R"(Compute necessary information to start using SCAF + +@param[in] V #V by 3 list of mesh vertex positions +@param[in] F #F by 3/3 list of mesh faces (triangles/tets) +@param[in] V_init #V by 3 list of initial mesh vertex positions +@param[in,out] data resulting precomputed data +@param[in] slim_energy Energy type to minimize +@param[in] b list of boundary indices into V (soft constraint) +@param[in] bc #b by dim list of boundary conditions (soft constraint) +@param[in] soft_p Soft penalty factor (can be zero) +)"); + m.def( + "scaf_solve", + &pyigl::scaf_solve, + "iter_num"_a, + "data"_a, + R"(Run iter_num iterations of SCAF, with precomputed data +@param[in] data precomputed data +@param[in] iter_num number of iterations to run +@returns resulting V_o (in SLIMData): #V by dim list of mesh vertex positions + )"); + m.def( + "scaf_system", + &pyigl::scaf_system, + "s"_a, + R"(Set up the SCAF system L * uv = rhs, without solving it. +@param[in] s: igl::SCAFData. Will be modified by energy and Jacobian computation. +@param[out] L: m by m matrix +@param[out] rhs: m by 1 vector +with m = dim * (#V_mesh + #V_scaf - #V_frame) + )"); + + +} + + + + + + diff --git a/src/writeDMAT.cpp b/src/writeDMAT.cpp index bdfe2e26..6c528fa2 100644 --- a/src/writeDMAT.cpp +++ b/src/writeDMAT.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace nb = nanobind; using namespace nb::literals; @@ -12,7 +12,7 @@ namespace pyigl { // Wrapper for writeDMAT with Eigen matrix input bool writeDMAT( - const std::string &file_name, + const std::filesystem::path &file_name, const nb::DRef &W, const bool ascii) { diff --git a/src/writeMESH.cpp b/src/writeMESH.cpp index 9936a4cf..a150f31d 100644 --- a/src/writeMESH.cpp +++ b/src/writeMESH.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include namespace nb = nanobind; @@ -11,14 +11,14 @@ using namespace nb::literals; namespace pyigl { void writeMESH( - const std::string &mesh_file_name, + const std::filesystem::path &mesh_file_name, const nb::DRef &V, const nb::DRef &T, const nb::DRef &F) { if (!igl::writeMESH(mesh_file_name, V, T, F)) { - throw std::runtime_error("Failed to write .mesh file: " + mesh_file_name); + throw std::runtime_error("Failed to write .mesh file: " + mesh_file_name.generic_string()); } } } diff --git a/src/writeMSH.cpp b/src/writeMSH.cpp new file mode 100644 index 00000000..cc57b563 --- /dev/null +++ b/src/writeMSH.cpp @@ -0,0 +1,58 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + void writeMSH( + const std::filesystem::path &mesh_file_name, + const nb::DRef &V, + const nb::DRef &Tri, + const nb::DRef &Tet, + const nb::DRef &TriTag, + const nb::DRef &TetTag) + { + std::vector XF; + std::vector XFields; + std::vector EFields; + std::vector TriF; + std::vector TetF; + if(!igl::writeMSH(mesh_file_name, V, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF)) + { + throw std::runtime_error("Failed to write .msh file: " + mesh_file_name.generic_string()); + } + } +} + +// Bind the wrapper to the Python module +void bind_writeMSH(nb::module_ &m) +{ + m.def( + "writeMSH", + &pyigl::writeMSH, + "mesh_file_name"_a, + "V"_a=Eigen::MatrixXN(), + "Tri"_a=Eigen::MatrixXI(), + "Tet"_a=Eigen::MatrixXI(), + "TriTag"_a=Eigen::VectorXI(), + "TetTag"_a=Eigen::VectorXI(), +R"(Save a tetrahedral volume mesh to a .msh file. + +@param[in] mesh_file_name Path to the .mesh file to save +@param[in] V #V by 3 matrix of vertex positions +@param[in] Tri #Tri by 3 matrix of face indices +@param[in] Tet #Tet by 4 matrix of tetrahedral indices +@param[in] TriTag #Tri vector of face tags +@param[in] TetTag #Tet vector of tetrahedral tags +@throws std::runtime_error if file writing fails +)"); +} + diff --git a/src/writeOBJ.cpp b/src/writeOBJ.cpp index db2412f3..aa089730 100644 --- a/src/writeOBJ.cpp +++ b/src/writeOBJ.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include namespace nb = nanobind; @@ -12,7 +12,7 @@ using namespace nb::literals; namespace pyigl { void writeOBJ( - const std::string & filename, + const std::filesystem::path & filename, const nb::DRef &V, const nb::DRef &F, const nb::DRef &CN, @@ -23,7 +23,7 @@ namespace pyigl if(!igl::writeOBJ(filename,V,F,CN,FN,TC,FTC)) { // throw runtime exception - throw std::runtime_error("Failed to write mesh to: " + filename); + throw std::runtime_error("Failed to write mesh to: " + filename.generic_string()); } } } diff --git a/src/writePLY.cpp b/src/writePLY.cpp index 39382c0a..50b51e57 100644 --- a/src/writePLY.cpp +++ b/src/writePLY.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace nb = nanobind; @@ -12,7 +13,7 @@ using namespace nb::literals; namespace pyigl { void writePLY( - const std::string & filename, + const std::filesystem::path & filename, const Eigen::MatrixXN & V, const Eigen::MatrixXI & F_, const Eigen::MatrixXI & E_, @@ -25,10 +26,8 @@ namespace pyigl const Eigen::MatrixXN & ED, const std::vector & EDheader, const std::vector & comments, - std::string encoding_) + const igl::FileEncoding encoding) { - const auto encoding = encoding_ == "binary" ? igl::FileEncoding::Binary : igl::FileEncoding::Ascii; - // tinyply doesn't support int64 Eigen::MatrixXi F = F_.cast(); Eigen::MatrixXi E = E_.cast(); @@ -50,7 +49,7 @@ namespace pyigl encoding )) { - throw std::runtime_error("Error writing " + filename); + throw std::runtime_error("Error writing " + filename.generic_string()); } } } @@ -74,7 +73,7 @@ void bind_writePLY(nb::module_ &m) "ED"_a = Eigen::MatrixXN(), "EDheader"_a = std::vector(), "comments"_a = std::vector(), - "encoding"_a = "binary", + "encoding"_a = igl::FileEncoding::Binary, R"(Write a mesh to a .ply file. @tparam Derived from Eigen matrix parameters diff --git a/src/write_triangle_mesh.cpp b/src/write_triangle_mesh.cpp index a285366d..d87f8e7a 100644 --- a/src/write_triangle_mesh.cpp +++ b/src/write_triangle_mesh.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include namespace nb = nanobind; @@ -12,20 +12,11 @@ using namespace nb::literals; namespace pyigl { void write_triangle_mesh( - const std::string & filename, + const std::filesystem::path & filename, const nb::DRef V, const nb::DRef F, - const std::string encoding) + const igl::FileEncoding encoding_enum) { - auto encoding_enum = igl::FileEncoding::Ascii; - if(encoding == "binary") - { - encoding_enum = igl::FileEncoding::Binary; - }else if(encoding != "ascii") - { - // throw runtime exception - throw std::runtime_error("Invalid encoding: " + encoding); - } // Throw an error if entries in F are bigger than can be cast to int32_t if(F.maxCoeff() > std::numeric_limits::max()) { @@ -36,7 +27,7 @@ namespace pyigl if(!igl::write_triangle_mesh(filename,V,F32,encoding_enum)) { // throw runtime exception - throw std::runtime_error("Failed to write mesh to: " + filename); + throw std::runtime_error("Failed to write mesh to: " + filename.generic_string()); } } } @@ -50,7 +41,7 @@ void bind_write_triangle_mesh(nb::module_ &m) "filename"_a, "V"_a, "F"_a, - "encoding"_a="ascii", + "encoding"_a=igl::FileEncoding::Ascii, R"(write mesh to a file with automatic detection of file format. supported: obj, off, stl, wrl, ply, mesh). diff --git a/tests/test.py b/tests/test.py index ba29ba5e..fb1ba8fa 100644 --- a/tests/test.py +++ b/tests/test.py @@ -63,7 +63,7 @@ l = igl.edge_lengths(V,F) igl.write_triangle_mesh("out.obj",V,F) igl.write_triangle_mesh("out.off",V,F) -igl.write_triangle_mesh("out.ply",V,F,encoding="binary") +igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) igl.writePLY("out.ply",V,F) igl.writeOBJ("out.obj",V,F) igl.writeOBJ("out.obj",V,F,V,F,V,F) @@ -74,6 +74,7 @@ I = np.array([0,1,2,3],dtype=np.int64) C = np.array([0,4],dtype=np.int64) L,M,P = igl.cotmatrix_polygon(V,I,C) +A = igl.facet_adjacency_matrix(F) A = igl.adjacency_matrix(F) A = igl.adjacency_matrix_polygon(I,C) E = igl.edges(F) @@ -81,17 +82,17 @@ E = igl.edges(A) M = igl.massmatrix(V,F) -M = igl.massmatrix(V,F,type="barycentric") -M = igl.massmatrix(V,F,type="voronoi") -M = igl.massmatrix(V,F,type="full") +M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) +M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) +M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) FN,_,_,_ = igl.per_face_normals(V,I,C) FN = igl.per_face_normals(V,F) FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) VN = igl.per_vertex_normals(V,F) -VN = igl.per_vertex_normals(V,F,weighting="uniform") -VN = igl.per_vertex_normals(V,F,weighting="area") -VN = igl.per_vertex_normals(V,F,weighting="angle") -VN = igl.per_vertex_normals(V,F,weighting="angle",FN=FN) +VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) +VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) +VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) +VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) dblA = igl.doublearea(V,F) dblA = igl.doublearea(l=l) @@ -102,7 +103,7 @@ W = igl.winding_number(V,F,P[1,:]) W = igl.fast_winding_number(V,F,P) S,I,C,N = igl.signed_distance(P,V,F) -S,I,C,N = igl.signed_distance(P,V,F,sign_type="fast_winding_number") +S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) BC = igl.barycenter(V,F) @@ -114,11 +115,14 @@ V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) T = np.array([[0,1,2,3]],dtype=np.int64) F,J,K = igl.boundary_facets(T) igl.writeMESH("out.mesh",V,T) igl.writeMESH("out.mesh",V,T,F=F) V,T,F = igl.readMESH("out.mesh") +igl.writeMSH("out.msh",V,F,T) +V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") tree = igl.AABB() tree.init(V,T) @@ -127,6 +131,9 @@ I = tree.find(V,T,q) i = tree.find(V,T,q,first=True) +Q = igl.barycenter(V,T) +I = igl.in_element(V,T,Q,tree) + tree = igl.AABB() tree.init(V,F) sqrD,_,_ = tree.squared_distance(V,F,P) @@ -141,14 +148,26 @@ hits = igl.ray_mesh_intersect(o,d,V,F) +# upsample so there's something to collapse +V,F = igl.upsample(V,F,number_of_subdivs=1) E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) EF,EI = igl.edge_flaps(F,uE,EMAP) uE,EMAP,EF,EI = igl.edge_flaps(F) Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) +e = 0 +p = V[uE[e,0],:] +e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) +# restore single tet +V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +T = np.array([[0,1,2,3]],dtype=np.int64) +C = np.ones(F.shape,dtype=bool) +Vn,Fn,I = igl.cut_mesh(V,F,C) + TT,TTi = igl.triangle_triangle_adjacency(F) TT,TTi = igl.triangle_triangle_adjacency_lists(F) @@ -159,6 +178,10 @@ F120 = np.roll(F012,1,axis=1) FF = np.vstack((F012,F120)) F,IA,IC = igl.unique_simplices(FF) +A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) +B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) +IA,LOCB = igl.ismember_rows(A,B) + L = igl.cotmatrix(V,F) @@ -176,7 +199,9 @@ Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) Beq = np.zeros((1,1),dtype=np.float64) Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) -Z = igl.MinQuadWithFixed(A,known,Aeq,True).solve(B,Y,Beq) +data = igl.min_quad_with_fixed_data() +igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) +Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) T = np.array([[0,1,2,3]],dtype=np.int64) @@ -242,6 +267,7 @@ V,F = igl.loop(V,F,number_of_subdivs=1) # remove first and last F = F[1:-1,:] +L_all = igl.boundary_loop_all(F) L = igl.boundary_loop(F) A = igl.adjacency_list(F) @@ -251,8 +277,12 @@ GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) C = igl.vertex_components(F) +nc,C = igl.facet_components(F) B,I,X = igl.random_points_on_mesh(1000,V,F) +point_indices, CH,CN,W = igl.octree(X) +I = igl.knn(X,X,1,point_indices,CH,CN,W) + igl.writeDMAT("out.dmat",V) igl.writeDMAT("out.dmat",V,ascii=True) V = igl.readDMAT("out.dmat") @@ -278,7 +308,7 @@ V_init[:,0] = V_init[:,0] * 2 b = np.array([0,1],dtype=np.int64) bc = V[b,:2] -data = igl.slim_precompute(V,F,V_init,"symmetric_dirichlet",b,bc,soft_p=1e10) +data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) U = igl.slim_solve(data,iter_num=1) data = igl.ARAPData() @@ -289,6 +319,7 @@ U,Q = igl.lscm(V,F,b,bc) + res = np.array([3,3,3],dtype=np.int64) GV = igl.grid(res) S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; @@ -296,16 +327,29 @@ # unpack keys into (i,j,v) index triplets EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) -try: - import igl.triangle - V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) - E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) - V,T,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") - V,T,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") - V,T,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") -except ImportError: - warnings.warn("igl.triangle not available") - pass + +h = igl.avg_edge_length(V,F) +m0,m1,m2 = igl.moments(V,F) +D = V[:,0] +vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) +iV,iE,I = igl.isolines(V,F,S,vals) +iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) + + +x0 = V[0,:] +f = np.array([0,0,1],dtype=np.float64) +R = np.eye(3).astype(np.float64) +U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) + +SV = V[:,0] +SF = igl.average_onto_faces(F,SV) +SV = igl.average_onto_vertices(V,F,SF) + +P = [[0,1,2],[2,1,3,4]] +I,C = igl.polygon_corners(P) +Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) +I,C = igl.polygon_corners(Q) +F,J = igl.polygons_to_triangles(I,C) try: import igl.copyleft @@ -320,16 +364,6 @@ warnings.warn("igl.copyleft not available") pass -try: - import igl.copyleft.tetgen - # octahedron - V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) - F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) - V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") -except ImportError: - warnings.warn("igl.copyleft.tetgen not available") - pass - try: import igl.copyleft.cgal # tetrahedron @@ -361,6 +395,70 @@ VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) + _,I,X = igl.random_points_on_mesh(1000,VC,FC) + N = igl.per_face_normals(VC,FC) + N = N[I,:] + point_indices, CH,CN,W = igl.octree(X) + I = igl.knn(X,X,20,point_indices,CH,CN,W) + A,T = igl.copyleft.cgal.point_areas(X,I,N) + except ImportError: warnings.warn("igl.copyleft.cgal not available") pass + +try: + import igl.embree + # octahedron + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) + N = igl.per_vertex_normals(V,F) + ei = igl.embree.EmbreeIntersector(); + ei.init(V,F) + S = igl.embree.ambient_occlusion(V,F,V,N,100) + S = igl.embree.ambient_occlusion(ei,V,N,100) + N = -N + S = igl.embree.shape_diameter_function(V,F,V,N,100) + S = igl.embree.shape_diameter_function(ei,V,N,100) + origin = np.array([2,2,2],dtype=np.float64) + direction = np.array([-2,-2,-2],dtype=np.float64) + hit = ei.intersectRay_first(origin,direction) + hits = ei.intersectRay(origin,direction) + hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) + I,C = igl.embree.reorient_facets_raycast(V,F) +except ImportError: + warnings.warn("igl.embree not available") + pass + +try: + import igl.copyleft.tetgen + # octahedron + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) + V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") +except ImportError: + warnings.warn("igl.copyleft.tetgen not available") + pass + + +try: + import igl.triangle + V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) + E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + scaf_data = igl.triangle.SCAFData() + b = np.array([0,1,2],dtype=np.int64) + bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) + V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) + soft_p = 0; + igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) + L,rhs = igl.triangle.scaf_system(scaf_data) + U = igl.triangle.scaf_solve(1,scaf_data) + + +except ImportError: + warnings.warn("igl.triangle not available") + pass From 6305f7022e2b1b0c15a4fc6681cae704d46edd12 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Mon, 18 Nov 2024 16:03:17 -0500 Subject: [PATCH 014/102] intrinsic stuff --- src/MassMatrixType.cpp | 20 ++++++ src/cotmatrix_intrinsic.cpp | 43 ++++++++++++ src/heat_geodesics.cpp | 89 ++++++++++++++++++++++++ src/icosahedron.cpp | 35 ++++++++++ src/intrinsic_delaunay_cotmatrix.cpp | 46 ++++++++++++ src/intrinsic_delaunay_triangulation.cpp | 63 +++++++++++++++++ src/massmatrix.cpp | 7 -- src/massmatrix_intrinsic.cpp | 45 ++++++++++++ src/matlab_format.cpp | 14 ++-- tests/test.py | 19 ++++- 10 files changed, 366 insertions(+), 15 deletions(-) create mode 100644 src/MassMatrixType.cpp create mode 100644 src/cotmatrix_intrinsic.cpp create mode 100644 src/heat_geodesics.cpp create mode 100644 src/icosahedron.cpp create mode 100644 src/intrinsic_delaunay_cotmatrix.cpp create mode 100644 src/intrinsic_delaunay_triangulation.cpp create mode 100644 src/massmatrix_intrinsic.cpp diff --git a/src/MassMatrixType.cpp b/src/MassMatrixType.cpp new file mode 100644 index 00000000..94a2d08c --- /dev/null +++ b/src/MassMatrixType.cpp @@ -0,0 +1,20 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + + +void bind_MassMatrixType(nb::module_ &m) +{ + nb::enum_(m, "MassMatrixType") + .value("MASSMATRIX_TYPE_BARYCENTRIC", igl::MASSMATRIX_TYPE_BARYCENTRIC) + .value("MASSMATRIX_TYPE_VORONOI", igl::MASSMATRIX_TYPE_VORONOI) + .value("MASSMATRIX_TYPE_FULL", igl::MASSMATRIX_TYPE_FULL) + .value("MASSMATRIX_TYPE_DEFAULT", igl::MASSMATRIX_TYPE_DEFAULT) + .export_values() + ; +} diff --git a/src/cotmatrix_intrinsic.cpp b/src/cotmatrix_intrinsic.cpp new file mode 100644 index 00000000..c08f15b5 --- /dev/null +++ b/src/cotmatrix_intrinsic.cpp @@ -0,0 +1,43 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto cotmatrix_intrinsic( + const nb::DRef &l, + const nb::DRef &F) + { + Eigen::SparseMatrixN L; + igl::cotmatrix_intrinsic(l,F,L); + return L; + } +} + +// Bind the wrapper to the Python module +void bind_cotmatrix_intrinsic(nb::module_ &m) +{ + m.def( + "cotmatrix_intrinsic", + &pyigl::cotmatrix_intrinsic, + "l"_a, + "F"_a, +R"( +Constructs the cotangent stiffness matrix (discrete laplacian) for a given +mesh with faces F and edge lengths l. + +@param[in] l #F by 3 list of (half-)edge lengths +@param[in] F #F by 3 list of face indices into some (not necessarily + determined/embedable) list of vertex positions V. It is assumed #V == + F.maxCoeff()+1 +@param[out] L #V by #V sparse Laplacian matrix + +\see cotmatrix, intrinsic_delaunay_cotmatrix)"); +} diff --git a/src/heat_geodesics.cpp b/src/heat_geodesics.cpp new file mode 100644 index 00000000..e7e276a7 --- /dev/null +++ b/src/heat_geodesics.cpp @@ -0,0 +1,89 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + void heat_geodesics_precompute_t( + const nb::DRef &V, + const nb::DRef &F, + const Numeric t, + igl::HeatGeodesicsData &data) + { + if(!igl::heat_geodesics_precompute(V,F,t,data)) + { + throw std::runtime_error("heat_geodesics: Precomputation failed."); + } + } + // Obnoxious way to have optional t + void heat_geodesics_precompute( + const nb::DRef &V, + const nb::DRef &F, + igl::HeatGeodesicsData &data) + { + if(!igl::heat_geodesics_precompute(V,F,data)) + { + throw std::runtime_error("heat_geodesics: Precomputation failed."); + } + } + + auto heat_geodesics_solve( + const igl::HeatGeodesicsData &data, + const nb::DRef &gamma) + { + Eigen::VectorXN D; + igl::heat_geodesics_solve(data, gamma, D); + return D; + } + +} + +// Bind the wrapper to the Python module +void bind_heat_geodesics(nb::module_ &m) +{ + nb::class_>(m, "HeatGeodesicsData") + .def(nb::init<>()) + .def_rw("use_intrinsic_delaunay", &igl::HeatGeodesicsData::use_intrinsic_delaunay) + ; + + m.def("heat_geodesics_precompute", + &pyigl::heat_geodesics_precompute_t, + "V"_a, "F"_a, "t"_a, "data"_a, + R"(Precompute factorized solvers for computing a fast approximation of +geodesic distances on a mesh (V,F). [Crane et al. 2013] + +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of mesh face indices into V +@param[in] t "heat" parameter (smaller --> more accurate, less stable) +@param[out] data precomputation data (see heat_geodesics_solve) + )"); + m.def("heat_geodesics_precompute", + &pyigl::heat_geodesics_precompute, + "V"_a, "F"_a, "data"_a, + R"(Precompute factorized solvers for computing a fast approximation of +geodesic distances on a mesh (V,F). [Crane et al. 2013] + +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of mesh face indices into V +@param[out] data precomputation data (see heat_geodesics_solve) + )"); + m.def("heat_geodesics_solve", + &pyigl::heat_geodesics_solve, + "data"_a, + "gamma"_a, + R"(Compute fast approximate geodesic distances using precomputed data from a +set of selected source vertices (gamma). + +@param[in] data precomputation data (see heat_geodesics_precompute) +@param[in] gamma #gamma list of indices into V of source vertices +@param[out] D #V list of distances to gamma + +\fileinfo + )"); +} diff --git a/src/icosahedron.cpp b/src/icosahedron.cpp new file mode 100644 index 00000000..25f26ade --- /dev/null +++ b/src/icosahedron.cpp @@ -0,0 +1,35 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto icosahedron() + { + Eigen::MatrixXN V; + Eigen::MatrixXI F; + igl::icosahedron(V,F); + return std::make_tuple(V,F); + } +} + +// Bind the wrapper to the Python module +void bind_icosahedron(nb::module_ &m) +{ + m.def( + "icosahedron", + &pyigl::icosahedron, +R"( +Construct a icosahedron with radius 1 centered at the origin + +Outputs: + V #V by 3 list of vertex positions + F #F by 3 list of triangle indices into rows of V)"); +} + diff --git a/src/intrinsic_delaunay_cotmatrix.cpp b/src/intrinsic_delaunay_cotmatrix.cpp new file mode 100644 index 00000000..59223ea9 --- /dev/null +++ b/src/intrinsic_delaunay_cotmatrix.cpp @@ -0,0 +1,46 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto intrinsic_delaunay_cotmatrix( + const nb::DRef &V, + const nb::DRef &F) + { + Eigen::SparseMatrixN L; + Eigen::MatrixXN il; + Eigen::MatrixXI iF; + igl::intrinsic_delaunay_cotmatrix(V,F,L,il,iF); + return std::make_tuple(L,il,iF); + } +} + +// Bind the wrapper to the Python module +void bind_intrinsic_delaunay_cotmatrix(nb::module_ &m) +{ + m.def( + "intrinsic_delaunay_cotmatrix", + &pyigl::intrinsic_delaunay_cotmatrix, + "V"_a, + "F"_a, +R"( +Computes the discrete cotangent Laplacian of a mesh after converting it +into its intrinsic Delaunay triangulation (see, e.g., [Fisher et al. +2007]. + +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of mesh elements (triangles or tetrahedra) +@param[out] L #V by #V cotangent matrix, each row i corresponding to V(i,:) +@param[out] l_intrinsic #F by 3 list of intrinsic edge-lengths used to compute L +@param[out] F_intrinsic #F by 3 list of intrinsic face indices used to compute L + +\see intrinsic_delaunay_triangulation, cotmatrix, cotmatrix_intrinsic)"); +} diff --git a/src/intrinsic_delaunay_triangulation.cpp b/src/intrinsic_delaunay_triangulation.cpp new file mode 100644 index 00000000..0b141fe6 --- /dev/null +++ b/src/intrinsic_delaunay_triangulation.cpp @@ -0,0 +1,63 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto intrinsic_delaunay_triangulation( + const nb::DRef &l_in, + const nb::DRef &F_in) + { + Eigen::MatrixXN l; + Eigen::MatrixXI F,E,uE; + Eigen::VectorXI EMAP; + std::vector > uE2E; + igl::intrinsic_delaunay_triangulation(l_in,F_in,l,F,E,uE,EMAP,uE2E); + return std::make_tuple(l,F,E,uE,EMAP,uE2E); + + } +} + +// Bind the wrapper to the Python module +void bind_intrinsic_delaunay_triangulation(nb::module_ &m) +{ + m.def( + "intrinsic_delaunay_triangulation", + &pyigl::intrinsic_delaunay_triangulation, + "l"_a, + "F"_a, +R"( +INTRINSIC_DELAUNAY_TRIANGULATION Flip edges _intrinsically_ until all are +"intrinsic Delaunay". See "An algorithm for the construction of intrinsic +delaunay triangulations with applications to digital geometry processing" +[Fisher et al. 2007]. + +@param[in] l_in #F_in by 3 list of edge lengths (see edge_lengths) +@param[in] F_in #F_in by 3 list of face indices into some unspecified vertex list V +@param[out] l #F by 3 list of edge lengths +@param[out] F #F by 3 list of new face indices. Note: Combinatorially F may contain + non-manifold edges, duplicate faces and self-loops (e.g., an edge [1,1] + or a face [1,1,1]). However, the *intrinsic geometry* is still + well-defined and correct. See [Fisher et al. 2007] Figure 3 and 2nd to + last paragraph of 1st page. Since F may be "non-eddge-manifold" in the + usual combinatorial sense, it may be useful to call the more verbose + overload below if disentangling edges will be necessary later on. + Calling unique_edge_map on this F will give a _different_ result than + those outputs. +@param[out] E #F*3 by 2 list of all directed edges, such that E.row(f+#F*c) is the +@param[out] edge opposite F(f,c) +@param[out] uE #uE by 2 list of unique undirected edges +@param[out] EMAP #F*3 list of indices into uE, mapping each directed edge to unique +@param[out] undirected edge +@param[out] uE2E #uE list of lists of indices into E of coexisting edges + +\see unique_edge_map +)"); +} diff --git a/src/massmatrix.cpp b/src/massmatrix.cpp index 07ac7519..f22e334d 100644 --- a/src/massmatrix.cpp +++ b/src/massmatrix.cpp @@ -25,13 +25,6 @@ namespace pyigl // Bind the wrapper to the Python module void bind_massmatrix(nb::module_ &m) { - nb::enum_(m, "MassMatrixType") - .value("MASSMATRIX_TYPE_BARYCENTRIC", igl::MASSMATRIX_TYPE_BARYCENTRIC) - .value("MASSMATRIX_TYPE_VORONOI", igl::MASSMATRIX_TYPE_VORONOI) - .value("MASSMATRIX_TYPE_FULL", igl::MASSMATRIX_TYPE_FULL) - .value("MASSMATRIX_TYPE_DEFAULT", igl::MASSMATRIX_TYPE_DEFAULT) - .export_values() - ; m.def( "massmatrix", &pyigl::massmatrix, diff --git a/src/massmatrix_intrinsic.cpp b/src/massmatrix_intrinsic.cpp new file mode 100644 index 00000000..e758646d --- /dev/null +++ b/src/massmatrix_intrinsic.cpp @@ -0,0 +1,45 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto massmatrix_intrinsic( + const nb::DRef &l, + const nb::DRef &F, + const igl::MassMatrixType type) + { + Eigen::SparseMatrixN M; + igl::massmatrix_intrinsic(l,F,type,M); + return M; + } +} + +// Bind the wrapper to the Python module +void bind_massmatrix_intrinsic(nb::module_ &m) +{ + m.def( + "massmatrix_intrinsic", + &pyigl::massmatrix_intrinsic, + "l"_a, + "F"_a, + "type"_a=igl::MASSMATRIX_TYPE_DEFAULT, +R"( +Constructs the mass matrix for a given +mesh with faces F and edge lengths l. + +@param[in] l #F by 3 list of (half-)edge lengths +@param[in] F #F by 3 list of face indices into some (not necessarily + determined/embedable) list of vertex positions V. It is assumed #V == + F.maxCoeff()+1 +@param[out] L #V by #V sparse Laplacian matrix + +\see massmatrix, intrinsic_delaunay_massmatrix)"); +} diff --git a/src/matlab_format.cpp b/src/matlab_format.cpp index b4dfbed8..26fc3d46 100644 --- a/src/matlab_format.cpp +++ b/src/matlab_format.cpp @@ -56,6 +56,13 @@ void bind_matlab_format(nb::module_ &m) "name"_a = "", R"(Format a dense matrix for MATLAB-style output.)"); + m.def( + "matlab_format", + &pyigl::matlab_format_dense_vector, + "M"_a, + "name"_a = "", + R"(Format a dense matrix for MATLAB-style output.)"); + m.def( "matlab_format", &pyigl::matlab_format_sparse, @@ -77,13 +84,6 @@ void bind_matlab_format(nb::module_ &m) "name"_a = "", R"(Format a matrix for MATLAB-style output with 1-based indexing.)"); - m.def( - "matlab_format", - &pyigl::matlab_format_dense_vector, - "M"_a, - "name"_a = "", - R"(Format a dense matrix for MATLAB-style output.)"); - m.def( "matlab_format_index", &pyigl::matlab_format_index_vector, diff --git a/tests/test.py b/tests/test.py index fb1ba8fa..ecb7559a 100644 --- a/tests/test.py +++ b/tests/test.py @@ -113,7 +113,6 @@ W = igl.harmonic(V,F,b,bc,k=1) W = igl.harmonic(V,F,b,bc,k=2) - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) T = np.array([[0,1,2,3]],dtype=np.int64) @@ -351,6 +350,24 @@ I,C = igl.polygon_corners(Q) F,J = igl.polygons_to_triangles(I,C) +V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +V,F = igl.upsample(V,F,number_of_subdivs=1) +data = igl.HeatGeodesicsData() +igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) +data = igl.HeatGeodesicsData() +data.use_intrinsic_delaunay = True +igl.heat_geodesics_precompute(V,F,data) +gamma = np.array([0],dtype=np.int64) +D = igl.heat_geodesics_solve(data,gamma) +l = igl.edge_lengths(V,F) +iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) +L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) +L = igl.cotmatrix_intrinsic(il,iF) +M = igl.massmatrix_intrinsic(il,iF) + +V,F = igl.icosahedron() + try: import igl.copyleft V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) From 85f5d3f761d118ebd4c02519881d76a7bd9833af Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Mon, 18 Nov 2024 21:20:37 -0500 Subject: [PATCH 015/102] refactor tests --- CMakeLists.txt | 2 +- src/adjacency_matrix.cpp | 2 +- src/bfs_orient.cpp | 38 + src/biharmonic_coordinates.cpp | 55 ++ src/bijective_composite_harmonic_mapping.cpp | 65 ++ src/blue_noise.cpp | 43 + src/cotmatrix.cpp | 2 +- src/ears.cpp | 31 + src/split_nonmanifold.cpp | 55 ++ src/triangle_fan.cpp | 29 + tests/test.py | 844 ++++++++++--------- 11 files changed, 766 insertions(+), 400 deletions(-) create mode 100644 src/bfs_orient.cpp create mode 100644 src/biharmonic_coordinates.cpp create mode 100644 src/bijective_composite_harmonic_mapping.cpp create mode 100644 src/blue_noise.cpp create mode 100644 src/ears.cpp create mode 100644 src/split_nonmanifold.cpp create mode 100644 src/triangle_fan.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bf6762db..e9b8b1cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 7326483d8dcea0169054599ae7e09917929b4b46 + GIT_TAG f82ad2b463e0307c56f8101bcfe5c290c9cd30cd ) FetchContent_MakeAvailable(libigl) diff --git a/src/adjacency_matrix.cpp b/src/adjacency_matrix.cpp index 361132c2..03d3459a 100644 --- a/src/adjacency_matrix.cpp +++ b/src/adjacency_matrix.cpp @@ -41,7 +41,7 @@ R"(Constructs the adjacency matrix for a simplicial mesh. @return A Sparse adjacency matrix of size max(F)+1 by max(F)+1)"); m.def( - "adjacency_matrix_polygon", + "adjacency_matrix", &pyigl::adjacency_matrix_polygon, "I"_a, "C"_a, diff --git a/src/bfs_orient.cpp b/src/bfs_orient.cpp new file mode 100644 index 00000000..05a879fa --- /dev/null +++ b/src/bfs_orient.cpp @@ -0,0 +1,38 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + auto bfs_orient( + const nb::DRef &F) + { + Eigen::MatrixXI FF; + Eigen::VectorXI C; + igl::bfs_orient(F,FF,C); + return std::make_tuple(FF,C); + } +} + +// Bind the wrapper to the Python module +void bind_bfs_orient(nb::module_ &m) +{ + m.def( + "bfs_orient", + &pyigl::bfs_orient, + "F"_a, + R"( +Consistently orient faces in orientable patches using BFS. + +@param[in] F #F by 3 list of faces +@param[out] FF #F by 3 list of faces (OK if same as F) +@param[out] C #F list of component ids)" + ); +} + diff --git a/src/biharmonic_coordinates.cpp b/src/biharmonic_coordinates.cpp new file mode 100644 index 00000000..59a26c2d --- /dev/null +++ b/src/biharmonic_coordinates.cpp @@ -0,0 +1,55 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto biharmonic_coordinates( + const nb::DRef &V, + const nb::DRef &T, + const std::vector> &S, + const int k) + { + Eigen::MatrixXN W; + igl::biharmonic_coordinates(V, T, S, k, W); + return W; + } +} + +void bind_biharmonic_coordinates(nb::module_ &m) +{ + m.def("biharmonic_coordinates", &pyigl::biharmonic_coordinates, + "V"_a, + "T"_a, + "S"_a, + "k"_a=2, + R"(Compute "discrete biharmonic generalized barycentric coordinates" as +described in "Linear Subspace Design for Real-Time Shape Deformation" +[Wang et al. 2015]. Not to be confused with "Bounded Biharmonic Weights +for Real-Time Deformation" [Jacobson et al. 2011] or "Biharmonic +Coordinates" (2D complex barycentric coordinates) [Weber et al. 2012]. +These weights minimize a discrete version of the squared Laplacian energy +subject to positional interpolation constraints at selected vertices +(point handles) and transformation interpolation constraints at regions +(region handles). +@tparam SType should be a simple index type e.g. `int`,`size_t` +@param[in] V #V by dim list of mesh vertex positions +@param[in] T #T by dim+1 list of / triangle indices into V if dim=2 + \ tetrahedron indices into V if dim=3 +@param[in] S #point-handles+#region-handles list of lists of selected vertices for + each handle. Point handles should have singleton lists and region + handles should have lists of size at least dim+1 (and these points + should be in general position). +@param[out] W #V by #points-handles+(#region-handles * dim+1) matrix of weights so + that columns correspond to each handles generalized barycentric + coordinates (for point-handles) or animation space weights (for region + handles). +@return true only on success + )"); +} diff --git a/src/bijective_composite_harmonic_mapping.cpp b/src/bijective_composite_harmonic_mapping.cpp new file mode 100644 index 00000000..12d6cec4 --- /dev/null +++ b/src/bijective_composite_harmonic_mapping.cpp @@ -0,0 +1,65 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto bijective_composite_harmonic_mapping( + const nb::DRef & V, + const nb::DRef & F, + const nb::DRef & b, + const nb::DRef & bc, + const int min_steps, + const int max_steps, + const int num_inner_iters, + const bool test_for_flips) + { + Eigen::MatrixXN U; + if(!igl::bijective_composite_harmonic_mapping(V, F, b, bc, min_steps, max_steps, num_inner_iters, test_for_flips, U)) + { + throw std::runtime_error("bijective_composite_harmonic_mapping failed"); + } + return U; + } +} + +void bind_bijective_composite_harmonic_mapping(nb::module_ &m) +{ + m.def("bijective_composite_harmonic_mapping", &pyigl::bijective_composite_harmonic_mapping, + "V"_a, + "F"_a, + "b"_a, + "bc"_a, + "min_steps"_a=1, + "max_steps"_a=200, + "num_inner_iters"_a=20, + "test_for_flips"_a=true, + R"(Compute a injective planar mapping of a triangulated polygon (V,F) subjected to +boundary conditions (b,bc). The mapping should be bijective in the sense +that no triangles' areas become negative (this assumes they started +positive). This mapping is computed by "composing" harmonic mappings +between incremental morphs of the boundary conditions. This is a bit like +a discrete version of "Bijective Composite Mean Value Mappings" [Schneider +et al. 2013] but with a discrete harmonic map (cf. harmonic coordinates) +instead of mean value coordinates. This is inspired by "Embedding a +triangular graph within a given boundary" [Xu et al. 2011]. +@param[in] V #V by 2 list of triangle mesh vertex positions +@param[in] F #F by 3 list of triangle indices into V +@param[in] b #b list of boundary indices into V +@param[in] bc #b by 2 list of boundary conditions corresponding to b +@param[in] min_steps minimum number of steps to take from V(b,:) to bc +@param[in] max_steps minimum number of steps to take from V(b,:) to bc (if + max_steps == min_steps then no further number of steps will be tried) +@param[in] num_inner_iters number of iterations of harmonic solves to run after + for each morph step (to try to push flips back in) +@param[in] test_for_flips whether to check if flips occurred (and trigger more + steps). if test_for_flips = false then this function always returns + true +@param[out] U #V by 2 list of output mesh vertex locations +@return true if and only if U contains a successful bijectie mapping)"); +} diff --git a/src/blue_noise.cpp b/src/blue_noise.cpp new file mode 100644 index 00000000..248d3bd9 --- /dev/null +++ b/src/blue_noise.cpp @@ -0,0 +1,43 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto blue_noise( + const nb::DRef & V, + const nb::DRef & F, + const Numeric r) + { + Eigen::MatrixXN B; + Eigen::VectorXI FI; + Eigen::MatrixXN P; + igl::blue_noise(V, F, r, B, FI, P); + return std::make_tuple(B, FI, P); + } +} + +void bind_blue_noise(nb::module_ &m) +{ + m.def("blue_noise", &pyigl::blue_noise, + "V"_a, + "F"_a, + "r"_a, + R"("Fast Poisson Disk Sampling in Arbitrary Dimensions" [Bridson 2007]. +For very dense samplings this is faster than (up to 2x) cyCodeBase's +implementation of "Sample Elimination for Generating Poisson Disk Sample +Sets" [Yuksel 2015]. YMMV +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of mesh triangle indices into rows of V +@param[in] r Poisson disk radius (evaluated according to Euclidean distance on V) +@param[out] B #P by 3 list of barycentric coordinates, ith row are coordinates of + ith sampled point in face FI(i) +@param[out] FI #P list of indices into F +@param[out] P #P by dim list of sample positions. +)"); +} diff --git a/src/cotmatrix.cpp b/src/cotmatrix.cpp index f16dfb18..cb8c9577 100644 --- a/src/cotmatrix.cpp +++ b/src/cotmatrix.cpp @@ -52,7 +52,7 @@ mesh (V,F). @param[out] L #V by #V cotangent matrix, each row i corresponding to V(i,:))"); m.def( - "cotmatrix_polygon", + "cotmatrix", &pyigl::cotmatrix_polygon, "V"_a, "I"_a, diff --git a/src/ears.cpp b/src/ears.cpp new file mode 100644 index 00000000..4649e628 --- /dev/null +++ b/src/ears.cpp @@ -0,0 +1,31 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto ears( + const nb::DRef & F) + { + Eigen::VectorXI ear,ear_opp; + igl::ears(F,ear,ear_opp); + return std::make_tuple(ear,ear_opp); + } +} + +void bind_ears(nb::module_ &m) +{ + m.def("ears", &pyigl::ears, + "F"_a, + R"(Find all ears (faces with two boundary edges) in a given mesh + +@param[in] F #F by 3 list of triangle mesh indices +@param[out] ears #ears list of indices into F of ears +@param[out] ear_opp #ears list of indices indicating which edge is non-boundary + (connecting to flops))"); +} diff --git a/src/split_nonmanifold.cpp b/src/split_nonmanifold.cpp new file mode 100644 index 00000000..96f076e9 --- /dev/null +++ b/src/split_nonmanifold.cpp @@ -0,0 +1,55 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto split_nonmanifold( + const nb::DRef & F) + { + Eigen::MatrixXI SF; + Eigen::VectorXI SVI; + igl::split_nonmanifold(F, SF, SVI); + return std::make_tuple(SF, SVI); + } + auto split_nonmanifold_VF( + const nb::DRef & V, + const nb::DRef & F) + { + Eigen::MatrixXI SF; + Eigen::MatrixXN SV; + Eigen::VectorXI SVI; + igl::split_nonmanifold(V, F, SV, SF, SVI); + return std::make_tuple(SV, SF, SVI); + } +} + +void bind_split_nonmanifold(nb::module_ &m) +{ + m.def("split_nonmanifold", &pyigl::split_nonmanifold, + "F"_a, + R"(Split a non-manifold (or non-orientable) mesh into a orientable manifold +mesh possibly with more connected components and geometrically duplicate +vertices. +@param[in] F #F by 3 list of mesh triangle indices into rows of some V +@param[out] SF #F by 3 list of mesh triangle indices into rows of a new vertex list + SV = V(SVI,:) +@param[out] SVI #SV list of indices into V identifying vertex positions)"); + m.def("split_nonmanifold", &pyigl::split_nonmanifold_VF, + "V"_a, + "F"_a, + R"(Split a non-manifold (or non-orientable) mesh into a orientable manifold +mesh possibly with more connected components and geometrically duplicate +vertices. +@param[in] V #V by dim explicit list of vertex positions +@param[in] F #F by 3 list of mesh triangle indices into rows of some V +@param[out] SV #SV by dim explicit list of vertex positions +@param[out] SF #F by 3 list of mesh triangle indices into rows of a new vertex list + SV = V(SVI,:) +@param[out] SVI #SV list of indices into V identifying vertex positions)"); +} diff --git a/src/triangle_fan.cpp b/src/triangle_fan.cpp new file mode 100644 index 00000000..701e0708 --- /dev/null +++ b/src/triangle_fan.cpp @@ -0,0 +1,29 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto triangle_fan( + const nb::DRef & E) + { + Eigen::MatrixXI cap; + igl::triangle_fan(E, cap); + return cap; + } +} + +void bind_triangle_fan(nb::module_ &m) +{ + m.def("triangle_fan", &pyigl::triangle_fan, + "E"_a, + R"(Given a list of faces tessellate all of the "exterior" edges forming another +list of +@param[in] E #E by simplex_size-1 list of exterior edges (see exterior_edges.h) +@param[out] cap #cap by simplex_size list of "faces" tessellating the boundary edges)"); +} diff --git a/tests/test.py b/tests/test.py index ecb7559a..2431df72 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,388 +1,457 @@ -import igl +import pytest import numpy as np # scipy sparse matrices import scipy.sparse -# timing import time import warnings - - -#def rand_sparse(n,density): -# n_features = n -# n_samples = n -# rng1 = np.random.RandomState(42) -# rng2 = np.random.RandomState(43) -# -# nnz = int(n_samples*n_features*density) -# -# row = rng1.randint(n_samples, size=nnz) -# cols = rng2.randint(n_features, size=nnz) -# data = rng1.rand(nnz) -# -# S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) -# return S.tocsc() -# -#def time_noop(): -# def helper(N,I,SN,SI): -# igl.noop(SN=SN) -# # start timer -# runs = 100 -# start = time.time() -# for i in range(runs): -# igl.noop(SN=SN) -# # end timer -# end = time.time() -# return (end - start)/runs -# n = 10000 -# m = 10 -# N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) -# I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) -# # random sparse matrix -# SN64 = rand_sparse(n,1.0/(n)) -# # print number of nonzeros -# SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) -# print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +import igl +import igl.copyleft +import igl.copyleft.cgal +import igl.copyleft.tetgen +import igl.embree +import igl.triangle + +##def rand_sparse(n,density): +## n_features = n +## n_samples = n +## rng1 = np.random.RandomState(42) +## rng2 = np.random.RandomState(43) +## +## nnz = int(n_samples*n_features*density) +## +## row = rng1.randint(n_samples, size=nnz) +## cols = rng2.randint(n_features, size=nnz) +## data = rng1.rand(nnz) +## +## S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) +## return S.tocsc() +## +##def time_noop(): +## def helper(N,I,SN,SI): +## igl.noop(SN=SN) +## # start timer +## runs = 100 +## start = time.time() +## for i in range(runs): +## igl.noop(SN=SN) +## # end timer +## end = time.time() +## return (end - start)/runs +## n = 10000 +## m = 10 +## N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) +## I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) +## # random sparse matrix +## SN64 = rand_sparse(n,1.0/(n)) +## # print number of nonzeros +## SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) +## print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +## +##time_noop() # -#time_noop() +## print(igl.matlab_format(V,"V")) +## print(igl.matlab_format_index(F,"F")) +## print(igl.matlab_format(dV,"dV")) +## print(igl.matlab_format_index(dF,"dF")) -# print(igl.matlab_format(V,"V")) -# print(igl.matlab_format_index(F,"F")) -# print(igl.matlab_format(dV,"dV")) -# print(igl.matlab_format_index(dF,"dF")) # seed numpy's random number generator -np.random.seed(42) - -F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) -E,oE = igl.orient_halfedges(F) -ne = E.max()+1 -uE = np.random.rand(ne).astype(np.float64) - -uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) -V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) -l = igl.edge_lengths(V,F) -igl.write_triangle_mesh("out.obj",V,F) -igl.write_triangle_mesh("out.off",V,F) -igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) -igl.writePLY("out.ply",V,F) -igl.writeOBJ("out.obj",V,F) -igl.writeOBJ("out.obj",V,F,V,F,V,F) -V,F,_ = igl.readOFF("out.off") -V,_,_,F,_,_ = igl.readOBJ("out.obj") -V,F = igl.read_triangle_mesh("out.ply") -L = igl.cotmatrix(V,F) -I = np.array([0,1,2,3],dtype=np.int64) -C = np.array([0,4],dtype=np.int64) -L,M,P = igl.cotmatrix_polygon(V,I,C) -A = igl.facet_adjacency_matrix(F) -A = igl.adjacency_matrix(F) -A = igl.adjacency_matrix_polygon(I,C) -E = igl.edges(F) -E = igl.edges(I,C) -E = igl.edges(A) - -M = igl.massmatrix(V,F) -M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) -M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) -M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) -FN,_,_,_ = igl.per_face_normals(V,I,C) -FN = igl.per_face_normals(V,F) -FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) -VN = igl.per_vertex_normals(V,F) -VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) -VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) -VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) -VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) - -dblA = igl.doublearea(V,F) -dblA = igl.doublearea(l=l) -dblA = igl.doublearea(l=l,nan_replacement=0.0) -P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) -sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) -W = igl.winding_number(V,F,P) -W = igl.winding_number(V,F,P[1,:]) -W = igl.fast_winding_number(V,F,P) -S,I,C,N = igl.signed_distance(P,V,F) -S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) - -BC = igl.barycenter(V,F) - -b = np.array([0,3],dtype=np.int64) -bc = np.array([[1,0],[0,1]],dtype=np.float64) -W = igl.bbw(V,F,b,bc) -W = igl.harmonic(V,F,b,bc,k=1) -W = igl.harmonic(V,F,b,bc,k=2) - -V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -T = np.array([[0,1,2,3]],dtype=np.int64) -F,J,K = igl.boundary_facets(T) -igl.writeMESH("out.mesh",V,T) -igl.writeMESH("out.mesh",V,T,F=F) -V,T,F = igl.readMESH("out.mesh") -igl.writeMSH("out.msh",V,F,T) -V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") - -tree = igl.AABB() -tree.init(V,T) -# first row of P -q = P[0,:] -I = tree.find(V,T,q) -i = tree.find(V,T,q,first=True) - -Q = igl.barycenter(V,T) -I = igl.in_element(V,T,Q,tree) - -tree = igl.AABB() -tree.init(V,F) -sqrD,_,_ = tree.squared_distance(V,F,P) - -O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); -D = V.mean(axis=0)-O -I,T,UV = tree.intersect_ray_first(V,F,O,D) -hits = tree.intersect_ray(V,F,O,D) -o = O[0,:] -d = D[0,:] -hit = igl.ray_mesh_intersect(o,d,V,F,first=True) -hits = igl.ray_mesh_intersect(o,d,V,F) - - -# upsample so there's something to collapse -V,F = igl.upsample(V,F,number_of_subdivs=1) -E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) -E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) -EF,EI = igl.edge_flaps(F,uE,EMAP) -uE,EMAP,EF,EI = igl.edge_flaps(F) -Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) - -e = 0 -p = V[uE[e,0],:] -e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) - - -# restore single tet -V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -T = np.array([[0,1,2,3]],dtype=np.int64) -C = np.ones(F.shape,dtype=bool) -Vn,Fn,I = igl.cut_mesh(V,F,C) - -TT,TTi = igl.triangle_triangle_adjacency(F) -TT,TTi = igl.triangle_triangle_adjacency_lists(F) - -VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) -VF,VFi = igl.vertex_triangle_adjacency(F) - -F012 = F; -F120 = np.roll(F012,1,axis=1) -FF = np.vstack((F012,F120)) -F,IA,IC = igl.unique_simplices(FF) -A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) -B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) -IA,LOCB = igl.ismember_rows(A,B) - - - -L = igl.cotmatrix(V,F) -# convert nonzeros to 1s to int64 -A = (L != 0).astype(np.int64) -# subtract diagonal -A = A - scipy.sparse.diags(A.diagonal()) -n,C,K = igl.connected_components(A) - - -A = -igl.cotmatrix(V,F) -B = np.zeros((V.shape[0],1),dtype=np.float64) -known = np.array([1,2],dtype=np.int64) -Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) -Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) -Beq = np.zeros((1,1),dtype=np.float64) -Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) -data = igl.min_quad_with_fixed_data() -igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) -Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) - -V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -T = np.array([[0,1,2,3]],dtype=np.int64) -L = igl.edge_lengths(V,T) -vol = igl.volume(V,T) -vol = igl.volume(L=L) -vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) - -F,_,_ = igl.boundary_facets(T) -# remove last face -F = F[:-1,:] -B = igl.is_border_vertex(F) -I,C = igl.on_boundary(F) -I,C = igl.on_boundary(T) -F = np.array([[2,1,3]],dtype=np.int64) -NV,NF,I,J = igl.remove_unreferenced(V,F) - -L = igl.squared_edge_lengths(V,F) -C = igl.cotmatrix_entries(V,F) -l = igl.edge_lengths(V,F) -C = igl.cotmatrix_entries(l=l) -E = igl.oriented_facets(F) -F = igl.oriented_facets(T) - -res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) -res,_,_,_,_ = igl.is_edge_manifold(F) - -L = igl.cotmatrix(V,F) -s = igl.matlab_format(V,"V") -s = igl.matlab_format_index(F,"F") -s = igl.matlab_format(L,"L") - -B1,B2,B3 = igl.local_basis(V,F) - -G = igl.grad(V,F) - -# random randn 3x3 matrix -A = np.random.randn(3,3).astype(np.float64) -igl.polar_svd(A) -out = igl.polar_svd(A) -R,T,_,_,_ = igl.polar_svd(A) -R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) - -SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) -SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) - - - -model = np.eye(4).astype(np.float64) -proj = np.eye(4).astype(np.float64) -viewport = np.array([0,0,640,480],dtype=np.float64) -win = igl.project(V,model,proj,viewport) -scene = igl.unproject(win,model,proj,viewport) - -S = np.array([0,0,0],dtype=np.float64) -D = np.array([0.2,0.2,0.2],dtype=np.float64) -t,sqrD = igl.project_to_line(V,S,D) -t,sqrD = igl.project_to_line_segment(V,S,D) - -S,_ = igl.upsample_matrix(F,n=V.shape[0]) -S,_ = igl.loop_matrix(F,n=V.shape[0]) -V,F = igl.upsample(V,F,number_of_subdivs=1) -V,F = igl.loop(V,F,number_of_subdivs=1) -# remove first and last -F = F[1:-1,:] -L_all = igl.boundary_loop_all(F) -L = igl.boundary_loop(F) - -A = igl.adjacency_list(F) -A = igl.adjacency_list(F,sorted=True) - -GV,side = igl.voxel_grid(V,s=10) -GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) - -C = igl.vertex_components(F) -nc,C = igl.facet_components(F) -B,I,X = igl.random_points_on_mesh(1000,V,F) - -point_indices, CH,CN,W = igl.octree(X) -I = igl.knn(X,X,1,point_indices,CH,CN,W) - -igl.writeDMAT("out.dmat",V) -igl.writeDMAT("out.dmat",V,ascii=True) -V = igl.readDMAT("out.dmat") -igl.writeDMAT("out.dmat",V,ascii=False) -V = igl.readDMAT("out.dmat") - -K = igl.gaussian_curvature(V,F) -PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) -PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) - -VS = np.array([0],dtype=np.int64) -VT = np.array([1],dtype=np.int64) -D = igl.exact_geodesic(V,F,VS=VS,VT=VT) - -dV,dF,J,I = igl.decimate(V,F) -dV,dF,J,I = igl.qslim(V,F) - -V,Q,E = igl.quad_grid(3,3); -V,F = igl.triangulated_grid(3,3); -# slim needs 3D data even if the problem is 2D -V = np.c_[V, np.zeros(V.shape[0])] -V_init = V[:,:2] -V_init[:,0] = V_init[:,0] * 2 -b = np.array([0,1],dtype=np.int64) -bc = V[b,:2] -data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) -U = igl.slim_solve(data,iter_num=1) - -data = igl.ARAPData() -data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS -V = V[:,:2] -igl.arap_precomputation(V,F,V.shape[1],b,data) -U = igl.arap_solve(bc,data,U) - -U,Q = igl.lscm(V,F,b,bc) - - -res = np.array([3,3,3],dtype=np.int64) -GV = igl.grid(res) -S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; -V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) -# unpack keys into (i,j,v) index triplets -EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) - - -h = igl.avg_edge_length(V,F) -m0,m1,m2 = igl.moments(V,F) -D = V[:,0] -vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) -iV,iE,I = igl.isolines(V,F,S,vals) -iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) - - -x0 = V[0,:] -f = np.array([0,0,1],dtype=np.float64) -R = np.eye(3).astype(np.float64) -U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) - -SV = V[:,0] -SF = igl.average_onto_faces(F,SV) -SV = igl.average_onto_vertices(V,F,SF) - -P = [[0,1,2],[2,1,3,4]] -I,C = igl.polygon_corners(P) -Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) -I,C = igl.polygon_corners(Q) -F,J = igl.polygons_to_triangles(I,C) - -V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -V,F = igl.upsample(V,F,number_of_subdivs=1) -data = igl.HeatGeodesicsData() -igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) -data = igl.HeatGeodesicsData() -data.use_intrinsic_delaunay = True -igl.heat_geodesics_precompute(V,F,data) -gamma = np.array([0],dtype=np.int64) -D = igl.heat_geodesics_solve(data,gamma) -l = igl.edge_lengths(V,F) -iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) -L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) -L = igl.cotmatrix_intrinsic(il,iF) -M = igl.massmatrix_intrinsic(il,iF) - -V,F = igl.icosahedron() - -try: - import igl.copyleft +# +def triangulated_square(): + V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) + F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) + return V,F + +def single_tet(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + T = np.array([[0,1,2,3]],dtype=np.int64) + return V,F,T + +def test_edges(): + F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) + E,oE = igl.orient_halfedges(F) + ne = E.max()+1 + np.random.seed(42) + uE = np.random.rand(ne).astype(np.float64) + + uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) + V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) + +def test_read_write(): + V,F = triangulated_square() + igl.write_triangle_mesh("out.obj",V,F) + igl.write_triangle_mesh("out.off",V,F) + igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) + igl.writePLY("out.ply",V,F) + igl.writeOBJ("out.obj",V,F) + igl.writeOBJ("out.obj",V,F,V,F,V,F) + V,F,_ = igl.readOFF("out.off") + V,_,_,F,_,_ = igl.readOBJ("out.obj") + V,F = igl.read_triangle_mesh("out.ply") + igl.writeDMAT("out.dmat",V) + igl.writeDMAT("out.dmat",V,ascii=True) + V = igl.readDMAT("out.dmat") + igl.writeDMAT("out.dmat",V,ascii=False) + V = igl.readDMAT("out.dmat") + +def test_operators(): + V,F = triangulated_square() + L = igl.cotmatrix(V,F) + I = np.array([0,1,2,3],dtype=np.int64) + C = np.array([0,4],dtype=np.int64) + L,M,P = igl.cotmatrix(V,I,C) + L = igl.squared_edge_lengths(V,F) + K = igl.cotmatrix_entries(V,F) + l = igl.edge_lengths(V,F) + K = igl.cotmatrix_entries(l=l) + B1,B2,B3 = igl.local_basis(V,F) + G = igl.grad(V,F) + + M = igl.massmatrix(V,F) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) + + A = igl.facet_adjacency_matrix(F) + A = igl.adjacency_matrix(F) + A = igl.adjacency_matrix(I,C) + E = igl.edges(F) + E = igl.edges(I,C) + E = igl.edges(A) + n,C,K = igl.connected_components(A) + C = igl.vertex_components(F) + nc,C = igl.facet_components(F) + +def test_normals_and_distances(): + V,F = triangulated_square() + I = np.array([0,1,2,3],dtype=np.int64) + C = np.array([0,4],dtype=np.int64) + FN,_,_,_ = igl.per_face_normals(V,I,C) + FN = igl.per_face_normals(V,F) + FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) + VN = igl.per_vertex_normals(V,F) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) + VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) + dblA = igl.doublearea(V,F) + l = igl.edge_lengths(V,F) + dblA = igl.doublearea(l=l) + dblA = igl.doublearea(l=l,nan_replacement=0.0) + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) + W = igl.winding_number(V,F,P) + W = igl.winding_number(V,F,P[1,:]) + W = igl.fast_winding_number(V,F,P) + S,I,C,N = igl.signed_distance(P,V,F) + S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) + BC = igl.barycenter(V,F) + +def test_harmonic(): + V,F = triangulated_square() + b = np.array([0,3],dtype=np.int64) + bc = np.array([[1,0],[0,1]],dtype=np.float64) + W = igl.bbw(V,F,b,bc) + W = igl.harmonic(V,F,b,bc,k=1) + W = igl.harmonic(V,F,b,bc,k=2) + +def test_tets(): + V,F,T = single_tet() + F,J,K = igl.boundary_facets(T) + igl.writeMESH("out.mesh",V,T) + igl.writeMESH("out.mesh",V,T,F=F) + V,T,F = igl.readMESH("out.mesh") + igl.writeMSH("out.msh",V,F,T) + V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") + +def test_bvh(): + V,F,T = single_tet() + tree = igl.AABB() + tree.init(V,T) + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + # first row of P + q = P[0,:] + I = tree.find(V,T,q) + i = tree.find(V,T,q,first=True) + + Q = igl.barycenter(V,T) + I = igl.in_element(V,T,Q,tree) + + tree = igl.AABB() + tree.init(V,F) + sqrD,_,_ = tree.squared_distance(V,F,P) + + O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); + D = V.mean(axis=0)-O + I,T,UV = tree.intersect_ray_first(V,F,O,D) + hits = tree.intersect_ray(V,F,O,D) + o = O[0,:] + d = D[0,:] + hit = igl.ray_mesh_intersect(o,d,V,F,first=True) + hits = igl.ray_mesh_intersect(o,d,V,F) + + +def test_upsample(): + V,F,T = single_tet() + # upsample so there's something to collapse + V,F = igl.upsample(V,F,number_of_subdivs=1) + E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) + E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) + EF,EI = igl.edge_flaps(F,uE,EMAP) + uE,EMAP,EF,EI = igl.edge_flaps(F) + Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) + + e = 0 + p = V[uE[e,0],:] + e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) + +def test_cut(): + V,F,T = single_tet() + C = np.ones(F.shape,dtype=bool) + Vn,Fn,I = igl.cut_mesh(V,F,C) + +def test_adjacency(): + V,F,T = single_tet() + TT,TTi = igl.triangle_triangle_adjacency(F) + TT,TTi = igl.triangle_triangle_adjacency_lists(F) + + VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) + VF,VFi = igl.vertex_triangle_adjacency(F) + + F012 = F; + F120 = np.roll(F012,1,axis=1) + FF = np.vstack((F012,F120)) + F,IA,IC = igl.unique_simplices(FF) + A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) + B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) + IA,LOCB = igl.ismember_rows(A,B) + A = igl.adjacency_list(F) + A = igl.adjacency_list(F,sorted=True) + +def test_min_quad(): + V,F,T = single_tet() + A = -igl.cotmatrix(V,F) + B = np.zeros((V.shape[0],1),dtype=np.float64) + known = np.array([1,2],dtype=np.int64) + Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) + Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) + Beq = np.zeros((1,1),dtype=np.float64) + Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) + data = igl.min_quad_with_fixed_data() + igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) + Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) + +def test_volume(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + L = igl.edge_lengths(V,T) + vol = igl.volume(V,T) + vol = igl.volume(L=L) + vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) + F,_,_ = igl.boundary_facets(T) + # remove last face + F = F[:-1,:] + B = igl.is_border_vertex(F) + I,C = igl.on_boundary(F) + I,C = igl.on_boundary(T) + F = np.array([[2,1,3]],dtype=np.int64) + NV,NF,I,J = igl.remove_unreferenced(V,F) + +def test_oriented_facets(): + V,F,T = single_tet() + E = igl.oriented_facets(F) + F = igl.oriented_facets(T) + + res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) + res,_,_,_,_ = igl.is_edge_manifold(F) + +def test_matlab_format(): + V,F = triangulated_square() + L = igl.cotmatrix(V,F) + s = igl.matlab_format(V,"V") + s = igl.matlab_format_index(F,"F") + s = igl.matlab_format(L,"L") + +def test_polar(): + # random randn 3x3 matrix + np.random.seed(42) + A = np.random.randn(3,3).astype(np.float64) + igl.polar_svd(A) + out = igl.polar_svd(A) + R,T,_,_,_ = igl.polar_svd(A) + R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) + +def test_remove(): + V,F = triangulated_square() + SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) + SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) + +def test_project(): + V,F,_ = single_tet() + model = np.eye(4).astype(np.float64) + proj = np.eye(4).astype(np.float64) + viewport = np.array([0,0,640,480],dtype=np.float64) + win = igl.project(V,model,proj,viewport) + scene = igl.unproject(win,model,proj,viewport) + + S = np.array([0,0,0],dtype=np.float64) + D = np.array([0.2,0.2,0.2],dtype=np.float64) + t,sqrD = igl.project_to_line(V,S,D) + t,sqrD = igl.project_to_line_segment(V,S,D) + +def test_boundary_loop(): + V,F,_ = single_tet() + S,_ = igl.upsample_matrix(F,n=V.shape[0]) + S,_ = igl.loop_matrix(F,n=V.shape[0]) + V,F = igl.upsample(V,F,number_of_subdivs=1) + V,F = igl.loop(V,F,number_of_subdivs=1) + # remove first and last + F = F[1:-1,:] + L_all = igl.boundary_loop_all(F) + L = igl.boundary_loop(F) + +def test_voxel(): + V,_,_ = single_tet() + GV,side = igl.voxel_grid(V,s=10) + GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) + + +def test_sample(): + V,F = igl.icosahedron() + B,I,X = igl.random_points_on_mesh(10,V,F) + point_indices, CH,CN,W = igl.octree(X) + I = igl.knn(X,X,1,point_indices,CH,CN,W) + B,FI,P = igl.blue_noise(V,F,0.5) + +def test_curvature(): + V,F = igl.icosahedron() + K = igl.gaussian_curvature(V,F) + PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) + PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) + +def test_geodesic(): + V,F = igl.icosahedron() + VS = np.array([0],dtype=np.int64) + VT = np.array([1],dtype=np.int64) + D = igl.exact_geodesic(V,F,VS=VS,VT=VT) + +def test_decimate(): + V,F = igl.icosahedron() + dV,dF,J,I = igl.decimate(V,F) + dV,dF,J,I = igl.qslim(V,F) + +def test_parameterization(): + V,Q,E = igl.quad_grid(3,3); + V,F = igl.triangulated_grid(3,3); + # slim needs 3D data even if the problem is 2D + V = np.c_[V, np.zeros(V.shape[0])] + V_init = V[:,:2] + V_init[:,0] = V_init[:,0] * 2 + b = np.array([0,1],dtype=np.int64) + bc = V[b,:2] + data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) + U = igl.slim_solve(data,iter_num=1) + + data = igl.ARAPData() + data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS + V = V[:,:2] + igl.arap_precomputation(V,F,V.shape[1],b,data) + U = igl.arap_solve(bc,data,U) + + U,Q = igl.lscm(V,F,b,bc) + + +def test_implicit(): + res = np.array([3,3,3],dtype=np.int64) + GV = igl.grid(res) + S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; + V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) + # unpack keys into (i,j,v) index triplets + EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) + + h = igl.avg_edge_length(V,F) + m0,m1,m2 = igl.moments(V,F) + D = V[:,0] + vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) + iV,iE,I = igl.isolines(V,F,S,vals) + iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) + + +def test_kelvinlets(): + V,F = igl.icosahedron() + x0 = V[0,:] + f = np.array([0,0,1],dtype=np.float64) + R = np.eye(3).astype(np.float64) + U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) + + SV = V[:,0] + SF = igl.average_onto_faces(F,SV) + SV = igl.average_onto_vertices(V,F,SF) + +def test_polygons(): + P = [[0,1,2],[2,1,3,4]] + I,C = igl.polygon_corners(P) + Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) + I,C = igl.polygon_corners(Q) + F,J = igl.polygons_to_triangles(I,C) + +def test_heat(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + V,F = igl.upsample(V,F,number_of_subdivs=1) + data = igl.HeatGeodesicsData() + igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) + data = igl.HeatGeodesicsData() + data.use_intrinsic_delaunay = True + igl.heat_geodesics_precompute(V,F,data) + gamma = np.array([0],dtype=np.int64) + D = igl.heat_geodesics_solve(data,gamma) + l = igl.edge_lengths(V,F) + iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) + L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) + L = igl.cotmatrix_intrinsic(il,iF) + M = igl.massmatrix_intrinsic(il,iF) + +def test_nonmanifold(): + V,F = igl.icosahedron() + FF,C = igl.bfs_orient(F) + SF,SVI = igl.split_nonmanifold(F) + SV,SF,SVI = igl.split_nonmanifold(V,F) + +def test_biharmonic(): + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) + S = [[0],[5]] + W = igl.biharmonic_coordinates(V,T,S) + +def test_bijective(): + # 2D triangulated grid + V,F = igl.triangulated_grid(3,3) + ear,ear_opp = igl.ears(F) + # we don't have igl.flip_ears so construct it by hand + res = np.array([3,3],dtype=np.int64) + E,_,_ = igl.boundary_facets(F) + F = E + # add a 3rd column of 4s + F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) + # boundary b + b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) + bc = V[b,:] + bc[-1,:] = [0.5,0.5] + U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) + + +def test_copyleft(): V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) T = np.array([[0,1,2,3]],dtype=np.int64) F,_,_ = igl.boundary_facets(T) V,F = igl.loop(V,F) - + dV,dF,J = igl.copyleft.progressive_hulls(V,F) + -except ImportError: - warnings.warn("igl.copyleft not available") - pass - -try: - import igl.copyleft.cgal +def test_cgal(): # tetrahedron VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) T = np.array([[0,1,2,3]],dtype=np.int64) @@ -399,32 +468,27 @@ FC = np.vstack((FA,FB+VA.shape[0])) VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) - + p = np.array([0,0,0],dtype=np.float64) n = np.array([1,1,1],dtype=np.float64) VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) equ = np.hstack((n,-n.dot(p))) VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) - + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) - + VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) - + _,I,X = igl.random_points_on_mesh(1000,VC,FC) N = igl.per_face_normals(VC,FC) N = N[I,:] point_indices, CH,CN,W = igl.octree(X) I = igl.knn(X,X,20,point_indices,CH,CN,W) A,T = igl.copyleft.cgal.point_areas(X,I,N) - -except ImportError: - warnings.warn("igl.copyleft.cgal not available") - pass - -try: - import igl.embree + +def test_embree(): # octahedron V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) @@ -442,23 +506,14 @@ hits = ei.intersectRay(origin,direction) hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) I,C = igl.embree.reorient_facets_raycast(V,F) -except ImportError: - warnings.warn("igl.embree not available") - pass -try: - import igl.copyleft.tetgen +def test_tetgen(): # octahedron V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") -except ImportError: - warnings.warn("igl.copyleft.tetgen not available") - pass - -try: - import igl.triangle +def test_triangle(): V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") @@ -474,8 +529,3 @@ igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) L,rhs = igl.triangle.scaf_system(scaf_data) U = igl.triangle.scaf_solve(1,scaf_data) - - -except ImportError: - warnings.warn("igl.triangle not available") - pass From 0e5e621ccc8fbd6c6efe9dacb4fa011bd4e4226b Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 23 Nov 2024 15:54:17 -0500 Subject: [PATCH 016/102] more bindings --- CMakeLists.txt | 2 +- src/bounding_box.cpp | 34 +++++++++++++++++++ src/circumradius.cpp | 35 +++++++++++++++++++ src/crouzeix_raviart_cotmatrix.cpp | 41 +++++++++++++++++++++++ src/crouzeix_raviart_massmatrix.cpp | 43 ++++++++++++++++++++++++ src/cut_mesh_from_singularities.cpp | 37 +++++++++++++++++++++ src/cut_to_disk.cpp | 48 +++++++++++++++++++++++++++ src/cylinder.cpp | 33 ++++++++++++++++++ src/dihedral_angles.cpp | 33 ++++++++++++++++++ src/dihedral_angles_intrinsic.cpp | 34 +++++++++++++++++++ src/face_areas.cpp | 32 ++++++++++++++++++ src/false_barycentric_subdivision.cpp | 35 +++++++++++++++++++ src/inradius.cpp | 31 +++++++++++++++++ tests/test.py | 19 +++++++++++ 14 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 src/bounding_box.cpp create mode 100644 src/circumradius.cpp create mode 100644 src/crouzeix_raviart_cotmatrix.cpp create mode 100644 src/crouzeix_raviart_massmatrix.cpp create mode 100644 src/cut_mesh_from_singularities.cpp create mode 100644 src/cut_to_disk.cpp create mode 100644 src/cylinder.cpp create mode 100644 src/dihedral_angles.cpp create mode 100644 src/dihedral_angles_intrinsic.cpp create mode 100644 src/face_areas.cpp create mode 100644 src/false_barycentric_subdivision.cpp create mode 100644 src/inradius.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e9b8b1cb..68a9129e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG f82ad2b463e0307c56f8101bcfe5c290c9cd30cd + GIT_TAG 753f4d3c88ddc9280696852d4740e58a815e365d ) FetchContent_MakeAvailable(libigl) diff --git a/src/bounding_box.cpp b/src/bounding_box.cpp new file mode 100644 index 00000000..94ac139c --- /dev/null +++ b/src/bounding_box.cpp @@ -0,0 +1,34 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto bounding_box( + const nb::DRef& V, + const Numeric pad) + { + Eigen::MatrixXN BV; + Eigen::MatrixXI BF; + igl::bounding_box(V, pad, BV, BF); + return std::make_tuple(BV, BF); + } +} + +void bind_bounding_box(nb::module_ &m) +{ + m.def("bounding_box", &pyigl::bounding_box, + "V"_a, + "pad"_a=0, + R"(Build a triangle mesh of the bounding box of a given list of vertices + +@param[in] V #V by dim list of rest domain positions +@param[in] pad padding offset +@param[out] BV 2^dim by dim list of bounding box corners positions +@param[out] BF #BF by dim list of simplex facets )"); +} diff --git a/src/circumradius.cpp b/src/circumradius.cpp new file mode 100644 index 00000000..ce21051a --- /dev/null +++ b/src/circumradius.cpp @@ -0,0 +1,35 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto circumradius( + const nb::DRef & V , + const nb::DRef & T) + { + Eigen::VectorXN R; + Eigen::MatrixXN C,B; + igl::circumradius(V, T, R, C, B); + return std::make_tuple(R, C, B); + } +} + +void bind_circumradius(nb::module_ &m) +{ + m.def("circumradius", &pyigl::circumradius, + ""_a, + "F"_a, + R"(Compute the circumradius of each triangle in a mesh (V,F) +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of triangle indices into V +@param[out] R #F list of circumradius +@param[out] R #T list of circumradius +@param[out] C #T by dim list of circumcenter +@param[out] B #T by simplex-size list of barycentric coordinates of circumcenter)"); +} diff --git a/src/crouzeix_raviart_cotmatrix.cpp b/src/crouzeix_raviart_cotmatrix.cpp new file mode 100644 index 00000000..8099b404 --- /dev/null +++ b/src/crouzeix_raviart_cotmatrix.cpp @@ -0,0 +1,41 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto crouzeix_raviart_cotmatrix( + const nb::DRef & V, + const nb::DRef & F, + const nb::DRef & E, + const nb::DRef & EMAP) + { + Eigen::SparseMatrix L; + igl::crouzeix_raviart_cotmatrix(V, F, E, EMAP, L); + return L; + } +} + +void bind_crouzeix_raviart_cotmatrix(nb::module_ &m) +{ + m.def("crouzeix_raviart_cotmatrix", &pyigl::crouzeix_raviart_cotmatrix, + "V"_a, + "F"_a, + "E"_a, + "EMAP"_a, + R"(Compute the Crouzeix-Raviart cotangent stiffness matrix. +See for example "Discrete Quadratic Curvature Energies" [Wardetzky, Bergou, +Harmon, Zorin, Grinspun 2007] +@param[in] V #V by dim list of vertex positions +@param[in] F #F by 3/4 list of triangle/tetrahedron indices +@param[in] E #E by 2/3 list of edges/faces +@param[in] EMAP #F*3/4 list of indices mapping allE to E +@param[out] L #E by #E edge/face-based diagonal cotangent matrix +\see crouzeix_raviart_massmatrix)"); +} diff --git a/src/crouzeix_raviart_massmatrix.cpp b/src/crouzeix_raviart_massmatrix.cpp new file mode 100644 index 00000000..71e244fa --- /dev/null +++ b/src/crouzeix_raviart_massmatrix.cpp @@ -0,0 +1,43 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto crouzeix_raviart_massmatrix( + const nb::DRef & V, + const nb::DRef & F, + const nb::DRef & E, + const nb::DRef & EMAP) + { + Eigen::SparseMatrix L; + igl::crouzeix_raviart_massmatrix(V, F, E, EMAP, L); + return L; + } +} + +void bind_crouzeix_raviart_massmatrix(nb::module_ &m) +{ + m.def("crouzeix_raviart_massmatrix", &pyigl::crouzeix_raviart_massmatrix, + "V"_a, + "F"_a, + "E"_a, + "EMAP"_a, + R"(CROUZEIX_RAVIART_MASSMATRIX Compute the Crouzeix-Raviart mass matrix where +M(e,e) is just the sum of the areas of the triangles on either side of an +edge e. +See for example "Discrete Quadratic Curvature Energies" [Wardetzky, Bergou, +Harmon, Zorin, Grinspun 2007] +@param[in] V #V by dim list of vertex positions +@param[in] F #F by 3/4 list of triangle/tetrahedron indices +@param[in] E #E by 2/3 list of edges/faces +@param[in] EMAP #F*3/4 list of indices mapping allE to E +@param[out] M #E by #E edge/face-based diagonal mass matrix +\see crouzeix_raviart_cotmatrix)"); +} diff --git a/src/cut_mesh_from_singularities.cpp b/src/cut_mesh_from_singularities.cpp new file mode 100644 index 00000000..3ce69007 --- /dev/null +++ b/src/cut_mesh_from_singularities.cpp @@ -0,0 +1,37 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto cut_mesh_from_singularities( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &MMatch) + { + Eigen::MatrixXB seams; + igl::cut_mesh_from_singularities(V, F, MMatch, seams); + return seams; + } +} + +void bind_cut_mesh_from_singularities(nb::module_ &m) +{ + m.def("cut_mesh_from_singularities", &pyigl::cut_mesh_from_singularities, + "V"_a, + "F"_a, + "MMatch"_a, + R"(Given a mesh (V,F) and the integer mismatch of a cross field per edge +(mismatch), finds the cut_graph connecting the singularities (seams) and the +degree of the singularities singularity_index +@param[in] V #V by 3 list of mesh vertex positions +@param[in] F #F by 3 list of faces +@param[in] mismatch #F by 3 list of per corner integer mismatch +@param[out] seams #F by 3 list of per corner booleans that denotes if an edge is a + seam or not)"); +} diff --git a/src/cut_to_disk.cpp b/src/cut_to_disk.cpp new file mode 100644 index 00000000..85e856b7 --- /dev/null +++ b/src/cut_to_disk.cpp @@ -0,0 +1,48 @@ +#include "default_types.h" +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto cut_to_disk( const nb::DRef & F) + { + std::vector> cuts; + igl::cut_to_disk(F, cuts); + return cuts; + } +} + +void bind_cut_to_disk(nb::module_ &m) +{ + m.def("cut_to_disk", &pyigl::cut_to_disk, + "F"_a, + R"(Given a triangle mesh, computes a set of edge cuts sufficient to carve the +mesh into a topological disk, without disconnecting any connected components. +Nothing else about the cuts (including number, total length, or smoothness) +is guaranteed to be optimal. +Simply-connected components without boundary (topological spheres) are left +untouched (delete any edge if you really want a disk). +All other connected components are cut into disks. Meshes with boundary are +supported; boundary edges will be included as cuts. + +The cut mesh itself can be materialized using cut_mesh(). + +Implements the triangle-deletion approach described by Gu et al's +"Geometry Images." + +@tparam Index Integrable type large enough to represent the total number of faces + and edges in the surface represented by F, and all entries of F. +@param[in] F #F by 3 list of the faces (must be triangles) +@param[out] cuts List of cuts. Each cut is a sequence of vertex indices (where + pairs of consecutive vertices share a face), is simple, and is either + a closed loop (in which the first and last indices are identical) or + an open curve. Cuts are edge-disjoint. + +)"); +} diff --git a/src/cylinder.cpp b/src/cylinder.cpp new file mode 100644 index 00000000..8a6963de --- /dev/null +++ b/src/cylinder.cpp @@ -0,0 +1,33 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto cylinder( + int axis_devisions, + int height_devisions) + { + Eigen::MatrixXN V; + Eigen::MatrixXI F; + igl::cylinder(axis_devisions, height_devisions, V, F); + return std::make_tuple(V, F); + } +} + +void bind_cylinder(nb::module_ &m) +{ + m.def("cylinder", &pyigl::cylinder, + "axis_devisions"_a, + "height_devisions"_a, + R"(Construct a triangle mesh of a cylinder (without caps) +@param[in] axis_devisions number of vertices _around the cylinder_ +@param[in] height_devisions number of vertices _up the cylinder_ +@param[out] V #V by 3 list of mesh vertex positions +@param[out] F #F by 3 list of triangle indices into V)"); +} diff --git a/src/dihedral_angles.cpp b/src/dihedral_angles.cpp new file mode 100644 index 00000000..84f22549 --- /dev/null +++ b/src/dihedral_angles.cpp @@ -0,0 +1,33 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto dihedral_angles( + const nb::DRef& V, + const nb::DRef& T) + { + Eigen::MatrixXN theta, cos_theta; + igl::dihedral_angles(V, T, theta, cos_theta); + return std::make_tuple(theta, cos_theta); + + } +} + +void bind_dihedral_angles(nb::module_ &m) +{ + m.def("dihedral_angles", &pyigl::dihedral_angles, + "V"_a, + "T"_a, + R"(Compute dihedral angles for all tets of a given tet mesh (V,T). +@param[in] V #V by dim list of vertex positions +@param[in] T #V by 4 list of tet indices +@param[out] theta #T by 6 list of dihedral angles (in radians) +@param[out] cos_theta #T by 6 list of cosine of dihedral angles (in radians))"); +} diff --git a/src/dihedral_angles_intrinsic.cpp b/src/dihedral_angles_intrinsic.cpp new file mode 100644 index 00000000..3eb3d41e --- /dev/null +++ b/src/dihedral_angles_intrinsic.cpp @@ -0,0 +1,34 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto dihedral_angles_intrinsic( + const nb::DRef& A, + const nb::DRef& L) + { + Eigen::MatrixXN theta, cos_theta; + igl::dihedral_angles_intrinsic(A, L, theta, cos_theta); + return std::make_tuple(theta, cos_theta); + + } +} + +void bind_dihedral_angles_intrinsic(nb::module_ &m) +{ + m.def("dihedral_angles_intrinsic", &pyigl::dihedral_angles_intrinsic, + "A"_a, + "L"_a, + R"(Compute dihedral angles for all tets of a given tet mesh's intrinsics. +@param[in] V #V by dim list of vertex positions +@param[in] T #V by 4 list of tet indices +@param[out] theta #T by 6 list of dihedral angles (in radians) +@param[out] cos_theta #T by 6 list of cosine of dihedral angles (in radians))"); +} + diff --git a/src/face_areas.cpp b/src/face_areas.cpp new file mode 100644 index 00000000..c39b4ad0 --- /dev/null +++ b/src/face_areas.cpp @@ -0,0 +1,32 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto face_areas( + const nb::DRef& V, + const nb::DRef& T) + { + Eigen::MatrixXN A; + igl::face_areas(V, T, A); + return A; + } +} + +void bind_face_areas(nb::module_ &m) +{ + m.def("face_areas", &pyigl::face_areas, + "V"_a, + "T"_a, + R"(Constructs a list of face areas of faces opposite each index in a tet list +@param[in] V #V by 3 list of mesh vertex positions +@param[in] T #T by 3 list of tet mesh indices into V +@param[out] A #T by 4 list of face areas corresponding to faces opposite vertices + 0,1,2,3)"); +} diff --git a/src/false_barycentric_subdivision.cpp b/src/false_barycentric_subdivision.cpp new file mode 100644 index 00000000..d236f17c --- /dev/null +++ b/src/false_barycentric_subdivision.cpp @@ -0,0 +1,35 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto false_barycentric_subdivision( + const nb::DRef& V, + const nb::DRef& F) + { + Eigen::MatrixXN VD; + Eigen::MatrixXI FD; + igl::false_barycentric_subdivision(V, F, VD, FD); + return std::make_tuple(VD, FD); + } +} + +void bind_false_barycentric_subdivision(nb::module_ &m) +{ + m.def("false_barycentric_subdivision", &pyigl::false_barycentric_subdivision, + "V"_a, + "F"_a, + R"(Refine the mesh by adding the barycenter of each face +@param[in] V #V by 3 coordinates of the vertices +@param[in] F #F by 3 list of mesh faces (must be triangles) +@param[out] VD #V + #F by 3 coordinate of the vertices of the dual mesh + The added vertices are added at the end of VD (should not be + same references as (V,F) +@param[out] FD #F*3 by 3 faces of the dual mesh)"); +} diff --git a/src/inradius.cpp b/src/inradius.cpp new file mode 100644 index 00000000..37187f6c --- /dev/null +++ b/src/inradius.cpp @@ -0,0 +1,31 @@ +#include "default_types.h" +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; +namespace pyigl +{ + auto inradius( + const nb::DRef & V, + const nb::DRef & F) + { + Eigen::VectorXN R; + igl::inradius(V, F, R); + return R; + } +} + +void bind_inradius(nb::module_ &m) +{ + m.def("inradius", &pyigl::inradius, + "V"_a, + "F"_a, + R"(Compute the inradius of each triangle in a mesh (V,F) +@param[in] V #V by dim list of mesh vertex positions +@param[in] F #F by 3 list of triangle indices into V +@param[out] R #F list of inradii)"); +} diff --git a/tests/test.py b/tests/test.py index 2431df72..62c378db 100644 --- a/tests/test.py +++ b/tests/test.py @@ -529,3 +529,22 @@ def test_triangle(): igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) L,rhs = igl.triangle.scaf_system(scaf_data) U = igl.triangle.scaf_solve(1,scaf_data) + +def test_misc(): + V,F = igl.icosahedron() + BV,BF = igl.bounding_box(V,pad=1.0) + R,C,B = igl.circumradius(V,F) + R = igl.inradius(V,F) + _,E,EMAP,_,_ = igl.unique_edge_map(F) + L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) + M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) + cuts = igl.cut_to_disk(F) + V,F = igl.cylinder(10,10) + VD,FD = igl.false_barycentric_subdivision(V,F) + V,F,T = single_tet() + theta, cos_theta = igl.dihedral_angles(V,T) + L = igl.edge_lengths(V,T) + A = igl.face_areas(V,T) + theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) + + From 6836453a8fdd0884f2a73a6638c96c2347921f8e Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Tue, 26 Nov 2024 19:56:18 -0500 Subject: [PATCH 017/102] list of priority bindings --- priority.txt | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 priority.txt diff --git a/priority.txt b/priority.txt new file mode 100644 index 00000000..74e863fc --- /dev/null +++ b/priority.txt @@ -0,0 +1,108 @@ + +false_barycentric_subdivision.cpp +fit_cubic_bezier.cpp +flip_edge.cpp +hausdorff.cpp +euler_characteristic.cpp +grad_intrinsic.cpp +hessian.cpp +hessian_energy.cpp +internal_angles.cpp +is_delaunay.cpp +is_intrinsic_delaunay.cpp +is_irregular_vertex.cpp +iterative_closest_point.cpp +lbs_matrix.cpp +map_vertices_to_circle.cpp +marching_tets.cpp +mvc.cpp +offset_surface.cpp +orient_outward.cpp +per_corner_normals.cpp +per_edge_normals.cpp +planarize_quad_mesh.cpp +piecewise_constant_winding_number.cpp +project_isometrically_to_plane.cpp +quad_planarity.cpp +ramer_douglas_peucker.cpp +random_points_on_mesh_intrinsic.cpp +shape_diameter_function.cpp +sharp_edges.cpp +signed_angle.cpp +simplify_polyhedron.cpp +solid_angle.cpp +sparse_voxel_grid.cpp +tet_tet_adjacency.cpp + +active_set.cpp +all_pairs_distances.cpp +ambient_occlusion.cpp +arap_linear_block.cpp +arap_rhs.cpp +bfs.cpp +bone_parents.cpp +direct_delta_mush.cpp +boundary_conditions.cpp +bounding_box_diagonal.cpp +collapse_small_triangles.cpp +comb_cross_field.cpp +comb_frame_field.cpp +comb_line_field.cpp +compute_frame_field_bisectors.cpp +connect_boundary_to_infinity.cpp +cross_field_missmatch.cpp +deform_skeleton.cpp +directed_edge_orientations.cpp +directed_edge_parents.cpp +dqs.cpp +edge_collapse_is_valid.cpp +edge_topology.cpp +edges_to_path.cpp +exterior_edges.cpp +extract_manifold_patches.cpp +extract_non_manifold_edge_curves.cpp +face_occurrences.cpp +faces_first.cpp +find_cross_field_singularities.cpp +fit_plane.cpp +flip_avoiding_line_search.cpp +flipped_triangles.cpp +forward_kinematics.cpp +vector_area_matrix.cpp + +line_segment_in_rectangle.cpp +look_at.cpp +normal_derivative.cpp +orientable_patches.cpp +outer_element.cpp +partition.cpp +path_to_edges.cpp +per_vertex_attribute_smoothing.cpp +point_in_circle.cpp +point_simplex_squared_distance.cpp +polar_dec.cpp +procrustes.cpp +pso.cpp +random_search.cpp +ray_box_intersect.cpp +ray_sphere_intersect.cpp +readTGF.cpp +resolve_duplicated_faces.cpp +rigid_alignment.cpp +rotate_vectors.cpp +sample_edges.cpp +segment_segment_intersect.cpp +snap_points.cpp +sort_angles.cpp +swept_volume_bounding_box.cpp +topological_hole_fill.cpp +triangle_fan.cpp +triangles_from_strip.cpp +two_axis_valuator_fixed_up.cpp +uniformly_sample_two_manifold.cpp +unproject_in_mesh.cpp +unproject_on_line.cpp +unproject_on_plane.cpp +unproject_onto_mesh.cpp +unproject_ray.cpp +writeOFF.cpp From 7d757e0b3ec75392361801f444980d77b98445c7 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Tue, 26 Nov 2024 22:50:46 -0500 Subject: [PATCH 018/102] bump libigl (cgal;boost) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68a9129e..7e7399b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 753f4d3c88ddc9280696852d4740e58a815e365d + GIT_TAG 1f75bc3b617f1966d7c1d8c586463c2a5f11554b ) FetchContent_MakeAvailable(libigl) From b0451740eca82fcde03bda1218984afc47640728 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 27 Nov 2024 11:01:15 -0500 Subject: [PATCH 019/102] 10.5 --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e7399b9..4be97b2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,8 @@ cmake_minimum_required(VERSION 3.15...3.27) project(pyigl) +# For std::filesystem::path (generic_string) +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15") # ≥17 for return value optimization # <20 for embree set(CMAKE_CXX_STANDARD 17) From 483026a12113434b189cb2780825b1ca5c2bd02b Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 6 Dec 2024 14:56:37 -0500 Subject: [PATCH 020/102] tone down verbosity in hopes that link warning vomit goes away --- .github/workflows/wheels.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index ea6e16bd..dbcec034 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -31,7 +31,6 @@ jobs: runs-on: ${{ matrix.os.runs-on }} env: - CIBW_BUILD_VERBOSITY: 3 # This is very dubious... It *may* work because these are just cpp libraries that should not depend on the python version. Still, super-dubious. CIBW_TEST_REQUIRES: "gitpython" CIBW_TEST_COMMAND: "python {project}/tests/test_basic.py" From c665a9e5d1a4c6e301ecf400ec59f8593697413b Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 6 Dec 2024 14:59:04 -0500 Subject: [PATCH 021/102] rm constraints --- .github/workflows/wheels.yml | 4 ++-- constraints.txt | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 constraints.txt diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index dbcec034..b19fe1e2 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -36,7 +36,7 @@ jobs: CIBW_TEST_COMMAND: "python {project}/tests/test_basic.py" CIBW_BUILD: "${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }}" CIBW_TEST_SKIP: "*-macosx_arm64" - CIBW_ENVIRONMENT: "MAX_JOBS=${{ matrix.os.runs-on == 'macos-latest' && 3 || 2 }} PIP_CONSTRAINT=constraints.txt" + CIBW_ENVIRONMENT: "MAX_JOBS=${{ matrix.os.runs-on == 'macos-latest' && 3 || 2 }}" # Why universal2 here? It's not included above in CIBW_BUILD CIBW_ARCHS_MACOS: "x86_64 arm64 universal2" CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=10.13 CMAKE_OSX_ARCHITECTURES=\"${{ matrix.os.cibw-arch == 'macosx_x86_64' && 'x86_64' || matrix.os.cibw-arch == 'macosx_arm64' && 'arm64' || matrix.os.cibw-arch == 'macosx_universal2' && 'arm64;x86_64' || '' }}\"" @@ -90,7 +90,7 @@ jobs: - name: Build wheels shell: bash run: | - PIP_CONSTRAINT=$GITHUB_WORKSPACE/constraints.txt python -m cibuildwheel --output-dir wheelhouse + python -m cibuildwheel --output-dir wheelhouse # Upload binaries to github - uses: actions/upload-artifact@v4 diff --git a/constraints.txt b/constraints.txt deleted file mode 100644 index e55abda1..00000000 --- a/constraints.txt +++ /dev/null @@ -1,2 +0,0 @@ -numpy<=1.26.4 - From caeef48fde2ab959515e0643cf0ca79daf4e607f Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 6 Dec 2024 15:05:10 -0500 Subject: [PATCH 022/102] rm env --- environment.yml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 environment.yml diff --git a/environment.yml b/environment.yml deleted file mode 100644 index f83c6e88..00000000 --- a/environment.yml +++ /dev/null @@ -1,7 +0,0 @@ -channels: - - conda-forge -dependencies: - - numpy - - scipy - - meshplot - - igl \ No newline at end of file From 8a5681bfc76ca69e94b8227c2ded0f0f8f8519f0 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 6 Dec 2024 15:09:17 -0500 Subject: [PATCH 023/102] tone down output here too --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 14fb3222..3057fa21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.cibuildwheel] -build-verbosity = "3" +build-verbosity = "0" [build-system] requires = [ "numpy", From e19d416a48debc15ec30b032dcc4c62c8b132324 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 6 Dec 2024 15:12:41 -0500 Subject: [PATCH 024/102] typing extensions --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 3057fa21..275bbe79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ requires = [ "wheel", "cmake>=3.16", "packaging", + "typing_extensions", ] build-backend = "setuptools.build_meta" From ac8fca0cf6befb0f06c508d4074238b4c8a823c4 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 6 Dec 2024 15:58:36 -0500 Subject: [PATCH 025/102] numpy>1.20 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 275bbe79..b5ad892d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ build-verbosity = "0" [build-system] requires = [ - "numpy", + "numpy>=1.20", "scipy", "setuptools>=42", "wheel", From 4a73d55aefda326369d76b01835a9e2fa990d142 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 6 Dec 2024 17:20:10 -0500 Subject: [PATCH 026/102] test --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index b19fe1e2..7a76e023 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -33,7 +33,7 @@ jobs: env: # This is very dubious... It *may* work because these are just cpp libraries that should not depend on the python version. Still, super-dubious. CIBW_TEST_REQUIRES: "gitpython" - CIBW_TEST_COMMAND: "python {project}/tests/test_basic.py" + CIBW_TEST_COMMAND: "python {project}/tests/test.py" CIBW_BUILD: "${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }}" CIBW_TEST_SKIP: "*-macosx_arm64" CIBW_ENVIRONMENT: "MAX_JOBS=${{ matrix.os.runs-on == 'macos-latest' && 3 || 2 }}" From f08c290ba1041bc97f232f3f38a05f9c8c2cd661 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Mon, 9 Dec 2024 10:33:57 -0500 Subject: [PATCH 027/102] at least one --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b5ad892d..0a6420f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.cibuildwheel] -build-verbosity = "0" +build-verbosity = "1" [build-system] requires = [ "numpy>=1.20", From 56d6625fe04205d13a9573f86b5403b5eccca77a Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 11:22:05 -0500 Subject: [PATCH 028/102] switch to pure pyproject.toml --- pyproject.toml | 51 +++++++++++++-- setup.py | 171 ------------------------------------------------- 2 files changed, 44 insertions(+), 178 deletions(-) delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml index 0a6420f1..c87ea148 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,52 @@ -[tool.cibuildwheel] -build-verbosity = "1" [build-system] requires = [ - "numpy>=1.20", - "scipy", - "setuptools>=42", - "wheel", "cmake>=3.16", + "nanobind >=1.3.2", + "numpy>=2.0.0", "packaging", + "scikit-build-core >=0.10", + "scipy", "typing_extensions", + ] +build-backend = "scikit_build_core.build" + +[project] +name = "libigl" +version = "2.5.4dev" +description = "libigl: A simple C++ geometry processing library" +readme = "README.md" +requires-python = ">=3.8" +authors = [ + { name = "Alec Jacobson", email = "alecjacobson@gmail.com" }, +] +classifiers = [ + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)" ] -build-backend = "setuptools.build_meta" +[project.urls] +Homepage = "https://github.com/libigl/libigl-python-bindings" + +[tool.scikit-build] +# Protect the configuration against future changes in scikit-build-core +minimum-version = "build-system.requires" + +# Setuptools-style build caching in a local directory +build-dir = "build/{wheel_tag}" + +# Build stable ABI wheels for CPython 3.12+ +wheel.py-api = "cp312" + +[tool.cibuildwheel] +# Necessary to see build output from the actual compilation +build-verbosity = 1 + +# Run pytest to ensure that the package was correctly built +test-command = "pytest {project}/tests" +test-requires = "pytest" + +# Don't test Python 3.8 wheels on macOS/arm64 +test-skip="cp38-macosx_*:arm64" +# Needed for full C++17 support +[tool.cibuildwheel.macos.environment] +MACOSX_DEPLOYMENT_TARGET = "10.14" diff --git a/setup.py b/setup.py deleted file mode 100644 index 245507f2..00000000 --- a/setup.py +++ /dev/null @@ -1,171 +0,0 @@ -import os -import re -import sys -import platform -import subprocess -import warnings -# Define a custom format warning function -def custom_formatwarning(msg, category, filename, lineno, line=None): - return f"\033[91m{category.__name__}: {msg}\033[0m\n" - -# Apply the custom warning format -warnings.formatwarning = custom_formatwarning - -from packaging.version import Version -from setuptools import setup, Extension, find_packages -from setuptools.command.build_ext import build_ext - -__version__ = '0.0.1' - -class CMakeExtension(Extension): - def __init__(self, name, sourcedir=''): - Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) - - -class CMakeBuild(build_ext): - def run(self): - try: - out = subprocess.check_output(['cmake', '--version']) - except OSError: - raise RuntimeError( - "CMake must be installed to build the following extensions: , ".join(e.name for e in self.extensions)) - - warnings.warn("Debug mode is on") - self.debug = True - - cmake_version = Version(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) - if cmake_version < Version('3.2.0'): - raise RuntimeError("CMake >= 3.2.0 is required") - - for ext in self.extensions: - self.build_extension(ext) - - - def build_extension(self, ext): - extdir = os.path.join(os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))),"igl") - - cmake_args = ['-DPYIGL_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable] - - - cfg = 'Debug' if self.debug else 'Release' - build_args = ['--config', cfg] - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - # cmake_args += ['-DDEBUG_TRACE=ON'] - # build_args += ['-v'] - - if platform.system() == "Windows": - cmake_generator = os.environ.get('CMAKE_GENERATOR', '') - if cmake_generator != "NMake Makefiles" and "Ninja" not in cmake_generator: - if sys.maxsize > 2**32: - cmake_args += ['-A', 'x64'] - # build_args += ['--', '/m'] - else: - if "MAX_JOBS" in os.environ: - build_args += ['--', f"-j{os.environ['MAX_JOBS']}"] - else: - build_args += ['--', '-j8'] - - - tmp = os.environ.get("AR", "") - if "arm64-apple" in tmp: - tmp = os.environ.get("CMAKE_ARGS", "") - if tmp: - cmake_args += tmp.split(" ") - - tmp = os.environ.get("CC", "") - print("C compiler", tmp) - if tmp: - cmake_args += ["-DCMAKE_C_COMPILER={}".format(tmp)] - - tmp = os.environ.get("CXX", "") - print("CXX compiler", tmp) - if tmp: - cmake_args += ["-DCMAKE_CXX_COMPILER={}".format(tmp)] - else: - tmp = os.getenv('CC_FOR_BUILD', '') - if tmp: - print("Setting c compiler to", tmp) - cmake_args += ["-DCMAKE_C_COMPILER=" + tmp] - - tmp = os.getenv('CXX_FOR_BUILD', '') - if tmp: - print("Setting cxx compiler to", tmp) - cmake_args += ["-DCMAKE_CXX_COMPILER="+ tmp] - - env = os.environ.copy() - env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''),self.distribution.get_version()) - - - - tmp = os.getenv("target_platform", "") - if tmp: - print("target platfrom", tmp) - if "arm" in tmp: - cmake_args += ["-DCMAKE_OSX_ARCHITECTURES=arm64"] - - # print(cmake_args) - # tmp = os.getenv('CMAKE_ARGS', '') - - # if tmp: - # tmp = tmp.split(" ") - # print("tmp", tmp) - # cmake_args += tmp - - # cmake_args += ["-DCMAKE_OSX_ARCHITECTURES" , "arm64"] - # print(cmake_args) - - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) - subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) - - # print build_args - print("********************************************************************") - print("********************************************************************") - print("********************************************************************") - print("********************************************************************") - print("build_args:", build_args) - print("********************************************************************") - print("********************************************************************") - print("********************************************************************") - print("********************************************************************") - subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) - - print() # Add an empty line for cleaner output - - -with open("README.md", "r") as fh: - long_description = fh.read() - - -# https://stackoverflow.com/a/7071358/148668 -import re -VERSIONFILE="igl/_version.py" -verstrline = open(VERSIONFILE, "rt").read() -VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" -mo = re.search(VSRE, verstrline, re.M) -if mo: - verstr = mo.group(1) -else: - raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) - -setup( - name="libigl", - version=verstr, - author="libigl", - author_email="", - description="libigl Python Bindings", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://libigl.github.io/libigl-python-bindings/", - ext_modules=[CMakeExtension('pyigl')], - install_requires=[ 'numpy', 'scipy' ], - cmdclass=dict(build_ext=CMakeBuild), - packages=find_packages(), - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License" - ], - test_suite="tests" -) From 9a693def3692e5740247baa3c602d622d6318d36 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 15:11:33 -0500 Subject: [PATCH 029/102] scikit build core --- CMakeLists.txt | 8 +++----- README.md | 36 ++++-------------------------------- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4be97b2f..8a7b0ee0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,14 +47,12 @@ option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 1f75bc3b617f1966d7c1d8c586463c2a5f11554b + GIT_TAG a221faf1e4bd571529ca2101c08bc2458579b1da ) FetchContent_MakeAvailable(libigl) -message(STATUS "PYIGL_OUTPUT_DIRECTORY: ${PYIGL_OUTPUT_DIRECTORY}") -if (NOT DEFINED PYIGL_OUTPUT_DIRECTORY) - message(FATAL_ERROR "PYIGL_OUTPUT_DIRECTORY must be defined externally") -endif() +# set PYIGL_OUTPUT_DIRECTORY to [this dir]/igl +set(PYIGL_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/igl") list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # Color output diff --git a/README.md b/README.md index 3efa075d..4632af7f 100644 --- a/README.md +++ b/README.md @@ -26,40 +26,12 @@ python -m pip install ./ * A function reference can be found [here](https://libigl.github.io/libigl-python-bindings/igl_docs/) ## Compiling and modifying the bindiings -After installing numpy & scipy and then cloning this repository, you can compile the bindings from scratch by running: -``` -python setup.py develop -``` +According to the [scikit-build-core documentation](https://scikit-build-core.readthedocs.io/en/latest/configuration.html#editable-installs), the way to make an editable (incremental) build is to: -or + 1. Preinstall the dependencies (at the top of pyproject.toml + 2. Then use this very long command: ``` -python setup.py build --debug develop +python -m pip install --no-build-isolation --config-settings=editable.rebuild=true -Cbuild-dir=build -ve. ``` - -for debug compilation. This command will make the package `igl` available for import in the -current shell. - -To run the tests: - -``` -python setup.py test -``` - -or - -``` -python tests/test_basic.py -``` - -and if developing and trying to run from this directory. You could use: - -``` -PYTHONPATH=. python tests/test_basic.py -``` - -## License - -Like libigl, the wrapper source code is licensed under MPL2. Code included via -the copyleft and restricted sub-directories may have more restrictive licenses. From 5aacf3b13087a36dde20833d448c8e9a7fd76230 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 20:59:17 -0500 Subject: [PATCH 030/102] use nanobind_example wheels.yml --- .github/workflows/wheels.yml | 128 +++++++++++------------------------ 1 file changed, 41 insertions(+), 87 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 7a76e023..fb813cef 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,12 +1,14 @@ -name: Pip build +name: Wheels on: - push: - branches: - - main + workflow_dispatch: pull_request: + push: branches: - - main + - master + release: + types: + - published jobs: build_sdist: @@ -14,6 +16,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Build SDist run: pipx run build --sdist @@ -23,101 +27,51 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: artifact-sdist + name: dist-sdist path: dist/*.tar.gz - build_wheels: - name: Build wheels ${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }} - runs-on: ${{ matrix.os.runs-on }} - - env: - # This is very dubious... It *may* work because these are just cpp libraries that should not depend on the python version. Still, super-dubious. - CIBW_TEST_REQUIRES: "gitpython" - CIBW_TEST_COMMAND: "python {project}/tests/test.py" - CIBW_BUILD: "${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }}" - CIBW_TEST_SKIP: "*-macosx_arm64" - CIBW_ENVIRONMENT: "MAX_JOBS=${{ matrix.os.runs-on == 'macos-latest' && 3 || 2 }}" - # Why universal2 here? It's not included above in CIBW_BUILD - CIBW_ARCHS_MACOS: "x86_64 arm64 universal2" - CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=10.13 CMAKE_OSX_ARCHITECTURES=\"${{ matrix.os.cibw-arch == 'macosx_x86_64' && 'x86_64' || matrix.os.cibw-arch == 'macosx_arm64' && 'arm64' || matrix.os.cibw-arch == 'macosx_universal2' && 'arm64;x86_64' || '' }}\"" + build_wheels: + name: Wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - # Launch separate job for each python. The build is so much longer than - # machine configuration/setup, so parallel builds will be faster. More - # importantly, github times out after 6 hours _per job_. - cpversion: - - cp36 - - cp37 - - cp38 - - cp39 - - cp310 - - cp311 - - cp312 - os: - - runs-on: ubuntu-latest - cibw-arch: manylinux_x86_64 - - runs-on: macos-latest - cibw-arch: macosx_x86_64 - - runs-on: macos-latest - cibw-arch: macosx_arm64 - - runs-on: windows-latest - cibw-arch: win_amd64 - exclude: - - cpversion: cp36 - os: - runs-on: macos-latest - cibw-arch: macosx_arm64 - - cpversion: cp37 - os: - runs-on: macos-latest - cibw-arch: macosx_arm64 + os: [ubuntu-latest, macos-13, macos-14, windows-latest] steps: - - uses: actions/checkout@v4 - - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" + - uses: actions/checkout@v4 + with: + submodules: true - - name: Install cibuildwheel - run: | - python -m pip install --upgrade pip - python -m pip install cibuildwheel + - uses: pypa/cibuildwheel@v2.22 - - name: Build wheels - shell: bash - run: | - python -m cibuildwheel --output-dir wheelhouse + - name: Verify clean directory + run: git diff --exit-code + shell: bash - # Upload binaries to github - - uses: actions/upload-artifact@v4 - with: - name: artifact-wheel-${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }} - path: |- - ./wheelhouse/*.whl - ./wheelhouse/*.tar.gz + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + path: wheelhouse/*.whl + name: dist-${{ matrix.os }} - # # Push the resulting binaries to pypi on a tag starting with 'v' - upload_pypi: - needs: build_wheels + upload_all: + name: Upload if release + needs: [build_wheels, build_sdist] runs-on: ubuntu-latest - # upload to PyPI on every tag starting with 'v' - # if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') - # alternatively, to publish when a GitHub Release is created, use the following rule: if: github.event_name == 'release' && github.event.action == 'published' + steps: - - uses: actions/download-artifact@v4 - with: - pattern: artifact-* - merge-multiple: true - path: dist + - uses: actions/setup-python@v5 + - uses: actions/download-artifact@v4 + with: + name: dist + pattern: dist-* + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.pypi_password }} - - uses: pypa/gh-action-pypi-publish@v1.8.11 - with: - user: __token__ - password: ${{ secrets.pypi_password }} - skip_existing: true - # To test: repository_url: https://test.pypi.org/legacy/ From 0a5671fb9a17984b8e1443137586933233f10d6f Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:02:31 -0500 Subject: [PATCH 031/102] use dr jit wheels.yml --- .github/workflows/wheels.yml | 152 ++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 58 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index fb813cef..e303dc14 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,77 +1,113 @@ -name: Wheels - +name: Build Python wheels on: workflow_dispatch: - pull_request: - push: - branches: - - master - release: - types: - - published + inputs: + upload: + description: 'Upload wheels to PyPI? (0: no, 1: yes)' + required: true + default: '0' jobs: - build_sdist: - name: Build SDist - runs-on: ubuntu-latest + build_wheels: + strategy: + matrix: + os: [ubuntu-20.04, windows-latest, macos-13, macos-14] + python: [cp38, cp39, cp310, cp311, cp312, cp312_stable, cp313] + exclude: + # The first Python version to target Apple arm64 architectures is 3.9 + - os: macos-14 + python: cp38 + fail-fast: false + + name: > + ${{ matrix.python }} wheel for ${{ matrix.os }} + ${{ (endsWith(matrix.python, '_stable') && '(stable ABI)') || '' }} + runs-on: ${{ matrix.os }} + steps: - - uses: actions/checkout@v4 - with: - submodules: true + ########################################################### + # Setup local environment (source code, build requirements) + ########################################################### + - uses: Wandalen/wretry.action@v1.2.0 + with: + action: actions/checkout@v3 + with: | + submodules: recursive + attempt_limit: 3 + attempt_delay: 2000 - - name: Build SDist - run: pipx run build --sdist + - uses: actions/setup-python@v4 + name: Install Python + with: + python-version: '3.10' - - name: Check metadata - run: pipx run twine check dist/* + - name: Prepare compiler environment for Windows + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 - - uses: actions/upload-artifact@v4 - with: - name: dist-sdist - path: dist/*.tar.gz + - name: Install cibuildwheel + run: | + python -m pip install cibuildwheel==2.20.0 - build_wheels: - name: Wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-13, macos-14, windows-latest] + ################################################################ + # Set up envvars to build the correct wheel (stable ABI or not) + ################################################################ + - name: Prepare cibuildwheel environment (UNIX) + if: ${{ ! endsWith(matrix.python, '_stable') && runner.os != 'Windows' }} + run: | + echo "CIBW_BUILD=${{ matrix.python }}-*" >> $GITHUB_ENV - steps: - - uses: actions/checkout@v4 - with: - submodules: true + - name: Prepare cibuildwheel environment (Windows) + if: ${{ ! endsWith(matrix.python, '_stable') && runner.os == 'Windows' }} + run: | + echo "CIBW_BUILD=${{ matrix.python }}-*" >> $env:GITHUB_ENV + + - name: Prepare cibuildwheel environment for stable ABI wheel (UNIX) + if: ${{ endsWith(matrix.python, '_stable') && runner.os != 'Windows' }} + run: | + stable_cp=$(echo ${{ matrix.python }} | cut -d_ -f1) && + echo "CIBW_BUILD=${stable_cp}-*" >> $GITHUB_ENV && + echo "CIBW_CONFIG_SETTINGS=\"wheel.py-api=cp312\" \"cmake.args=-DDRJIT_STABLE_ABI=ON\"" >> $GITHUB_ENV + + - name: Prepare cibuildwheel environment for stable ABI wheel (Windows) + if: ${{ endsWith(matrix.python, '_stable') && runner.os == 'Windows' }} + run: | + $stable_cp = '${{ matrix.python }}' -split '_' + echo "CIBW_BUILD=$($stable_cp[0])-*" >> $env:GITHUB_ENV + echo "CIBW_CONFIG_SETTINGS=wheel.py-api=cp312 cmake.args=-DDRJIT_STABLE_ABI=ON" >> $env:GITHUB_ENV - - uses: pypa/cibuildwheel@v2.22 - - name: Verify clean directory - run: git diff --exit-code - shell: bash + ######################### + # Build and store wheels + ######################### + - name: Build wheel + run: | + python -m cibuildwheel --output-dir wheelhouse - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - path: wheelhouse/*.whl - name: dist-${{ matrix.os }} + - uses: actions/upload-artifact@v3 + with: + name: wheels + path: ./wheelhouse/*.whl - upload_all: - name: Upload if release - needs: [build_wheels, build_sdist] + upload_pypi: + name: Upload wheels to PyPI runs-on: ubuntu-latest - if: github.event_name == 'release' && github.event.action == 'published' + if: ${{ github.event.inputs.upload == '1'}} + needs: [build_wheels] steps: - - uses: actions/setup-python@v5 - - uses: actions/download-artifact@v4 - with: - name: dist - pattern: dist-* - merge-multiple: true - - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.pypi_password }} + ######################### + # Fetch and upload wheels + ######################### + - uses: actions/download-artifact@v3 + with: + name: wheels + path: dist + - uses: pypa/gh-action-pypi-publish@v1.10.2 + with: + user: __token__ + password: ${{ secrets.PYPI_PASSWORD }} From 40b995a1fbd03f83f8a3a09d6e0d8fd187d4625d Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:03:43 -0500 Subject: [PATCH 032/102] use dr jit wheels.yml --- .github/workflows/wheels.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e303dc14..6d6d108d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,4 +1,5 @@ name: Build Python wheels + on: workflow_dispatch: inputs: From e4cdf40172ff369daef0587dc6f45a2efec222a8 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:04:04 -0500 Subject: [PATCH 033/102] use dr jit wheels.yml --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6d6d108d..50e84124 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,4 +1,4 @@ -name: Build Python wheels +name: Wheels on: workflow_dispatch: From 3c495f8fa3d83c4c0faf6e16f34c1f18aa4e3230 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:06:07 -0500 Subject: [PATCH 034/102] use dr jit wheels.yml --- .github/workflows/wheels.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 50e84124..66972b2e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -2,11 +2,13 @@ name: Wheels on: workflow_dispatch: - inputs: - upload: - description: 'Upload wheels to PyPI? (0: no, 1: yes)' - required: true - default: '0' + pull_request: + push: + branches: + - master + release: + types: + - published jobs: build_wheels: From 07cb7405d3345ce3217f2b83e87b9ea8a107fe15 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:16:37 -0500 Subject: [PATCH 035/102] pip too --- .github/workflows/pip.yml | 38 +++++++++ .github/workflows/wheels.yml | 151 +++++++++++++---------------------- 2 files changed, 95 insertions(+), 94 deletions(-) create mode 100644 .github/workflows/pip.yml diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml new file mode 100644 index 00000000..45a8e978 --- /dev/null +++ b/.github/workflows/pip.yml @@ -0,0 +1,38 @@ +name: Pip + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +jobs: + build: + name: Build with Pip + runs-on: ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + platform: [windows-latest, macos-latest, ubuntu-latest] + python-version: ["3.8", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Set min macOS version + if: runner.os == 'macOS' + run: | + echo "MACOSX_DEPLOYMENT_TARGET=10.14" >> $GITHUB_ENV + + - name: Build and install + run: | + python -m pip install pytest + pip install --verbose . + + - name: Test + run: python -m pytest diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 66972b2e..8fd540a7 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,116 +1,79 @@ -name: Wheels +name: Pip build on: - workflow_dispatch: - pull_request: push: + branches: + - main + pull_request: branches: - - master - release: - types: - - published + - main jobs: - build_wheels: - strategy: - matrix: - os: [ubuntu-20.04, windows-latest, macos-13, macos-14] - python: [cp38, cp39, cp310, cp311, cp312, cp312_stable, cp313] - exclude: - # The first Python version to target Apple arm64 architectures is 3.9 - - os: macos-14 - python: cp38 - fail-fast: false - - name: > - ${{ matrix.python }} wheel for ${{ matrix.os }} - ${{ (endsWith(matrix.python, '_stable') && '(stable ABI)') || '' }} - runs-on: ${{ matrix.os }} - + build_sdist: + name: Build SDist + runs-on: ubuntu-latest steps: - ########################################################### - # Setup local environment (source code, build requirements) - ########################################################### - - uses: Wandalen/wretry.action@v1.2.0 - with: - action: actions/checkout@v3 - with: | - submodules: recursive - attempt_limit: 3 - attempt_delay: 2000 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 - name: Install Python - with: - python-version: '3.10' + - name: Build SDist + run: pipx run build --sdist - - name: Prepare compiler environment for Windows - if: runner.os == 'Windows' - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x64 + - name: Check metadata + run: pipx run twine check dist/* - - name: Install cibuildwheel - run: | - python -m pip install cibuildwheel==2.20.0 + - uses: actions/upload-artifact@v4 + with: + name: artifact-sdist + path: dist/*.tar.gz + build_wheels: + name: Build wheels ${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }} + runs-on: ${{ matrix.os.runs-on }} - ################################################################ - # Set up envvars to build the correct wheel (stable ABI or not) - ################################################################ - - name: Prepare cibuildwheel environment (UNIX) - if: ${{ ! endsWith(matrix.python, '_stable') && runner.os != 'Windows' }} - run: | - echo "CIBW_BUILD=${{ matrix.python }}-*" >> $GITHUB_ENV + env: + CIBW_BUILD: "${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }}" + CIBW_ENVIRONMENT: "MAX_JOBS=${{ matrix.os.runs-on == 'macos-latest' && 3 || 2 }}" + # Why universal2 here? It's not included above in CIBW_BUILD + CIBW_ARCHS_MACOS: "x86_64 arm64 universal2" + CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=10.13 CMAKE_OSX_ARCHITECTURES=\"${{ matrix.os.cibw-arch == 'macosx_x86_64' && 'x86_64' || matrix.os.cibw-arch == 'macosx_arm64' && 'arm64' || matrix.os.cibw-arch == 'macosx_universal2' && 'arm64;x86_64' || '' }}\"" - - name: Prepare cibuildwheel environment (Windows) - if: ${{ ! endsWith(matrix.python, '_stable') && runner.os == 'Windows' }} - run: | - echo "CIBW_BUILD=${{ matrix.python }}-*" >> $env:GITHUB_ENV + strategy: + fail-fast: false + matrix: + # Launch separate job for each python. The build is so much longer than + # machine configuration/setup, so parallel builds will be faster. More + # importantly, github times out after 6 hours _per job_. + cpversion: + - cp310 + os: + - runs-on: macos-latest + cibw-arch: macosx_x86_64 + - runs-on: macos-latest + cibw-arch: macosx_arm64 - - name: Prepare cibuildwheel environment for stable ABI wheel (UNIX) - if: ${{ endsWith(matrix.python, '_stable') && runner.os != 'Windows' }} - run: | - stable_cp=$(echo ${{ matrix.python }} | cut -d_ -f1) && - echo "CIBW_BUILD=${stable_cp}-*" >> $GITHUB_ENV && - echo "CIBW_CONFIG_SETTINGS=\"wheel.py-api=cp312\" \"cmake.args=-DDRJIT_STABLE_ABI=ON\"" >> $GITHUB_ENV + steps: + - uses: actions/checkout@v4 - - name: Prepare cibuildwheel environment for stable ABI wheel (Windows) - if: ${{ endsWith(matrix.python, '_stable') && runner.os == 'Windows' }} - run: | - $stable_cp = '${{ matrix.python }}' -split '_' - echo "CIBW_BUILD=$($stable_cp[0])-*" >> $env:GITHUB_ENV - echo "CIBW_CONFIG_SETTINGS=wheel.py-api=cp312 cmake.args=-DDRJIT_STABLE_ABI=ON" >> $env:GITHUB_ENV + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install cibuildwheel + run: | + python -m pip install --upgrade pip + python -m pip install cibuildwheel - ######################### - # Build and store wheels - ######################### - - name: Build wheel + - name: Build wheels + shell: bash run: | python -m cibuildwheel --output-dir wheelhouse - - uses: actions/upload-artifact@v3 - with: - name: wheels - path: ./wheelhouse/*.whl - - upload_pypi: - name: Upload wheels to PyPI - runs-on: ubuntu-latest - if: ${{ github.event.inputs.upload == '1'}} - needs: [build_wheels] - - steps: - ######################### - # Fetch and upload wheels - ######################### - - uses: actions/download-artifact@v3 + # Upload binaries to github + - uses: actions/upload-artifact@v4 with: - name: wheels - path: dist + name: artifact-wheel-${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }} + path: |- + ./wheelhouse/*.whl + ./wheelhouse/*.tar.gz - - uses: pypa/gh-action-pypi-publish@v1.10.2 - with: - user: __token__ - password: ${{ secrets.PYPI_PASSWORD }} From 61d33ed828ee8d3841689dda6e75e09c1f62e985 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:31:28 -0500 Subject: [PATCH 036/102] compile just a single file --- CMakeLists.txt | 17 +- tests/test.py | 1082 ++++++++++++++++++++++++------------------------ 2 files changed, 553 insertions(+), 546 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a7b0ee0..0f20ee4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,11 +39,11 @@ FetchContent_Declare( FetchContent_MakeAvailable(nanobind) # Download and set up libigl -option(LIBIGL_COPYLEFT_CORE "Build target igl_copyleft::core" ON) -option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) -option(LIBIGL_EMBREE "Build target igl::embree" ON) -option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) -option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) +option(LIBIGL_COPYLEFT_CORE "Build target igl_copyleft::core" OFF) +option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" OFF) +option(LIBIGL_EMBREE "Build target igl::embree" OFF) +option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" OFF) +option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" OFF) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git @@ -95,8 +95,15 @@ function(pyigl_include prefix name) endif() file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/*.cpp") + # Just compile a single file + list(GET sources 0 sources) + list(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/module.cpp") + set(BINDING_SOURCES ${sources}) list(FILTER BINDING_SOURCES EXCLUDE REGEX ".*/module\\.cpp$") + + # keep just the first in the list and overwrite BINDING_SOURCES + # Generate the function calls based on filenames set(BINDING_DECLARATIONS "") foreach(source_file ${BINDING_SOURCES}) diff --git a/tests/test.py b/tests/test.py index 62c378db..eceffccd 100644 --- a/tests/test.py +++ b/tests/test.py @@ -5,546 +5,546 @@ import time import warnings import igl -import igl.copyleft -import igl.copyleft.cgal -import igl.copyleft.tetgen -import igl.embree -import igl.triangle - -##def rand_sparse(n,density): -## n_features = n -## n_samples = n -## rng1 = np.random.RandomState(42) -## rng2 = np.random.RandomState(43) -## -## nnz = int(n_samples*n_features*density) -## -## row = rng1.randint(n_samples, size=nnz) -## cols = rng2.randint(n_features, size=nnz) -## data = rng1.rand(nnz) -## -## S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) -## return S.tocsc() +#import igl.copyleft +#import igl.copyleft.cgal +#import igl.copyleft.tetgen +#import igl.embree +#import igl.triangle +# +###def rand_sparse(n,density): +### n_features = n +### n_samples = n +### rng1 = np.random.RandomState(42) +### rng2 = np.random.RandomState(43) +### +### nnz = int(n_samples*n_features*density) +### +### row = rng1.randint(n_samples, size=nnz) +### cols = rng2.randint(n_features, size=nnz) +### data = rng1.rand(nnz) +### +### S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) +### return S.tocsc() +### +###def time_noop(): +### def helper(N,I,SN,SI): +### igl.noop(SN=SN) +### # start timer +### runs = 100 +### start = time.time() +### for i in range(runs): +### igl.noop(SN=SN) +### # end timer +### end = time.time() +### return (end - start)/runs +### n = 10000 +### m = 10 +### N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) +### I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) +### # random sparse matrix +### SN64 = rand_sparse(n,1.0/(n)) +### # print number of nonzeros +### SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) +### print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +### +###time_noop() ## -##def time_noop(): -## def helper(N,I,SN,SI): -## igl.noop(SN=SN) -## # start timer -## runs = 100 -## start = time.time() -## for i in range(runs): -## igl.noop(SN=SN) -## # end timer -## end = time.time() -## return (end - start)/runs -## n = 10000 -## m = 10 -## N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) -## I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) -## # random sparse matrix -## SN64 = rand_sparse(n,1.0/(n)) -## # print number of nonzeros -## SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) -## print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +### print(igl.matlab_format(V,"V")) +### print(igl.matlab_format_index(F,"F")) +### print(igl.matlab_format(dV,"dV")) +### print(igl.matlab_format_index(dF,"dF")) +# +# +## seed numpy's random number generator ## -##time_noop() -# -## print(igl.matlab_format(V,"V")) -## print(igl.matlab_format_index(F,"F")) -## print(igl.matlab_format(dV,"dV")) -## print(igl.matlab_format_index(dF,"dF")) - - -# seed numpy's random number generator -# -def triangulated_square(): - V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) - F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) - return V,F - -def single_tet(): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) - T = np.array([[0,1,2,3]],dtype=np.int64) - return V,F,T - -def test_edges(): - F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) - E,oE = igl.orient_halfedges(F) - ne = E.max()+1 - np.random.seed(42) - uE = np.random.rand(ne).astype(np.float64) - - uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) - V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) - -def test_read_write(): - V,F = triangulated_square() - igl.write_triangle_mesh("out.obj",V,F) - igl.write_triangle_mesh("out.off",V,F) - igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) - igl.writePLY("out.ply",V,F) - igl.writeOBJ("out.obj",V,F) - igl.writeOBJ("out.obj",V,F,V,F,V,F) - V,F,_ = igl.readOFF("out.off") - V,_,_,F,_,_ = igl.readOBJ("out.obj") - V,F = igl.read_triangle_mesh("out.ply") - igl.writeDMAT("out.dmat",V) - igl.writeDMAT("out.dmat",V,ascii=True) - V = igl.readDMAT("out.dmat") - igl.writeDMAT("out.dmat",V,ascii=False) - V = igl.readDMAT("out.dmat") - -def test_operators(): - V,F = triangulated_square() - L = igl.cotmatrix(V,F) - I = np.array([0,1,2,3],dtype=np.int64) - C = np.array([0,4],dtype=np.int64) - L,M,P = igl.cotmatrix(V,I,C) - L = igl.squared_edge_lengths(V,F) - K = igl.cotmatrix_entries(V,F) - l = igl.edge_lengths(V,F) - K = igl.cotmatrix_entries(l=l) - B1,B2,B3 = igl.local_basis(V,F) - G = igl.grad(V,F) - - M = igl.massmatrix(V,F) - M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) - M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) - M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) - - A = igl.facet_adjacency_matrix(F) - A = igl.adjacency_matrix(F) - A = igl.adjacency_matrix(I,C) - E = igl.edges(F) - E = igl.edges(I,C) - E = igl.edges(A) - n,C,K = igl.connected_components(A) - C = igl.vertex_components(F) - nc,C = igl.facet_components(F) - -def test_normals_and_distances(): - V,F = triangulated_square() - I = np.array([0,1,2,3],dtype=np.int64) - C = np.array([0,4],dtype=np.int64) - FN,_,_,_ = igl.per_face_normals(V,I,C) - FN = igl.per_face_normals(V,F) - FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) - VN = igl.per_vertex_normals(V,F) - VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) - VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) - VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) - VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) - dblA = igl.doublearea(V,F) - l = igl.edge_lengths(V,F) - dblA = igl.doublearea(l=l) - dblA = igl.doublearea(l=l,nan_replacement=0.0) - P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) - sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) - W = igl.winding_number(V,F,P) - W = igl.winding_number(V,F,P[1,:]) - W = igl.fast_winding_number(V,F,P) - S,I,C,N = igl.signed_distance(P,V,F) - S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) - BC = igl.barycenter(V,F) - -def test_harmonic(): - V,F = triangulated_square() - b = np.array([0,3],dtype=np.int64) - bc = np.array([[1,0],[0,1]],dtype=np.float64) - W = igl.bbw(V,F,b,bc) - W = igl.harmonic(V,F,b,bc,k=1) - W = igl.harmonic(V,F,b,bc,k=2) - -def test_tets(): - V,F,T = single_tet() - F,J,K = igl.boundary_facets(T) - igl.writeMESH("out.mesh",V,T) - igl.writeMESH("out.mesh",V,T,F=F) - V,T,F = igl.readMESH("out.mesh") - igl.writeMSH("out.msh",V,F,T) - V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") - -def test_bvh(): - V,F,T = single_tet() - tree = igl.AABB() - tree.init(V,T) - P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) - # first row of P - q = P[0,:] - I = tree.find(V,T,q) - i = tree.find(V,T,q,first=True) - - Q = igl.barycenter(V,T) - I = igl.in_element(V,T,Q,tree) - - tree = igl.AABB() - tree.init(V,F) - sqrD,_,_ = tree.squared_distance(V,F,P) - - O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); - D = V.mean(axis=0)-O - I,T,UV = tree.intersect_ray_first(V,F,O,D) - hits = tree.intersect_ray(V,F,O,D) - o = O[0,:] - d = D[0,:] - hit = igl.ray_mesh_intersect(o,d,V,F,first=True) - hits = igl.ray_mesh_intersect(o,d,V,F) - - -def test_upsample(): - V,F,T = single_tet() - # upsample so there's something to collapse - V,F = igl.upsample(V,F,number_of_subdivs=1) - E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) - E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) - EF,EI = igl.edge_flaps(F,uE,EMAP) - uE,EMAP,EF,EI = igl.edge_flaps(F) - Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) - - e = 0 - p = V[uE[e,0],:] - e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) - -def test_cut(): - V,F,T = single_tet() - C = np.ones(F.shape,dtype=bool) - Vn,Fn,I = igl.cut_mesh(V,F,C) - -def test_adjacency(): - V,F,T = single_tet() - TT,TTi = igl.triangle_triangle_adjacency(F) - TT,TTi = igl.triangle_triangle_adjacency_lists(F) - - VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) - VF,VFi = igl.vertex_triangle_adjacency(F) - - F012 = F; - F120 = np.roll(F012,1,axis=1) - FF = np.vstack((F012,F120)) - F,IA,IC = igl.unique_simplices(FF) - A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) - B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) - IA,LOCB = igl.ismember_rows(A,B) - A = igl.adjacency_list(F) - A = igl.adjacency_list(F,sorted=True) - -def test_min_quad(): - V,F,T = single_tet() - A = -igl.cotmatrix(V,F) - B = np.zeros((V.shape[0],1),dtype=np.float64) - known = np.array([1,2],dtype=np.int64) - Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) - Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) - Beq = np.zeros((1,1),dtype=np.float64) - Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) - data = igl.min_quad_with_fixed_data() - igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) - Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) - -def test_volume(): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - T = np.array([[0,1,2,3]],dtype=np.int64) - L = igl.edge_lengths(V,T) - vol = igl.volume(V,T) - vol = igl.volume(L=L) - vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) - F,_,_ = igl.boundary_facets(T) - # remove last face - F = F[:-1,:] - B = igl.is_border_vertex(F) - I,C = igl.on_boundary(F) - I,C = igl.on_boundary(T) - F = np.array([[2,1,3]],dtype=np.int64) - NV,NF,I,J = igl.remove_unreferenced(V,F) - -def test_oriented_facets(): - V,F,T = single_tet() - E = igl.oriented_facets(F) - F = igl.oriented_facets(T) - - res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) - res,_,_,_,_ = igl.is_edge_manifold(F) - -def test_matlab_format(): - V,F = triangulated_square() - L = igl.cotmatrix(V,F) - s = igl.matlab_format(V,"V") - s = igl.matlab_format_index(F,"F") - s = igl.matlab_format(L,"L") - -def test_polar(): - # random randn 3x3 matrix - np.random.seed(42) - A = np.random.randn(3,3).astype(np.float64) - igl.polar_svd(A) - out = igl.polar_svd(A) - R,T,_,_,_ = igl.polar_svd(A) - R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) - -def test_remove(): - V,F = triangulated_square() - SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) - SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) - -def test_project(): - V,F,_ = single_tet() - model = np.eye(4).astype(np.float64) - proj = np.eye(4).astype(np.float64) - viewport = np.array([0,0,640,480],dtype=np.float64) - win = igl.project(V,model,proj,viewport) - scene = igl.unproject(win,model,proj,viewport) - - S = np.array([0,0,0],dtype=np.float64) - D = np.array([0.2,0.2,0.2],dtype=np.float64) - t,sqrD = igl.project_to_line(V,S,D) - t,sqrD = igl.project_to_line_segment(V,S,D) - -def test_boundary_loop(): - V,F,_ = single_tet() - S,_ = igl.upsample_matrix(F,n=V.shape[0]) - S,_ = igl.loop_matrix(F,n=V.shape[0]) - V,F = igl.upsample(V,F,number_of_subdivs=1) - V,F = igl.loop(V,F,number_of_subdivs=1) - # remove first and last - F = F[1:-1,:] - L_all = igl.boundary_loop_all(F) - L = igl.boundary_loop(F) - -def test_voxel(): - V,_,_ = single_tet() - GV,side = igl.voxel_grid(V,s=10) - GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) - - -def test_sample(): - V,F = igl.icosahedron() - B,I,X = igl.random_points_on_mesh(10,V,F) - point_indices, CH,CN,W = igl.octree(X) - I = igl.knn(X,X,1,point_indices,CH,CN,W) - B,FI,P = igl.blue_noise(V,F,0.5) - -def test_curvature(): - V,F = igl.icosahedron() - K = igl.gaussian_curvature(V,F) - PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) - PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) - -def test_geodesic(): - V,F = igl.icosahedron() - VS = np.array([0],dtype=np.int64) - VT = np.array([1],dtype=np.int64) - D = igl.exact_geodesic(V,F,VS=VS,VT=VT) - -def test_decimate(): - V,F = igl.icosahedron() - dV,dF,J,I = igl.decimate(V,F) - dV,dF,J,I = igl.qslim(V,F) - -def test_parameterization(): - V,Q,E = igl.quad_grid(3,3); - V,F = igl.triangulated_grid(3,3); - # slim needs 3D data even if the problem is 2D - V = np.c_[V, np.zeros(V.shape[0])] - V_init = V[:,:2] - V_init[:,0] = V_init[:,0] * 2 - b = np.array([0,1],dtype=np.int64) - bc = V[b,:2] - data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) - U = igl.slim_solve(data,iter_num=1) - - data = igl.ARAPData() - data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS - V = V[:,:2] - igl.arap_precomputation(V,F,V.shape[1],b,data) - U = igl.arap_solve(bc,data,U) - - U,Q = igl.lscm(V,F,b,bc) - - -def test_implicit(): - res = np.array([3,3,3],dtype=np.int64) - GV = igl.grid(res) - S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; - V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) - # unpack keys into (i,j,v) index triplets - EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) - - h = igl.avg_edge_length(V,F) - m0,m1,m2 = igl.moments(V,F) - D = V[:,0] - vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) - iV,iE,I = igl.isolines(V,F,S,vals) - iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) - - -def test_kelvinlets(): - V,F = igl.icosahedron() - x0 = V[0,:] - f = np.array([0,0,1],dtype=np.float64) - R = np.eye(3).astype(np.float64) - U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) - - SV = V[:,0] - SF = igl.average_onto_faces(F,SV) - SV = igl.average_onto_vertices(V,F,SF) - -def test_polygons(): - P = [[0,1,2],[2,1,3,4]] - I,C = igl.polygon_corners(P) - Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) - I,C = igl.polygon_corners(Q) - F,J = igl.polygons_to_triangles(I,C) - -def test_heat(): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) - V,F = igl.upsample(V,F,number_of_subdivs=1) - data = igl.HeatGeodesicsData() - igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) - data = igl.HeatGeodesicsData() - data.use_intrinsic_delaunay = True - igl.heat_geodesics_precompute(V,F,data) - gamma = np.array([0],dtype=np.int64) - D = igl.heat_geodesics_solve(data,gamma) - l = igl.edge_lengths(V,F) - iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) - L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) - L = igl.cotmatrix_intrinsic(il,iF) - M = igl.massmatrix_intrinsic(il,iF) - -def test_nonmanifold(): - V,F = igl.icosahedron() - FF,C = igl.bfs_orient(F) - SF,SVI = igl.split_nonmanifold(F) - SV,SF,SVI = igl.split_nonmanifold(V,F) - -def test_biharmonic(): - V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) - T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) - S = [[0],[5]] - W = igl.biharmonic_coordinates(V,T,S) - -def test_bijective(): - # 2D triangulated grid - V,F = igl.triangulated_grid(3,3) - ear,ear_opp = igl.ears(F) - # we don't have igl.flip_ears so construct it by hand - res = np.array([3,3],dtype=np.int64) - E,_,_ = igl.boundary_facets(F) - F = E - # add a 3rd column of 4s - F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) - # boundary b - b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) - bc = V[b,:] - bc[-1,:] = [0.5,0.5] - U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) - - -def test_copyleft(): - V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) - T = np.array([[0,1,2,3]],dtype=np.int64) - F,_,_ = igl.boundary_facets(T) - V,F = igl.loop(V,F) - - dV,dF,J = igl.copyleft.progressive_hulls(V,F) - - -def test_cgal(): - # tetrahedron - VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) - T = np.array([[0,1,2,3]],dtype=np.int64) - FA,_,_ = igl.boundary_facets(T) - # flip z - VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) - FB = FA[:,::-1] - IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) - IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) - VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") - H = igl.copyleft.cgal.convex_hull(VC) - # concatenate A and B meshes - VC = np.vstack((VA,VB)) - FC = np.vstack((FA,FB+VA.shape[0])) - VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) - _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) - - p = np.array([0,0,0],dtype=np.float64) - n = np.array([1,1,1],dtype=np.float64) - VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) - equ = np.hstack((n,-n.dot(p))) - VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) - - P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) - W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) - W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) - - VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) - - _,I,X = igl.random_points_on_mesh(1000,VC,FC) - N = igl.per_face_normals(VC,FC) - N = N[I,:] - point_indices, CH,CN,W = igl.octree(X) - I = igl.knn(X,X,20,point_indices,CH,CN,W) - A,T = igl.copyleft.cgal.point_areas(X,I,N) - -def test_embree(): - # octahedron - V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) - F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) - N = igl.per_vertex_normals(V,F) - ei = igl.embree.EmbreeIntersector(); - ei.init(V,F) - S = igl.embree.ambient_occlusion(V,F,V,N,100) - S = igl.embree.ambient_occlusion(ei,V,N,100) - N = -N - S = igl.embree.shape_diameter_function(V,F,V,N,100) - S = igl.embree.shape_diameter_function(ei,V,N,100) - origin = np.array([2,2,2],dtype=np.float64) - direction = np.array([-2,-2,-2],dtype=np.float64) - hit = ei.intersectRay_first(origin,direction) - hits = ei.intersectRay(origin,direction) - hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) - I,C = igl.embree.reorient_facets_raycast(V,F) - -def test_tetgen(): - # octahedron - V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) - F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) - V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") - -def test_triangle(): - V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) - E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) - V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") - V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") - V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) - scaf_data = igl.triangle.SCAFData() - b = np.array([0,1,2],dtype=np.int64) - bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) - V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) - soft_p = 0; - igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) - L,rhs = igl.triangle.scaf_system(scaf_data) - U = igl.triangle.scaf_solve(1,scaf_data) - -def test_misc(): - V,F = igl.icosahedron() - BV,BF = igl.bounding_box(V,pad=1.0) - R,C,B = igl.circumradius(V,F) - R = igl.inradius(V,F) - _,E,EMAP,_,_ = igl.unique_edge_map(F) - L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) - M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) - cuts = igl.cut_to_disk(F) - V,F = igl.cylinder(10,10) - VD,FD = igl.false_barycentric_subdivision(V,F) - V,F,T = single_tet() - theta, cos_theta = igl.dihedral_angles(V,T) - L = igl.edge_lengths(V,T) - A = igl.face_areas(V,T) - theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) - - +#def triangulated_square(): +# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) +# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) +# return V,F +# +#def single_tet(): +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# return V,F,T +# +#def test_edges(): +# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) +# E,oE = igl.orient_halfedges(F) +# ne = E.max()+1 +# np.random.seed(42) +# uE = np.random.rand(ne).astype(np.float64) +# +# uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) +# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) +# +#def test_read_write(): +# V,F = triangulated_square() +# igl.write_triangle_mesh("out.obj",V,F) +# igl.write_triangle_mesh("out.off",V,F) +# igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) +# igl.writePLY("out.ply",V,F) +# igl.writeOBJ("out.obj",V,F) +# igl.writeOBJ("out.obj",V,F,V,F,V,F) +# V,F,_ = igl.readOFF("out.off") +# V,_,_,F,_,_ = igl.readOBJ("out.obj") +# V,F = igl.read_triangle_mesh("out.ply") +# igl.writeDMAT("out.dmat",V) +# igl.writeDMAT("out.dmat",V,ascii=True) +# V = igl.readDMAT("out.dmat") +# igl.writeDMAT("out.dmat",V,ascii=False) +# V = igl.readDMAT("out.dmat") +# +#def test_operators(): +# V,F = triangulated_square() +# L = igl.cotmatrix(V,F) +# I = np.array([0,1,2,3],dtype=np.int64) +# C = np.array([0,4],dtype=np.int64) +# L,M,P = igl.cotmatrix(V,I,C) +# L = igl.squared_edge_lengths(V,F) +# K = igl.cotmatrix_entries(V,F) +# l = igl.edge_lengths(V,F) +# K = igl.cotmatrix_entries(l=l) +# B1,B2,B3 = igl.local_basis(V,F) +# G = igl.grad(V,F) +# +# M = igl.massmatrix(V,F) +# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) +# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) +# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) +# +# A = igl.facet_adjacency_matrix(F) +# A = igl.adjacency_matrix(F) +# A = igl.adjacency_matrix(I,C) +# E = igl.edges(F) +# E = igl.edges(I,C) +# E = igl.edges(A) +# n,C,K = igl.connected_components(A) +# C = igl.vertex_components(F) +# nc,C = igl.facet_components(F) +# +#def test_normals_and_distances(): +# V,F = triangulated_square() +# I = np.array([0,1,2,3],dtype=np.int64) +# C = np.array([0,4],dtype=np.int64) +# FN,_,_,_ = igl.per_face_normals(V,I,C) +# FN = igl.per_face_normals(V,F) +# FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) +# VN = igl.per_vertex_normals(V,F) +# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) +# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) +# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) +# VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) +# dblA = igl.doublearea(V,F) +# l = igl.edge_lengths(V,F) +# dblA = igl.doublearea(l=l) +# dblA = igl.doublearea(l=l,nan_replacement=0.0) +# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) +# sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) +# W = igl.winding_number(V,F,P) +# W = igl.winding_number(V,F,P[1,:]) +# W = igl.fast_winding_number(V,F,P) +# S,I,C,N = igl.signed_distance(P,V,F) +# S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) +# BC = igl.barycenter(V,F) +# +#def test_harmonic(): +# V,F = triangulated_square() +# b = np.array([0,3],dtype=np.int64) +# bc = np.array([[1,0],[0,1]],dtype=np.float64) +# W = igl.bbw(V,F,b,bc) +# W = igl.harmonic(V,F,b,bc,k=1) +# W = igl.harmonic(V,F,b,bc,k=2) +# +#def test_tets(): +# V,F,T = single_tet() +# F,J,K = igl.boundary_facets(T) +# igl.writeMESH("out.mesh",V,T) +# igl.writeMESH("out.mesh",V,T,F=F) +# V,T,F = igl.readMESH("out.mesh") +# igl.writeMSH("out.msh",V,F,T) +# V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") +# +#def test_bvh(): +# V,F,T = single_tet() +# tree = igl.AABB() +# tree.init(V,T) +# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) +# # first row of P +# q = P[0,:] +# I = tree.find(V,T,q) +# i = tree.find(V,T,q,first=True) +# +# Q = igl.barycenter(V,T) +# I = igl.in_element(V,T,Q,tree) +# +# tree = igl.AABB() +# tree.init(V,F) +# sqrD,_,_ = tree.squared_distance(V,F,P) +# +# O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); +# D = V.mean(axis=0)-O +# I,T,UV = tree.intersect_ray_first(V,F,O,D) +# hits = tree.intersect_ray(V,F,O,D) +# o = O[0,:] +# d = D[0,:] +# hit = igl.ray_mesh_intersect(o,d,V,F,first=True) +# hits = igl.ray_mesh_intersect(o,d,V,F) +# +# +#def test_upsample(): +# V,F,T = single_tet() +# # upsample so there's something to collapse +# V,F = igl.upsample(V,F,number_of_subdivs=1) +# E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) +# E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) +# EF,EI = igl.edge_flaps(F,uE,EMAP) +# uE,EMAP,EF,EI = igl.edge_flaps(F) +# Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) +# +# e = 0 +# p = V[uE[e,0],:] +# e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) +# +#def test_cut(): +# V,F,T = single_tet() +# C = np.ones(F.shape,dtype=bool) +# Vn,Fn,I = igl.cut_mesh(V,F,C) +# +#def test_adjacency(): +# V,F,T = single_tet() +# TT,TTi = igl.triangle_triangle_adjacency(F) +# TT,TTi = igl.triangle_triangle_adjacency_lists(F) +# +# VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) +# VF,VFi = igl.vertex_triangle_adjacency(F) +# +# F012 = F; +# F120 = np.roll(F012,1,axis=1) +# FF = np.vstack((F012,F120)) +# F,IA,IC = igl.unique_simplices(FF) +# A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) +# B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) +# IA,LOCB = igl.ismember_rows(A,B) +# A = igl.adjacency_list(F) +# A = igl.adjacency_list(F,sorted=True) +# +#def test_min_quad(): +# V,F,T = single_tet() +# A = -igl.cotmatrix(V,F) +# B = np.zeros((V.shape[0],1),dtype=np.float64) +# known = np.array([1,2],dtype=np.int64) +# Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) +# Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) +# Beq = np.zeros((1,1),dtype=np.float64) +# Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) +# data = igl.min_quad_with_fixed_data() +# igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) +# Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) +# +#def test_volume(): +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# L = igl.edge_lengths(V,T) +# vol = igl.volume(V,T) +# vol = igl.volume(L=L) +# vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) +# F,_,_ = igl.boundary_facets(T) +# # remove last face +# F = F[:-1,:] +# B = igl.is_border_vertex(F) +# I,C = igl.on_boundary(F) +# I,C = igl.on_boundary(T) +# F = np.array([[2,1,3]],dtype=np.int64) +# NV,NF,I,J = igl.remove_unreferenced(V,F) +# +#def test_oriented_facets(): +# V,F,T = single_tet() +# E = igl.oriented_facets(F) +# F = igl.oriented_facets(T) +# +# res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) +# res,_,_,_,_ = igl.is_edge_manifold(F) +# +#def test_matlab_format(): +# V,F = triangulated_square() +# L = igl.cotmatrix(V,F) +# s = igl.matlab_format(V,"V") +# s = igl.matlab_format_index(F,"F") +# s = igl.matlab_format(L,"L") +# +#def test_polar(): +# # random randn 3x3 matrix +# np.random.seed(42) +# A = np.random.randn(3,3).astype(np.float64) +# igl.polar_svd(A) +# out = igl.polar_svd(A) +# R,T,_,_,_ = igl.polar_svd(A) +# R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) +# +#def test_remove(): +# V,F = triangulated_square() +# SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) +# SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) +# +#def test_project(): +# V,F,_ = single_tet() +# model = np.eye(4).astype(np.float64) +# proj = np.eye(4).astype(np.float64) +# viewport = np.array([0,0,640,480],dtype=np.float64) +# win = igl.project(V,model,proj,viewport) +# scene = igl.unproject(win,model,proj,viewport) +# +# S = np.array([0,0,0],dtype=np.float64) +# D = np.array([0.2,0.2,0.2],dtype=np.float64) +# t,sqrD = igl.project_to_line(V,S,D) +# t,sqrD = igl.project_to_line_segment(V,S,D) +# +#def test_boundary_loop(): +# V,F,_ = single_tet() +# S,_ = igl.upsample_matrix(F,n=V.shape[0]) +# S,_ = igl.loop_matrix(F,n=V.shape[0]) +# V,F = igl.upsample(V,F,number_of_subdivs=1) +# V,F = igl.loop(V,F,number_of_subdivs=1) +# # remove first and last +# F = F[1:-1,:] +# L_all = igl.boundary_loop_all(F) +# L = igl.boundary_loop(F) +# +#def test_voxel(): +# V,_,_ = single_tet() +# GV,side = igl.voxel_grid(V,s=10) +# GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) +# +# +#def test_sample(): +# V,F = igl.icosahedron() +# B,I,X = igl.random_points_on_mesh(10,V,F) +# point_indices, CH,CN,W = igl.octree(X) +# I = igl.knn(X,X,1,point_indices,CH,CN,W) +# B,FI,P = igl.blue_noise(V,F,0.5) +# +#def test_curvature(): +# V,F = igl.icosahedron() +# K = igl.gaussian_curvature(V,F) +# PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) +# PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) +# +#def test_geodesic(): +# V,F = igl.icosahedron() +# VS = np.array([0],dtype=np.int64) +# VT = np.array([1],dtype=np.int64) +# D = igl.exact_geodesic(V,F,VS=VS,VT=VT) +# +#def test_decimate(): +# V,F = igl.icosahedron() +# dV,dF,J,I = igl.decimate(V,F) +# dV,dF,J,I = igl.qslim(V,F) +# +#def test_parameterization(): +# V,Q,E = igl.quad_grid(3,3); +# V,F = igl.triangulated_grid(3,3); +# # slim needs 3D data even if the problem is 2D +# V = np.c_[V, np.zeros(V.shape[0])] +# V_init = V[:,:2] +# V_init[:,0] = V_init[:,0] * 2 +# b = np.array([0,1],dtype=np.int64) +# bc = V[b,:2] +# data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) +# U = igl.slim_solve(data,iter_num=1) +# +# data = igl.ARAPData() +# data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS +# V = V[:,:2] +# igl.arap_precomputation(V,F,V.shape[1],b,data) +# U = igl.arap_solve(bc,data,U) +# +# U,Q = igl.lscm(V,F,b,bc) +# +# +#def test_implicit(): +# res = np.array([3,3,3],dtype=np.int64) +# GV = igl.grid(res) +# S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; +# V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) +# # unpack keys into (i,j,v) index triplets +# EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) +# +# h = igl.avg_edge_length(V,F) +# m0,m1,m2 = igl.moments(V,F) +# D = V[:,0] +# vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) +# iV,iE,I = igl.isolines(V,F,S,vals) +# iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) +# +# +#def test_kelvinlets(): +# V,F = igl.icosahedron() +# x0 = V[0,:] +# f = np.array([0,0,1],dtype=np.float64) +# R = np.eye(3).astype(np.float64) +# U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) +# +# SV = V[:,0] +# SF = igl.average_onto_faces(F,SV) +# SV = igl.average_onto_vertices(V,F,SF) +# +#def test_polygons(): +# P = [[0,1,2],[2,1,3,4]] +# I,C = igl.polygon_corners(P) +# Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) +# I,C = igl.polygon_corners(Q) +# F,J = igl.polygons_to_triangles(I,C) +# +#def test_heat(): +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +# V,F = igl.upsample(V,F,number_of_subdivs=1) +# data = igl.HeatGeodesicsData() +# igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) +# data = igl.HeatGeodesicsData() +# data.use_intrinsic_delaunay = True +# igl.heat_geodesics_precompute(V,F,data) +# gamma = np.array([0],dtype=np.int64) +# D = igl.heat_geodesics_solve(data,gamma) +# l = igl.edge_lengths(V,F) +# iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) +# L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) +# L = igl.cotmatrix_intrinsic(il,iF) +# M = igl.massmatrix_intrinsic(il,iF) +# +#def test_nonmanifold(): +# V,F = igl.icosahedron() +# FF,C = igl.bfs_orient(F) +# SF,SVI = igl.split_nonmanifold(F) +# SV,SF,SVI = igl.split_nonmanifold(V,F) +# +#def test_biharmonic(): +# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) +# T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) +# S = [[0],[5]] +# W = igl.biharmonic_coordinates(V,T,S) +# +#def test_bijective(): +# # 2D triangulated grid +# V,F = igl.triangulated_grid(3,3) +# ear,ear_opp = igl.ears(F) +# # we don't have igl.flip_ears so construct it by hand +# res = np.array([3,3],dtype=np.int64) +# E,_,_ = igl.boundary_facets(F) +# F = E +# # add a 3rd column of 4s +# F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) +# # boundary b +# b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) +# bc = V[b,:] +# bc[-1,:] = [0.5,0.5] +# U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) +# +# +#def test_copyleft(): +# V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# F,_,_ = igl.boundary_facets(T) +# V,F = igl.loop(V,F) +# +# dV,dF,J = igl.copyleft.progressive_hulls(V,F) +# +# +#def test_cgal(): +# # tetrahedron +# VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# FA,_,_ = igl.boundary_facets(T) +# # flip z +# VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) +# FB = FA[:,::-1] +# IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) +# IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) +# VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") +# H = igl.copyleft.cgal.convex_hull(VC) +# # concatenate A and B meshes +# VC = np.vstack((VA,VB)) +# FC = np.vstack((FA,FB+VA.shape[0])) +# VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) +# _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) +# +# p = np.array([0,0,0],dtype=np.float64) +# n = np.array([1,1,1],dtype=np.float64) +# VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) +# equ = np.hstack((n,-n.dot(p))) +# VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) +# +# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) +# W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) +# W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) +# +# VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) +# +# _,I,X = igl.random_points_on_mesh(1000,VC,FC) +# N = igl.per_face_normals(VC,FC) +# N = N[I,:] +# point_indices, CH,CN,W = igl.octree(X) +# I = igl.knn(X,X,20,point_indices,CH,CN,W) +# A,T = igl.copyleft.cgal.point_areas(X,I,N) +# +#def test_embree(): +# # octahedron +# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) +# F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) +# N = igl.per_vertex_normals(V,F) +# ei = igl.embree.EmbreeIntersector(); +# ei.init(V,F) +# S = igl.embree.ambient_occlusion(V,F,V,N,100) +# S = igl.embree.ambient_occlusion(ei,V,N,100) +# N = -N +# S = igl.embree.shape_diameter_function(V,F,V,N,100) +# S = igl.embree.shape_diameter_function(ei,V,N,100) +# origin = np.array([2,2,2],dtype=np.float64) +# direction = np.array([-2,-2,-2],dtype=np.float64) +# hit = ei.intersectRay_first(origin,direction) +# hits = ei.intersectRay(origin,direction) +# hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) +# I,C = igl.embree.reorient_facets_raycast(V,F) +# +#def test_tetgen(): +# # octahedron +# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) +# F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) +# V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") +# +#def test_triangle(): +# V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) +# E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) +# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") +# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") +# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +# scaf_data = igl.triangle.SCAFData() +# b = np.array([0,1,2],dtype=np.int64) +# bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) +# V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) +# soft_p = 0; +# igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) +# L,rhs = igl.triangle.scaf_system(scaf_data) +# U = igl.triangle.scaf_solve(1,scaf_data) +# +#def test_misc(): +# V,F = igl.icosahedron() +# BV,BF = igl.bounding_box(V,pad=1.0) +# R,C,B = igl.circumradius(V,F) +# R = igl.inradius(V,F) +# _,E,EMAP,_,_ = igl.unique_edge_map(F) +# L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) +# M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) +# cuts = igl.cut_to_disk(F) +# V,F = igl.cylinder(10,10) +# VD,FD = igl.false_barycentric_subdivision(V,F) +# V,F,T = single_tet() +# theta, cos_theta = igl.dihedral_angles(V,T) +# L = igl.edge_lengths(V,T) +# A = igl.face_areas(V,T) +# theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) +# +# From 5610bab84832be41c1e944f4a08db1b7f62faca7 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:32:18 -0500 Subject: [PATCH 037/102] tests --- .github/workflows/pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 45a8e978..0e085f9a 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -35,4 +35,4 @@ jobs: pip install --verbose . - name: Test - run: python -m pytest + run: python -m pytest tests/test.py From 62f9705cc9a69b8b9ae6a81089d5523c2098292c Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:33:51 -0500 Subject: [PATCH 038/102] naming convention --- .github/workflows/pip.yml | 2 +- tests/{test.py => test_all.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{test.py => test_all.py} (100%) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 0e085f9a..45a8e978 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -35,4 +35,4 @@ jobs: pip install --verbose . - name: Test - run: python -m pytest tests/test.py + run: python -m pytest diff --git a/tests/test.py b/tests/test_all.py similarity index 100% rename from tests/test.py rename to tests/test_all.py From bec7428ff3937b7ca1d6c6b5d0aae0dccb957cbe Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:41:14 -0500 Subject: [PATCH 039/102] try to use dependencies --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c87ea148..dc710394 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ requires = [ ] build-backend = "scikit_build_core.build" + [project] name = "libigl" version = "2.5.4dev" @@ -22,6 +23,11 @@ authors = [ classifiers = [ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)" ] +dependencies = [ + "numpy>=2.0.0", + "scipy", + "pytest" +] [project.urls] Homepage = "https://github.com/libigl/libigl-python-bindings" From f1d2b27d8680aae445d6d1f721460b3d06c17246 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:42:47 -0500 Subject: [PATCH 040/102] test nothing --- tests/test_all.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_all.py b/tests/test_all.py index eceffccd..b6d221d4 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -10,6 +10,10 @@ #import igl.copyleft.tetgen #import igl.embree #import igl.triangle + +def test_nothing(): + pass + # ###def rand_sparse(n,density): ### n_features = n From 0114f5c34bec9e965e3d5699538b86eb5d916295 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:43:23 -0500 Subject: [PATCH 041/102] 3.10 --- .github/workflows/pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 45a8e978..05bb3141 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: platform: [windows-latest, macos-latest, ubuntu-latest] - python-version: ["3.8", "3.12"] + python-version: ["3.10"] steps: - uses: actions/checkout@v4 From e8af26bc41f91c7460a41d3078b638b7c5f832f2 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:51:13 -0500 Subject: [PATCH 042/102] simplify --- .github/workflows/wheels.yml | 66 +++++++----------------------------- 1 file changed, 12 insertions(+), 54 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8fd540a7..ad1e8e06 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -9,71 +9,29 @@ on: - main jobs: - build_sdist: - name: Build SDist - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build SDist - run: pipx run build --sdist - - - name: Check metadata - run: pipx run twine check dist/* - - - uses: actions/upload-artifact@v4 - with: - name: artifact-sdist - path: dist/*.tar.gz - build_wheels: - name: Build wheels ${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }} - runs-on: ${{ matrix.os.runs-on }} - - env: - CIBW_BUILD: "${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }}" - CIBW_ENVIRONMENT: "MAX_JOBS=${{ matrix.os.runs-on == 'macos-latest' && 3 || 2 }}" - # Why universal2 here? It's not included above in CIBW_BUILD - CIBW_ARCHS_MACOS: "x86_64 arm64 universal2" - CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=10.13 CMAKE_OSX_ARCHITECTURES=\"${{ matrix.os.cibw-arch == 'macosx_x86_64' && 'x86_64' || matrix.os.cibw-arch == 'macosx_arm64' && 'arm64' || matrix.os.cibw-arch == 'macosx_universal2' && 'arm64;x86_64' || '' }}\"" - + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: - fail-fast: false matrix: - # Launch separate job for each python. The build is so much longer than - # machine configuration/setup, so parallel builds will be faster. More - # importantly, github times out after 6 hours _per job_. - cpversion: - - cp310 - os: - - runs-on: macos-latest - cibw-arch: macosx_x86_64 - - runs-on: macos-latest - cibw-arch: macosx_arm64 + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-latest] steps: - uses: actions/checkout@v4 - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" + # Used to host cibuildwheel + - uses: actions/setup-python@v5 - name: Install cibuildwheel - run: | - python -m pip install --upgrade pip - python -m pip install cibuildwheel + run: python -m pip install cibuildwheel==2.22.0 - name: Build wheels - shell: bash - run: | - python -m cibuildwheel --output-dir wheelhouse + run: python -m cibuildwheel --output-dir wheelhouse + # to supply options, put them in 'env', like: + # env: + # CIBW_SOME_OPTION: value - # Upload binaries to github - uses: actions/upload-artifact@v4 with: - name: artifact-wheel-${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }} - path: |- - ./wheelhouse/*.whl - ./wheelhouse/*.tar.gz - + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl From 9fd850416e421b76cc13840b375bfbe358ef2427 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:54:27 -0500 Subject: [PATCH 043/102] 3.10 --- .github/workflows/wheels.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index ad1e8e06..2586513d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -19,8 +19,10 @@ jobs: steps: - uses: actions/checkout@v4 - # Used to host cibuildwheel + # Set up Python 3.10 - uses: actions/setup-python@v5 + with: + python-version: '3.10' - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 From 0f1657344b557c75928b0f798ca52a67c543a8e5 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 21:56:30 -0500 Subject: [PATCH 044/102] 3.10 --- .github/workflows/wheels.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2586513d..b60a1dd5 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,4 +1,4 @@ -name: Pip build +name: Wheels on: push: @@ -28,10 +28,9 @@ jobs: run: python -m pip install cibuildwheel==2.22.0 - name: Build wheels + env: + CIBW_BUILD: "cp310-*" run: python -m cibuildwheel --output-dir wheelhouse - # to supply options, put them in 'env', like: - # env: - # CIBW_SOME_OPTION: value - uses: actions/upload-artifact@v4 with: From d357f6dc76fcf2ce30dca4a9432332f22b12b9a5 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 22:01:06 -0500 Subject: [PATCH 045/102] 14 --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index b60a1dd5..cc9a17a4 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-latest] + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] steps: - uses: actions/checkout@v4 From efceaa9dbedea3cdab7724876d065d478f6e609b Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 22:04:42 -0500 Subject: [PATCH 046/102] try to get the damn file --- .github/workflows/wheels.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index cc9a17a4..ce65add5 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -32,7 +32,14 @@ jobs: CIBW_BUILD: "cp310-*" run: python -m cibuildwheel --output-dir wheelhouse - - uses: actions/upload-artifact@v4 + - name: Repair wheel + run: | + delocate-wheel --require-archs arm64 -w repaired_wheel wheelhouse/*.whl + continue-on-error: true # Allow the job to proceed even if this fails + + - name: Upload wheel artifact + if: always() # Ensure this step always runs + uses: actions/upload-artifact@v3 with: - name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} - path: ./wheelhouse/*.whl + name: built-wheels + path: wheelhouse/ From 149bc9b9f2e375603b592e359fb15cb4a866fc38 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 22:16:15 -0500 Subject: [PATCH 047/102] turn off repair --- .github/workflows/wheels.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index ce65add5..ec755fe7 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -30,6 +30,7 @@ jobs: - name: Build wheels env: CIBW_BUILD: "cp310-*" + CIBW_REPAIR_WHEEL_COMMAND: "" run: python -m cibuildwheel --output-dir wheelhouse - name: Repair wheel From 3668330e314b2b48eba2e4f709a75c2780048b50 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 22:42:47 -0500 Subject: [PATCH 048/102] install --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f20ee4e..12e29e57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,10 @@ function(pyigl_include prefix name) set(target_name "pyigl${prefix_lc}_${name}") nanobind_add_module(${target_name} ${sources}) + # important for scikit-build + install(TARGETS ${target_name} LIBRARY DESTINATION "igl/${subpath}") + + if("${name}" STREQUAL "core") target_link_libraries(${target_name} PRIVATE igl::core) else() @@ -174,3 +178,4 @@ if(LIBIGL_RESTRICTED_TRIANGLE) endif() + From 4ea33e9a3e6316e5b64a1700091d7592e1225fc9 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 22:46:54 -0500 Subject: [PATCH 049/102] restore --- .github/workflows/wheels.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index ec755fe7..cc9a17a4 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -30,17 +30,9 @@ jobs: - name: Build wheels env: CIBW_BUILD: "cp310-*" - CIBW_REPAIR_WHEEL_COMMAND: "" run: python -m cibuildwheel --output-dir wheelhouse - - name: Repair wheel - run: | - delocate-wheel --require-archs arm64 -w repaired_wheel wheelhouse/*.whl - continue-on-error: true # Allow the job to proceed even if this fails - - - name: Upload wheel artifact - if: always() # Ensure this step always runs - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: - name: built-wheels - path: wheelhouse/ + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl From ff11f32430bb76f6c7fc40959b7d8b3709520c8b Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 22:50:31 -0500 Subject: [PATCH 050/102] deploy --- .github/workflows/wheels.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index cc9a17a4..50b8b93c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -2,7 +2,7 @@ name: Wheels on: push: - branches: + branches: - main pull_request: branches: @@ -24,6 +24,13 @@ jobs: with: python-version: '3.10' + # Set the MACOSX_DEPLOYMENT_TARGET for macOS builds + - name: Set MACOSX_DEPLOYMENT_TARGET for macOS + if: runner.os == 'macOS' + run: | + echo "Setting MACOSX_DEPLOYMENT_TARGET to 10.15" + echo "MACOSX_DEPLOYMENT_TARGET=10.15" >> $GITHUB_ENV + - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 From 3a2c24a4e856844c81118b90496ffacadf68f00b Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 22:56:50 -0500 Subject: [PATCH 051/102] 15 --- .github/workflows/wheels.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 50b8b93c..9135c7f5 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -37,6 +37,7 @@ jobs: - name: Build wheels env: CIBW_BUILD: "cp310-*" + MACOSX_DEPLOYMENT_TARGET: 10.15 run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From 353b5d7436a23ae03c76aa9718ea2b593cee934a Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 22:57:46 -0500 Subject: [PATCH 052/102] 15 --- .github/workflows/wheels.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 9135c7f5..8eda45a4 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -24,20 +24,13 @@ jobs: with: python-version: '3.10' - # Set the MACOSX_DEPLOYMENT_TARGET for macOS builds - - name: Set MACOSX_DEPLOYMENT_TARGET for macOS - if: runner.os == 'macOS' - run: | - echo "Setting MACOSX_DEPLOYMENT_TARGET to 10.15" - echo "MACOSX_DEPLOYMENT_TARGET=10.15" >> $GITHUB_ENV - - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 - name: Build wheels env: CIBW_BUILD: "cp310-*" - MACOSX_DEPLOYMENT_TARGET: 10.15 + MACOSX_DEPLOYMENT_TARGET: "10.15" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From 1513cb9dd1b6ae93267637b989f5958a3121a547 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 23:01:32 -0500 Subject: [PATCH 053/102] 10.15 --- .github/workflows/wheels.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8eda45a4..64124b2c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -27,6 +27,11 @@ jobs: - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 + - name: Configure cibuildwheel + shell: bash + run: | + echo "MACOSX_DEPLOYMENT_TARGET=10.15" >> $GITHUB_ENV + - name: Build wheels env: CIBW_BUILD: "cp310-*" From e905830d6537d913a76a80738f6240d866be1506 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 23:02:51 -0500 Subject: [PATCH 054/102] 15 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dc710394..fcf0cd23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,4 +55,4 @@ test-skip="cp38-macosx_*:arm64" # Needed for full C++17 support [tool.cibuildwheel.macos.environment] -MACOSX_DEPLOYMENT_TARGET = "10.14" +MACOSX_DEPLOYMENT_TARGET = "10.15" From 7e0c3f61eebb3e9929d799e0d2183b52414cca8d Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 23:03:48 -0500 Subject: [PATCH 055/102] cleanup --- .github/workflows/wheels.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 64124b2c..7046dd79 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -27,15 +27,9 @@ jobs: - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 - - name: Configure cibuildwheel - shell: bash - run: | - echo "MACOSX_DEPLOYMENT_TARGET=10.15" >> $GITHUB_ENV - - name: Build wheels env: CIBW_BUILD: "cp310-*" - MACOSX_DEPLOYMENT_TARGET: "10.15" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From dd54eff5b2c57c13c3235c61461fa783a72b79fa Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 23:06:47 -0500 Subject: [PATCH 056/102] newer pythong --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 7046dd79..2e06e37c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -22,7 +22,7 @@ jobs: # Set up Python 3.10 - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 From 79a4e66fa468e29461975d22137fd3c1b5f6c5c9 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 23:10:06 -0500 Subject: [PATCH 057/102] all 12 --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2e06e37c..432bd55c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -29,7 +29,7 @@ jobs: - name: Build wheels env: - CIBW_BUILD: "cp310-*" + CIBW_BUILD: "cp312-*" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From 5236a7edaa45a77a00b6f2f1cfa73636e65923a3 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 23:11:39 -0500 Subject: [PATCH 058/102] numpy dep --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fcf0cd23..33d23ebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)" ] dependencies = [ - "numpy>=2.0.0", + "numpy", "scipy", "pytest" ] From fe40913bb6be4143fe2ce823d05c117794eb713f Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 22 Jan 2025 23:12:57 -0500 Subject: [PATCH 059/102] 11 --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 432bd55c..6f494a60 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -29,7 +29,7 @@ jobs: - name: Build wheels env: - CIBW_BUILD: "cp312-*" + CIBW_BUILD: "cp311-*" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From ea21f33213a0ad9b36351cbfb4622742c31998e9 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Thu, 23 Jan 2025 13:28:28 -0500 Subject: [PATCH 060/102] preinstall numpy --- .github/workflows/wheels.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6f494a60..e1a948fa 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -27,6 +27,9 @@ jobs: - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 + - name: Preinstall numpy + run: python -m pip install numpy --only-binary :all: + - name: Build wheels env: CIBW_BUILD: "cp311-*" From ae8b2a645e7a2746c2a5cbeb52f3fadc51e95d8c Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Thu, 23 Jan 2025 13:30:13 -0500 Subject: [PATCH 061/102] preinstall numpy --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e1a948fa..2a4adf19 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -28,7 +28,7 @@ jobs: run: python -m pip install cibuildwheel==2.22.0 - name: Preinstall numpy - run: python -m pip install numpy --only-binary :all: + run: python -m pip install numpy --only-binary ":all:" - name: Build wheels env: From 38c775c36bd6bc0335d68dab26e97dbde60f2bd8 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Thu, 23 Jan 2025 13:35:20 -0500 Subject: [PATCH 062/102] use matrix --- .github/workflows/wheels.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2a4adf19..1e629d1e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -15,6 +15,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] + cpversion: + - cp311 steps: - uses: actions/checkout@v4 @@ -27,12 +29,7 @@ jobs: - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 - - name: Preinstall numpy - run: python -m pip install numpy --only-binary ":all:" - - name: Build wheels - env: - CIBW_BUILD: "cp311-*" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From b1e44993b22375724e76f2edaabd934e34fb2f81 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:05:48 -0500 Subject: [PATCH 063/102] name --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1e629d1e..2b3c9993 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -10,7 +10,7 @@ on: jobs: build_wheels: - name: Build wheels on ${{ matrix.os }} + name: Build ${{ cpversion}} wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: From d18a2d48503bdc97080374c422084e66a7c93a63 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:06:18 -0500 Subject: [PATCH 064/102] name --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2b3c9993..687c3cb6 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -10,7 +10,7 @@ on: jobs: build_wheels: - name: Build ${{ cpversion}} wheels on ${{ matrix.os }} + name: Build ${{ matrix.cpversion }} wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: From 8b0bd7ec0c579404c3a8a3ace2e12cef867b3605 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:06:52 -0500 Subject: [PATCH 065/102] disable? --- .github/workflows/pip.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 05bb3141..8ec06d53 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -1,11 +1,11 @@ name: Pip -on: - workflow_dispatch: - pull_request: - push: - branches: - - master +#on: +# workflow_dispatch: +# pull_request: +# push: +# branches: +# - master jobs: build: From 346baef99b5ed8cc28aabb7bde0c33d3d37327d9 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:10:46 -0500 Subject: [PATCH 066/102] cpversion --- .github/workflows/wheels.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 687c3cb6..2157ac1d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -10,7 +10,7 @@ on: jobs: build_wheels: - name: Build ${{ matrix.cpversion }} wheels on ${{ matrix.os }} + name: Build ${{ matrix.cpversionj}} wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: @@ -30,6 +30,9 @@ jobs: run: python -m pip install cibuildwheel==2.22.0 - name: Build wheels + # why do I need to specify this cpversion here? + env: + CIBW_BUILD: "${{ matrix.cpversion }}-*" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From 3164a984733160df594ae608ae3488cf38324e1f Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:11:16 -0500 Subject: [PATCH 067/102] disable --- .github/workflows/pip.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 8ec06d53..8b69b272 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -1,11 +1,11 @@ name: Pip -#on: -# workflow_dispatch: -# pull_request: -# push: -# branches: -# - master +on: + #workflow_dispatch: + #pull_request: + #push: + # branches: + # - master jobs: build: From ff06871f8ae9d89bafcea38513bd6ca11acb99cf Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:11:38 -0500 Subject: [PATCH 068/102] disable --- .github/workflows/pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 8b69b272..8ad0cc19 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -1,7 +1,7 @@ name: Pip on: - #workflow_dispatch: + workflow_dispatch: #pull_request: #push: # branches: From fbfdcaf899caccb1fa62c97357ffdbe9f4b4c6d5 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:16:17 -0500 Subject: [PATCH 069/102] auto64 --- .github/workflows/wheels.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2157ac1d..43272f1b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -32,6 +32,7 @@ jobs: - name: Build wheels # why do I need to specify this cpversion here? env: + CIBW_ARCHS: auto64 CIBW_BUILD: "${{ matrix.cpversion }}-*" run: python -m cibuildwheel --output-dir wheelhouse From 0eba13807ac7927dbc18081aa67598b67b6ed8bb Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:25:32 -0500 Subject: [PATCH 070/102] skip 32 --- .github/workflows/wheels.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 43272f1b..1856653e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -32,8 +32,10 @@ jobs: - name: Build wheels # why do I need to specify this cpversion here? env: - CIBW_ARCHS: auto64 + CIBW_ARCHS: "auto64" CIBW_BUILD: "${{ matrix.cpversion }}-*" + # why isn't auto64 working? + CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux_i686 cp*-win32" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From 71fb1129cdf45fadef0d1ec5269d014f1da8b5f8 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:29:03 -0500 Subject: [PATCH 071/102] skip all musllinux --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1856653e..078f68e5 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -35,7 +35,7 @@ jobs: CIBW_ARCHS: "auto64" CIBW_BUILD: "${{ matrix.cpversion }}-*" # why isn't auto64 working? - CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux_i686 cp*-win32" + CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux* cp*-win32" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 From 056699550f7878264f9a4436866366e1927fd0c4 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:41:59 -0500 Subject: [PATCH 072/102] rm pytest from deps --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 33d23ebc..8a77bb4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,7 @@ classifiers = [ ] dependencies = [ "numpy", - "scipy", - "pytest" + "scipy" ] [project.urls] From 57b91901ab5019a3b1a6bcbb8d724ace05618455 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:44:14 -0500 Subject: [PATCH 073/102] test AABB --- tests/test_all.py | 119 +++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/tests/test_all.py b/tests/test_all.py index b6d221d4..1025eb54 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -14,64 +14,64 @@ def test_nothing(): pass -# -###def rand_sparse(n,density): -### n_features = n -### n_samples = n -### rng1 = np.random.RandomState(42) -### rng2 = np.random.RandomState(43) -### -### nnz = int(n_samples*n_features*density) -### -### row = rng1.randint(n_samples, size=nnz) -### cols = rng2.randint(n_features, size=nnz) -### data = rng1.rand(nnz) -### -### S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) -### return S.tocsc() -### -###def time_noop(): -### def helper(N,I,SN,SI): -### igl.noop(SN=SN) -### # start timer -### runs = 100 -### start = time.time() -### for i in range(runs): -### igl.noop(SN=SN) -### # end timer -### end = time.time() -### return (end - start)/runs -### n = 10000 -### m = 10 -### N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) -### I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) -### # random sparse matrix -### SN64 = rand_sparse(n,1.0/(n)) -### # print number of nonzeros -### SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) -### print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") -### -###time_noop() + +##def rand_sparse(n,density): +## n_features = n +## n_samples = n +## rng1 = np.random.RandomState(42) +## rng2 = np.random.RandomState(43) ## -### print(igl.matlab_format(V,"V")) -### print(igl.matlab_format_index(F,"F")) -### print(igl.matlab_format(dV,"dV")) -### print(igl.matlab_format_index(dF,"dF")) -# -# -## seed numpy's random number generator +## nnz = int(n_samples*n_features*density) ## -#def triangulated_square(): -# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) -# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) -# return V,F +## row = rng1.randint(n_samples, size=nnz) +## cols = rng2.randint(n_features, size=nnz) +## data = rng1.rand(nnz) +## +## S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) +## return S.tocsc() +## +##def time_noop(): +## def helper(N,I,SN,SI): +## igl.noop(SN=SN) +## # start timer +## runs = 100 +## start = time.time() +## for i in range(runs): +## igl.noop(SN=SN) +## # end timer +## end = time.time() +## return (end - start)/runs +## n = 10000 +## m = 10 +## N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) +## I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) +## # random sparse matrix +## SN64 = rand_sparse(n,1.0/(n)) +## # print number of nonzeros +## SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) +## print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +## +##time_noop() # -#def single_tet(): -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# return V,F,T +## print(igl.matlab_format(V,"V")) +## print(igl.matlab_format_index(F,"F")) +## print(igl.matlab_format(dV,"dV")) +## print(igl.matlab_format_index(dF,"dF")) + + +# seed numpy's random number generator # +def triangulated_square(): + V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) + F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) + return V,F + +def single_tet(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + T = np.array([[0,1,2,3]],dtype=np.int64) + return V,F,T + #def test_edges(): # F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) # E,oE = igl.orient_halfedges(F) @@ -168,11 +168,12 @@ def test_nothing(): # V,T,F = igl.readMESH("out.mesh") # igl.writeMSH("out.msh",V,F,T) # V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") -# -#def test_bvh(): -# V,F,T = single_tet() -# tree = igl.AABB() -# tree.init(V,T) + +def test_bvh(): + V,F,T = single_tet() + tree = igl.AABB() + tree.init(V,T) + # P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) # # first row of P # q = P[0,:] From aaf363202811a0abc92b11702dbf49d1afa057eb Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 12:52:08 -0500 Subject: [PATCH 074/102] version --- tests/test_all.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/test_all.py b/tests/test_all.py index 1025eb54..a4bbf55b 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -11,7 +11,8 @@ #import igl.embree #import igl.triangle -def test_nothing(): +def test_version(): + version = igl.__version__ pass @@ -61,16 +62,16 @@ def test_nothing(): # seed numpy's random number generator # -def triangulated_square(): - V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) - F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) - return V,F - -def single_tet(): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) - T = np.array([[0,1,2,3]],dtype=np.int64) - return V,F,T +#def triangulated_square(): +# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) +# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) +# return V,F +# +#def single_tet(): +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# return V,F,T #def test_edges(): # F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) @@ -168,11 +169,11 @@ def single_tet(): # V,T,F = igl.readMESH("out.mesh") # igl.writeMSH("out.msh",V,F,T) # V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") - -def test_bvh(): - V,F,T = single_tet() - tree = igl.AABB() - tree.init(V,T) +# +#def test_bvh(): +# V,F,T = single_tet() +# tree = igl.AABB() +# tree.init(V,T) # P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) # # first row of P From 428d60af4bde11cf9a12f80ab22c9245f4063a86 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 15:05:41 -0500 Subject: [PATCH 075/102] install --- CMakeLists.txt | 7 ++- README.md | 7 +++ igl/helpers.py | 60 ------------------------ tests/test_all.py | 116 +++++++++++++++++++++++----------------------- 4 files changed, 71 insertions(+), 119 deletions(-) delete mode 100644 igl/helpers.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 12e29e57..ced9f8b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,22 +135,27 @@ function(pyigl_include prefix name) set(output_dir "${PYIGL_OUTPUT_DIRECTORY}/${subpath}") file(MAKE_DIRECTORY ${output_dir}) file(WRITE "${output_dir}/__init__.py" "from .${target_name} import *\n") + install(FILES "${output_dir}/__init__.py" DESTINATION "igl/${subpath}") if("${name}" STREQUAL "core" AND "${prefix}" STREQUAL "") + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/igl/_version.py" DESTINATION "igl/${subpath}") file(APPEND "${output_dir}/__init__.py" "from ._version import __version__\n") endif() + set_target_properties(${target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${output_dir}" RUNTIME_OUTPUT_DIRECTORY "${output_dir}" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${output_dir}" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${output_dir}" ) + set(PYI_OUTPUT "${output_dir}/${target_name}.pyi") nanobind_add_stub( ${target_name}_stub MODULE ${target_name} - OUTPUT "${output_dir}/${target_name}.pyi" + OUTPUT ${PYI_OUTPUT} PYTHON_PATH $ DEPENDS ${target_name} ) + install(FILES "${PYI_OUTPUT}" DESTINATION "igl/${subpath}") # just to add dependency? if("${name}" STREQUAL "core") diff --git a/README.md b/README.md index 4632af7f..24e76931 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,10 @@ According to the [scikit-build-core documentation](https://scikit-build-core.rea ``` python -m pip install --no-build-isolation --config-settings=editable.rebuild=true -Cbuild-dir=build -ve. ``` + +## Testing cibuildwheel locally + +Install whichever version of Python from the [official website](https://www.python.org/downloads/) and then run: + + /Library/Frameworks/Python.framework/Versions/[version]/bin/python -m pip install cibuildwheel + CIBW_BUILD="cp311-*" python -m cibuildwheel --output-dir wheelhouse --platform macos diff --git a/igl/helpers.py b/igl/helpers.py deleted file mode 100644 index 76ccf97b..00000000 --- a/igl/helpers.py +++ /dev/null @@ -1,60 +0,0 @@ -# This file is part of libigl, a simple c++ geometry processing library. -# -# Copyright (C) 2017 Sebastian Koch and Daniele Panozzo -# -# This Source Code Form is subject to the terms of the Mozilla Public License -# v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at http://mozilla.org/MPL/2.0/. -import os - -import numpy as np -from scipy import sparse -from scipy.sparse.linalg import spsolve -import igl - -# Enum definitions -MASSMATRIX_TYPE_BARYCENTRIC = 0 -MASSMATRIX_TYPE_VORONOI = 1 -MASSMATRIX_TYPE_FULL = 2 - -PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM = 0 -PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA = 1 -PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE = 2 - -SLIM_ENERGY_TYPE_ARAP = 0 -SLIM_ENERGY_TYPE_LOG_ARAP = 1 -SLIM_ENERGY_TYPE_SYMMETRIC_DIRICHLET = 2 -SLIM_ENERGY_TYPE_CONFORMAL = 3 -SLIM_ENERGY_TYPE_EXP_CONFORMAL = 4 -SLIM_ENERGY_TYPE_EXP_SYMMETRIC_DIRICHLET = 5 - -SIGNED_DISTANCE_TYPE_PSEUDONORMAL = 0 # Use fast pseudo-normal test [Bærentzen & Aanæs 2005] -SIGNED_DISTANCE_TYPE_WINDING_NUMBER = 1 # Use winding number [Jacobson, Kavan Sorking-Hornug 2013] -SIGNED_DISTANCE_TYPE_DEFAULT = 2 -SIGNED_DISTANCE_TYPE_UNSIGNED = 3 -SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER = 4 # Use Fast winding number [Barill, Dickson, Schmidt, Levin, Jacobson 2018] - -ARAP_ENERGY_TYPE_SPOKES = 0 -ARAP_ENERGY_TYPE_SPOKES_AND_RIMS = 1 -ARAP_ENERGY_TYPE_ELEMENTS = 2 -ARAP_ENERGY_TYPE_DEFAULT = 3 - - -def check_dependencies(deps): - import sys - available = [hasattr(igl, m) for m in deps] - all_available = True - for i, d in enumerate(available): - if not d: - all_available = False - print("The libigl python bindings were compiled without %s support. " - "Please recompile with the CMAKE flag LIBIGL_WITH_%s." %(deps[i], deps[i].upper())) - - if not all_available: - sys.exit(-1) - - -def print_usage(key_dict): - print("Usage:") - for k in key_dict.keys(): - print("%s : %s" %(k, key_dict[k])) diff --git a/tests/test_all.py b/tests/test_all.py index a4bbf55b..c8276916 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -16,62 +16,62 @@ def test_version(): pass -##def rand_sparse(n,density): -## n_features = n -## n_samples = n -## rng1 = np.random.RandomState(42) -## rng2 = np.random.RandomState(43) -## -## nnz = int(n_samples*n_features*density) -## -## row = rng1.randint(n_samples, size=nnz) -## cols = rng2.randint(n_features, size=nnz) -## data = rng1.rand(nnz) -## -## S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) -## return S.tocsc() -## -##def time_noop(): -## def helper(N,I,SN,SI): -## igl.noop(SN=SN) -## # start timer -## runs = 100 -## start = time.time() -## for i in range(runs): -## igl.noop(SN=SN) -## # end timer -## end = time.time() -## return (end - start)/runs -## n = 10000 -## m = 10 -## N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) -## I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) -## # random sparse matrix -## SN64 = rand_sparse(n,1.0/(n)) -## # print number of nonzeros -## SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) -## print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") -## -##time_noop() -# -## print(igl.matlab_format(V,"V")) -## print(igl.matlab_format_index(F,"F")) -## print(igl.matlab_format(dV,"dV")) -## print(igl.matlab_format_index(dF,"dF")) +#def rand_sparse(n,density): +# n_features = n +# n_samples = n +# rng1 = np.random.RandomState(42) +# rng2 = np.random.RandomState(43) +# +# nnz = int(n_samples*n_features*density) +# +# row = rng1.randint(n_samples, size=nnz) +# cols = rng2.randint(n_features, size=nnz) +# data = rng1.rand(nnz) +# +# S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) +# return S.tocsc() +# +#def time_noop(): +# def helper(N,I,SN,SI): +# igl.noop(SN=SN) +# # start timer +# runs = 100 +# start = time.time() +# for i in range(runs): +# igl.noop(SN=SN) +# # end timer +# end = time.time() +# return (end - start)/runs +# n = 10000 +# m = 10 +# N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) +# I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) +# # random sparse matrix +# SN64 = rand_sparse(n,1.0/(n)) +# # print number of nonzeros +# SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) +# print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +# +#time_noop() +# print(igl.matlab_format(V,"V")) +# print(igl.matlab_format_index(F,"F")) +# print(igl.matlab_format(dV,"dV")) +# print(igl.matlab_format_index(dF,"dF")) -# seed numpy's random number generator -# -#def triangulated_square(): -# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) -# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) -# return V,F -# -#def single_tet(): -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# return V,F,T + +#seed numpy's random number generator + +def triangulated_square(): + V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) + F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) + return V,F + +def single_tet(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + T = np.array([[0,1,2,3]],dtype=np.int64) + return V,F,T #def test_edges(): # F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) @@ -170,10 +170,10 @@ def test_version(): # igl.writeMSH("out.msh",V,F,T) # V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") # -#def test_bvh(): -# V,F,T = single_tet() -# tree = igl.AABB() -# tree.init(V,T) +def test_bvh(): + V,F,T = single_tet() + tree = igl.AABB() + tree.init(V,T) # P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) # # first row of P From 2a87174a1e26346ab85aa1ed4ae39761883d51d3 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 15:29:05 -0500 Subject: [PATCH 076/102] add 'em all back --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ced9f8b6..05692d06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,9 +95,9 @@ function(pyigl_include prefix name) endif() file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/*.cpp") - # Just compile a single file - list(GET sources 0 sources) - list(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/module.cpp") + ## Just compile a single file + #list(GET sources 0 sources) + #list(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/module.cpp") set(BINDING_SOURCES ${sources}) list(FILTER BINDING_SOURCES EXCLUDE REGEX ".*/module\\.cpp$") From b582b2cb150b8c31c37dd29f61c27dab1072cff0 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 15:36:30 -0500 Subject: [PATCH 077/102] add back tests --- pyproject.toml | 3 + tests/test_all.py | 740 +++++++++++++++++++++++----------------------- 2 files changed, 373 insertions(+), 370 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8a77bb4e..0c5c7b68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,9 @@ build-dir = "build/{wheel_tag}" # Build stable ABI wheels for CPython 3.12+ wheel.py-api = "cp312" +[tool.scikit-build.cmake] +build-type = "Release" + [tool.cibuildwheel] # Necessary to see build output from the actual compilation build-verbosity = 1 diff --git a/tests/test_all.py b/tests/test_all.py index c8276916..38501d6d 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -73,381 +73,381 @@ def single_tet(): T = np.array([[0,1,2,3]],dtype=np.int64) return V,F,T -#def test_edges(): -# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) -# E,oE = igl.orient_halfedges(F) -# ne = E.max()+1 -# np.random.seed(42) -# uE = np.random.rand(ne).astype(np.float64) -# -# uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) -# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) -# -#def test_read_write(): -# V,F = triangulated_square() -# igl.write_triangle_mesh("out.obj",V,F) -# igl.write_triangle_mesh("out.off",V,F) -# igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) -# igl.writePLY("out.ply",V,F) -# igl.writeOBJ("out.obj",V,F) -# igl.writeOBJ("out.obj",V,F,V,F,V,F) -# V,F,_ = igl.readOFF("out.off") -# V,_,_,F,_,_ = igl.readOBJ("out.obj") -# V,F = igl.read_triangle_mesh("out.ply") -# igl.writeDMAT("out.dmat",V) -# igl.writeDMAT("out.dmat",V,ascii=True) -# V = igl.readDMAT("out.dmat") -# igl.writeDMAT("out.dmat",V,ascii=False) -# V = igl.readDMAT("out.dmat") -# -#def test_operators(): -# V,F = triangulated_square() -# L = igl.cotmatrix(V,F) -# I = np.array([0,1,2,3],dtype=np.int64) -# C = np.array([0,4],dtype=np.int64) -# L,M,P = igl.cotmatrix(V,I,C) -# L = igl.squared_edge_lengths(V,F) -# K = igl.cotmatrix_entries(V,F) -# l = igl.edge_lengths(V,F) -# K = igl.cotmatrix_entries(l=l) -# B1,B2,B3 = igl.local_basis(V,F) -# G = igl.grad(V,F) -# -# M = igl.massmatrix(V,F) -# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) -# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) -# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) -# -# A = igl.facet_adjacency_matrix(F) -# A = igl.adjacency_matrix(F) -# A = igl.adjacency_matrix(I,C) -# E = igl.edges(F) -# E = igl.edges(I,C) -# E = igl.edges(A) -# n,C,K = igl.connected_components(A) -# C = igl.vertex_components(F) -# nc,C = igl.facet_components(F) -# -#def test_normals_and_distances(): -# V,F = triangulated_square() -# I = np.array([0,1,2,3],dtype=np.int64) -# C = np.array([0,4],dtype=np.int64) -# FN,_,_,_ = igl.per_face_normals(V,I,C) -# FN = igl.per_face_normals(V,F) -# FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) -# VN = igl.per_vertex_normals(V,F) -# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) -# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) -# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) -# VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) -# dblA = igl.doublearea(V,F) -# l = igl.edge_lengths(V,F) -# dblA = igl.doublearea(l=l) -# dblA = igl.doublearea(l=l,nan_replacement=0.0) -# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) -# sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) -# W = igl.winding_number(V,F,P) -# W = igl.winding_number(V,F,P[1,:]) -# W = igl.fast_winding_number(V,F,P) -# S,I,C,N = igl.signed_distance(P,V,F) -# S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) -# BC = igl.barycenter(V,F) -# -#def test_harmonic(): -# V,F = triangulated_square() -# b = np.array([0,3],dtype=np.int64) -# bc = np.array([[1,0],[0,1]],dtype=np.float64) -# W = igl.bbw(V,F,b,bc) -# W = igl.harmonic(V,F,b,bc,k=1) -# W = igl.harmonic(V,F,b,bc,k=2) -# -#def test_tets(): -# V,F,T = single_tet() -# F,J,K = igl.boundary_facets(T) -# igl.writeMESH("out.mesh",V,T) -# igl.writeMESH("out.mesh",V,T,F=F) -# V,T,F = igl.readMESH("out.mesh") -# igl.writeMSH("out.msh",V,F,T) -# V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") -# +def test_edges(): + F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) + E,oE = igl.orient_halfedges(F) + ne = E.max()+1 + np.random.seed(42) + uE = np.random.rand(ne).astype(np.float64) + + uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) + V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) + +def test_read_write(): + V,F = triangulated_square() + igl.write_triangle_mesh("out.obj",V,F) + igl.write_triangle_mesh("out.off",V,F) + igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) + igl.writePLY("out.ply",V,F) + igl.writeOBJ("out.obj",V,F) + igl.writeOBJ("out.obj",V,F,V,F,V,F) + V,F,_ = igl.readOFF("out.off") + V,_,_,F,_,_ = igl.readOBJ("out.obj") + V,F = igl.read_triangle_mesh("out.ply") + igl.writeDMAT("out.dmat",V) + igl.writeDMAT("out.dmat",V,ascii=True) + V = igl.readDMAT("out.dmat") + igl.writeDMAT("out.dmat",V,ascii=False) + V = igl.readDMAT("out.dmat") + +def test_operators(): + V,F = triangulated_square() + L = igl.cotmatrix(V,F) + I = np.array([0,1,2,3],dtype=np.int64) + C = np.array([0,4],dtype=np.int64) + L,M,P = igl.cotmatrix(V,I,C) + L = igl.squared_edge_lengths(V,F) + K = igl.cotmatrix_entries(V,F) + l = igl.edge_lengths(V,F) + K = igl.cotmatrix_entries(l=l) + B1,B2,B3 = igl.local_basis(V,F) + G = igl.grad(V,F) + + M = igl.massmatrix(V,F) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) + + A = igl.facet_adjacency_matrix(F) + A = igl.adjacency_matrix(F) + A = igl.adjacency_matrix(I,C) + E = igl.edges(F) + E = igl.edges(I,C) + E = igl.edges(A) + n,C,K = igl.connected_components(A) + C = igl.vertex_components(F) + nc,C = igl.facet_components(F) + +def test_normals_and_distances(): + V,F = triangulated_square() + I = np.array([0,1,2,3],dtype=np.int64) + C = np.array([0,4],dtype=np.int64) + FN,_,_,_ = igl.per_face_normals(V,I,C) + FN = igl.per_face_normals(V,F) + FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) + VN = igl.per_vertex_normals(V,F) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) + VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) + dblA = igl.doublearea(V,F) + l = igl.edge_lengths(V,F) + dblA = igl.doublearea(l=l) + dblA = igl.doublearea(l=l,nan_replacement=0.0) + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) + W = igl.winding_number(V,F,P) + W = igl.winding_number(V,F,P[1,:]) + W = igl.fast_winding_number(V,F,P) + S,I,C,N = igl.signed_distance(P,V,F) + S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) + BC = igl.barycenter(V,F) + +def test_harmonic(): + V,F = triangulated_square() + b = np.array([0,3],dtype=np.int64) + bc = np.array([[1,0],[0,1]],dtype=np.float64) + W = igl.bbw(V,F,b,bc) + W = igl.harmonic(V,F,b,bc,k=1) + W = igl.harmonic(V,F,b,bc,k=2) + +def test_tets(): + V,F,T = single_tet() + F,J,K = igl.boundary_facets(T) + igl.writeMESH("out.mesh",V,T) + igl.writeMESH("out.mesh",V,T,F=F) + V,T,F = igl.readMESH("out.mesh") + igl.writeMSH("out.msh",V,F,T) + V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") + def test_bvh(): V,F,T = single_tet() tree = igl.AABB() tree.init(V,T) -# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) -# # first row of P -# q = P[0,:] -# I = tree.find(V,T,q) -# i = tree.find(V,T,q,first=True) -# -# Q = igl.barycenter(V,T) -# I = igl.in_element(V,T,Q,tree) -# -# tree = igl.AABB() -# tree.init(V,F) -# sqrD,_,_ = tree.squared_distance(V,F,P) -# -# O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); -# D = V.mean(axis=0)-O -# I,T,UV = tree.intersect_ray_first(V,F,O,D) -# hits = tree.intersect_ray(V,F,O,D) -# o = O[0,:] -# d = D[0,:] -# hit = igl.ray_mesh_intersect(o,d,V,F,first=True) -# hits = igl.ray_mesh_intersect(o,d,V,F) -# -# -#def test_upsample(): -# V,F,T = single_tet() -# # upsample so there's something to collapse -# V,F = igl.upsample(V,F,number_of_subdivs=1) -# E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) -# E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) -# EF,EI = igl.edge_flaps(F,uE,EMAP) -# uE,EMAP,EF,EI = igl.edge_flaps(F) -# Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) -# -# e = 0 -# p = V[uE[e,0],:] -# e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) -# -#def test_cut(): -# V,F,T = single_tet() -# C = np.ones(F.shape,dtype=bool) -# Vn,Fn,I = igl.cut_mesh(V,F,C) -# -#def test_adjacency(): -# V,F,T = single_tet() -# TT,TTi = igl.triangle_triangle_adjacency(F) -# TT,TTi = igl.triangle_triangle_adjacency_lists(F) -# -# VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) -# VF,VFi = igl.vertex_triangle_adjacency(F) -# -# F012 = F; -# F120 = np.roll(F012,1,axis=1) -# FF = np.vstack((F012,F120)) -# F,IA,IC = igl.unique_simplices(FF) -# A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) -# B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) -# IA,LOCB = igl.ismember_rows(A,B) -# A = igl.adjacency_list(F) -# A = igl.adjacency_list(F,sorted=True) -# -#def test_min_quad(): -# V,F,T = single_tet() -# A = -igl.cotmatrix(V,F) -# B = np.zeros((V.shape[0],1),dtype=np.float64) -# known = np.array([1,2],dtype=np.int64) -# Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) -# Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) -# Beq = np.zeros((1,1),dtype=np.float64) -# Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) -# data = igl.min_quad_with_fixed_data() -# igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) -# Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) -# -#def test_volume(): -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# L = igl.edge_lengths(V,T) -# vol = igl.volume(V,T) -# vol = igl.volume(L=L) -# vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) -# F,_,_ = igl.boundary_facets(T) -# # remove last face -# F = F[:-1,:] -# B = igl.is_border_vertex(F) -# I,C = igl.on_boundary(F) -# I,C = igl.on_boundary(T) -# F = np.array([[2,1,3]],dtype=np.int64) -# NV,NF,I,J = igl.remove_unreferenced(V,F) -# -#def test_oriented_facets(): -# V,F,T = single_tet() -# E = igl.oriented_facets(F) -# F = igl.oriented_facets(T) -# -# res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) -# res,_,_,_,_ = igl.is_edge_manifold(F) -# -#def test_matlab_format(): -# V,F = triangulated_square() -# L = igl.cotmatrix(V,F) -# s = igl.matlab_format(V,"V") -# s = igl.matlab_format_index(F,"F") -# s = igl.matlab_format(L,"L") -# -#def test_polar(): -# # random randn 3x3 matrix -# np.random.seed(42) -# A = np.random.randn(3,3).astype(np.float64) -# igl.polar_svd(A) -# out = igl.polar_svd(A) -# R,T,_,_,_ = igl.polar_svd(A) -# R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) -# -#def test_remove(): -# V,F = triangulated_square() -# SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) -# SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) -# -#def test_project(): -# V,F,_ = single_tet() -# model = np.eye(4).astype(np.float64) -# proj = np.eye(4).astype(np.float64) -# viewport = np.array([0,0,640,480],dtype=np.float64) -# win = igl.project(V,model,proj,viewport) -# scene = igl.unproject(win,model,proj,viewport) -# -# S = np.array([0,0,0],dtype=np.float64) -# D = np.array([0.2,0.2,0.2],dtype=np.float64) -# t,sqrD = igl.project_to_line(V,S,D) -# t,sqrD = igl.project_to_line_segment(V,S,D) -# -#def test_boundary_loop(): -# V,F,_ = single_tet() -# S,_ = igl.upsample_matrix(F,n=V.shape[0]) -# S,_ = igl.loop_matrix(F,n=V.shape[0]) -# V,F = igl.upsample(V,F,number_of_subdivs=1) -# V,F = igl.loop(V,F,number_of_subdivs=1) -# # remove first and last -# F = F[1:-1,:] -# L_all = igl.boundary_loop_all(F) -# L = igl.boundary_loop(F) -# -#def test_voxel(): -# V,_,_ = single_tet() -# GV,side = igl.voxel_grid(V,s=10) -# GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) -# -# -#def test_sample(): -# V,F = igl.icosahedron() -# B,I,X = igl.random_points_on_mesh(10,V,F) -# point_indices, CH,CN,W = igl.octree(X) -# I = igl.knn(X,X,1,point_indices,CH,CN,W) -# B,FI,P = igl.blue_noise(V,F,0.5) -# -#def test_curvature(): -# V,F = igl.icosahedron() -# K = igl.gaussian_curvature(V,F) -# PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) -# PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) -# -#def test_geodesic(): -# V,F = igl.icosahedron() -# VS = np.array([0],dtype=np.int64) -# VT = np.array([1],dtype=np.int64) -# D = igl.exact_geodesic(V,F,VS=VS,VT=VT) -# -#def test_decimate(): -# V,F = igl.icosahedron() -# dV,dF,J,I = igl.decimate(V,F) -# dV,dF,J,I = igl.qslim(V,F) -# -#def test_parameterization(): -# V,Q,E = igl.quad_grid(3,3); -# V,F = igl.triangulated_grid(3,3); -# # slim needs 3D data even if the problem is 2D -# V = np.c_[V, np.zeros(V.shape[0])] -# V_init = V[:,:2] -# V_init[:,0] = V_init[:,0] * 2 -# b = np.array([0,1],dtype=np.int64) -# bc = V[b,:2] -# data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) -# U = igl.slim_solve(data,iter_num=1) -# -# data = igl.ARAPData() -# data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS -# V = V[:,:2] -# igl.arap_precomputation(V,F,V.shape[1],b,data) -# U = igl.arap_solve(bc,data,U) -# -# U,Q = igl.lscm(V,F,b,bc) -# -# -#def test_implicit(): -# res = np.array([3,3,3],dtype=np.int64) -# GV = igl.grid(res) -# S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; -# V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) -# # unpack keys into (i,j,v) index triplets -# EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) -# -# h = igl.avg_edge_length(V,F) -# m0,m1,m2 = igl.moments(V,F) -# D = V[:,0] -# vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) -# iV,iE,I = igl.isolines(V,F,S,vals) -# iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) -# -# -#def test_kelvinlets(): -# V,F = igl.icosahedron() -# x0 = V[0,:] -# f = np.array([0,0,1],dtype=np.float64) -# R = np.eye(3).astype(np.float64) -# U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) -# -# SV = V[:,0] -# SF = igl.average_onto_faces(F,SV) -# SV = igl.average_onto_vertices(V,F,SF) -# -#def test_polygons(): -# P = [[0,1,2],[2,1,3,4]] -# I,C = igl.polygon_corners(P) -# Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) -# I,C = igl.polygon_corners(Q) -# F,J = igl.polygons_to_triangles(I,C) -# -#def test_heat(): -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -# V,F = igl.upsample(V,F,number_of_subdivs=1) -# data = igl.HeatGeodesicsData() -# igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) -# data = igl.HeatGeodesicsData() -# data.use_intrinsic_delaunay = True -# igl.heat_geodesics_precompute(V,F,data) -# gamma = np.array([0],dtype=np.int64) -# D = igl.heat_geodesics_solve(data,gamma) -# l = igl.edge_lengths(V,F) -# iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) -# L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) -# L = igl.cotmatrix_intrinsic(il,iF) -# M = igl.massmatrix_intrinsic(il,iF) -# -#def test_nonmanifold(): -# V,F = igl.icosahedron() -# FF,C = igl.bfs_orient(F) -# SF,SVI = igl.split_nonmanifold(F) -# SV,SF,SVI = igl.split_nonmanifold(V,F) -# -#def test_biharmonic(): -# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) -# T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) -# S = [[0],[5]] -# W = igl.biharmonic_coordinates(V,T,S) -# -#def test_bijective(): -# # 2D triangulated grid -# V,F = igl.triangulated_grid(3,3) -# ear,ear_opp = igl.ears(F) -# # we don't have igl.flip_ears so construct it by hand -# res = np.array([3,3],dtype=np.int64) -# E,_,_ = igl.boundary_facets(F) -# F = E -# # add a 3rd column of 4s -# F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) -# # boundary b -# b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) -# bc = V[b,:] -# bc[-1,:] = [0.5,0.5] -# U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) -# -# + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + # first row of P + q = P[0,:] + I = tree.find(V,T,q) + i = tree.find(V,T,q,first=True) + + Q = igl.barycenter(V,T) + I = igl.in_element(V,T,Q,tree) + + tree = igl.AABB() + tree.init(V,F) + sqrD,_,_ = tree.squared_distance(V,F,P) + + O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); + D = V.mean(axis=0)-O + I,T,UV = tree.intersect_ray_first(V,F,O,D) + hits = tree.intersect_ray(V,F,O,D) + o = O[0,:] + d = D[0,:] + hit = igl.ray_mesh_intersect(o,d,V,F,first=True) + hits = igl.ray_mesh_intersect(o,d,V,F) + + +def test_upsample(): + V,F,T = single_tet() + # upsample so there's something to collapse + V,F = igl.upsample(V,F,number_of_subdivs=1) + E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) + E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) + EF,EI = igl.edge_flaps(F,uE,EMAP) + uE,EMAP,EF,EI = igl.edge_flaps(F) + Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) + + e = 0 + p = V[uE[e,0],:] + e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) + +def test_cut(): + V,F,T = single_tet() + C = np.ones(F.shape,dtype=bool) + Vn,Fn,I = igl.cut_mesh(V,F,C) + +def test_adjacency(): + V,F,T = single_tet() + TT,TTi = igl.triangle_triangle_adjacency(F) + TT,TTi = igl.triangle_triangle_adjacency_lists(F) + + VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) + VF,VFi = igl.vertex_triangle_adjacency(F) + + F012 = F; + F120 = np.roll(F012,1,axis=1) + FF = np.vstack((F012,F120)) + F,IA,IC = igl.unique_simplices(FF) + A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) + B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) + IA,LOCB = igl.ismember_rows(A,B) + A = igl.adjacency_list(F) + A = igl.adjacency_list(F,sorted=True) + +def test_min_quad(): + V,F,T = single_tet() + A = -igl.cotmatrix(V,F) + B = np.zeros((V.shape[0],1),dtype=np.float64) + known = np.array([1,2],dtype=np.int64) + Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) + Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) + Beq = np.zeros((1,1),dtype=np.float64) + Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) + data = igl.min_quad_with_fixed_data() + igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) + Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) + +def test_volume(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + L = igl.edge_lengths(V,T) + vol = igl.volume(V,T) + vol = igl.volume(L=L) + vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) + F,_,_ = igl.boundary_facets(T) + # remove last face + F = F[:-1,:] + B = igl.is_border_vertex(F) + I,C = igl.on_boundary(F) + I,C = igl.on_boundary(T) + F = np.array([[2,1,3]],dtype=np.int64) + NV,NF,I,J = igl.remove_unreferenced(V,F) + +def test_oriented_facets(): + V,F,T = single_tet() + E = igl.oriented_facets(F) + F = igl.oriented_facets(T) + + res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) + res,_,_,_,_ = igl.is_edge_manifold(F) + +def test_matlab_format(): + V,F = triangulated_square() + L = igl.cotmatrix(V,F) + s = igl.matlab_format(V,"V") + s = igl.matlab_format_index(F,"F") + s = igl.matlab_format(L,"L") + +def test_polar(): + # random randn 3x3 matrix + np.random.seed(42) + A = np.random.randn(3,3).astype(np.float64) + igl.polar_svd(A) + out = igl.polar_svd(A) + R,T,_,_,_ = igl.polar_svd(A) + R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) + +def test_remove(): + V,F = triangulated_square() + SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) + SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) + +def test_project(): + V,F,_ = single_tet() + model = np.eye(4).astype(np.float64) + proj = np.eye(4).astype(np.float64) + viewport = np.array([0,0,640,480],dtype=np.float64) + win = igl.project(V,model,proj,viewport) + scene = igl.unproject(win,model,proj,viewport) + + S = np.array([0,0,0],dtype=np.float64) + D = np.array([0.2,0.2,0.2],dtype=np.float64) + t,sqrD = igl.project_to_line(V,S,D) + t,sqrD = igl.project_to_line_segment(V,S,D) + +def test_boundary_loop(): + V,F,_ = single_tet() + S,_ = igl.upsample_matrix(F,n=V.shape[0]) + S,_ = igl.loop_matrix(F,n=V.shape[0]) + V,F = igl.upsample(V,F,number_of_subdivs=1) + V,F = igl.loop(V,F,number_of_subdivs=1) + # remove first and last + F = F[1:-1,:] + L_all = igl.boundary_loop_all(F) + L = igl.boundary_loop(F) + +def test_voxel(): + V,_,_ = single_tet() + GV,side = igl.voxel_grid(V,s=10) + GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) + + +def test_sample(): + V,F = igl.icosahedron() + B,I,X = igl.random_points_on_mesh(10,V,F) + point_indices, CH,CN,W = igl.octree(X) + I = igl.knn(X,X,1,point_indices,CH,CN,W) + B,FI,P = igl.blue_noise(V,F,0.5) + +def test_curvature(): + V,F = igl.icosahedron() + K = igl.gaussian_curvature(V,F) + PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) + PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) + +def test_geodesic(): + V,F = igl.icosahedron() + VS = np.array([0],dtype=np.int64) + VT = np.array([1],dtype=np.int64) + D = igl.exact_geodesic(V,F,VS=VS,VT=VT) + +def test_decimate(): + V,F = igl.icosahedron() + dV,dF,J,I = igl.decimate(V,F) + dV,dF,J,I = igl.qslim(V,F) + +def test_parameterization(): + V,Q,E = igl.quad_grid(3,3); + V,F = igl.triangulated_grid(3,3); + # slim needs 3D data even if the problem is 2D + V = np.c_[V, np.zeros(V.shape[0])] + V_init = V[:,:2] + V_init[:,0] = V_init[:,0] * 2 + b = np.array([0,1],dtype=np.int64) + bc = V[b,:2] + data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) + U = igl.slim_solve(data,iter_num=1) + + data = igl.ARAPData() + data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS + V = V[:,:2] + igl.arap_precomputation(V,F,V.shape[1],b,data) + U = igl.arap_solve(bc,data,U) + + U,Q = igl.lscm(V,F,b,bc) + + +def test_implicit(): + res = np.array([3,3,3],dtype=np.int64) + GV = igl.grid(res) + S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; + V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) + # unpack keys into (i,j,v) index triplets + EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) + + h = igl.avg_edge_length(V,F) + m0,m1,m2 = igl.moments(V,F) + D = V[:,0] + vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) + iV,iE,I = igl.isolines(V,F,S,vals) + iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) + + +def test_kelvinlets(): + V,F = igl.icosahedron() + x0 = V[0,:] + f = np.array([0,0,1],dtype=np.float64) + R = np.eye(3).astype(np.float64) + U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) + + SV = V[:,0] + SF = igl.average_onto_faces(F,SV) + SV = igl.average_onto_vertices(V,F,SF) + +def test_polygons(): + P = [[0,1,2],[2,1,3,4]] + I,C = igl.polygon_corners(P) + Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) + I,C = igl.polygon_corners(Q) + F,J = igl.polygons_to_triangles(I,C) + +def test_heat(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + V,F = igl.upsample(V,F,number_of_subdivs=1) + data = igl.HeatGeodesicsData() + igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) + data = igl.HeatGeodesicsData() + data.use_intrinsic_delaunay = True + igl.heat_geodesics_precompute(V,F,data) + gamma = np.array([0],dtype=np.int64) + D = igl.heat_geodesics_solve(data,gamma) + l = igl.edge_lengths(V,F) + iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) + L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) + L = igl.cotmatrix_intrinsic(il,iF) + M = igl.massmatrix_intrinsic(il,iF) + +def test_nonmanifold(): + V,F = igl.icosahedron() + FF,C = igl.bfs_orient(F) + SF,SVI = igl.split_nonmanifold(F) + SV,SF,SVI = igl.split_nonmanifold(V,F) + +def test_biharmonic(): + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) + S = [[0],[5]] + W = igl.biharmonic_coordinates(V,T,S) + +def test_bijective(): + # 2D triangulated grid + V,F = igl.triangulated_grid(3,3) + ear,ear_opp = igl.ears(F) + # we don't have igl.flip_ears so construct it by hand + res = np.array([3,3],dtype=np.int64) + E,_,_ = igl.boundary_facets(F) + F = E + # add a 3rd column of 4s + F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) + # boundary b + b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) + bc = V[b,:] + bc[-1,:] = [0.5,0.5] + U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) + + #def test_copyleft(): # V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) # T = np.array([[0,1,2,3]],dtype=np.int64) From db0689e2574022b24b2cbc96e261843a726994ba Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 15:53:24 -0500 Subject: [PATCH 078/102] generic string --- src/readDMAT.cpp | 2 +- src/readMESH.cpp | 2 +- src/readMSH.cpp | 2 +- src/readOBJ.cpp | 2 +- src/readOFF.cpp | 2 +- src/read_triangle_mesh.cpp | 2 +- src/writeDMAT.cpp | 2 +- src/writeMESH.cpp | 2 +- src/writeMSH.cpp | 2 +- src/writeOBJ.cpp | 2 +- src/writePLY.cpp | 2 +- src/write_triangle_mesh.cpp | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/readDMAT.cpp b/src/readDMAT.cpp index 706e35eb..04001635 100644 --- a/src/readDMAT.cpp +++ b/src/readDMAT.cpp @@ -13,7 +13,7 @@ namespace pyigl Eigen::MatrixXN readDMAT(const std::filesystem::path &file_name) { Eigen::MatrixXN W; - if (!igl::readDMAT(file_name, W)) + if (!igl::readDMAT(file_name.generic_string(), W)) { throw std::runtime_error("readDMAT: Failed to read DMAT file."); } diff --git a/src/readMESH.cpp b/src/readMESH.cpp index a794c77b..4f6fe2ba 100644 --- a/src/readMESH.cpp +++ b/src/readMESH.cpp @@ -17,7 +17,7 @@ namespace pyigl Eigen::MatrixXI T; // Tetrahedral indices Eigen::MatrixXI F; // Face indices - if (!igl::readMESH(mesh_file_name, V, T, F)) + if (!igl::readMESH(mesh_file_name.generic_string(), V, T, F)) { throw std::runtime_error("Failed to read .mesh file: " + mesh_file_name.generic_string()); } diff --git a/src/readMSH.cpp b/src/readMSH.cpp index c3a13550..42e495ab 100644 --- a/src/readMSH.cpp +++ b/src/readMSH.cpp @@ -26,7 +26,7 @@ namespace pyigl std::vector TriF; std::vector TetF; - if (!igl::readMSH(msh_file_name, V, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF)) + if (!igl::readMSH(msh_file_name.generic_string(), V, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF)) { throw std::runtime_error("Failed to read .msh file: " + msh_file_name.generic_string()); } diff --git a/src/readOBJ.cpp b/src/readOBJ.cpp index 6990592f..2e33d7d9 100644 --- a/src/readOBJ.cpp +++ b/src/readOBJ.cpp @@ -15,7 +15,7 @@ namespace pyigl { Eigen::MatrixXN V,TC,CN; Eigen::MatrixXI F,FTC,FN; - if(!igl::readOBJ(filename,V,TC,CN,F,FTC,FN)) + if(!igl::readOBJ(filename.generic_string(),V,TC,CN,F,FTC,FN)) { // throw runtime exception throw std::runtime_error("Failed to read mesh from: " + filename.generic_string()); diff --git a/src/readOFF.cpp b/src/readOFF.cpp index cf01b551..cdf6767b 100644 --- a/src/readOFF.cpp +++ b/src/readOFF.cpp @@ -15,7 +15,7 @@ namespace pyigl { Eigen::MatrixXN V,N; Eigen::MatrixXI F; - if(!igl::readOFF(filename,V,F,N)) + if(!igl::readOFF(filename.generic_string(),V,F,N)) { // throw runtime exception throw std::runtime_error("Failed to read mesh from: " + filename.generic_string() ); diff --git a/src/read_triangle_mesh.cpp b/src/read_triangle_mesh.cpp index e535407f..083c02a2 100644 --- a/src/read_triangle_mesh.cpp +++ b/src/read_triangle_mesh.cpp @@ -17,7 +17,7 @@ namespace pyigl { Eigen::MatrixXN V; Eigen::MatrixXI F; - if(!igl::read_triangle_mesh(path,V,F)) + if(!igl::read_triangle_mesh(path.generic_string(),V,F)) { // throw runtime exception throw std::runtime_error("Failed to read mesh from: " + path.generic_string()); diff --git a/src/writeDMAT.cpp b/src/writeDMAT.cpp index 6c528fa2..aaf390a4 100644 --- a/src/writeDMAT.cpp +++ b/src/writeDMAT.cpp @@ -16,7 +16,7 @@ namespace pyigl const nb::DRef &W, const bool ascii) { - if (!igl::writeDMAT(file_name, W, ascii)) + if (!igl::writeDMAT(file_name.generic_string(), W, ascii)) { throw std::runtime_error("writeDMAT: Failed to write DMAT file."); } diff --git a/src/writeMESH.cpp b/src/writeMESH.cpp index a150f31d..6088daf7 100644 --- a/src/writeMESH.cpp +++ b/src/writeMESH.cpp @@ -16,7 +16,7 @@ namespace pyigl const nb::DRef &T, const nb::DRef &F) { - if (!igl::writeMESH(mesh_file_name, V, T, F)) + if (!igl::writeMESH(mesh_file_name.generic_string(), V, T, F)) { throw std::runtime_error("Failed to write .mesh file: " + mesh_file_name.generic_string()); } diff --git a/src/writeMSH.cpp b/src/writeMSH.cpp index cc57b563..853afdaf 100644 --- a/src/writeMSH.cpp +++ b/src/writeMSH.cpp @@ -25,7 +25,7 @@ namespace pyigl std::vector EFields; std::vector TriF; std::vector TetF; - if(!igl::writeMSH(mesh_file_name, V, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF)) + if(!igl::writeMSH(mesh_file_name.generic_string(), V, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF)) { throw std::runtime_error("Failed to write .msh file: " + mesh_file_name.generic_string()); } diff --git a/src/writeOBJ.cpp b/src/writeOBJ.cpp index aa089730..acbb7467 100644 --- a/src/writeOBJ.cpp +++ b/src/writeOBJ.cpp @@ -20,7 +20,7 @@ namespace pyigl const nb::DRef &TC, const nb::DRef &FTC) { - if(!igl::writeOBJ(filename,V,F,CN,FN,TC,FTC)) + if(!igl::writeOBJ(filename.generic_string(),V,F,CN,FN,TC,FTC)) { // throw runtime exception throw std::runtime_error("Failed to write mesh to: " + filename.generic_string()); diff --git a/src/writePLY.cpp b/src/writePLY.cpp index 50b51e57..dfa8b049 100644 --- a/src/writePLY.cpp +++ b/src/writePLY.cpp @@ -33,7 +33,7 @@ namespace pyigl Eigen::MatrixXi E = E_.cast(); if(!igl::writePLY( - filename, + filename.generic_string(), V, F, E, diff --git a/src/write_triangle_mesh.cpp b/src/write_triangle_mesh.cpp index d87f8e7a..c07b2130 100644 --- a/src/write_triangle_mesh.cpp +++ b/src/write_triangle_mesh.cpp @@ -24,7 +24,7 @@ namespace pyigl throw std::runtime_error("Entries in F are too big to be cast to int32_t (required by igl::write_triangle_mesh)"); } Eigen::MatrixXi F32 = F.cast(); - if(!igl::write_triangle_mesh(filename,V,F32,encoding_enum)) + if(!igl::write_triangle_mesh(filename.generic_string(),V,F32,encoding_enum)) { // throw runtime exception throw std::runtime_error("Failed to write mesh to: " + filename.generic_string()); From abf24d01038fe3156a17b2911de325dc5fa3f41d Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 16:00:23 -0500 Subject: [PATCH 079/102] enable all submodules --- CMakeLists.txt | 10 +-- tests/test_all.py | 212 +++++++++++++++++++++++----------------------- 2 files changed, 111 insertions(+), 111 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 05692d06..2d8a51aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,11 +39,11 @@ FetchContent_Declare( FetchContent_MakeAvailable(nanobind) # Download and set up libigl -option(LIBIGL_COPYLEFT_CORE "Build target igl_copyleft::core" OFF) -option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" OFF) -option(LIBIGL_EMBREE "Build target igl::embree" OFF) -option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" OFF) -option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" OFF) +option(LIBIGL_COPYLEFT_CORE "Build target igl_copyleft::core" ON) +option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) +option(LIBIGL_EMBREE "Build target igl::embree" ON) +option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) +option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git diff --git a/tests/test_all.py b/tests/test_all.py index 38501d6d..b0666a03 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -448,109 +448,109 @@ def test_bijective(): U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) -#def test_copyleft(): -# V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# F,_,_ = igl.boundary_facets(T) -# V,F = igl.loop(V,F) -# -# dV,dF,J = igl.copyleft.progressive_hulls(V,F) -# -# -#def test_cgal(): -# # tetrahedron -# VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# FA,_,_ = igl.boundary_facets(T) -# # flip z -# VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) -# FB = FA[:,::-1] -# IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) -# IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) -# VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") -# H = igl.copyleft.cgal.convex_hull(VC) -# # concatenate A and B meshes -# VC = np.vstack((VA,VB)) -# FC = np.vstack((FA,FB+VA.shape[0])) -# VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) -# _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) -# -# p = np.array([0,0,0],dtype=np.float64) -# n = np.array([1,1,1],dtype=np.float64) -# VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) -# equ = np.hstack((n,-n.dot(p))) -# VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) -# -# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) -# W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) -# W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) -# -# VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) -# -# _,I,X = igl.random_points_on_mesh(1000,VC,FC) -# N = igl.per_face_normals(VC,FC) -# N = N[I,:] -# point_indices, CH,CN,W = igl.octree(X) -# I = igl.knn(X,X,20,point_indices,CH,CN,W) -# A,T = igl.copyleft.cgal.point_areas(X,I,N) -# -#def test_embree(): -# # octahedron -# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) -# F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) -# N = igl.per_vertex_normals(V,F) -# ei = igl.embree.EmbreeIntersector(); -# ei.init(V,F) -# S = igl.embree.ambient_occlusion(V,F,V,N,100) -# S = igl.embree.ambient_occlusion(ei,V,N,100) -# N = -N -# S = igl.embree.shape_diameter_function(V,F,V,N,100) -# S = igl.embree.shape_diameter_function(ei,V,N,100) -# origin = np.array([2,2,2],dtype=np.float64) -# direction = np.array([-2,-2,-2],dtype=np.float64) -# hit = ei.intersectRay_first(origin,direction) -# hits = ei.intersectRay(origin,direction) -# hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) -# I,C = igl.embree.reorient_facets_raycast(V,F) -# -#def test_tetgen(): -# # octahedron -# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) -# F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) -# V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") -# -#def test_triangle(): -# V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) -# E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) -# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") -# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") -# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -# scaf_data = igl.triangle.SCAFData() -# b = np.array([0,1,2],dtype=np.int64) -# bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) -# V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) -# soft_p = 0; -# igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) -# L,rhs = igl.triangle.scaf_system(scaf_data) -# U = igl.triangle.scaf_solve(1,scaf_data) -# -#def test_misc(): -# V,F = igl.icosahedron() -# BV,BF = igl.bounding_box(V,pad=1.0) -# R,C,B = igl.circumradius(V,F) -# R = igl.inradius(V,F) -# _,E,EMAP,_,_ = igl.unique_edge_map(F) -# L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) -# M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) -# cuts = igl.cut_to_disk(F) -# V,F = igl.cylinder(10,10) -# VD,FD = igl.false_barycentric_subdivision(V,F) -# V,F,T = single_tet() -# theta, cos_theta = igl.dihedral_angles(V,T) -# L = igl.edge_lengths(V,T) -# A = igl.face_areas(V,T) -# theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) -# -# +def test_copyleft(): + V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + F,_,_ = igl.boundary_facets(T) + V,F = igl.loop(V,F) + + dV,dF,J = igl.copyleft.progressive_hulls(V,F) + + +def test_cgal(): + # tetrahedron + VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + FA,_,_ = igl.boundary_facets(T) + # flip z + VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) + FB = FA[:,::-1] + IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) + IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) + VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") + H = igl.copyleft.cgal.convex_hull(VC) + # concatenate A and B meshes + VC = np.vstack((VA,VB)) + FC = np.vstack((FA,FB+VA.shape[0])) + VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) + _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) + + p = np.array([0,0,0],dtype=np.float64) + n = np.array([1,1,1],dtype=np.float64) + VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) + equ = np.hstack((n,-n.dot(p))) + VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) + + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) + W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) + + VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) + + _,I,X = igl.random_points_on_mesh(1000,VC,FC) + N = igl.per_face_normals(VC,FC) + N = N[I,:] + point_indices, CH,CN,W = igl.octree(X) + I = igl.knn(X,X,20,point_indices,CH,CN,W) + A,T = igl.copyleft.cgal.point_areas(X,I,N) + +def test_embree(): + # octahedron + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) + N = igl.per_vertex_normals(V,F) + ei = igl.embree.EmbreeIntersector(); + ei.init(V,F) + S = igl.embree.ambient_occlusion(V,F,V,N,100) + S = igl.embree.ambient_occlusion(ei,V,N,100) + N = -N + S = igl.embree.shape_diameter_function(V,F,V,N,100) + S = igl.embree.shape_diameter_function(ei,V,N,100) + origin = np.array([2,2,2],dtype=np.float64) + direction = np.array([-2,-2,-2],dtype=np.float64) + hit = ei.intersectRay_first(origin,direction) + hits = ei.intersectRay(origin,direction) + hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) + I,C = igl.embree.reorient_facets_raycast(V,F) + +def test_tetgen(): + # octahedron + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) + V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") + +def test_triangle(): + V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) + E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + scaf_data = igl.triangle.SCAFData() + b = np.array([0,1,2],dtype=np.int64) + bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) + V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) + soft_p = 0; + igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) + L,rhs = igl.triangle.scaf_system(scaf_data) + U = igl.triangle.scaf_solve(1,scaf_data) + +def test_misc(): + V,F = igl.icosahedron() + BV,BF = igl.bounding_box(V,pad=1.0) + R,C,B = igl.circumradius(V,F) + R = igl.inradius(V,F) + _,E,EMAP,_,_ = igl.unique_edge_map(F) + L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) + M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) + cuts = igl.cut_to_disk(F) + V,F = igl.cylinder(10,10) + VD,FD = igl.false_barycentric_subdivision(V,F) + V,F,T = single_tet() + theta, cos_theta = igl.dihedral_angles(V,T) + L = igl.edge_lengths(V,T) + A = igl.face_areas(V,T) + theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) + + From f08c6f13f6ed628a9fcb1f247181df65464b1a4f Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 19:16:32 -0500 Subject: [PATCH 080/102] just import tetgen/triangle --- CMakeLists.txt | 14 +- tests/test_all.py | 1089 +++++++++++++++++++++++---------------------- 2 files changed, 554 insertions(+), 549 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d8a51aa..495cff2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,8 +40,8 @@ FetchContent_MakeAvailable(nanobind) # Download and set up libigl option(LIBIGL_COPYLEFT_CORE "Build target igl_copyleft::core" ON) -option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) -option(LIBIGL_EMBREE "Build target igl::embree" ON) +option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" OFF) +option(LIBIGL_EMBREE "Build target igl::embree" OFF) option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) FetchContent_Declare( @@ -95,9 +95,13 @@ function(pyigl_include prefix name) endif() file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/*.cpp") - ## Just compile a single file - #list(GET sources 0 sources) - #list(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/module.cpp") + # Just compile a single file + list(FILTER sources EXCLUDE REGEX ".*/module\\.cpp$") + list(GET sources 0 sources) + list(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/module.cpp") + + message(STATUS "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$") + message(STATUS "${prefix} ${name} sources: ${sources}") set(BINDING_SOURCES ${sources}) list(FILTER BINDING_SOURCES EXCLUDE REGEX ".*/module\\.cpp$") diff --git a/tests/test_all.py b/tests/test_all.py index b0666a03..6b42021c 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -5,552 +5,553 @@ import time import warnings import igl -#import igl.copyleft -#import igl.copyleft.cgal -#import igl.copyleft.tetgen -#import igl.embree -#import igl.triangle def test_version(): version = igl.__version__ pass - -#def rand_sparse(n,density): -# n_features = n -# n_samples = n -# rng1 = np.random.RandomState(42) -# rng2 = np.random.RandomState(43) -# -# nnz = int(n_samples*n_features*density) -# -# row = rng1.randint(n_samples, size=nnz) -# cols = rng2.randint(n_features, size=nnz) -# data = rng1.rand(nnz) -# -# S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) -# return S.tocsc() -# -#def time_noop(): -# def helper(N,I,SN,SI): -# igl.noop(SN=SN) -# # start timer -# runs = 100 -# start = time.time() -# for i in range(runs): -# igl.noop(SN=SN) -# # end timer -# end = time.time() -# return (end - start)/runs -# n = 10000 -# m = 10 -# N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) -# I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) -# # random sparse matrix -# SN64 = rand_sparse(n,1.0/(n)) -# # print number of nonzeros -# SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) -# print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") -# -#time_noop() - -# print(igl.matlab_format(V,"V")) -# print(igl.matlab_format_index(F,"F")) -# print(igl.matlab_format(dV,"dV")) -# print(igl.matlab_format_index(dF,"dF")) - - -#seed numpy's random number generator - -def triangulated_square(): - V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) - F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) - return V,F - -def single_tet(): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) - T = np.array([[0,1,2,3]],dtype=np.int64) - return V,F,T - -def test_edges(): - F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) - E,oE = igl.orient_halfedges(F) - ne = E.max()+1 - np.random.seed(42) - uE = np.random.rand(ne).astype(np.float64) - - uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) - V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) - -def test_read_write(): - V,F = triangulated_square() - igl.write_triangle_mesh("out.obj",V,F) - igl.write_triangle_mesh("out.off",V,F) - igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) - igl.writePLY("out.ply",V,F) - igl.writeOBJ("out.obj",V,F) - igl.writeOBJ("out.obj",V,F,V,F,V,F) - V,F,_ = igl.readOFF("out.off") - V,_,_,F,_,_ = igl.readOBJ("out.obj") - V,F = igl.read_triangle_mesh("out.ply") - igl.writeDMAT("out.dmat",V) - igl.writeDMAT("out.dmat",V,ascii=True) - V = igl.readDMAT("out.dmat") - igl.writeDMAT("out.dmat",V,ascii=False) - V = igl.readDMAT("out.dmat") - -def test_operators(): - V,F = triangulated_square() - L = igl.cotmatrix(V,F) - I = np.array([0,1,2,3],dtype=np.int64) - C = np.array([0,4],dtype=np.int64) - L,M,P = igl.cotmatrix(V,I,C) - L = igl.squared_edge_lengths(V,F) - K = igl.cotmatrix_entries(V,F) - l = igl.edge_lengths(V,F) - K = igl.cotmatrix_entries(l=l) - B1,B2,B3 = igl.local_basis(V,F) - G = igl.grad(V,F) - - M = igl.massmatrix(V,F) - M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) - M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) - M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) - - A = igl.facet_adjacency_matrix(F) - A = igl.adjacency_matrix(F) - A = igl.adjacency_matrix(I,C) - E = igl.edges(F) - E = igl.edges(I,C) - E = igl.edges(A) - n,C,K = igl.connected_components(A) - C = igl.vertex_components(F) - nc,C = igl.facet_components(F) - -def test_normals_and_distances(): - V,F = triangulated_square() - I = np.array([0,1,2,3],dtype=np.int64) - C = np.array([0,4],dtype=np.int64) - FN,_,_,_ = igl.per_face_normals(V,I,C) - FN = igl.per_face_normals(V,F) - FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) - VN = igl.per_vertex_normals(V,F) - VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) - VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) - VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) - VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) - dblA = igl.doublearea(V,F) - l = igl.edge_lengths(V,F) - dblA = igl.doublearea(l=l) - dblA = igl.doublearea(l=l,nan_replacement=0.0) - P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) - sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) - W = igl.winding_number(V,F,P) - W = igl.winding_number(V,F,P[1,:]) - W = igl.fast_winding_number(V,F,P) - S,I,C,N = igl.signed_distance(P,V,F) - S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) - BC = igl.barycenter(V,F) - -def test_harmonic(): - V,F = triangulated_square() - b = np.array([0,3],dtype=np.int64) - bc = np.array([[1,0],[0,1]],dtype=np.float64) - W = igl.bbw(V,F,b,bc) - W = igl.harmonic(V,F,b,bc,k=1) - W = igl.harmonic(V,F,b,bc,k=2) - -def test_tets(): - V,F,T = single_tet() - F,J,K = igl.boundary_facets(T) - igl.writeMESH("out.mesh",V,T) - igl.writeMESH("out.mesh",V,T,F=F) - V,T,F = igl.readMESH("out.mesh") - igl.writeMSH("out.msh",V,F,T) - V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") - -def test_bvh(): - V,F,T = single_tet() - tree = igl.AABB() - tree.init(V,T) - - P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) - # first row of P - q = P[0,:] - I = tree.find(V,T,q) - i = tree.find(V,T,q,first=True) - - Q = igl.barycenter(V,T) - I = igl.in_element(V,T,Q,tree) - - tree = igl.AABB() - tree.init(V,F) - sqrD,_,_ = tree.squared_distance(V,F,P) - - O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); - D = V.mean(axis=0)-O - I,T,UV = tree.intersect_ray_first(V,F,O,D) - hits = tree.intersect_ray(V,F,O,D) - o = O[0,:] - d = D[0,:] - hit = igl.ray_mesh_intersect(o,d,V,F,first=True) - hits = igl.ray_mesh_intersect(o,d,V,F) - - -def test_upsample(): - V,F,T = single_tet() - # upsample so there's something to collapse - V,F = igl.upsample(V,F,number_of_subdivs=1) - E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) - E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) - EF,EI = igl.edge_flaps(F,uE,EMAP) - uE,EMAP,EF,EI = igl.edge_flaps(F) - Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) - - e = 0 - p = V[uE[e,0],:] - e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) - -def test_cut(): - V,F,T = single_tet() - C = np.ones(F.shape,dtype=bool) - Vn,Fn,I = igl.cut_mesh(V,F,C) - -def test_adjacency(): - V,F,T = single_tet() - TT,TTi = igl.triangle_triangle_adjacency(F) - TT,TTi = igl.triangle_triangle_adjacency_lists(F) - - VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) - VF,VFi = igl.vertex_triangle_adjacency(F) - - F012 = F; - F120 = np.roll(F012,1,axis=1) - FF = np.vstack((F012,F120)) - F,IA,IC = igl.unique_simplices(FF) - A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) - B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) - IA,LOCB = igl.ismember_rows(A,B) - A = igl.adjacency_list(F) - A = igl.adjacency_list(F,sorted=True) - -def test_min_quad(): - V,F,T = single_tet() - A = -igl.cotmatrix(V,F) - B = np.zeros((V.shape[0],1),dtype=np.float64) - known = np.array([1,2],dtype=np.int64) - Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) - Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) - Beq = np.zeros((1,1),dtype=np.float64) - Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) - data = igl.min_quad_with_fixed_data() - igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) - Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) - -def test_volume(): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - T = np.array([[0,1,2,3]],dtype=np.int64) - L = igl.edge_lengths(V,T) - vol = igl.volume(V,T) - vol = igl.volume(L=L) - vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) - F,_,_ = igl.boundary_facets(T) - # remove last face - F = F[:-1,:] - B = igl.is_border_vertex(F) - I,C = igl.on_boundary(F) - I,C = igl.on_boundary(T) - F = np.array([[2,1,3]],dtype=np.int64) - NV,NF,I,J = igl.remove_unreferenced(V,F) - -def test_oriented_facets(): - V,F,T = single_tet() - E = igl.oriented_facets(F) - F = igl.oriented_facets(T) - - res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) - res,_,_,_,_ = igl.is_edge_manifold(F) - -def test_matlab_format(): - V,F = triangulated_square() - L = igl.cotmatrix(V,F) - s = igl.matlab_format(V,"V") - s = igl.matlab_format_index(F,"F") - s = igl.matlab_format(L,"L") - -def test_polar(): - # random randn 3x3 matrix - np.random.seed(42) - A = np.random.randn(3,3).astype(np.float64) - igl.polar_svd(A) - out = igl.polar_svd(A) - R,T,_,_,_ = igl.polar_svd(A) - R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) - -def test_remove(): - V,F = triangulated_square() - SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) - SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) - -def test_project(): - V,F,_ = single_tet() - model = np.eye(4).astype(np.float64) - proj = np.eye(4).astype(np.float64) - viewport = np.array([0,0,640,480],dtype=np.float64) - win = igl.project(V,model,proj,viewport) - scene = igl.unproject(win,model,proj,viewport) - - S = np.array([0,0,0],dtype=np.float64) - D = np.array([0.2,0.2,0.2],dtype=np.float64) - t,sqrD = igl.project_to_line(V,S,D) - t,sqrD = igl.project_to_line_segment(V,S,D) - -def test_boundary_loop(): - V,F,_ = single_tet() - S,_ = igl.upsample_matrix(F,n=V.shape[0]) - S,_ = igl.loop_matrix(F,n=V.shape[0]) - V,F = igl.upsample(V,F,number_of_subdivs=1) - V,F = igl.loop(V,F,number_of_subdivs=1) - # remove first and last - F = F[1:-1,:] - L_all = igl.boundary_loop_all(F) - L = igl.boundary_loop(F) - -def test_voxel(): - V,_,_ = single_tet() - GV,side = igl.voxel_grid(V,s=10) - GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) - - -def test_sample(): - V,F = igl.icosahedron() - B,I,X = igl.random_points_on_mesh(10,V,F) - point_indices, CH,CN,W = igl.octree(X) - I = igl.knn(X,X,1,point_indices,CH,CN,W) - B,FI,P = igl.blue_noise(V,F,0.5) - -def test_curvature(): - V,F = igl.icosahedron() - K = igl.gaussian_curvature(V,F) - PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) - PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) - -def test_geodesic(): - V,F = igl.icosahedron() - VS = np.array([0],dtype=np.int64) - VT = np.array([1],dtype=np.int64) - D = igl.exact_geodesic(V,F,VS=VS,VT=VT) - -def test_decimate(): - V,F = igl.icosahedron() - dV,dF,J,I = igl.decimate(V,F) - dV,dF,J,I = igl.qslim(V,F) - -def test_parameterization(): - V,Q,E = igl.quad_grid(3,3); - V,F = igl.triangulated_grid(3,3); - # slim needs 3D data even if the problem is 2D - V = np.c_[V, np.zeros(V.shape[0])] - V_init = V[:,:2] - V_init[:,0] = V_init[:,0] * 2 - b = np.array([0,1],dtype=np.int64) - bc = V[b,:2] - data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) - U = igl.slim_solve(data,iter_num=1) - - data = igl.ARAPData() - data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS - V = V[:,:2] - igl.arap_precomputation(V,F,V.shape[1],b,data) - U = igl.arap_solve(bc,data,U) - - U,Q = igl.lscm(V,F,b,bc) - - -def test_implicit(): - res = np.array([3,3,3],dtype=np.int64) - GV = igl.grid(res) - S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; - V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) - # unpack keys into (i,j,v) index triplets - EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) - - h = igl.avg_edge_length(V,F) - m0,m1,m2 = igl.moments(V,F) - D = V[:,0] - vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) - iV,iE,I = igl.isolines(V,F,S,vals) - iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) - - -def test_kelvinlets(): - V,F = igl.icosahedron() - x0 = V[0,:] - f = np.array([0,0,1],dtype=np.float64) - R = np.eye(3).astype(np.float64) - U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) - - SV = V[:,0] - SF = igl.average_onto_faces(F,SV) - SV = igl.average_onto_vertices(V,F,SF) - -def test_polygons(): - P = [[0,1,2],[2,1,3,4]] - I,C = igl.polygon_corners(P) - Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) - I,C = igl.polygon_corners(Q) - F,J = igl.polygons_to_triangles(I,C) - -def test_heat(): - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) - V,F = igl.upsample(V,F,number_of_subdivs=1) - data = igl.HeatGeodesicsData() - igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) - data = igl.HeatGeodesicsData() - data.use_intrinsic_delaunay = True - igl.heat_geodesics_precompute(V,F,data) - gamma = np.array([0],dtype=np.int64) - D = igl.heat_geodesics_solve(data,gamma) - l = igl.edge_lengths(V,F) - iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) - L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) - L = igl.cotmatrix_intrinsic(il,iF) - M = igl.massmatrix_intrinsic(il,iF) - -def test_nonmanifold(): - V,F = igl.icosahedron() - FF,C = igl.bfs_orient(F) - SF,SVI = igl.split_nonmanifold(F) - SV,SF,SVI = igl.split_nonmanifold(V,F) - -def test_biharmonic(): - V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) - T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) - S = [[0],[5]] - W = igl.biharmonic_coordinates(V,T,S) - -def test_bijective(): - # 2D triangulated grid - V,F = igl.triangulated_grid(3,3) - ear,ear_opp = igl.ears(F) - # we don't have igl.flip_ears so construct it by hand - res = np.array([3,3],dtype=np.int64) - E,_,_ = igl.boundary_facets(F) - F = E - # add a 3rd column of 4s - F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) - # boundary b - b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) - bc = V[b,:] - bc[-1,:] = [0.5,0.5] - U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) - - -def test_copyleft(): - V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) - T = np.array([[0,1,2,3]],dtype=np.int64) - F,_,_ = igl.boundary_facets(T) - V,F = igl.loop(V,F) - - dV,dF,J = igl.copyleft.progressive_hulls(V,F) - - -def test_cgal(): - # tetrahedron - VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) - T = np.array([[0,1,2,3]],dtype=np.int64) - FA,_,_ = igl.boundary_facets(T) - # flip z - VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) - FB = FA[:,::-1] - IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) - IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) - VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") - H = igl.copyleft.cgal.convex_hull(VC) - # concatenate A and B meshes - VC = np.vstack((VA,VB)) - FC = np.vstack((FA,FB+VA.shape[0])) - VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) - _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) - - p = np.array([0,0,0],dtype=np.float64) - n = np.array([1,1,1],dtype=np.float64) - VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) - equ = np.hstack((n,-n.dot(p))) - VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) - - P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) - W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) - W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) - - VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) - - _,I,X = igl.random_points_on_mesh(1000,VC,FC) - N = igl.per_face_normals(VC,FC) - N = N[I,:] - point_indices, CH,CN,W = igl.octree(X) - I = igl.knn(X,X,20,point_indices,CH,CN,W) - A,T = igl.copyleft.cgal.point_areas(X,I,N) - -def test_embree(): - # octahedron - V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) - F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) - N = igl.per_vertex_normals(V,F) - ei = igl.embree.EmbreeIntersector(); - ei.init(V,F) - S = igl.embree.ambient_occlusion(V,F,V,N,100) - S = igl.embree.ambient_occlusion(ei,V,N,100) - N = -N - S = igl.embree.shape_diameter_function(V,F,V,N,100) - S = igl.embree.shape_diameter_function(ei,V,N,100) - origin = np.array([2,2,2],dtype=np.float64) - direction = np.array([-2,-2,-2],dtype=np.float64) - hit = ei.intersectRay_first(origin,direction) - hits = ei.intersectRay(origin,direction) - hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) - I,C = igl.embree.reorient_facets_raycast(V,F) - -def test_tetgen(): - # octahedron - V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) - F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) - V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") - -def test_triangle(): - V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) - E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) - V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") - V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") - V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") - V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) - F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) - scaf_data = igl.triangle.SCAFData() - b = np.array([0,1,2],dtype=np.int64) - bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) - V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) - soft_p = 0; - igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) - L,rhs = igl.triangle.scaf_system(scaf_data) - U = igl.triangle.scaf_solve(1,scaf_data) - -def test_misc(): - V,F = igl.icosahedron() - BV,BF = igl.bounding_box(V,pad=1.0) - R,C,B = igl.circumradius(V,F) - R = igl.inradius(V,F) - _,E,EMAP,_,_ = igl.unique_edge_map(F) - L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) - M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) - cuts = igl.cut_to_disk(F) - V,F = igl.cylinder(10,10) - VD,FD = igl.false_barycentric_subdivision(V,F) - V,F,T = single_tet() - theta, cos_theta = igl.dihedral_angles(V,T) - L = igl.edge_lengths(V,T) - A = igl.face_areas(V,T) - theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) - - +import igl.copyleft +import igl.copyleft.tetgen +#import igl.copyleft.cgal +#import igl.embree +import igl.triangle +# +# +##def rand_sparse(n,density): +## n_features = n +## n_samples = n +## rng1 = np.random.RandomState(42) +## rng2 = np.random.RandomState(43) +## +## nnz = int(n_samples*n_features*density) +## +## row = rng1.randint(n_samples, size=nnz) +## cols = rng2.randint(n_features, size=nnz) +## data = rng1.rand(nnz) +## +## S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) +## return S.tocsc() +## +##def time_noop(): +## def helper(N,I,SN,SI): +## igl.noop(SN=SN) +## # start timer +## runs = 100 +## start = time.time() +## for i in range(runs): +## igl.noop(SN=SN) +## # end timer +## end = time.time() +## return (end - start)/runs +## n = 10000 +## m = 10 +## N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) +## I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) +## # random sparse matrix +## SN64 = rand_sparse(n,1.0/(n)) +## # print number of nonzeros +## SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) +## print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +## +##time_noop() +# +## print(igl.matlab_format(V,"V")) +## print(igl.matlab_format_index(F,"F")) +## print(igl.matlab_format(dV,"dV")) +## print(igl.matlab_format_index(dF,"dF")) +# +# +##seed numpy's random number generator +# +#def triangulated_square(): +# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) +# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) +# return V,F +# +#def single_tet(): +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# return V,F,T +# +#def test_edges(): +# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) +# E,oE = igl.orient_halfedges(F) +# ne = E.max()+1 +# np.random.seed(42) +# uE = np.random.rand(ne).astype(np.float64) +# +# uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) +# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) +# +#def test_read_write(): +# V,F = triangulated_square() +# igl.write_triangle_mesh("out.obj",V,F) +# igl.write_triangle_mesh("out.off",V,F) +# igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) +# igl.writePLY("out.ply",V,F) +# igl.writeOBJ("out.obj",V,F) +# igl.writeOBJ("out.obj",V,F,V,F,V,F) +# V,F,_ = igl.readOFF("out.off") +# V,_,_,F,_,_ = igl.readOBJ("out.obj") +# V,F = igl.read_triangle_mesh("out.ply") +# igl.writeDMAT("out.dmat",V) +# igl.writeDMAT("out.dmat",V,ascii=True) +# V = igl.readDMAT("out.dmat") +# igl.writeDMAT("out.dmat",V,ascii=False) +# V = igl.readDMAT("out.dmat") +# +#def test_operators(): +# V,F = triangulated_square() +# L = igl.cotmatrix(V,F) +# I = np.array([0,1,2,3],dtype=np.int64) +# C = np.array([0,4],dtype=np.int64) +# L,M,P = igl.cotmatrix(V,I,C) +# L = igl.squared_edge_lengths(V,F) +# K = igl.cotmatrix_entries(V,F) +# l = igl.edge_lengths(V,F) +# K = igl.cotmatrix_entries(l=l) +# B1,B2,B3 = igl.local_basis(V,F) +# G = igl.grad(V,F) +# +# M = igl.massmatrix(V,F) +# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) +# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) +# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) +# +# A = igl.facet_adjacency_matrix(F) +# A = igl.adjacency_matrix(F) +# A = igl.adjacency_matrix(I,C) +# E = igl.edges(F) +# E = igl.edges(I,C) +# E = igl.edges(A) +# n,C,K = igl.connected_components(A) +# C = igl.vertex_components(F) +# nc,C = igl.facet_components(F) +# +#def test_normals_and_distances(): +# V,F = triangulated_square() +# I = np.array([0,1,2,3],dtype=np.int64) +# C = np.array([0,4],dtype=np.int64) +# FN,_,_,_ = igl.per_face_normals(V,I,C) +# FN = igl.per_face_normals(V,F) +# FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) +# VN = igl.per_vertex_normals(V,F) +# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) +# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) +# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) +# VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) +# dblA = igl.doublearea(V,F) +# l = igl.edge_lengths(V,F) +# dblA = igl.doublearea(l=l) +# dblA = igl.doublearea(l=l,nan_replacement=0.0) +# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) +# sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) +# W = igl.winding_number(V,F,P) +# W = igl.winding_number(V,F,P[1,:]) +# W = igl.fast_winding_number(V,F,P) +# S,I,C,N = igl.signed_distance(P,V,F) +# S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) +# BC = igl.barycenter(V,F) +# +#def test_harmonic(): +# V,F = triangulated_square() +# b = np.array([0,3],dtype=np.int64) +# bc = np.array([[1,0],[0,1]],dtype=np.float64) +# W = igl.bbw(V,F,b,bc) +# W = igl.harmonic(V,F,b,bc,k=1) +# W = igl.harmonic(V,F,b,bc,k=2) +# +#def test_tets(): +# V,F,T = single_tet() +# F,J,K = igl.boundary_facets(T) +# igl.writeMESH("out.mesh",V,T) +# igl.writeMESH("out.mesh",V,T,F=F) +# V,T,F = igl.readMESH("out.mesh") +# igl.writeMSH("out.msh",V,F,T) +# V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") +# +#def test_bvh(): +# V,F,T = single_tet() +# tree = igl.AABB() +# tree.init(V,T) +# +# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) +# # first row of P +# q = P[0,:] +# I = tree.find(V,T,q) +# i = tree.find(V,T,q,first=True) +# +# Q = igl.barycenter(V,T) +# I = igl.in_element(V,T,Q,tree) +# +# tree = igl.AABB() +# tree.init(V,F) +# sqrD,_,_ = tree.squared_distance(V,F,P) +# +# O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); +# D = V.mean(axis=0)-O +# I,T,UV = tree.intersect_ray_first(V,F,O,D) +# hits = tree.intersect_ray(V,F,O,D) +# o = O[0,:] +# d = D[0,:] +# hit = igl.ray_mesh_intersect(o,d,V,F,first=True) +# hits = igl.ray_mesh_intersect(o,d,V,F) +# +# +#def test_upsample(): +# V,F,T = single_tet() +# # upsample so there's something to collapse +# V,F = igl.upsample(V,F,number_of_subdivs=1) +# E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) +# E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) +# EF,EI = igl.edge_flaps(F,uE,EMAP) +# uE,EMAP,EF,EI = igl.edge_flaps(F) +# Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) +# +# e = 0 +# p = V[uE[e,0],:] +# e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) +# +#def test_cut(): +# V,F,T = single_tet() +# C = np.ones(F.shape,dtype=bool) +# Vn,Fn,I = igl.cut_mesh(V,F,C) +# +#def test_adjacency(): +# V,F,T = single_tet() +# TT,TTi = igl.triangle_triangle_adjacency(F) +# TT,TTi = igl.triangle_triangle_adjacency_lists(F) +# +# VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) +# VF,VFi = igl.vertex_triangle_adjacency(F) +# +# F012 = F; +# F120 = np.roll(F012,1,axis=1) +# FF = np.vstack((F012,F120)) +# F,IA,IC = igl.unique_simplices(FF) +# A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) +# B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) +# IA,LOCB = igl.ismember_rows(A,B) +# A = igl.adjacency_list(F) +# A = igl.adjacency_list(F,sorted=True) +# +#def test_min_quad(): +# V,F,T = single_tet() +# A = -igl.cotmatrix(V,F) +# B = np.zeros((V.shape[0],1),dtype=np.float64) +# known = np.array([1,2],dtype=np.int64) +# Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) +# Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) +# Beq = np.zeros((1,1),dtype=np.float64) +# Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) +# data = igl.min_quad_with_fixed_data() +# igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) +# Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) +# +#def test_volume(): +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# L = igl.edge_lengths(V,T) +# vol = igl.volume(V,T) +# vol = igl.volume(L=L) +# vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) +# F,_,_ = igl.boundary_facets(T) +# # remove last face +# F = F[:-1,:] +# B = igl.is_border_vertex(F) +# I,C = igl.on_boundary(F) +# I,C = igl.on_boundary(T) +# F = np.array([[2,1,3]],dtype=np.int64) +# NV,NF,I,J = igl.remove_unreferenced(V,F) +# +#def test_oriented_facets(): +# V,F,T = single_tet() +# E = igl.oriented_facets(F) +# F = igl.oriented_facets(T) +# +# res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) +# res,_,_,_,_ = igl.is_edge_manifold(F) +# +#def test_matlab_format(): +# V,F = triangulated_square() +# L = igl.cotmatrix(V,F) +# s = igl.matlab_format(V,"V") +# s = igl.matlab_format_index(F,"F") +# s = igl.matlab_format(L,"L") +# +#def test_polar(): +# # random randn 3x3 matrix +# np.random.seed(42) +# A = np.random.randn(3,3).astype(np.float64) +# igl.polar_svd(A) +# out = igl.polar_svd(A) +# R,T,_,_,_ = igl.polar_svd(A) +# R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) +# +#def test_remove(): +# V,F = triangulated_square() +# SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) +# SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) +# +#def test_project(): +# V,F,_ = single_tet() +# model = np.eye(4).astype(np.float64) +# proj = np.eye(4).astype(np.float64) +# viewport = np.array([0,0,640,480],dtype=np.float64) +# win = igl.project(V,model,proj,viewport) +# scene = igl.unproject(win,model,proj,viewport) +# +# S = np.array([0,0,0],dtype=np.float64) +# D = np.array([0.2,0.2,0.2],dtype=np.float64) +# t,sqrD = igl.project_to_line(V,S,D) +# t,sqrD = igl.project_to_line_segment(V,S,D) +# +#def test_boundary_loop(): +# V,F,_ = single_tet() +# S,_ = igl.upsample_matrix(F,n=V.shape[0]) +# S,_ = igl.loop_matrix(F,n=V.shape[0]) +# V,F = igl.upsample(V,F,number_of_subdivs=1) +# V,F = igl.loop(V,F,number_of_subdivs=1) +# # remove first and last +# F = F[1:-1,:] +# L_all = igl.boundary_loop_all(F) +# L = igl.boundary_loop(F) +# +#def test_voxel(): +# V,_,_ = single_tet() +# GV,side = igl.voxel_grid(V,s=10) +# GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) +# +# +#def test_sample(): +# V,F = igl.icosahedron() +# B,I,X = igl.random_points_on_mesh(10,V,F) +# point_indices, CH,CN,W = igl.octree(X) +# I = igl.knn(X,X,1,point_indices,CH,CN,W) +# B,FI,P = igl.blue_noise(V,F,0.5) +# +#def test_curvature(): +# V,F = igl.icosahedron() +# K = igl.gaussian_curvature(V,F) +# PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) +# PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) +# +#def test_geodesic(): +# V,F = igl.icosahedron() +# VS = np.array([0],dtype=np.int64) +# VT = np.array([1],dtype=np.int64) +# D = igl.exact_geodesic(V,F,VS=VS,VT=VT) +# +#def test_decimate(): +# V,F = igl.icosahedron() +# dV,dF,J,I = igl.decimate(V,F) +# dV,dF,J,I = igl.qslim(V,F) +# +#def test_parameterization(): +# V,Q,E = igl.quad_grid(3,3); +# V,F = igl.triangulated_grid(3,3); +# # slim needs 3D data even if the problem is 2D +# V = np.c_[V, np.zeros(V.shape[0])] +# V_init = V[:,:2] +# V_init[:,0] = V_init[:,0] * 2 +# b = np.array([0,1],dtype=np.int64) +# bc = V[b,:2] +# data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) +# U = igl.slim_solve(data,iter_num=1) +# +# data = igl.ARAPData() +# data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS +# V = V[:,:2] +# igl.arap_precomputation(V,F,V.shape[1],b,data) +# U = igl.arap_solve(bc,data,U) +# +# U,Q = igl.lscm(V,F,b,bc) +# +# +#def test_implicit(): +# res = np.array([3,3,3],dtype=np.int64) +# GV = igl.grid(res) +# S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; +# V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) +# # unpack keys into (i,j,v) index triplets +# EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) +# +# h = igl.avg_edge_length(V,F) +# m0,m1,m2 = igl.moments(V,F) +# D = V[:,0] +# vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) +# iV,iE,I = igl.isolines(V,F,S,vals) +# iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) +# +# +#def test_kelvinlets(): +# V,F = igl.icosahedron() +# x0 = V[0,:] +# f = np.array([0,0,1],dtype=np.float64) +# R = np.eye(3).astype(np.float64) +# U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) +# +# SV = V[:,0] +# SF = igl.average_onto_faces(F,SV) +# SV = igl.average_onto_vertices(V,F,SF) +# +#def test_polygons(): +# P = [[0,1,2],[2,1,3,4]] +# I,C = igl.polygon_corners(P) +# Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) +# I,C = igl.polygon_corners(Q) +# F,J = igl.polygons_to_triangles(I,C) +# +#def test_heat(): +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +# V,F = igl.upsample(V,F,number_of_subdivs=1) +# data = igl.HeatGeodesicsData() +# igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) +# data = igl.HeatGeodesicsData() +# data.use_intrinsic_delaunay = True +# igl.heat_geodesics_precompute(V,F,data) +# gamma = np.array([0],dtype=np.int64) +# D = igl.heat_geodesics_solve(data,gamma) +# l = igl.edge_lengths(V,F) +# iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) +# L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) +# L = igl.cotmatrix_intrinsic(il,iF) +# M = igl.massmatrix_intrinsic(il,iF) +# +#def test_nonmanifold(): +# V,F = igl.icosahedron() +# FF,C = igl.bfs_orient(F) +# SF,SVI = igl.split_nonmanifold(F) +# SV,SF,SVI = igl.split_nonmanifold(V,F) +# +#def test_biharmonic(): +# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) +# T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) +# S = [[0],[5]] +# W = igl.biharmonic_coordinates(V,T,S) +# +#def test_bijective(): +# # 2D triangulated grid +# V,F = igl.triangulated_grid(3,3) +# ear,ear_opp = igl.ears(F) +# # we don't have igl.flip_ears so construct it by hand +# res = np.array([3,3],dtype=np.int64) +# E,_,_ = igl.boundary_facets(F) +# F = E +# # add a 3rd column of 4s +# F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) +# # boundary b +# b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) +# bc = V[b,:] +# bc[-1,:] = [0.5,0.5] +# U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) +# +# +#def test_copyleft(): +# V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# F,_,_ = igl.boundary_facets(T) +# V,F = igl.loop(V,F) +# +# dV,dF,J = igl.copyleft.progressive_hulls(V,F) +# +# +#def test_cgal(): +# # tetrahedron +# VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) +# T = np.array([[0,1,2,3]],dtype=np.int64) +# FA,_,_ = igl.boundary_facets(T) +# # flip z +# VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) +# FB = FA[:,::-1] +# IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) +# IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) +# VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") +# H = igl.copyleft.cgal.convex_hull(VC) +# # concatenate A and B meshes +# VC = np.vstack((VA,VB)) +# FC = np.vstack((FA,FB+VA.shape[0])) +# VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) +# _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) +# +# p = np.array([0,0,0],dtype=np.float64) +# n = np.array([1,1,1],dtype=np.float64) +# VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) +# equ = np.hstack((n,-n.dot(p))) +# VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) +# +# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) +# W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) +# W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) +# +# VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) +# +# _,I,X = igl.random_points_on_mesh(1000,VC,FC) +# N = igl.per_face_normals(VC,FC) +# N = N[I,:] +# point_indices, CH,CN,W = igl.octree(X) +# I = igl.knn(X,X,20,point_indices,CH,CN,W) +# A,T = igl.copyleft.cgal.point_areas(X,I,N) +# +#def test_embree(): +# # octahedron +# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) +# F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) +# N = igl.per_vertex_normals(V,F) +# ei = igl.embree.EmbreeIntersector(); +# ei.init(V,F) +# S = igl.embree.ambient_occlusion(V,F,V,N,100) +# S = igl.embree.ambient_occlusion(ei,V,N,100) +# N = -N +# S = igl.embree.shape_diameter_function(V,F,V,N,100) +# S = igl.embree.shape_diameter_function(ei,V,N,100) +# origin = np.array([2,2,2],dtype=np.float64) +# direction = np.array([-2,-2,-2],dtype=np.float64) +# hit = ei.intersectRay_first(origin,direction) +# hits = ei.intersectRay(origin,direction) +# hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) +# I,C = igl.embree.reorient_facets_raycast(V,F) +# +#def test_tetgen(): +# # octahedron +# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) +# F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) +# V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") +# +#def test_triangle(): +# V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) +# E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) +# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") +# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") +# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") +# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) +# F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) +# scaf_data = igl.triangle.SCAFData() +# b = np.array([0,1,2],dtype=np.int64) +# bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) +# V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) +# soft_p = 0; +# igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) +# L,rhs = igl.triangle.scaf_system(scaf_data) +# U = igl.triangle.scaf_solve(1,scaf_data) +# +#def test_misc(): +# V,F = igl.icosahedron() +# BV,BF = igl.bounding_box(V,pad=1.0) +# R,C,B = igl.circumradius(V,F) +# R = igl.inradius(V,F) +# _,E,EMAP,_,_ = igl.unique_edge_map(F) +# L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) +# M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) +# cuts = igl.cut_to_disk(F) +# V,F = igl.cylinder(10,10) +# VD,FD = igl.false_barycentric_subdivision(V,F) +# V,F,T = single_tet() +# theta, cos_theta = igl.dihedral_angles(V,T) +# L = igl.edge_lengths(V,T) +# A = igl.face_areas(V,T) +# theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) +# +# From e1c798333ae52c510165e3133517ac79ba417986 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 19:23:34 -0500 Subject: [PATCH 081/102] all triangle and tetgen --- CMakeLists.txt | 8 ++++---- tests/test_all.py | 50 +++++++++++++++++++++++------------------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 495cff2e..0dac25d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,10 +95,10 @@ function(pyigl_include prefix name) endif() file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/*.cpp") - # Just compile a single file - list(FILTER sources EXCLUDE REGEX ".*/module\\.cpp$") - list(GET sources 0 sources) - list(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/module.cpp") + ## Just compile a single file + #list(FILTER sources EXCLUDE REGEX ".*/module\\.cpp$") + #list(GET sources 0 sources) + #list(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/src/${subpath}/module.cpp") message(STATUS "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$") message(STATUS "${prefix} ${name} sources: ${sources}") diff --git a/tests/test_all.py b/tests/test_all.py index 6b42021c..617a0e7a 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -10,11 +10,11 @@ def test_version(): version = igl.__version__ pass +import igl.triangle import igl.copyleft import igl.copyleft.tetgen #import igl.copyleft.cgal #import igl.embree -import igl.triangle # # ##def rand_sparse(n,density): @@ -513,30 +513,30 @@ def test_version(): # hits = ei.intersectRay(origin,direction) # hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) # I,C = igl.embree.reorient_facets_raycast(V,F) -# -#def test_tetgen(): -# # octahedron -# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) -# F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) -# V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") -# -#def test_triangle(): -# V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) -# E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) -# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") -# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") -# V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -# scaf_data = igl.triangle.SCAFData() -# b = np.array([0,1,2],dtype=np.int64) -# bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) -# V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) -# soft_p = 0; -# igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) -# L,rhs = igl.triangle.scaf_system(scaf_data) -# U = igl.triangle.scaf_solve(1,scaf_data) -# + +def test_tetgen(): + # octahedron + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) + V,T,F,_,_,_,_,_,_ = igl.copyleft.tetgen.tetrahedralize(V,F,flags="Q") + +def test_triangle(): + V = np.array([[0,0],[1,0],[1,1],[0,1]],dtype=np.float64) + E = np.array([[0,1],[1,2],[2,3],[3,0]],dtype=np.int64) + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qc") + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Q") + V,F,_,_,_ = igl.triangle.triangulate(V,E,flags="Qqa0.1") + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + scaf_data = igl.triangle.SCAFData() + b = np.array([0,1,2],dtype=np.int64) + bc = np.array([[0,0],[1,0],[0,1]],dtype=np.float64) + V_init = np.array([[0,0],[1,0],[0,1],[0.1,0.1]],dtype=np.float64) + soft_p = 0; + igl.triangle.scaf_precompute(V,F,V_init,igl.ARAP,b,bc,soft_p,scaf_data) + L,rhs = igl.triangle.scaf_system(scaf_data) + U = igl.triangle.scaf_solve(1,scaf_data) + #def test_misc(): # V,F = igl.icosahedron() # BV,BF = igl.bounding_box(V,pad=1.0) From 8811c4656fbe3c562d001649753f417f70424115 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 19:41:32 -0500 Subject: [PATCH 082/102] enable all tests --- tests/test_all.py | 868 +++++++++++++++++++++++----------------------- 1 file changed, 434 insertions(+), 434 deletions(-) diff --git a/tests/test_all.py b/tests/test_all.py index 617a0e7a..be2e30b6 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -15,440 +15,440 @@ def test_version(): import igl.copyleft.tetgen #import igl.copyleft.cgal #import igl.embree -# -# -##def rand_sparse(n,density): -## n_features = n -## n_samples = n -## rng1 = np.random.RandomState(42) -## rng2 = np.random.RandomState(43) -## -## nnz = int(n_samples*n_features*density) -## -## row = rng1.randint(n_samples, size=nnz) -## cols = rng2.randint(n_features, size=nnz) -## data = rng1.rand(nnz) -## -## S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) -## return S.tocsc() -## -##def time_noop(): -## def helper(N,I,SN,SI): -## igl.noop(SN=SN) -## # start timer -## runs = 100 -## start = time.time() -## for i in range(runs): -## igl.noop(SN=SN) -## # end timer -## end = time.time() -## return (end - start)/runs -## n = 10000 -## m = 10 -## N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) -## I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) -## # random sparse matrix -## SN64 = rand_sparse(n,1.0/(n)) -## # print number of nonzeros -## SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) -## print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") -## -##time_noop() -# -## print(igl.matlab_format(V,"V")) -## print(igl.matlab_format_index(F,"F")) -## print(igl.matlab_format(dV,"dV")) -## print(igl.matlab_format_index(dF,"dF")) -# -# -##seed numpy's random number generator -# -#def triangulated_square(): -# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) -# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) -# return V,F -# -#def single_tet(): -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# return V,F,T -# -#def test_edges(): -# F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) -# E,oE = igl.orient_halfedges(F) -# ne = E.max()+1 -# np.random.seed(42) -# uE = np.random.rand(ne).astype(np.float64) -# -# uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) -# V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) -# -#def test_read_write(): -# V,F = triangulated_square() -# igl.write_triangle_mesh("out.obj",V,F) -# igl.write_triangle_mesh("out.off",V,F) -# igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) -# igl.writePLY("out.ply",V,F) -# igl.writeOBJ("out.obj",V,F) -# igl.writeOBJ("out.obj",V,F,V,F,V,F) -# V,F,_ = igl.readOFF("out.off") -# V,_,_,F,_,_ = igl.readOBJ("out.obj") -# V,F = igl.read_triangle_mesh("out.ply") -# igl.writeDMAT("out.dmat",V) -# igl.writeDMAT("out.dmat",V,ascii=True) -# V = igl.readDMAT("out.dmat") -# igl.writeDMAT("out.dmat",V,ascii=False) -# V = igl.readDMAT("out.dmat") -# -#def test_operators(): -# V,F = triangulated_square() -# L = igl.cotmatrix(V,F) -# I = np.array([0,1,2,3],dtype=np.int64) -# C = np.array([0,4],dtype=np.int64) -# L,M,P = igl.cotmatrix(V,I,C) -# L = igl.squared_edge_lengths(V,F) -# K = igl.cotmatrix_entries(V,F) -# l = igl.edge_lengths(V,F) -# K = igl.cotmatrix_entries(l=l) -# B1,B2,B3 = igl.local_basis(V,F) -# G = igl.grad(V,F) -# -# M = igl.massmatrix(V,F) -# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) -# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) -# M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) -# -# A = igl.facet_adjacency_matrix(F) -# A = igl.adjacency_matrix(F) -# A = igl.adjacency_matrix(I,C) -# E = igl.edges(F) -# E = igl.edges(I,C) -# E = igl.edges(A) -# n,C,K = igl.connected_components(A) -# C = igl.vertex_components(F) -# nc,C = igl.facet_components(F) -# -#def test_normals_and_distances(): -# V,F = triangulated_square() -# I = np.array([0,1,2,3],dtype=np.int64) -# C = np.array([0,4],dtype=np.int64) -# FN,_,_,_ = igl.per_face_normals(V,I,C) -# FN = igl.per_face_normals(V,F) -# FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) -# VN = igl.per_vertex_normals(V,F) -# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) -# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) -# VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) -# VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) -# dblA = igl.doublearea(V,F) -# l = igl.edge_lengths(V,F) -# dblA = igl.doublearea(l=l) -# dblA = igl.doublearea(l=l,nan_replacement=0.0) -# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) -# sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) -# W = igl.winding_number(V,F,P) -# W = igl.winding_number(V,F,P[1,:]) -# W = igl.fast_winding_number(V,F,P) -# S,I,C,N = igl.signed_distance(P,V,F) -# S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) -# BC = igl.barycenter(V,F) -# -#def test_harmonic(): -# V,F = triangulated_square() -# b = np.array([0,3],dtype=np.int64) -# bc = np.array([[1,0],[0,1]],dtype=np.float64) -# W = igl.bbw(V,F,b,bc) -# W = igl.harmonic(V,F,b,bc,k=1) -# W = igl.harmonic(V,F,b,bc,k=2) -# -#def test_tets(): -# V,F,T = single_tet() -# F,J,K = igl.boundary_facets(T) -# igl.writeMESH("out.mesh",V,T) -# igl.writeMESH("out.mesh",V,T,F=F) -# V,T,F = igl.readMESH("out.mesh") -# igl.writeMSH("out.msh",V,F,T) -# V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") -# -#def test_bvh(): -# V,F,T = single_tet() -# tree = igl.AABB() -# tree.init(V,T) -# -# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) -# # first row of P -# q = P[0,:] -# I = tree.find(V,T,q) -# i = tree.find(V,T,q,first=True) -# -# Q = igl.barycenter(V,T) -# I = igl.in_element(V,T,Q,tree) -# -# tree = igl.AABB() -# tree.init(V,F) -# sqrD,_,_ = tree.squared_distance(V,F,P) -# -# O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); -# D = V.mean(axis=0)-O -# I,T,UV = tree.intersect_ray_first(V,F,O,D) -# hits = tree.intersect_ray(V,F,O,D) -# o = O[0,:] -# d = D[0,:] -# hit = igl.ray_mesh_intersect(o,d,V,F,first=True) -# hits = igl.ray_mesh_intersect(o,d,V,F) -# -# -#def test_upsample(): -# V,F,T = single_tet() -# # upsample so there's something to collapse -# V,F = igl.upsample(V,F,number_of_subdivs=1) -# E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) -# E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) -# EF,EI = igl.edge_flaps(F,uE,EMAP) -# uE,EMAP,EF,EI = igl.edge_flaps(F) -# Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) -# -# e = 0 -# p = V[uE[e,0],:] -# e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) -# -#def test_cut(): -# V,F,T = single_tet() -# C = np.ones(F.shape,dtype=bool) -# Vn,Fn,I = igl.cut_mesh(V,F,C) -# -#def test_adjacency(): -# V,F,T = single_tet() -# TT,TTi = igl.triangle_triangle_adjacency(F) -# TT,TTi = igl.triangle_triangle_adjacency_lists(F) -# -# VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) -# VF,VFi = igl.vertex_triangle_adjacency(F) -# -# F012 = F; -# F120 = np.roll(F012,1,axis=1) -# FF = np.vstack((F012,F120)) -# F,IA,IC = igl.unique_simplices(FF) -# A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) -# B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) -# IA,LOCB = igl.ismember_rows(A,B) -# A = igl.adjacency_list(F) -# A = igl.adjacency_list(F,sorted=True) -# -#def test_min_quad(): -# V,F,T = single_tet() -# A = -igl.cotmatrix(V,F) -# B = np.zeros((V.shape[0],1),dtype=np.float64) -# known = np.array([1,2],dtype=np.int64) -# Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) -# Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) -# Beq = np.zeros((1,1),dtype=np.float64) -# Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) -# data = igl.min_quad_with_fixed_data() -# igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) -# Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) -# -#def test_volume(): -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# L = igl.edge_lengths(V,T) -# vol = igl.volume(V,T) -# vol = igl.volume(L=L) -# vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) -# F,_,_ = igl.boundary_facets(T) -# # remove last face -# F = F[:-1,:] -# B = igl.is_border_vertex(F) -# I,C = igl.on_boundary(F) -# I,C = igl.on_boundary(T) -# F = np.array([[2,1,3]],dtype=np.int64) -# NV,NF,I,J = igl.remove_unreferenced(V,F) -# -#def test_oriented_facets(): -# V,F,T = single_tet() -# E = igl.oriented_facets(F) -# F = igl.oriented_facets(T) -# -# res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) -# res,_,_,_,_ = igl.is_edge_manifold(F) -# -#def test_matlab_format(): -# V,F = triangulated_square() -# L = igl.cotmatrix(V,F) -# s = igl.matlab_format(V,"V") -# s = igl.matlab_format_index(F,"F") -# s = igl.matlab_format(L,"L") -# -#def test_polar(): -# # random randn 3x3 matrix -# np.random.seed(42) -# A = np.random.randn(3,3).astype(np.float64) -# igl.polar_svd(A) -# out = igl.polar_svd(A) -# R,T,_,_,_ = igl.polar_svd(A) -# R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) -# -#def test_remove(): -# V,F = triangulated_square() -# SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) -# SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) -# -#def test_project(): -# V,F,_ = single_tet() -# model = np.eye(4).astype(np.float64) -# proj = np.eye(4).astype(np.float64) -# viewport = np.array([0,0,640,480],dtype=np.float64) -# win = igl.project(V,model,proj,viewport) -# scene = igl.unproject(win,model,proj,viewport) -# -# S = np.array([0,0,0],dtype=np.float64) -# D = np.array([0.2,0.2,0.2],dtype=np.float64) -# t,sqrD = igl.project_to_line(V,S,D) -# t,sqrD = igl.project_to_line_segment(V,S,D) -# -#def test_boundary_loop(): -# V,F,_ = single_tet() -# S,_ = igl.upsample_matrix(F,n=V.shape[0]) -# S,_ = igl.loop_matrix(F,n=V.shape[0]) -# V,F = igl.upsample(V,F,number_of_subdivs=1) -# V,F = igl.loop(V,F,number_of_subdivs=1) -# # remove first and last -# F = F[1:-1,:] -# L_all = igl.boundary_loop_all(F) -# L = igl.boundary_loop(F) -# -#def test_voxel(): -# V,_,_ = single_tet() -# GV,side = igl.voxel_grid(V,s=10) -# GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) -# -# -#def test_sample(): -# V,F = igl.icosahedron() -# B,I,X = igl.random_points_on_mesh(10,V,F) -# point_indices, CH,CN,W = igl.octree(X) -# I = igl.knn(X,X,1,point_indices,CH,CN,W) -# B,FI,P = igl.blue_noise(V,F,0.5) -# -#def test_curvature(): -# V,F = igl.icosahedron() -# K = igl.gaussian_curvature(V,F) -# PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) -# PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) -# -#def test_geodesic(): -# V,F = igl.icosahedron() -# VS = np.array([0],dtype=np.int64) -# VT = np.array([1],dtype=np.int64) -# D = igl.exact_geodesic(V,F,VS=VS,VT=VT) -# -#def test_decimate(): -# V,F = igl.icosahedron() -# dV,dF,J,I = igl.decimate(V,F) -# dV,dF,J,I = igl.qslim(V,F) -# -#def test_parameterization(): -# V,Q,E = igl.quad_grid(3,3); -# V,F = igl.triangulated_grid(3,3); -# # slim needs 3D data even if the problem is 2D -# V = np.c_[V, np.zeros(V.shape[0])] -# V_init = V[:,:2] -# V_init[:,0] = V_init[:,0] * 2 -# b = np.array([0,1],dtype=np.int64) -# bc = V[b,:2] -# data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) -# U = igl.slim_solve(data,iter_num=1) -# -# data = igl.ARAPData() -# data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS -# V = V[:,:2] -# igl.arap_precomputation(V,F,V.shape[1],b,data) -# U = igl.arap_solve(bc,data,U) -# -# U,Q = igl.lscm(V,F,b,bc) -# -# -#def test_implicit(): -# res = np.array([3,3,3],dtype=np.int64) -# GV = igl.grid(res) -# S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; -# V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) -# # unpack keys into (i,j,v) index triplets -# EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) -# -# h = igl.avg_edge_length(V,F) -# m0,m1,m2 = igl.moments(V,F) -# D = V[:,0] -# vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) -# iV,iE,I = igl.isolines(V,F,S,vals) -# iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) -# -# -#def test_kelvinlets(): -# V,F = igl.icosahedron() -# x0 = V[0,:] -# f = np.array([0,0,1],dtype=np.float64) -# R = np.eye(3).astype(np.float64) -# U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) -# -# SV = V[:,0] -# SF = igl.average_onto_faces(F,SV) -# SV = igl.average_onto_vertices(V,F,SF) -# -#def test_polygons(): -# P = [[0,1,2],[2,1,3,4]] -# I,C = igl.polygon_corners(P) -# Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) -# I,C = igl.polygon_corners(Q) -# F,J = igl.polygons_to_triangles(I,C) -# -#def test_heat(): -# V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) -# F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) -# V,F = igl.upsample(V,F,number_of_subdivs=1) -# data = igl.HeatGeodesicsData() -# igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) -# data = igl.HeatGeodesicsData() -# data.use_intrinsic_delaunay = True -# igl.heat_geodesics_precompute(V,F,data) -# gamma = np.array([0],dtype=np.int64) -# D = igl.heat_geodesics_solve(data,gamma) -# l = igl.edge_lengths(V,F) -# iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) -# L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) -# L = igl.cotmatrix_intrinsic(il,iF) -# M = igl.massmatrix_intrinsic(il,iF) -# -#def test_nonmanifold(): -# V,F = igl.icosahedron() -# FF,C = igl.bfs_orient(F) -# SF,SVI = igl.split_nonmanifold(F) -# SV,SF,SVI = igl.split_nonmanifold(V,F) -# -#def test_biharmonic(): -# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) -# T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) -# S = [[0],[5]] -# W = igl.biharmonic_coordinates(V,T,S) -# -#def test_bijective(): -# # 2D triangulated grid -# V,F = igl.triangulated_grid(3,3) -# ear,ear_opp = igl.ears(F) -# # we don't have igl.flip_ears so construct it by hand -# res = np.array([3,3],dtype=np.int64) -# E,_,_ = igl.boundary_facets(F) -# F = E -# # add a 3rd column of 4s -# F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) -# # boundary b -# b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) -# bc = V[b,:] -# bc[-1,:] = [0.5,0.5] -# U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) -# -# + + +#def rand_sparse(n,density): +# n_features = n +# n_samples = n +# rng1 = np.random.RandomState(42) +# rng2 = np.random.RandomState(43) +# +# nnz = int(n_samples*n_features*density) +# +# row = rng1.randint(n_samples, size=nnz) +# cols = rng2.randint(n_features, size=nnz) +# data = rng1.rand(nnz) +# +# S = scipy.sparse.coo_matrix((data, (row, cols)), shape=(n_samples, n_features)) +# return S.tocsc() +# +#def time_noop(): +# def helper(N,I,SN,SI): +# igl.noop(SN=SN) +# # start timer +# runs = 100 +# start = time.time() +# for i in range(runs): +# igl.noop(SN=SN) +# # end timer +# end = time.time() +# return (end - start)/runs +# n = 10000 +# m = 10 +# N64_f = np.asfortranarray(np.random.randn(n,m).astype(np.float64)) +# I64_f = np.asfortranarray(np.random.randn(n,m).astype(np.int64)) +# # random sparse matrix +# SN64 = rand_sparse(n,1.0/(n)) +# # print number of nonzeros +# SI64 = (rand_sparse(n,1.0/(n))*1000).astype(np.int64) +# print(f"noop<{n},{m}>: {helper(N64_f,I64_f,SN64,SI64)} secs") +# +#time_noop() + +# print(igl.matlab_format(V,"V")) +# print(igl.matlab_format_index(F,"F")) +# print(igl.matlab_format(dV,"dV")) +# print(igl.matlab_format_index(dF,"dF")) + + +#seed numpy's random number generator + +def triangulated_square(): + V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) + F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) + return V,F + +def single_tet(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + T = np.array([[0,1,2,3]],dtype=np.int64) + return V,F,T + +def test_edges(): + F = np.array([[0,1,2],[0,2,3]],dtype=np.int64) + E,oE = igl.orient_halfedges(F) + ne = E.max()+1 + np.random.seed(42) + uE = np.random.rand(ne).astype(np.float64) + + uV = igl.average_from_edges_onto_vertices(F,E,oE,uE) + V = np.array([[0,0,0],[1,0,0],[1,1,0],[0,1,0]],dtype=np.float64) + +def test_read_write(): + V,F = triangulated_square() + igl.write_triangle_mesh("out.obj",V,F) + igl.write_triangle_mesh("out.off",V,F) + igl.write_triangle_mesh("out.ply",V,F,encoding=igl.Binary) + igl.writePLY("out.ply",V,F) + igl.writeOBJ("out.obj",V,F) + igl.writeOBJ("out.obj",V,F,V,F,V,F) + V,F,_ = igl.readOFF("out.off") + V,_,_,F,_,_ = igl.readOBJ("out.obj") + V,F = igl.read_triangle_mesh("out.ply") + igl.writeDMAT("out.dmat",V) + igl.writeDMAT("out.dmat",V,ascii=True) + V = igl.readDMAT("out.dmat") + igl.writeDMAT("out.dmat",V,ascii=False) + V = igl.readDMAT("out.dmat") + +def test_operators(): + V,F = triangulated_square() + L = igl.cotmatrix(V,F) + I = np.array([0,1,2,3],dtype=np.int64) + C = np.array([0,4],dtype=np.int64) + L,M,P = igl.cotmatrix(V,I,C) + L = igl.squared_edge_lengths(V,F) + K = igl.cotmatrix_entries(V,F) + l = igl.edge_lengths(V,F) + K = igl.cotmatrix_entries(l=l) + B1,B2,B3 = igl.local_basis(V,F) + G = igl.grad(V,F) + + M = igl.massmatrix(V,F) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_BARYCENTRIC) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_VORONOI) + M = igl.massmatrix(V,F,type=igl.MASSMATRIX_TYPE_DEFAULT) + + A = igl.facet_adjacency_matrix(F) + A = igl.adjacency_matrix(F) + A = igl.adjacency_matrix(I,C) + E = igl.edges(F) + E = igl.edges(I,C) + E = igl.edges(A) + n,C,K = igl.connected_components(A) + C = igl.vertex_components(F) + nc,C = igl.facet_components(F) + +def test_normals_and_distances(): + V,F = triangulated_square() + I = np.array([0,1,2,3],dtype=np.int64) + C = np.array([0,4],dtype=np.int64) + FN,_,_,_ = igl.per_face_normals(V,I,C) + FN = igl.per_face_normals(V,F) + FN = igl.per_face_normals(V,F,Z=np.array([0,0,1],dtype=np.float64)) + VN = igl.per_vertex_normals(V,F) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA) + VN = igl.per_vertex_normals(V,F, weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) + VN = igl.per_vertex_normals(V,F,FN=FN,weighting=igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) + dblA = igl.doublearea(V,F) + l = igl.edge_lengths(V,F) + dblA = igl.doublearea(l=l) + dblA = igl.doublearea(l=l,nan_replacement=0.0) + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + sqrD,I,C = igl.point_mesh_squared_distance(P,V,F) + W = igl.winding_number(V,F,P) + W = igl.winding_number(V,F,P[1,:]) + W = igl.fast_winding_number(V,F,P) + S,I,C,N = igl.signed_distance(P,V,F) + S,I,C,N = igl.signed_distance(P,V,F,sign_type=igl.SignedDistanceType.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) + BC = igl.barycenter(V,F) + +def test_harmonic(): + V,F = triangulated_square() + b = np.array([0,3],dtype=np.int64) + bc = np.array([[1,0],[0,1]],dtype=np.float64) + W = igl.bbw(V,F,b,bc) + W = igl.harmonic(V,F,b,bc,k=1) + W = igl.harmonic(V,F,b,bc,k=2) + +def test_tets(): + V,F,T = single_tet() + F,J,K = igl.boundary_facets(T) + igl.writeMESH("out.mesh",V,T) + igl.writeMESH("out.mesh",V,T,F=F) + V,T,F = igl.readMESH("out.mesh") + igl.writeMSH("out.msh",V,F,T) + V,F,T,_,_,_,_,_,_,_ = igl.readMSH("out.msh") + +def test_bvh(): + V,F,T = single_tet() + tree = igl.AABB() + tree.init(V,T) + + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + # first row of P + q = P[0,:] + I = tree.find(V,T,q) + i = tree.find(V,T,q,first=True) + + Q = igl.barycenter(V,T) + I = igl.in_element(V,T,Q,tree) + + tree = igl.AABB() + tree.init(V,F) + sqrD,_,_ = tree.squared_distance(V,F,P) + + O = np.array([[2,1,1],[0.2,0.2,-1]],np.float64); + D = V.mean(axis=0)-O + I,T,UV = tree.intersect_ray_first(V,F,O,D) + hits = tree.intersect_ray(V,F,O,D) + o = O[0,:] + d = D[0,:] + hit = igl.ray_mesh_intersect(o,d,V,F,first=True) + hits = igl.ray_mesh_intersect(o,d,V,F) + + +def test_upsample(): + V,F,T = single_tet() + # upsample so there's something to collapse + V,F = igl.upsample(V,F,number_of_subdivs=1) + E,uE,EMAP,uE2E = igl.unique_edge_map_lists(F) + E,uE,EMAP,uEC,uEE = igl.unique_edge_map(F) + EF,EI = igl.edge_flaps(F,uE,EMAP) + uE,EMAP,EF,EI = igl.edge_flaps(F) + Nv,Nf = igl.circulation(0,True,F,EMAP,EF,EI) + + e = 0 + p = V[uE[e,0],:] + e1,e2,f1,f2 = igl.collapse_edge(e,p,V,F,uE,EMAP,EF,EI) + +def test_cut(): + V,F,T = single_tet() + C = np.ones(F.shape,dtype=bool) + Vn,Fn,I = igl.cut_mesh(V,F,C) + +def test_adjacency(): + V,F,T = single_tet() + TT,TTi = igl.triangle_triangle_adjacency(F) + TT,TTi = igl.triangle_triangle_adjacency_lists(F) + + VF,NI = igl.vertex_triangle_adjacency(F,n=V.shape[0]) + VF,VFi = igl.vertex_triangle_adjacency(F) + + F012 = F; + F120 = np.roll(F012,1,axis=1) + FF = np.vstack((F012,F120)) + F,IA,IC = igl.unique_simplices(FF) + A = np.array([[1,2,3],[1,2,4],[3,2,1],[5,6,7]],dtype=np.int64) + B = np.array([[1,2,3],[5,6,2],[1,2,4],[3,2,1]],dtype=np.int64) + IA,LOCB = igl.ismember_rows(A,B) + A = igl.adjacency_list(F) + A = igl.adjacency_list(F,sorted=True) + +def test_min_quad(): + V,F,T = single_tet() + A = -igl.cotmatrix(V,F) + B = np.zeros((V.shape[0],1),dtype=np.float64) + known = np.array([1,2],dtype=np.int64) + Y = np.array([-1,1],dtype=np.float64).reshape(-1, 1) + Aeq = scipy.sparse.csc_matrix(([-1,1],([0,0],[0,3])),shape=(1,V.shape[0])) + Beq = np.zeros((1,1),dtype=np.float64) + Z = igl.min_quad_with_fixed(A,B,known,Y,Aeq,Beq,pd=True) + data = igl.min_quad_with_fixed_data() + igl.min_quad_with_fixed_precompute(A,known,Aeq,True,data) + Z = igl.min_quad_with_fixed_solve(data,B,Y,Beq) + +def test_volume(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + L = igl.edge_lengths(V,T) + vol = igl.volume(V,T) + vol = igl.volume(L=L) + vol = igl.volume(A=V[T[:,0],:],B=V[T[:,1],:],C=V[T[:,2],:],D=V[T[:,3],:]) + F,_,_ = igl.boundary_facets(T) + # remove last face + F = F[:-1,:] + B = igl.is_border_vertex(F) + I,C = igl.on_boundary(F) + I,C = igl.on_boundary(T) + F = np.array([[2,1,3]],dtype=np.int64) + NV,NF,I,J = igl.remove_unreferenced(V,F) + +def test_oriented_facets(): + V,F,T = single_tet() + E = igl.oriented_facets(F) + F = igl.oriented_facets(T) + + res,BF,EF,EMAP,BE = igl.is_edge_manifold(F) + res,_,_,_,_ = igl.is_edge_manifold(F) + +def test_matlab_format(): + V,F = triangulated_square() + L = igl.cotmatrix(V,F) + s = igl.matlab_format(V,"V") + s = igl.matlab_format_index(F,"F") + s = igl.matlab_format(L,"L") + +def test_polar(): + # random randn 3x3 matrix + np.random.seed(42) + A = np.random.randn(3,3).astype(np.float64) + igl.polar_svd(A) + out = igl.polar_svd(A) + R,T,_,_,_ = igl.polar_svd(A) + R,T,sU,sS,sV = igl.polar_svd(A,include_reflections=True) + +def test_remove(): + V,F = triangulated_square() + SV,SVI,SVJ = igl.remove_duplicate_vertices(V,epsilon=1e-10) + SV,SVI,SVJ,F = igl.remove_duplicate_vertices(V,F,epsilon=1e-10) + +def test_project(): + V,F,_ = single_tet() + model = np.eye(4).astype(np.float64) + proj = np.eye(4).astype(np.float64) + viewport = np.array([0,0,640,480],dtype=np.float64) + win = igl.project(V,model,proj,viewport) + scene = igl.unproject(win,model,proj,viewport) + + S = np.array([0,0,0],dtype=np.float64) + D = np.array([0.2,0.2,0.2],dtype=np.float64) + t,sqrD = igl.project_to_line(V,S,D) + t,sqrD = igl.project_to_line_segment(V,S,D) + +def test_boundary_loop(): + V,F,_ = single_tet() + S,_ = igl.upsample_matrix(F,n=V.shape[0]) + S,_ = igl.loop_matrix(F,n=V.shape[0]) + V,F = igl.upsample(V,F,number_of_subdivs=1) + V,F = igl.loop(V,F,number_of_subdivs=1) + # remove first and last + F = F[1:-1,:] + L_all = igl.boundary_loop_all(F) + L = igl.boundary_loop(F) + +def test_voxel(): + V,_,_ = single_tet() + GV,side = igl.voxel_grid(V,s=10) + GV,side = igl.voxel_grid(V,s=10,offset=0.1,pad_count=2) + + +def test_sample(): + V,F = igl.icosahedron() + B,I,X = igl.random_points_on_mesh(10,V,F) + point_indices, CH,CN,W = igl.octree(X) + I = igl.knn(X,X,1,point_indices,CH,CN,W) + B,FI,P = igl.blue_noise(V,F,0.5) + +def test_curvature(): + V,F = igl.icosahedron() + K = igl.gaussian_curvature(V,F) + PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F) + PD1,PD2,PV1,PV2,_ = igl.principal_curvature(V,F,radius=10,useKring=False) + +def test_geodesic(): + V,F = igl.icosahedron() + VS = np.array([0],dtype=np.int64) + VT = np.array([1],dtype=np.int64) + D = igl.exact_geodesic(V,F,VS=VS,VT=VT) + +def test_decimate(): + V,F = igl.icosahedron() + dV,dF,J,I = igl.decimate(V,F) + dV,dF,J,I = igl.qslim(V,F) + +def test_parameterization(): + V,Q,E = igl.quad_grid(3,3); + V,F = igl.triangulated_grid(3,3); + # slim needs 3D data even if the problem is 2D + V = np.c_[V, np.zeros(V.shape[0])] + V_init = V[:,:2] + V_init[:,0] = V_init[:,0] * 2 + b = np.array([0,1],dtype=np.int64) + bc = V[b,:2] + data = igl.slim_precompute(V,F,V_init,igl.MappingEnergyType.ARAP,b,bc,soft_p=1e10) + U = igl.slim_solve(data,iter_num=1) + + data = igl.ARAPData() + data.energy = igl.ARAPEnergyType.ARAP_ENERGY_TYPE_SPOKES_AND_RIMS + V = V[:,:2] + igl.arap_precomputation(V,F,V.shape[1],b,data) + U = igl.arap_solve(bc,data,U) + + U,Q = igl.lscm(V,F,b,bc) + + +def test_implicit(): + res = np.array([3,3,3],dtype=np.int64) + GV = igl.grid(res) + S = np.sqrt(((GV - np.array([0.5,0.5,0.5],dtype=np.float64))**2).sum(axis=1))-0.25; + V,F,E2V = igl.marching_cubes(S,GV,res[0],res[1],res[2]) + # unpack keys into (i,j,v) index triplets + EV = np.array([[k & 0xFFFFFFFF, k >> 32, v] for k, v in E2V.items()], dtype=np.int64) + + h = igl.avg_edge_length(V,F) + m0,m1,m2 = igl.moments(V,F) + D = V[:,0] + vals = np.array([D.min(),D.mean(),D.max()],dtype=np.float64) + iV,iE,I = igl.isolines(V,F,S,vals) + iB,iF,iE,I = igl.isolines_intrinsic(F,S,vals) + + +def test_kelvinlets(): + V,F = igl.icosahedron() + x0 = V[0,:] + f = np.array([0,0,1],dtype=np.float64) + R = np.eye(3).astype(np.float64) + U = igl.kelvinlets(V,x0,f,R,epsilon=1.0,falloff=1.0,brushType=igl.GRAB) + + SV = V[:,0] + SF = igl.average_onto_faces(F,SV) + SV = igl.average_onto_vertices(V,F,SF) + +def test_polygons(): + P = [[0,1,2],[2,1,3,4]] + I,C = igl.polygon_corners(P) + Q = np.array([[0,1,2,3],[2,1,4,5]],dtype=np.int64) + I,C = igl.polygon_corners(Q) + F,J = igl.polygons_to_triangles(I,C) + +def test_heat(): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype=np.float64) + F = np.array([[1,3,0],[3,2,0],[2,3,1]],dtype=np.int64) + V,F = igl.upsample(V,F,number_of_subdivs=1) + data = igl.HeatGeodesicsData() + igl.heat_geodesics_precompute(V,F,t=1e-3,data=data) + data = igl.HeatGeodesicsData() + data.use_intrinsic_delaunay = True + igl.heat_geodesics_precompute(V,F,data) + gamma = np.array([0],dtype=np.int64) + D = igl.heat_geodesics_solve(data,gamma) + l = igl.edge_lengths(V,F) + iV,iF,E,uE,EMAP,uE2E = igl.intrinsic_delaunay_triangulation(l,F) + L,il,iF = igl.intrinsic_delaunay_cotmatrix(V,F) + L = igl.cotmatrix_intrinsic(il,iF) + M = igl.massmatrix_intrinsic(il,iF) + +def test_nonmanifold(): + V,F = igl.icosahedron() + FF,C = igl.bfs_orient(F) + SF,SVI = igl.split_nonmanifold(F) + SV,SF,SVI = igl.split_nonmanifold(V,F) + +def test_biharmonic(): + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + T = np.array([ [2,1,5,3], [0,1,5,2], [5,4,0,2], [2,5,4,3]],dtype=np.int64) + S = [[0],[5]] + W = igl.biharmonic_coordinates(V,T,S) + +def test_bijective(): + # 2D triangulated grid + V,F = igl.triangulated_grid(3,3) + ear,ear_opp = igl.ears(F) + # we don't have igl.flip_ears so construct it by hand + res = np.array([3,3],dtype=np.int64) + E,_,_ = igl.boundary_facets(F) + F = E + # add a 3rd column of 4s + F = np.hstack((F,np.ones((F.shape[0],1),dtype=np.int64)*4)) + # boundary b + b = np.array([0,1,2,3,5,6,7,8],dtype=np.int64) + bc = V[b,:] + bc[-1,:] = [0.5,0.5] + U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) + + #def test_copyleft(): # V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) # T = np.array([[0,1,2,3]],dtype=np.int64) From 9c54a714982284e62aa76e7dbefccfaba794334e Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 19:43:52 -0500 Subject: [PATCH 083/102] enable cgal and embree --- CMakeLists.txt | 4 +- tests/test_all.py | 132 +++++++++++++++++++++++----------------------- 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0dac25d9..775a7d7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,8 +40,8 @@ FetchContent_MakeAvailable(nanobind) # Download and set up libigl option(LIBIGL_COPYLEFT_CORE "Build target igl_copyleft::core" ON) -option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" OFF) -option(LIBIGL_EMBREE "Build target igl::embree" OFF) +option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) +option(LIBIGL_EMBREE "Build target igl::embree" ON) option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) FetchContent_Declare( diff --git a/tests/test_all.py b/tests/test_all.py index be2e30b6..99826a0c 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -13,8 +13,8 @@ def test_version(): import igl.triangle import igl.copyleft import igl.copyleft.tetgen -#import igl.copyleft.cgal -#import igl.embree +import igl.copyleft.cgal +import igl.embree #def rand_sparse(n,density): @@ -449,70 +449,70 @@ def test_bijective(): U = igl.bijective_composite_harmonic_mapping(V,F,b,bc) -#def test_copyleft(): -# V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# F,_,_ = igl.boundary_facets(T) -# V,F = igl.loop(V,F) -# -# dV,dF,J = igl.copyleft.progressive_hulls(V,F) -# -# -#def test_cgal(): -# # tetrahedron -# VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) -# T = np.array([[0,1,2,3]],dtype=np.int64) -# FA,_,_ = igl.boundary_facets(T) -# # flip z -# VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) -# FB = FA[:,::-1] -# IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) -# IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) -# VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") -# H = igl.copyleft.cgal.convex_hull(VC) -# # concatenate A and B meshes -# VC = np.vstack((VA,VB)) -# FC = np.vstack((FA,FB+VA.shape[0])) -# VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) -# _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) -# -# p = np.array([0,0,0],dtype=np.float64) -# n = np.array([1,1,1],dtype=np.float64) -# VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) -# equ = np.hstack((n,-n.dot(p))) -# VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) -# -# P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) -# W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) -# W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) -# -# VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) -# -# _,I,X = igl.random_points_on_mesh(1000,VC,FC) -# N = igl.per_face_normals(VC,FC) -# N = N[I,:] -# point_indices, CH,CN,W = igl.octree(X) -# I = igl.knn(X,X,20,point_indices,CH,CN,W) -# A,T = igl.copyleft.cgal.point_areas(X,I,N) -# -#def test_embree(): -# # octahedron -# V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) -# F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) -# N = igl.per_vertex_normals(V,F) -# ei = igl.embree.EmbreeIntersector(); -# ei.init(V,F) -# S = igl.embree.ambient_occlusion(V,F,V,N,100) -# S = igl.embree.ambient_occlusion(ei,V,N,100) -# N = -N -# S = igl.embree.shape_diameter_function(V,F,V,N,100) -# S = igl.embree.shape_diameter_function(ei,V,N,100) -# origin = np.array([2,2,2],dtype=np.float64) -# direction = np.array([-2,-2,-2],dtype=np.float64) -# hit = ei.intersectRay_first(origin,direction) -# hits = ei.intersectRay(origin,direction) -# hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) -# I,C = igl.embree.reorient_facets_raycast(V,F) +def test_copyleft(): + V = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + F,_,_ = igl.boundary_facets(T) + V,F = igl.loop(V,F) + + dV,dF,J = igl.copyleft.progressive_hulls(V,F) + + +def test_cgal(): + # tetrahedron + VA = np.array([[0,0,-1],[2,0,-1],[0,2,-1],[1,1,1]],dtype=np.float64) + T = np.array([[0,1,2,3]],dtype=np.int64) + FA,_,_ = igl.boundary_facets(T) + # flip z + VB = np.array([[0,0,1],[2,0,1],[0,2,1],[1,1,-1]],dtype=np.float64) + FB = FA[:,::-1] + IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB) + IF,_,_,_,_ = igl.copyleft.cgal.intersect_other(VA,FA,VB,FB,detect_only=True,first_only=True) + VC,FC,J = igl.copyleft.cgal.mesh_boolean(VA,FA,VB,FB,"union") + H = igl.copyleft.cgal.convex_hull(VC) + # concatenate A and B meshes + VC = np.vstack((VA,VB)) + FC = np.vstack((FA,FB+VA.shape[0])) + VV,FF,IF,J,IM = igl.copyleft.cgal.remesh_self_intersections(VC,FC) + _,_,IF,_,_ = igl.copyleft.cgal.remesh_self_intersections(VC,FC,detect_only=True,first_only=True) + + p = np.array([0,0,0],dtype=np.float64) + n = np.array([1,1,1],dtype=np.float64) + VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,p,n) + equ = np.hstack((n,-n.dot(p))) + VV,FF,J = igl.copyleft.cgal.intersect_with_half_space(VC,FC,equ) + + P = np.array([[0.5,0.5,0.0],[0.5,0.5,0.5]],dtype=np.float64) + W = igl.copyleft.cgal.fast_winding_number(VA,FA,P) + W = igl.copyleft.cgal.fast_winding_number(VA,FA,P,expansion_order=2,beta=2.0) + + VC,FC,D,J = igl.copyleft.cgal.trim_with_solid(VA,FA,VB,FB) + + _,I,X = igl.random_points_on_mesh(1000,VC,FC) + N = igl.per_face_normals(VC,FC) + N = N[I,:] + point_indices, CH,CN,W = igl.octree(X) + I = igl.knn(X,X,20,point_indices,CH,CN,W) + A,T = igl.copyleft.cgal.point_areas(X,I,N) + +def test_embree(): + # octahedron + V = np.array([[1,0,0],[0,1,0],[0,0,1],[-1,0,0],[0,-1,0],[0,0,-1]],dtype=np.float64) + F = np.array([[0,1,2], [0,2,4], [0,4,5], [0,5,1], [1,3,2], [1,5,3], [2,3,4], [3,5,4]],dtype=np.int64) + N = igl.per_vertex_normals(V,F) + ei = igl.embree.EmbreeIntersector(); + ei.init(V,F) + S = igl.embree.ambient_occlusion(V,F,V,N,100) + S = igl.embree.ambient_occlusion(ei,V,N,100) + N = -N + S = igl.embree.shape_diameter_function(V,F,V,N,100) + S = igl.embree.shape_diameter_function(ei,V,N,100) + origin = np.array([2,2,2],dtype=np.float64) + direction = np.array([-2,-2,-2],dtype=np.float64) + hit = ei.intersectRay_first(origin,direction) + hits = ei.intersectRay(origin,direction) + hits = ei.intersectRay(origin,direction,tnear=0,tfar=1) + I,C = igl.embree.reorient_facets_raycast(V,F) def test_tetgen(): # octahedron From 25e27012f9db6400ff779468359ff4beb9b62ac4 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 19:49:02 -0500 Subject: [PATCH 084/102] [ci skip] print build type --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 775a7d7f..ea58637c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,8 @@ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() +message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") + # Enable FetchContent to download dependencies at configure time include(FetchContent) From fff8d57f7da570c903fd07215286bc5b84e2a878 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 19:58:37 -0500 Subject: [PATCH 085/102] wip tests --- tests/test_all.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/test_all.py b/tests/test_all.py index 99826a0c..d11048f9 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -537,21 +537,21 @@ def test_triangle(): L,rhs = igl.triangle.scaf_system(scaf_data) U = igl.triangle.scaf_solve(1,scaf_data) -#def test_misc(): -# V,F = igl.icosahedron() -# BV,BF = igl.bounding_box(V,pad=1.0) -# R,C,B = igl.circumradius(V,F) -# R = igl.inradius(V,F) -# _,E,EMAP,_,_ = igl.unique_edge_map(F) -# L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) -# M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) -# cuts = igl.cut_to_disk(F) -# V,F = igl.cylinder(10,10) -# VD,FD = igl.false_barycentric_subdivision(V,F) -# V,F,T = single_tet() -# theta, cos_theta = igl.dihedral_angles(V,T) -# L = igl.edge_lengths(V,T) -# A = igl.face_areas(V,T) -# theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) -# -# +def test_misc(): + V,F = igl.icosahedron() + BV,BF = igl.bounding_box(V,pad=1.0) + R,C,B = igl.circumradius(V,F) + R = igl.inradius(V,F) + _,E,EMAP,_,_ = igl.unique_edge_map(F) + L = igl.crouzeix_raviart_cotmatrix(V,F,E,EMAP) + M = igl.crouzeix_raviart_massmatrix(V,F,E,EMAP) + cuts = igl.cut_to_disk(F) + V,F = igl.cylinder(10,10) + VD,FD = igl.false_barycentric_subdivision(V,F) + V,F,T = single_tet() + theta, cos_theta = igl.dihedral_angles(V,T) + L = igl.edge_lengths(V,T) + A = igl.face_areas(V,T) + theta, cos_theta = igl.dihedral_angles_intrinsic(L,A) + + From 0b982618eb20dce213ee2a271a555a8b844e9c46 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 20:09:09 -0500 Subject: [PATCH 086/102] try to build all cpversions --- .github/workflows/wheels.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 078f68e5..737a2013 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -15,13 +15,14 @@ jobs: strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - cpversion: - - cp311 + #cpversion: + # - cp311 + # build all cpversion: 3.9 to 3.13 + cpversion: [cp39, cp310, cp311, cp312, cp313] steps: - uses: actions/checkout@v4 - # Set up Python 3.10 - uses: actions/setup-python@v5 with: python-version: '3.12' From 06db8d5fbe41a31077175cb83d6b45a55664e25d Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 20:11:05 -0500 Subject: [PATCH 087/102] restore just 311 --- .github/workflows/wheels.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 737a2013..044f6783 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -15,10 +15,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - #cpversion: - # - cp311 - # build all cpversion: 3.9 to 3.13 - cpversion: [cp39, cp310, cp311, cp312, cp313] + cpversion: + - cp311 steps: - uses: actions/checkout@v4 From dfcd41661ab370c53b13a2c8bb7f042d22e86da6 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 20:11:51 -0500 Subject: [PATCH 088/102] fix typo --- .github/workflows/wheels.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 044f6783..5fb4d55e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -10,13 +10,13 @@ on: jobs: build_wheels: - name: Build ${{ matrix.cpversionj}} wheels on ${{ matrix.os }} + name: Build ${{ matrix.cpversion }} wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - cpversion: - - cp311 + # build all cpversion: 3.9 to 3.13 + cpversion: [cp39, cp310, cp311, cp312, cp313] steps: - uses: actions/checkout@v4 From f555b078084973bac4ecce8ff4afb32d967e2a51 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 20:29:53 -0500 Subject: [PATCH 089/102] vesion only in pyproject; try to build 3.8 using old numpy --- .github/workflows/wheels.yml | 5 +---- CMakeLists.txt | 4 ---- igl/_version.py | 8 -------- 3 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 igl/_version.py diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 737a2013..f1ce39ab 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -15,10 +15,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - #cpversion: - # - cp311 - # build all cpversion: 3.9 to 3.13 - cpversion: [cp39, cp310, cp311, cp312, cp313] + cpversion: [cp38, cp39, cp310, cp311, cp312, cp313] steps: - uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index ea58637c..0e5ba48e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,10 +142,6 @@ function(pyigl_include prefix name) file(MAKE_DIRECTORY ${output_dir}) file(WRITE "${output_dir}/__init__.py" "from .${target_name} import *\n") install(FILES "${output_dir}/__init__.py" DESTINATION "igl/${subpath}") - if("${name}" STREQUAL "core" AND "${prefix}" STREQUAL "") - install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/igl/_version.py" DESTINATION "igl/${subpath}") - file(APPEND "${output_dir}/__init__.py" "from ._version import __version__\n") - endif() set_target_properties(${target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${output_dir}" diff --git a/igl/_version.py b/igl/_version.py deleted file mode 100644 index 887b40df..00000000 --- a/igl/_version.py +++ /dev/null @@ -1,8 +0,0 @@ -# This file is part of libigl, a simple c++ geometry processing library. -# -# Copyright (C) 2024 Alec Jacobson -# -# This Source Code Form is subject to the terms of the Mozilla Public License -# v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at http://mozilla.org/MPL/2.0/. -__version__ = "2.5.4dev" From 5566d4d52174219aa43569737160b53ac8e02a35 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 20:31:07 -0500 Subject: [PATCH 090/102] old numpy for 3.8 --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0c5c7b68..b6cc5884 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,8 @@ requires = [ "cmake>=3.16", "nanobind >=1.3.2", - "numpy>=2.0.0", + "numpy>=2.0.0; python_version >= '3.9'", + "numpy; python_version < '3.9'", "packaging", "scikit-build-core >=0.10", "scipy", From 2eb44d09b90816eac234678f500e40229aa9f7ba Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 20:32:34 -0500 Subject: [PATCH 091/102] rm 38 --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index a1346944..86176896 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - cpversion: [cp38, cp39, cp310, cp311, cp312, cp313] + cpversion: [cp39, cp310, cp311, cp312, cp313] steps: - uses: actions/checkout@v4 From fff4817127271103f59d1fe4f1ab414d8060e604 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 20:42:57 -0500 Subject: [PATCH 092/102] fail-fast false --- .github/workflows/wheels.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 86176896..2536e556 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -13,9 +13,10 @@ jobs: name: Build ${{ matrix.cpversion }} wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - cpversion: [cp39, cp310, cp311, cp312, cp313] + cpversion: [cp38, cp39, cp310, cp311, cp312, cp313] steps: - uses: actions/checkout@v4 From df88d537a147bd9ed0368c566056f8efb6c53b6e Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 20:56:16 -0500 Subject: [PATCH 093/102] rm test_version --- tests/test_all.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_all.py b/tests/test_all.py index d11048f9..1dd1a13e 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -6,10 +6,6 @@ import warnings import igl -def test_version(): - version = igl.__version__ - pass - import igl.triangle import igl.copyleft import igl.copyleft.tetgen From 3b06a209ac0c6f5f8dfb19636d33a2d6da58566c Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 21:48:18 -0500 Subject: [PATCH 094/102] exclude 38-mac14 --- .github/workflows/wheels.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2536e556..83b2dc35 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -17,6 +17,14 @@ jobs: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] cpversion: [cp38, cp39, cp310, cp311, cp312, cp313] + # github actions cp38 on macos-14 runners are cross compiling or + # something and confusing the stub generation. Just skip it for now. + # Maybe it'd be friendlier to disable the stub generation for this + # combination. + exclude: + - os: macos-14 + cpversion: cp38 + steps: - uses: actions/checkout@v4 From e161e29de318dad74a1f9fa976d97bf993223364 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 22:37:53 -0500 Subject: [PATCH 095/102] just sdist --- .github/workflows/wheels.yml | 93 +++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 83b2dc35..003f1e1e 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -9,43 +9,60 @@ on: - main jobs: - build_wheels: - name: Build ${{ matrix.cpversion }} wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - cpversion: [cp38, cp39, cp310, cp311, cp312, cp313] - # github actions cp38 on macos-14 runners are cross compiling or - # something and confusing the stub generation. Just skip it for now. - # Maybe it'd be friendlier to disable the stub generation for this - # combination. - exclude: - - os: macos-14 - cpversion: cp38 + build_sdist: + name: Build SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build SDist + run: pipx run build --sdist - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.22.0 - - - name: Build wheels - # why do I need to specify this cpversion here? - env: - CIBW_ARCHS: "auto64" - CIBW_BUILD: "${{ matrix.cpversion }}-*" - # why isn't auto64 working? - CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux* cp*-win32" - run: python -m cibuildwheel --output-dir wheelhouse - - - uses: actions/upload-artifact@v4 - with: - name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} - path: ./wheelhouse/*.whl + - name: Check metadata + run: pipx run twine check dist/* + + - uses: actions/upload-artifact@v4 + with: + name: artifact-sdist + path: dist/*.tar.gz + + #build_wheels: + # name: Build ${{ matrix.cpversion }} wheels on ${{ matrix.os }} + # runs-on: ${{ matrix.os }} + # strategy: + # fail-fast: false + # matrix: + # os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] + # cpversion: [cp38, cp39, cp310, cp311, cp312, cp313] + # # github actions cp38 on macos-14 runners are cross compiling or + # # something and confusing the stub generation. Just skip it for now. + # # Maybe it'd be friendlier to disable the stub generation for this + # # combination. + # exclude: + # - os: macos-14 + # cpversion: cp38 + + + # steps: + # - uses: actions/checkout@v4 + + # - uses: actions/setup-python@v5 + # with: + # python-version: '3.12' + + # - name: Install cibuildwheel + # run: python -m pip install cibuildwheel==2.22.0 + + # - name: Build wheels + # # why do I need to specify this cpversion here? + # env: + # CIBW_ARCHS: "auto64" + # CIBW_BUILD: "${{ matrix.cpversion }}-*" + # # why isn't auto64 working? + # CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux* cp*-win32" + # run: python -m cibuildwheel --output-dir wheelhouse + + # - uses: actions/upload-artifact@v4 + # with: + # name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + # path: ./wheelhouse/*.whl From f74c61ebd29b507241bd9bd4184ce59db71601d9 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 1 Feb 2025 23:04:11 -0500 Subject: [PATCH 096/102] another try to get sdist --- .github/workflows/wheels.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 003f1e1e..c4f41a34 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -14,6 +14,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Build SDist run: pipx run build --sdist @@ -23,7 +25,7 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: artifact-sdist + name: dist-sdist path: dist/*.tar.gz #build_wheels: @@ -33,7 +35,7 @@ jobs: # fail-fast: false # matrix: # os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - # cpversion: [cp38, cp39, cp310, cp311, cp312, cp313] + # cpversion: [cp38, cp39, cp310, cp311, cp312 ] # # github actions cp38 on macos-14 runners are cross compiling or # # something and confusing the stub generation. Just skip it for now. # # Maybe it'd be friendlier to disable the stub generation for this From c713af769364cb71b1c49fee3263acc8b0653576 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sun, 2 Feb 2025 10:18:05 -0500 Subject: [PATCH 097/102] restore pip workflow. rm 313 redundant --- .github/workflows/pip.yml | 8 ++--- .github/workflows/wheels.yml | 70 ++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 8ad0cc19..05bb3141 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -2,10 +2,10 @@ name: Pip on: workflow_dispatch: - #pull_request: - #push: - # branches: - # - master + pull_request: + push: + branches: + - master jobs: build: diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index c4f41a34..218ead93 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -28,43 +28,45 @@ jobs: name: dist-sdist path: dist/*.tar.gz - #build_wheels: - # name: Build ${{ matrix.cpversion }} wheels on ${{ matrix.os }} - # runs-on: ${{ matrix.os }} - # strategy: - # fail-fast: false - # matrix: - # os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] - # cpversion: [cp38, cp39, cp310, cp311, cp312 ] - # # github actions cp38 on macos-14 runners are cross compiling or - # # something and confusing the stub generation. Just skip it for now. - # # Maybe it'd be friendlier to disable the stub generation for this - # # combination. - # exclude: - # - os: macos-14 - # cpversion: cp38 + build_wheels: + name: Build ${{ matrix.cpversion }} wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-14] + # cp313 seemed to produce the same wheel name as cp312. Skip unless + # necessary. + cpversion: [cp38, cp39, cp310, cp311, cp312] + # github actions cp38 on macos-14 runners are cross compiling or + # something and confusing the stub generation. Just skip it for now. + # Maybe it'd be friendlier to disable the stub generation for this + # combination. + exclude: + - os: macos-14 + cpversion: cp38 - # steps: - # - uses: actions/checkout@v4 + steps: + - uses: actions/checkout@v4 - # - uses: actions/setup-python@v5 - # with: - # python-version: '3.12' + - uses: actions/setup-python@v5 + with: + python-version: '3.12' - # - name: Install cibuildwheel - # run: python -m pip install cibuildwheel==2.22.0 + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.22.0 - # - name: Build wheels - # # why do I need to specify this cpversion here? - # env: - # CIBW_ARCHS: "auto64" - # CIBW_BUILD: "${{ matrix.cpversion }}-*" - # # why isn't auto64 working? - # CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux* cp*-win32" - # run: python -m cibuildwheel --output-dir wheelhouse + - name: Build wheels + # why do I need to specify this cpversion here? + env: + CIBW_ARCHS: "auto64" + CIBW_BUILD: "${{ matrix.cpversion }}-*" + # why isn't auto64 working? + CIBW_SKIP: "cp*-manylinux_i686 cp*-musllinux* cp*-win32" + run: python -m cibuildwheel --output-dir wheelhouse - # - uses: actions/upload-artifact@v4 - # with: - # name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} - # path: ./wheelhouse/*.whl + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl From bc2cb765a8c143e14ce581f40175714ba139db02 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sun, 2 Feb 2025 10:23:12 -0500 Subject: [PATCH 098/102] [ci skip] note about version --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 24e76931..a0c2fc1f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,20 @@ python -m pip install ./ * A tutorial on how to use the bindings can be found [here](https://libigl.github.io/libigl-python-bindings/tutorials/) * A function reference can be found [here](https://libigl.github.io/libigl-python-bindings/igl_docs/) +## Getting the current version of libigl within python code + +Since version 2.5.4.dev0, the `igl.__version__` attribute has been removed. To +get the version of the libigl package you're using within your python code, you +can use the following code: + +``` +import importlib.metadata +libigl_version = importlib.metadata.version('libigl') +``` + +The version of libigl is defined in the [pyproject.toml](./pyproject.toml) file. + + ## Compiling and modifying the bindiings According to the [scikit-build-core documentation](https://scikit-build-core.readthedocs.io/en/latest/configuration.html#editable-installs), the way to make an editable (incremental) build is to: From a52175a408ebb48587c8fbd4c8f67c3e9829cd9f Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sun, 2 Feb 2025 10:36:44 -0500 Subject: [PATCH 099/102] [ci skip] clearer readme --- README.md | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a0c2fc1f..b5404947 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ [![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/) [![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain) - -This repository contains the source code for the libigl Python bindings. These bindings are fully compatible with NumPy and SciPy and offer a convenient interface similar to functions in those libraries. - -These bindings are still under active development and should still be considered alpha quality. We encourage users to post issues so we can improve the bindings! +This repository contains the source code for the libigl Python bindings written +using [nanobind](https://nanobind.readthedocs.io/en/latest/). Functions allow +NumPy arrays as input and output for dense matrices and vectors and SciPy sparse +matrices for sparse matrices. ## Installation @@ -13,15 +13,12 @@ These bindings are still under active development and should still be considered python -m pip install libigl ``` -If you wish to install the current development code, you can compile the library from scratch. Clone this repo and issue - -``` -python -m pip install ./ -``` - - ## [Help/Documentation](https://libigl.github.io/libigl-python-bindings/) +| :warning: WARNING | +|:----------------------------| +| This documentation is perennially out of date and will likely be removed/changed. | + * A tutorial on how to use the bindings can be found [here](https://libigl.github.io/libigl-python-bindings/tutorials/) * A function reference can be found [here](https://libigl.github.io/libigl-python-bindings/igl_docs/) @@ -50,9 +47,36 @@ According to the [scikit-build-core documentation](https://scikit-build-core.rea python -m pip install --no-build-isolation --config-settings=editable.rebuild=true -Cbuild-dir=build -ve. ``` +### Adding a missing binding + +Bindings are fairly mechanical to write. For example, suppose we didn't have a +binding for the c++ function `igl::moments`. The first step would be to look at +the corresponding `.h` header file in the C++ libigl library: +[moments.h](https://github.com/libigl/libigl/blob/main/include/igl/moments.h). + +Then we would create the [src/moments.cpp](src/moments.cpp) file in this project +which uses `Eigen::MatrixXN` for numeric types and `Eigen::MatrixXI` for integer +types. Typically this requires a simple wrapper around the function matching +its signature to these types and some boilerplate `void bind_moments(...` code which adds the function to the python module. + +Simply adding this `.cpp` file will be enough to add the bindings on the next +build. + +If submitting a pull request with a new binding, please also add an execution +test in `tests/test_all.py` to ensure the binding can at least be called as +expected. + + ## Testing cibuildwheel locally Install whichever version of Python from the [official website](https://www.python.org/downloads/) and then run: /Library/Frameworks/Python.framework/Versions/[version]/bin/python -m pip install cibuildwheel CIBW_BUILD="cp311-*" python -m cibuildwheel --output-dir wheelhouse --platform macos + +## Acknowledgements + +The original python bindings were generated and maintained by @teseoch, +@KarlLeell, @fwilliams, @skoch9, @danielepanozzo. + +The modern python bindings (since 2.5.4.dev0) can largely be blamed on @alecjacobson. From df097088fd55619563cf7485b811f8e84754937f Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sun, 2 Feb 2025 10:38:58 -0500 Subject: [PATCH 100/102] [ci skip] links --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b5404947..50a99c8c 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ [![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/) [![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain) -This repository contains the source code for the libigl Python bindings written -using [nanobind](https://nanobind.readthedocs.io/en/latest/). Functions allow -NumPy arrays as input and output for dense matrices and vectors and SciPy sparse +This repository contains the source code for the python bindings for the C++ +[libigl](https://github.com/libigl/libigl) library written using +[nanobind](https://nanobind.readthedocs.io/en/latest/). Functions allow NumPy +arrays as input and output for dense matrices and vectors and SciPy sparse matrices for sparse matrices. ## Installation @@ -13,11 +14,11 @@ matrices for sparse matrices. python -m pip install libigl ``` -## [Help/Documentation](https://libigl.github.io/libigl-python-bindings/) +## Documentation | :warning: WARNING | |:----------------------------| -| This documentation is perennially out of date and will likely be removed/changed. | +| The [python-binding documentation](https://libigl.github.io/libigl-python-bindings/) is perennially out of date and will likely be removed/changed. | * A tutorial on how to use the bindings can be found [here](https://libigl.github.io/libigl-python-bindings/tutorials/) * A function reference can be found [here](https://libigl.github.io/libigl-python-bindings/igl_docs/) From f75dad8c084c9fd4c6a5edbab4e7a14862d5b21c Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sun, 2 Feb 2025 10:41:32 -0500 Subject: [PATCH 101/102] [ci skip] username links --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 50a99c8c..3893d8a2 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,12 @@ Install whichever version of Python from the [official website](https://www.pyth ## Acknowledgements -The original python bindings were generated and maintained by @teseoch, -@KarlLeell, @fwilliams, @skoch9, @danielepanozzo. - -The modern python bindings (since 2.5.4.dev0) can largely be blamed on @alecjacobson. +The original python bindings were generated and maintained by +[@teseoch](https://github.com/teseoch), +[@KarlLeell](https://github.com/KarlLeell), +[@fwilliams](https://github.com/fwilliams), +[@skoch9](https://github.com/skoch9), and +[@danielepanozzo](https://github.com/danielepanozzo) + +The modern python bindings (since 2.5.4.dev0) can largely be blamed on +[@alecjacobson](https://github.com/alecjacobson). From e1b50343c15bdcf84aee002c04e8ced1e080fc7a Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sun, 2 Feb 2025 10:50:33 -0500 Subject: [PATCH 102/102] [ci skip] wheel download instructions --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 3893d8a2..76e49549 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,18 @@ Install whichever version of Python from the [official website](https://www.pyth /Library/Frameworks/Python.framework/Versions/[version]/bin/python -m pip install cibuildwheel CIBW_BUILD="cp311-*" python -m cibuildwheel --output-dir wheelhouse --platform macos +## Downloading all the artifacts + +A successful [.github/workflows/wheels.yml](.github/workflows/wheels.yml) run will a lot of `.whl` files. To download these all at once, you can use the following command: + + mkdir wheelhouse + cd wheelhouse + gh run download [runid] + +Then these can be uploaded to pypi using: + + python -m twine upload --repository testpypi wheelhouse/*/*.whl wheelhouse/*/*.tar.gz + ## Acknowledgements The original python bindings were generated and maintained by