issue_1118.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. from vedo import *
  2. class SubMesh:
  3. """
  4. Cut out a submesh and glue it back, possibly with updated vertices, to the original mesh.
  5. The number and ordering of vertices in the resultant mesh are preserved.
  6. Class properties:
  7. * original_mesh
  8. * submesh: sub-mesh cut from original_mesh
  9. * mesh: the resultant mesh with submesh glued back
  10. * old_pids: indices of the submesh vertices which are also the mesh vertices
  11. * new_pids: indices of the new vertices added to submesh along the cut lines
  12. * cut: the pointcloud of the new vertices
  13. * dist2cut: distances from the original vertices in the submesh (old_pids) to the cut
  14. """
  15. def __init__(self, msh: Mesh, cut_fn_name: str, **kwargs):
  16. """
  17. :param msh: a Mesh
  18. :param cut_fn_name: Mesh method name to cut the Mesh
  19. :param kwargs: keyworded arguments to the cut meshod
  20. """
  21. self.original_mesh = msh
  22. self.mesh = msh.clone()
  23. self.mesh.pointdata['pids'] = np.arange(self.mesh.nvertices)
  24. self.submesh = getattr(self.mesh.clone(), cut_fn_name)(**kwargs)
  25. verts = Points(self.mesh.vertices)
  26. self.old_pids = []
  27. self.new_pids = []
  28. for i, v in enumerate(self.submesh.vertices):
  29. if Point(v).distance_to(verts) < 1e-3:
  30. self.old_pids.append(i)
  31. else:
  32. self.new_pids.append(i)
  33. self.cut = Points(self.submesh.vertices[self.new_pids])
  34. self.dist2cut = dict()
  35. def glue_(self, radius, align):
  36. """
  37. Glue submesh with possibly modified vertex positions back to the original mesh.
  38. :param radius: smoothing radius. The vertices of submesh which were originally
  39. at the distance smaller than radius, are interpolated between the original and new positions proportionally
  40. to the distance
  41. :param align: align the cut of submesh to the cut of the original mesh before gluing
  42. :return: mesh with submesh glued back
  43. """
  44. sm = self.submesh.clone()
  45. if align:
  46. sm.align_with_landmarks(self.submesh.vertices[self.new_pids], self.cut.vertices, rigid=True)
  47. if radius > 0:
  48. if len(self.dist2cut) == 0: # pre-compute the distances for interactive gluing
  49. for i in self.old_pids:
  50. pos = self.original_mesh.vertices[self.submesh.pointdata['pids'][i]]
  51. self.dist2cut[i] = Point(pos).distance_to(self.cut).item()
  52. for i in self.old_pids:
  53. d = min(self.dist2cut[i] / radius, 1.)
  54. self.mesh.vertices[self.submesh.pointdata['pids'][i]] = (
  55. d * sm.vertices[i] + (1-d) * self.original_mesh.vertices[self.submesh.pointdata['pids'][i]])
  56. else:
  57. for i in self.old_pids:
  58. self.mesh.vertices[self.submesh.pointdata['pids'][i]] = sm.vertices[i]
  59. self.mesh.pointdata.remove('pids')
  60. def glue(self, radius: float=0, mesh_col="wheat", align=False, interactive=False):
  61. """
  62. Glue submesh with possibly modified vertex positions back to the original mesh.
  63. :param radius: smoothing radius. The vertices of submesh which were originally
  64. at the distance smaller than radius, are interpolated between the original and new positions proportionally
  65. to the distance
  66. :param mesh_col: colour of the mesh in the plot
  67. :param align: align the cut of submesh to the cut of the original mesh before gluing
  68. :param interactive: open an interactive plot to adjust the smoothing radius
  69. :return: mesh with submesh glued back
  70. """
  71. self.glue_(radius=radius, align=align)
  72. if not interactive:
  73. return
  74. else:
  75. if len(self.dist2cut) == 0: # pre-compute the distances for interactive gluing
  76. for i in self.old_pids:
  77. pos = self.original_mesh.vertices[self.submesh.pointdata['pids'][i]]
  78. self.dist2cut[i] = Point(pos).distance_to(self.cut).item()
  79. self.plt = Plotter()
  80. self.plt += self.mesh.c(mesh_col)
  81. def stitch(widget, event):
  82. self.glue_(radius=widget.value**2, align=align)
  83. self.plt -= self.mesh
  84. self.plt += self.mesh.c(mesh_col)
  85. self.plt.add_slider(
  86. stitch,
  87. value=radius,
  88. xmin=0,
  89. xmax=np.array(list(self.dist2cut.values())).max()**0.5 *2,
  90. pos="bottom",
  91. title="Smoothing radius",
  92. )
  93. self.plt.show(interactive=True).close()
  94. S = Sphere(r=1, res=50).lw(1).flat()
  95. box = Cube(side=1.5).wireframe()
  96. cups = SubMesh(S, 'cut_with_box', bounds=box, invert=True)
  97. cups.submesh.scale(1.2) # alter the submesh
  98. cups.glue(radius=0.2, mesh_col="coral", interactive=True)
  99. man = Mesh(dataurl+"man.vtk").rotate_x(-90)
  100. man.color('w').lw(1).flat()
  101. cut_height = 1.20
  102. head = SubMesh(man, 'cut_with_plane', origin=(0, cut_height, 0), normal=(0, 1, 0))
  103. # modify the head:
  104. head.submesh.scale(1.2, origin=(0,cut_height,0)).shift((0, 0.05, 0))
  105. head.glue(interactive=True)