瀏覽代碼

Added tutorial 403 and submodule bbw

Former-commit-id: a475a5d12431727ee6b82421bb937f923251a133
Sebastian Koch 9 年之前
父節點
當前提交
ba5cfc10ba

+ 5 - 0
python/CMakeLists.txt

@@ -120,6 +120,11 @@ if (LIBIGL_WITH_COPYLEFT)
   list(APPEND SHARED_SOURCES "modules/copyleft/py_igl_copyleft.cpp")
 endif ()
 
+if (LIBIGL_WITH_BBW)
+  add_definitions(-DPY_BBW)
+  list(APPEND SHARED_SOURCES "modules/py_igl_bbw.cpp")
+endif ()
+
 if (LIBIGL_WITH_PNG)
   add_definitions(-DPY_PNG)
   list(APPEND SHARED_SOURCES "modules/py_igl_png.cpp")

+ 18 - 0
python/modules/py_igl_bbw.cpp

@@ -0,0 +1,18 @@
+//#include <Eigen/Geometry>
+//#include <Eigen/Dense>
+//#include <Eigen/Sparse>
+
+
+#include "../python_shared.h"
+
+#include <igl/bbw/bbw.h>
+
+
+void python_export_igl_bbw(py::module &me) {
+
+  py::module m = me.def_submodule(
+    "bbw", "Wrappers for libigl functions that use bbw");
+
+  #include "../py_igl/bbw/py_bbw.cpp"
+
+}

+ 1 - 0
python/modules/py_typedefs.cpp

@@ -1,5 +1,6 @@
 py::class_<RotationList>(m, "RotationList")
     .def(py::init<>())
+    .def(py::init<size_t>())
     .def("pop_back", &RotationList::pop_back)
     /* There are multiple versions of push_back(), etc. Select the right ones. */
     .def("append", (void (RotationList::*)(const Eigen::Quaterniond &)) &RotationList::push_back)

+ 62 - 0
python/py_doc.cpp

@@ -76,6 +76,45 @@ const char *__doc_igl_barycentric_coordinates = R"igl_Qu8mg5v7(// Compute baryce
   // Outputs:
   //   L  #P by 4 list of barycentric coordinates
   //   )igl_Qu8mg5v7";
+const char *__doc_igl_bbw_bbw = R"igl_Qu8mg5v7(// Compute Bounded Biharmonic Weights on a given domain (V,Ele) with a given
+    // set of boundary conditions
+    //
+    // Templates
+    //   DerivedV  derived type of eigen matrix for V (e.g. MatrixXd)
+    //   DerivedF  derived type of eigen matrix for F (e.g. MatrixXi)
+    //   Derivedb  derived type of eigen matrix for b (e.g. VectorXi)
+    //   Derivedbc  derived type of eigen matrix for bc (e.g. MatrixXd)
+    //   DerivedW  derived type of eigen matrix for W (e.g. MatrixXd)
+    // Inputs:
+    //   V  #V by dim vertex positions
+    //   Ele  #Elements by simplex-size list of element indices
+    //   b  #b boundary indices into V
+    //   bc #b by #W list of boundary values
+    //   data  object containing options, intial guess --> solution and results
+    // Outputs:
+    //   W  #V by #W list of *unnormalized* weights to normalize use
+    //    igl::normalize_row_sums(W,W);
+    // Returns true on success, false on failure)igl_Qu8mg5v7";
+const char *__doc_igl_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.
+  //
+  // Inputs:
+  //   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*
+  // Outputs:
+  //   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 true if boundary conditions make sense)igl_Qu8mg5v7";
 const char *__doc_igl_boundary_facets = R"igl_Qu8mg5v7(// BOUNDARY_FACETS Determine boundary faces (edges) of tetrahedra (triangles)
   // stored in T (analogous to qptoolbox's `outline` and `boundary_faces`).
   //
