From e3ccc6d607543b2c250fd2d69a2325aaea30bc05 Mon Sep 17 00:00:00 2001 From: Christopher Lee Date: Mon, 13 Nov 2023 13:28:09 -0800 Subject: [PATCH] Print errors to console when hole filling fails (#55) * Print errors to console when hole filling fails Fixes #39 Signed-off-by: Christopher T. Lee * Update google-test repo Signed-off-by: Christopher T. Lee * feat: Bump metadata to support Blender 3.3 LTS Signed-off-by: Christopher T. Lee * refactor: Add offender info to runtime errors Signed-off-by: Christopher T. Lee * refactor(blendgamer): Update blendgamer cmap Aiming for blender3.3LTS and mpl 3.6.2 Signed-off-by: Christopher T. Lee * feat(blendgamer): Deviatoric curvature Signed-off-by: Christopher T. Lee * chore: bump support blender to 3.6 LTS Fixes #56 Signed-off-by: Christopher T. Lee * chore: Stop support for Blender 2.79b Signed-off-by: Christopher T. Lee --------- Signed-off-by: Christopher T. Lee --- .github/scripts/blender_pyversion_lookup.py | 7 +- .github/workflows/ci.yaml | 2 +- cmake/blenderexec.py.in | 4 +- include/gamer/gamer.h | 3 +- src/SurfaceMeshDetail.cpp | 48 +++++++--- tests/CMakeLists.txt | 11 +-- tools/blendgamer/__init__.py.in | 2 +- tools/blendgamer/src/colormap.py | 98 ++++----------------- tools/blendgamer/src/curvatures.py | 9 +- 9 files changed, 74 insertions(+), 110 deletions(-) diff --git a/.github/scripts/blender_pyversion_lookup.py b/.github/scripts/blender_pyversion_lookup.py index 2c1a08dd..f854229a 100644 --- a/.github/scripts/blender_pyversion_lookup.py +++ b/.github/scripts/blender_pyversion_lookup.py @@ -22,9 +22,12 @@ import sys bver_to_pyver = { - "2.79": '3.5', + # "2.79": '3.5', "2.83": '3.7', - "2.93": '3.9' + "2.93": '3.9', + "3.3": "3.10", + "3.6": "3.10", + } if __name__ == "__main__": diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9879751c..dfc9783a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,7 +20,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - blender: [2.79, 2.83, 2.93] + blender: [2.83, 2.93, 3.3, 3.6] runs-on: ${{ matrix.os }} defaults: run: diff --git a/cmake/blenderexec.py.in b/cmake/blenderexec.py.in index 3330eacc..09d0415d 100644 --- a/cmake/blenderexec.py.in +++ b/cmake/blenderexec.py.in @@ -4,6 +4,6 @@ f = open('@CMAKE_CURRENT_BINARY_DIR@/found_blender_info', 'w') f.write('{}\n'.format(str(bpy.app.version[0]) + '.' + str(bpy.app.version[1]) + '.' + str(bpy.app.version[2]))) f.write('{}\n'.format(bpy.utils.script_path_user())) f.write('{}\n'.format('.'.join(str(v) for v in sys.version_info[0:3]))); -f.write('{}\n'.format(bpy.app.binary_path_python)) +f.write('{}\n'.format(sys.executable)) f.write('{}\n'.format(struct.calcsize('@P'))) -f.close() \ No newline at end of file +f.close() diff --git a/include/gamer/gamer.h b/include/gamer/gamer.h index 32233788..0135f849 100644 --- a/include/gamer/gamer.h +++ b/include/gamer/gamer.h @@ -36,7 +36,8 @@ void throw_runtime_error(const char *function, const char *file, const int line, T &&...ts) { std::stringstream ss; ss << "Error: "; - int dummy[] = {0, ((ss << std::forward(ts)), 0)...}; + // https://stackoverflow.com/questions/25680461/variadic-template-pack-expansion/25683817#25683817 + int dummy[] = {0, ((ss << std::forward(ts) << " "), 0)...}; static_cast(dummy); // Avoid warning for unused variable ss << " in function " << function << " at " << file << ":" << line; throw std::runtime_error(ss.str()); diff --git a/src/SurfaceMeshDetail.cpp b/src/SurfaceMeshDetail.cpp index b6111488..95070209 100644 --- a/src/SurfaceMeshDetail.cpp +++ b/src/SurfaceMeshDetail.cpp @@ -142,9 +142,14 @@ void decimateVertex(SurfaceMesh &mesh, SurfaceMesh::SimplexID<1> vertexID, } // The ring isn't really a ring if (!success) { - gamer_runtime_error( - "ERROR(coarse): Hole ring is not closed. " - "Please contact the developers with this error."); + std::stringstream ss; + ss << "Offending vertices:"; + for (auto item : boundary) { + ss << item << ", "; + } + gamer_runtime_error("ERROR(coarse): Hole ring is not closed. " + "Please contact the developers with this error.", + ss.str()); } } @@ -237,8 +242,14 @@ void triangulateHole(SurfaceMesh &mesh, mesh, std::move(keys), backupVerts.begin(), backupVerts.end()); if (!computeLocalOrientation(mesh, holeEdges)) { - gamer_runtime_error( - "ERROR(triangulateHole): Mesh became non-orientable"); + + std::stringstream ss; + ss << "Offending edges:"; + for (auto item : holeEdges) { + ss << item << ", "; + } + gamer_runtime_error("ERROR(triangulateHole): Mesh became non-orientable.", + ss.str()); } } @@ -354,9 +365,11 @@ void weightedVertexSmooth(SurfaceMesh &mesh, SurfaceMesh::SimplexID<1> vertexID, nS = next - shared; normalize(nS); } catch (std::exception &e) { - gamer_runtime_error( - "ERROR: Zero length edge found. " - "weightedVertexSmooth expects no zero length edges."); + std::stringstream ss; + ss << "Offending edge: " << edge; + gamer_runtime_error("ERROR: Zero length edge found. " + "weightedVertexSmooth expects no zero length edges.", + ss.str()); } // Bisector of the 'rhombus' @@ -472,9 +485,11 @@ Vector weightedVertexSmoothCache(SurfaceMesh &mesh, nS = next - shared; normalize(nS); } catch (std::exception &e) { - gamer_runtime_error( - "ERROR: Zero length edge found. " - "weightedVertexSmooth expects no zero length edges."); + std::stringstream ss; + ss << "Offending edge: " << edge; + gamer_runtime_error("ERROR: Zero length edge found." + "weightedVertexSmooth expects no zero length edges.", + ss.str()); } // Bisector of the 'rhombus' @@ -558,7 +573,7 @@ void normalSmoothH(SurfaceMesh &mesh, SurfaceMesh::SimplexID<1> vertexID, auto p = (*vertexID).position; Eigen::Vector4d pos_e{p[0], p[1], p[2], 1}; // 4D for affine3D - Eigen::Vector4d newPos_e{0,0,0,0}; + Eigen::Vector4d newPos_e{0, 0, 0, 0}; // For each incident face get the average normal auto incidentFaces = mesh.up(mesh.up(vertexID)); @@ -892,7 +907,12 @@ void findHoles(const SurfaceMesh &mesh, // mesh.down(firstEdge, std::back_inserter(visitedVerts)); // Try to complete the ring if (!orderBoundaryEdgeRing(mesh, bdryEdges, visitedVerts, bdryRing)) { - gamer_runtime_error("Couldn't connect ring"); + std::stringstream ss; + ss << "Participating edges: "; + for (auto item : bdryEdges) { + ss << item << ", "; + } + gamer_runtime_error("Couldn't connect ring", ss.str()); } holeList.push_back(bdryRing); } @@ -1023,7 +1043,7 @@ bool checkEdgeFlip( // faces. " // << "Returning..." << std::endl; gamer_runtime_error("SurfaceMesh is not pseudomanifold. Found an edge " - "connected to more than 2 faces."); + "connected to more than 2 faces."); } else if (up.size() < 2) // Edge is a boundary { // std::cerr << "This edge participates in fewer than 2 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee4fdec0..75a1ece8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,11 +26,12 @@ else() endif() FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG origin/master - SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" - BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG origin/main + GIT_SHALLOW TRUE + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" ) # Prevent overriding the parent project's compiler/linker diff --git a/tools/blendgamer/__init__.py.in b/tools/blendgamer/__init__.py.in index 122761bd..7985a54a 100644 --- a/tools/blendgamer/__init__.py.in +++ b/tools/blendgamer/__init__.py.in @@ -24,7 +24,7 @@ bl_info = { "description": "Utilities for generating finite elements simulation compatible meshes", "author": "Christopher T. Lee, Justin Laughlin, John B. Moody, Zeyun Yu, Tom Bartol, Johan Hake, and Michael Holst", "version": (@PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@), - "blender": (2, 93, 0), + "blender": (3, 6, 0), "location": "3D View > Tool Shelf", "wiki_url": "https://github.com/ctlee/gamer", "tracker_url": "https://github.com/ctlee/gamer/issues", diff --git a/tools/blendgamer/src/colormap.py b/tools/blendgamer/src/colormap.py index 1cdb8bfb..e885bf60 100644 --- a/tools/blendgamer/src/colormap.py +++ b/tools/blendgamer/src/colormap.py @@ -39,76 +39,6 @@ } -class DivergingNorm(mpl.colors.Normalize): - def __init__(self, vcenter, vmin=None, vmax=None): - """ - Normalize data with a set center. - - Useful when mapping data with an unequal rates of change around a - conceptual center, e.g., data that range from -2 to 4, with 0 as - the midpoint. - - Parameters - ---------- - vcenter : float - The data value that defines ``0.5`` in the normalization. - vmin : float, optional - The data value that defines ``0.0`` in the normalization. - Defaults to the min value of the dataset. - vmax : float, optional - The data value that defines ``1.0`` in the normalization. - Defaults to the the max value of the dataset. - - Examples - -------- - This maps data value -4000 to 0., 0 to 0.5, and +10000 to 1.0; data - between is linearly interpolated:: - - >>> import matplotlib.colors as mcolors - >>> offset = mcolors.DivergingNorm(vmin=-4000., - vcenter=0., vmax=10000) - >>> data = [-4000., -2000., 0., 2500., 5000., 7500., 10000.] - >>> offset(data) - array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0]) - """ - - self.vcenter = vcenter - self.vmin = vmin - self.vmax = vmax - if not (vcenter and vmin and vmax) and (vcenter >= vmax or vcenter <= vmin): - raise ValueError( - "vmin(%f), vcenter(%f), and vmax(%f) must be in " - "ascending order" % (vmin, vcenter, vmax) - ) - - def autoscale_None(self, A): - """ - Get vmin and vmax, and then clip at vcenter - """ - super().autoscale_None(A) - if self.vmin > self.vcenter: - self.vmin = self.vcenter - if self.vmax < self.vcenter: - self.vmax = self.vcenter - - def __call__(self, value, clip=False): - """ - Map value to the interval [0, 1]. - """ - result, is_scalar = self.process_value(value) - self.autoscale_None(result) # sets self.vmin, self.vmax if None - - if not self.vmin <= self.vcenter <= self.vmax: - raise ValueError("vmin, vcenter, vmax must increase monotonically") - result = np.ma.masked_array( - np.interp(result, [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1.0]), - mask=np.ma.getmask(result), - ) - if is_scalar: - result = np.atleast_1d(result)[0] - return result - - def curveToData(crv, context): """ Helper function to take a curvature object and return smoothed data. @@ -241,7 +171,6 @@ def dataToVertexColor(crv, context, showplot=False, saveplot=False): amin = np.amin(data) amax = np.amax(data) - # if amin > tmin: # amin = tmin # if amax < tmax: @@ -249,9 +178,7 @@ def dataToVertexColor(crv, context, showplot=False, saveplot=False): # Construct the norm and colorbar if amin < 0 and amax > 0: - norm = DivergingNorm(vmin=amin, vcenter=0, vmax=amax) - # Python 3.5 matplotlib may not support? - # norm = mpl.colors.DivergingNorm(vmin=amin, vcenter=0, vmax=amax) + norm = mpl.colors.TwoSlopeNorm(0, vmin=amin, vmax=amax) colors_neg = cmap(np.linspace(0, crv.mixpoint, 256)) colors_pos = cmap(np.linspace(crv.mixpoint, 1, 256)) @@ -271,13 +198,15 @@ def dataToVertexColor(crv, context, showplot=False, saveplot=False): colors = colors[:, :3] mesh = bpy.context.object.data - vlayer = "%s%s" % (crv.algorithm, crv.curvatureType) + vlayer = "%s%s_color" % (crv.algorithm, crv.curvatureType) + if vlayer not in mesh.vertex_colors: - if len(mesh.vertex_colors) == 8: - raise RuntimeError( - "Maximum of 8 vertex Layers reached cannot create a new layer. Please delete a layer to continue." - ) + if bpy.app.version < (3, 3, 0): + if len(mesh.vertex_colors) == 8: + raise RuntimeError( + "Maximum of 8 vertex Layers reached cannot create a new layer. Please delete a layer to continue." + ) mesh.vertex_colors.new(name=vlayer) color_layer = mesh.vertex_colors[vlayer] @@ -289,13 +218,18 @@ def dataToVertexColor(crv, context, showplot=False, saveplot=False): # Add axis for colorbar and plot it ax = fig.add_axes([0.75, 0.05, 0.05, 0.9]) - cb = mpl.colorbar.ColorbarBase( + cb = mpl.colorbar.Colorbar( ax, cmap=curvature_map, norm=norm, orientation="vertical" ) ticks = cb.get_ticks() ticks.sort() + if ticks[0] < amin: + ticks = ticks[1:-1] + if ticks[-1] > amax: + ticks = ticks[0:-2] + if amin != ticks[0]: ticks = np.insert(ticks, 0, amin) if amax != ticks[-1]: @@ -399,9 +333,7 @@ def differencePlotter(context, difftype="K1"): # Construct the norm and colorbar if amin < 0 and amax > 0: - norm = DivergingNorm(vmin=amin, vcenter=0, vmax=amax) - # Python 3.5 matplotlib may not support? - # norm = mpl.colors.DivergingNorm(vmin=amin, vcenter=0, vmax=amax) + norm = mpl.colors.TwoSlopeNorm(0, vmin=amin, vmax=amax) colors_neg = cmap(np.linspace(0, 0.5, 256)) colors_pos = cmap(np.linspace(0.5, 1, 256)) diff --git a/tools/blendgamer/src/curvatures.py b/tools/blendgamer/src/curvatures.py index e2c07cd8..1f8e0fe8 100644 --- a/tools/blendgamer/src/curvatures.py +++ b/tools/blendgamer/src/curvatures.py @@ -50,6 +50,7 @@ ("K2", "k2", "Second principle curvature"), ("KG", "kg", "Gaussian curvature"), ("KH", "kh", "Mean curvature"), + ("D", "d", "Deviatoric curvature") ] curvatureCalcEnums = [ @@ -175,6 +176,11 @@ def execute(self, context): ml[i].value = kh[i] curvatures.add_curvature(context, "KH") + ml = getCurvatureLayer(obj, algorithm, "D") + for i in range(0, len(k1)): + ml[i].value = (k1[i]-k2[i]) / 2 + curvatures.add_curvature(context, "D") + # Explicitly free curvature arrays del kh del kg @@ -276,7 +282,8 @@ def remove_curvature(self, context): with BMeshContext(obj) as bm: name = "%s%s" % (crv.algorithm, crv.curvatureType) layer = bm.verts.layers.float.get(name) - bm.verts.layers.float.remove(layer) + if layer: + bm.verts.layers.float.remove(layer) self.curvature_list.remove(self.active_index) self.active_index -= 1