@@ -132,6 +171,13 @@ const char *__doc_igl_colon = R"igl_Qu8mg5v7(// Colon operator like matlab's col
   //     than hi, vice versa if hi<low
   // Output:
   //   I  list of values from low to hi with step size step)igl_Qu8mg5v7";
+const char *__doc_igl_column_to_quats = R"igl_Qu8mg5v7(// "Columnize" a list of quaternions (q1x,q1y,q1z,q1w,q2x,q2y,q2z,q2w,...)
+  //
+  // Inputs:
+  //   Q  n*4-long list of coefficients
+  // Outputs:
+  //   vQ  n-long list of quaternions
+  // Returns false if n%4!=0)igl_Qu8mg5v7";
 const char *__doc_igl_comb_cross_field = R"igl_Qu8mg5v7(// Inputs:
   //   V          #V by 3 eigen Matrix of mesh vertex 3D positions
   //   F          #F by 4 eigen Matrix of face (quad) indices
@@ -739,6 +785,22 @@ const char *__doc_igl_n_polyvector = R"igl_Qu8mg5v7(// Inputs:
   // Output:
   //                  3 by 3 rotation matrix that takes v0 to v1
   //)igl_Qu8mg5v7";
+const char *__doc_igl_normalize_row_lengths = R"igl_Qu8mg5v7(// Obsolete: just use A.rowwise().normalize() or B=A.rowwise().normalized();
+  //
+  // Normalize the rows in A so that their lengths are each 1 and place the new
+  // entries in B
+  // Inputs:
+  //   A  #rows by k input matrix
+  // Outputs:
+  //   B  #rows by k input matrix, can be the same as A)igl_Qu8mg5v7";
+const char *__doc_igl_normalize_row_sums = R"igl_Qu8mg5v7(// Normalize the rows in A so that their sums are each 1 and place the new
+  // entries in B
+  // Inputs:
+  //   A  #rows by k input matrix
+  // Outputs:
+  //   B  #rows by k input matrix, can be the same as A
+  //
+  // Note: This is just calling an Eigen one-liner.)igl_Qu8mg5v7";
 const char *__doc_igl_parula = R"igl_Qu8mg5v7(// PARULA like MATLAB's parula
   //
   // Inputs:

+ 5 - 0
python/py_doc.h

@@ -4,11 +4,14 @@ extern const char *__doc_igl_arap_solve;
 extern const char *__doc_igl_avg_edge_length;
 extern const char *__doc_igl_barycenter;
 extern const char *__doc_igl_barycentric_coordinates;
+extern const char *__doc_igl_bbw_bbw;
+extern const char *__doc_igl_boundary_conditions;
 extern const char *__doc_igl_boundary_facets;
 extern const char *__doc_igl_boundary_loop;
 extern const char *__doc_igl_cat;
 extern const char *__doc_igl_collapse_edge;
 extern const char *__doc_igl_colon;
+extern const char *__doc_igl_column_to_quats;
 extern const char *__doc_igl_comb_cross_field;
 extern const char *__doc_igl_comb_frame_field;
 extern const char *__doc_igl_compute_frame_field_bisectors;
@@ -58,6 +61,8 @@ extern const char *__doc_igl_min_quad_with_fixed_precompute;
 extern const char *__doc_igl_min_quad_with_fixed_solve;
 extern const char *__doc_igl_min_quad_with_fixed;
 extern const char *__doc_igl_n_polyvector;
+extern const char *__doc_igl_normalize_row_lengths;
+extern const char *__doc_igl_normalize_row_sums;
 extern const char *__doc_igl_parula;
 extern const char *__doc_igl_per_corner_normals;
 extern const char *__doc_igl_per_edge_normals;

+ 8 - 0
python/py_igl.cpp

@@ -12,11 +12,13 @@
 #include <igl/avg_edge_length.h>
 #include <igl/barycenter.h>
 #include <igl/barycentric_coordinates.h>
+#include <igl/boundary_conditions.h>
 #include <igl/boundary_facets.h>
 #include <igl/boundary_loop.h>
 #include <igl/cat.h>
 #include <igl/collapse_edge.h>
 #include <igl/colon.h>
+#include <igl/column_to_quats.h>
 #include <igl/comb_cross_field.h>
 #include <igl/comb_frame_field.h>
 #include <igl/compute_frame_field_bisectors.h>
@@ -51,6 +53,8 @@
 #include <igl/massmatrix.h>
 #include <igl/min_quad_with_fixed.h>
 #include <igl/n_polyvector.h>
+#include <igl/normalize_row_lengths.h>
+#include <igl/normalize_row_sums.h>
 #include <igl/parula.h>
 #include <igl/per_corner_normals.h>
 #include <igl/per_edge_normals.h>
@@ -97,11 +101,13 @@ void python_export_igl(py::module &m)
 #include "py_igl/py_avg_edge_length.cpp"
 #include "py_igl/py_barycenter.cpp"
 #include "py_igl/py_barycentric_coordinates.cpp"
+#include "py_igl/py_boundary_conditions.cpp"
 #include "py_igl/py_boundary_facets.cpp"
 #include "py_igl/py_boundary_loop.cpp"
 #include "py_igl/py_cat.cpp"
 #include "py_igl/py_collapse_edge.cpp"
 #include "py_igl/py_colon.cpp"
+#include "py_igl/py_column_to_quats.cpp"
 #include "py_igl/py_comb_cross_field.cpp"
 #include "py_igl/py_comb_frame_field.cpp"
 #include "py_igl/py_compute_frame_field_bisectors.cpp"
@@ -136,6 +142,8 @@ void python_export_igl(py::module &m)
 #include "py_igl/py_massmatrix.cpp"
 #include "py_igl/py_min_quad_with_fixed.cpp"
 #include "py_igl/py_n_polyvector.cpp"
+#include "py_igl/py_normalize_row_lengths.cpp"
+#include "py_igl/py_normalize_row_sums.cpp"
 #include "py_igl/py_parula.cpp"
 #include "py_igl/py_per_corner_normals.cpp"
 #include "py_igl/py_per_edge_normals.cpp"

+ 42 - 0
python/py_igl/bbw/py_bbw.cpp

@@ -0,0 +1,42 @@
+py::enum_<igl::bbw::QPSolver>(m, "QPSolver")
+    .value("QP_SOLVER_IGL_ACTIVE_SET", igl::bbw::QP_SOLVER_IGL_ACTIVE_SET)
+    .value("QP_SOLVER_MOSEK", igl::bbw::QP_SOLVER_MOSEK)
+    .value("NUM_QP_SOLVERS", igl::bbw::NUM_QP_SOLVERS)
+    .export_values();
+
+// Wrap the BBWData class
+py::class_<igl::bbw::BBWData > BBWData(m, "BBWData");
+
+BBWData
+.def(py::init<>())
+.def_readwrite("partition_unity", &igl::bbw::BBWData::partition_unity)
+.def_readwrite("W0", &igl::bbw::BBWData::W0)
+.def_readwrite("active_set_params", &igl::bbw::BBWData::active_set_params)
+.def_readwrite("qp_solver", &igl::bbw::BBWData::qp_solver)
+.def_readwrite("verbosity", &igl::bbw::BBWData::verbosity)
+#ifndef IGL_NO_MOSEK
+.def_readwrite("mosek_data", &igl::bbw::BBWData::mosek_data)
+#endif
+.def("print", [](igl::bbw::BBWData& data)
+{
+    return data.print();
+})
+;
+
+m.def("bbw", []
+(
+  const Eigen::MatrixXd& V,
+  const Eigen::MatrixXi& Ele,
+  const Eigen::MatrixXi& b,
+  const Eigen::MatrixXd& bc,
+  igl::bbw::BBWData& data,
+  Eigen::MatrixXd& W
+)
+{
+  assert_is_VectorX("b",b);
+  Eigen::VectorXi bv;
+  if (b.size() != 0)
+    bv = b;
+  return igl::bbw::bbw(V, Ele, bv, bc, data, W);
+}, __doc_igl_bbw_bbw,
+py::arg("V"), py::arg("Ele"), py::arg("b"), py::arg("bc"), py::arg("data"), py::arg("W"));

+ 24 - 0
python/py_igl/py_boundary_conditions.cpp

@@ -0,0 +1,24 @@
+
+
+m.def("boundary_conditions", []
+(
+  const Eigen::MatrixXd& V,
+  const Eigen::MatrixXi& Ele,
+  const Eigen::MatrixXd& C,
+  const Eigen::MatrixXi& P,
+  const Eigen::MatrixXi& BE,
+  const Eigen::MatrixXi& CE,
+  Eigen::MatrixXi& b,
+  Eigen::MatrixXd& bc
+)
+{
+  assert_is_VectorX("P", P);
+  Eigen::VectorXi Pv;
+  if (P.size() != 0)
+    Pv = P;
+  Eigen::VectorXi bv;
+  igl::boundary_conditions(V, Ele, C, Pv, BE, CE, bv, bc);
+  b = bv;
+}, __doc_igl_boundary_conditions,
+py::arg("V"), py::arg("Ele"), py::arg("C"), py::arg("P"), py::arg("BE"), py::arg("CE"), py::arg("b"), py::arg("bc"));
+

+ 10 - 0
python/py_igl/py_column_to_quats.cpp

@@ -0,0 +1,10 @@
+m.def("column_to_quats", []
+(
+  const Eigen::MatrixXd& Q,
+  RotationList& vQ
+)
+{
+  return igl::column_to_quats(Q, vQ);
+}, __doc_igl_column_to_quats,
+py::arg("Q"), py::arg("vQ"));
+

+ 12 - 0
python/py_igl/py_normalize_row_lengths.cpp

@@ -0,0 +1,12 @@
+
+
+m.def("normalize_row_lengths", []
+(
+  const Eigen::MatrixXd& A,
+  Eigen::MatrixXd& B
+)
+{
+  return igl::normalize_row_lengths(A, B);
+}, __doc_igl_normalize_row_lengths,
+py::arg("A"), py::arg("B"));
+

+ 12 - 0
python/py_igl/py_normalize_row_sums.cpp

@@ -0,0 +1,12 @@
+
+
+m.def("normalize_row_sums", []
+(
+  const Eigen::MatrixXd& A,
+  Eigen::MatrixXd& B
+)
+{
+  return igl::normalize_row_sums(A, B);
+}, __doc_igl_normalize_row_sums,
+py::arg("A"), py::arg("B"));
+

+ 17 - 4
python/python_shared.cpp

@@ -30,12 +30,16 @@ extern void python_export_igl_triangle(py::module &);
 extern void python_export_igl_cgal(py::module &);
 #endif
 
+#ifdef PY_COPYLEFT
+extern void python_export_igl_copyleft(py::module &);
+#endif
+
 #ifdef PY_PNG
 extern void python_export_igl_png(py::module &);
 #endif
 
-#ifdef PY_COPYLEFT
-extern void python_export_igl_copyleft(py::module &);
+#ifdef PY_BBW
+extern void python_export_igl_bbw(py::module &);
 #endif
 
 PYBIND11_PLUGIN(pyigl) {
@@ -57,11 +61,14 @@ PYBIND11_PLUGIN(pyigl) {
            avg_edge_length
            barycenter
            barycentric_coordinates
+           bbw_bbw
+           boundary_conditions
            boundary_facets
            boundary_loop
            cat
            collapse_edge
            colon
+           column_to_quats
            comb_cross_field
            comb_frame_field
            compute_frame_field_bisectors
@@ -104,6 +111,8 @@ PYBIND11_PLUGIN(pyigl) {
            massmatrix
            min_quad_with_fixed
            n_polyvector
+           normalize_row_lengths
+           normalize_row_sums
            parula
            per_corner_normals
            per_edge_normals
@@ -169,12 +178,16 @@ PYBIND11_PLUGIN(pyigl) {
     python_export_igl_cgal(m);
     #endif
 
+    #ifdef PY_COPYLEFT
+    python_export_igl_copyleft(m);
+    #endif
+
     #ifdef PY_PNG
     python_export_igl_png(m);
     #endif
 
-    #ifdef PY_COPYLEFT
-    python_export_igl_copyleft(m);
+    #ifdef PY_BBW
+    python_export_igl_bbw(m);
     #endif
 
     return m.ptr();

+ 12 - 4
python/scripts/python_shared.mako

@@ -30,12 +30,16 @@ extern void python_export_igl_triangle(py::module &);
 extern void python_export_igl_cgal(py::module &);
 #endif
 
+#ifdef PY_COPYLEFT
+extern void python_export_igl_copyleft(py::module &);
+#endif
+
 #ifdef PY_PNG
 extern void python_export_igl_png(py::module &);
 #endif
 
-#ifdef PY_COPYLEFT
-extern void python_export_igl_copyleft(py::module &);
+#ifdef PY_BBW
+extern void python_export_igl_bbw(py::module &);
 #endif
 
 PYBIND11_PLUGIN(pyigl) {
@@ -82,12 +86,16 @@ PYBIND11_PLUGIN(pyigl) {
     python_export_igl_cgal(m);
     #endif
 
+    #ifdef PY_COPYLEFT
+    python_export_igl_copyleft(m);
+    #endif
+
     #ifdef PY_PNG
     python_export_igl_png(m);
     #endif
 
-    #ifdef PY_COPYLEFT
-    python_export_igl_copyleft(m);
+    #ifdef PY_BBW
+    python_export_igl_bbw(m);
     #endif
 
     return m.ptr();

+ 145 - 0
python/tutorial/403_BoundedBiharmonicWeights.py

@@ -0,0 +1,145 @@
+import sys, os
+
+# Add the igl library to the modules search path
+sys.path.insert(0, os.getcwd() + "/../")
+import pyigl as igl
+
+from shared import TUTORIAL_SHARED_PATH, check_dependencies, print_usage
+
+dependencies = ["viewer", "bbw"]
+check_dependencies(dependencies)
+
+
+def pre_draw(viewer):
+    global pose, anim_t, C, BE, P, U, M, anim_t_dir
+
+    if viewer.core.is_animating:
+        # Interpolate pose and identity
+        anim_pose = igl.RotationList(len(pose))
+
+        for e in range(len(pose)):
+            anim_pose[e] = pose[e].slerp(anim_t, igl.eigen.Quaterniond.Identity())
+
+        # Propogate relative rotations via FK to retrieve absolute transformations
+        vQ = igl.RotationList()
+        vT = []
+        igl.forward_kinematics(C, BE, P, anim_pose, vQ, vT)
+        dim = C.cols()
+        T = igl.eigen.MatrixXd(BE.rows() * (dim + 1), dim)
+        for e in range(BE.rows()):
+            a = igl.eigen.Affine3d.Identity()
+            a.translate(vT[e])
+            a.rotate(vQ[e])
+            T.setBlock(e * (dim + 1), 0, dim + 1, dim, a.matrix().transpose().block(0, 0, dim + 1, dim))
+
+        # Compute deformation via LBS as matrix multiplication
+        U = M * T
+
+        # Also deform skeleton edges
+        CT = igl.eigen.MatrixXd()
+        BET = igl.eigen.MatrixXi()
+        igl.deform_skeleton(C, BE, T, CT, BET)
+
+        viewer.data.set_vertices(U)
+        viewer.data.set_edges(CT, BET, sea_green)
+        viewer.data.compute_normals()
+        anim_t += anim_t_dir
+        anim_t_dir *= -1.0 if (0.0 >= anim_t or anim_t >= 1.0) else 1.0
+
+    return False
+
+
+def key_down(viewer, key, mods):
+    global selected, W
+    if key == ord('.'):
+        selected += 1
+        selected = min(max(selected, 0), W.cols()-1)
+        set_color(viewer)
+    elif key == ord(','):
+        selected -= 1
+        selected = min(max(selected, 0), W.cols()-1)
+        set_color(viewer)
+    elif key == ord(' '):
+        viewer.core.is_animating = not viewer.core.is_animating
+
+    return True
+
+
+def set_color(viewer):
+    global selected, W
+    C = igl.eigen.MatrixXd()
+    igl.jet(W.col(selected), True, C)
+    viewer.data.set_colors(C)
+
+
+if __name__ == "__main__":
+    keys = {".": "show next weight function",
+            ",": "show previous weight function",
+            "space": "toggle animation"}
+
+    print_usage(keys)
+
+    V = igl.eigen.MatrixXd()
+    W = igl.eigen.MatrixXd()
+    U = igl.eigen.MatrixXd()
+    C = igl.eigen.MatrixXd()
+    M = igl.eigen.MatrixXd()
+    Q = igl.eigen.MatrixXd()
+    T = igl.eigen.MatrixXi()
+    F = igl.eigen.MatrixXi()
+    BE = igl.eigen.MatrixXi()
+    P = igl.eigen.MatrixXi()
+
+    sea_green = igl.eigen.MatrixXd([[70. / 255., 252. / 255., 167. / 255.]])
+
+    selected = 0
+    pose = igl.RotationList()
+    anim_t = 1.0
+    anim_t_dir = -0.03
+
+    igl.readMESH(TUTORIAL_SHARED_PATH + "hand.mesh", V, T, F)
+    U = igl.eigen.MatrixXd(V)
+    igl.readTGF(TUTORIAL_SHARED_PATH + "hand.tgf", C, BE)
+
+    # Retrieve parents for forward kinematics
+    igl.directed_edge_parents(BE, P)
+
+    # Read pose as matrix of quaternions per row
+    igl.readDMAT(TUTORIAL_SHARED_PATH + "hand-pose.dmat", Q)
+    igl.column_to_quats(Q, pose)
+    assert (len(pose) == BE.rows())
+
+    # List of boundary indices (aka fixed value indices into VV)
+    b = igl.eigen.MatrixXi()
+    # List of boundary conditions of each weight function
+    bc = igl.eigen.MatrixXd()
+
+    igl.boundary_conditions(V, T, C, igl.eigen.MatrixXi(), BE, igl.eigen.MatrixXi(), b, bc)
+
+    # compute BBW weights matrix
+    bbw_data = igl.bbw.BBWData()
+    # only a few iterations for sake of demo
+    bbw_data.active_set_params.max_iter = 8
+    bbw_data.verbosity = 2
+    if not igl.bbw.bbw(V, T, b, bc, bbw_data, W):
+        exit(-1)
+
+    # Normalize weights to sum to one
+    igl.normalize_row_sums(W, W)
+    # precompute linear blend skinning matrix
+    igl.lbs_matrix(V, W, M)
+
+    # Plot the mesh with pseudocolors
+    viewer = igl.viewer.Viewer()
+    viewer.data.set_mesh(U, F)
+    set_color(viewer)
+    viewer.data.set_edges(C, BE, sea_green)
+    viewer.core.show_lines = False
+    viewer.core.show_overlay_depth = False
+    viewer.core.line_width = 1
+    viewer.core.trackball_angle.normalize()
+    viewer.callback_pre_draw = pre_draw
+    viewer.callback_key_down = key_down
+    viewer.core.is_animating = False
+    viewer.core.animation_max_fps = 30.0
+    viewer.launch()