warp4a.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. # Morph one shape into another interactively
  2. # (can work in 3d too! see example warp4b.py)
  3. #
  4. from vedo import Plotter, Axes, dataurl, Assembly, printc, merge
  5. from vedo.shapes import Text2D, Points, Lines, Arrows2D, Grid
  6. class Morpher:
  7. def __init__(self, mesh1, mesh2, n): ############################### init
  8. self.n = n # desired nr. of intermediate shapes
  9. self.mode = '2d'
  10. self.mesh1 = mesh1
  11. self.mesh2 = mesh2
  12. self.merged_meshes = merge(mesh1, mesh2)
  13. self.mesh1.lw(4).c('grey2').pickable(False)
  14. self.mesh2.lw(4).c('grey1').pickable(False)
  15. self.arrow_starts = []
  16. self.arrow_stops = []
  17. self.dottedln = None
  18. self.toggle = False
  19. self.instructions = (
  20. "Click to add arrows interactively on the left panel\n"
  21. "right-click to remove last arrow. Then press:\n"
  22. "- m to morph the plane\n"
  23. "- c to clear\n"
  24. "- g to generate interpolation"
  25. )
  26. self.msg1 = Text2D(self.instructions, pos='top-left', font="VictorMono", bg='g2', alpha=0.6)
  27. self.msg2 = Text2D('[output will show here]', pos='top-left', font="VictorMono")
  28. sz = self.merged_meshes.diagonal_size()
  29. self.plane1 = Grid(s=[sz,sz], res=[50,50]).pos(self.merged_meshes.center_of_mass())
  30. self.plane1.wireframe(False).alpha(1).linewidth(0.1).c('white').lc('grey5')
  31. self.plane2 = self.plane1.clone().pickable(False)
  32. self.plotter = Plotter(N=2, bg='light blue', size=(2000,1000), sharecam=0)
  33. self.plotter.add_callback('left click', self.onleftclick)
  34. self.plotter.add_callback('right click', self.onrightclick)
  35. self.plotter.add_callback('key press', self.onkeypress)
  36. def start(self): ################################################ show stuff
  37. paxes = Axes(self.plane1, xygrid=0, text_scale=0.6)
  38. self.plotter.at(0).show(self.plane1, paxes, self.msg1, self.mesh1, self.mesh2)
  39. self.plotter.at(1).show(self.plane2, self.msg2, mode='image')
  40. if len(self.arrow_starts)>0:
  41. self.draw(True)
  42. self.draw(False)
  43. self.msg1.text(self.instructions)
  44. self.plotter.show(interactive=True, zoom=1.3).close()
  45. def draw(self, toggle=None): #################################### update scene
  46. if toggle is None:
  47. toggle = self.toggle
  48. if toggle:
  49. self.msg1.text("Choose start point or press:\nm to morph the shapes\ng to interpolate")
  50. self.plotter.at(0).remove("displacementArrows")
  51. if len(self.arrow_starts)==0: return
  52. arrows = Arrows2D(self.arrow_starts, self.arrow_stops).c('red4')
  53. arrows.name = "displacementArrows"
  54. self.plotter.add(arrows)
  55. else:
  56. self.msg1.text("Click to choose an end point")
  57. self.plotter.at(0).remove("displacementPoints")
  58. points = Points(self.arrow_starts).ps(15).c('green3',0.5)
  59. points.name = "displacementPoints"
  60. self.plotter.add(points)
  61. def onleftclick(self, evt): ############################################ add points
  62. msh = evt.object
  63. if not msh or msh.name!="Grid": return
  64. pt = self.merged_meshes.closest_point(evt.picked3d) # get the closest pt on the line
  65. self.arrow_stops.append(pt) if self.toggle else self.arrow_starts.append(pt)
  66. self.draw()
  67. self.toggle = not self.toggle
  68. def onrightclick(self, evt): ######################################## remove points
  69. if not self.arrow_starts: return
  70. self.arrow_starts.pop()
  71. if not self.toggle:
  72. self.arrow_stops.pop()
  73. self.plotter.at(0).clear().add_renderer_frame()
  74. self.plotter.add([self.plane1, self.msg1, self.mesh1, self.mesh2])
  75. self.draw(False)
  76. self.draw(True)
  77. def onkeypress(self, evt): ###################################### MORPH & GENERATE
  78. if evt.keypress == 'm': ##--------- morph mesh1 based on the existing arrows
  79. if len(self.arrow_starts) != len(self.arrow_stops):
  80. printc("You must select your end point first!", c='y')
  81. return
  82. warped_plane = self.plane1.clone().pickable(False)
  83. warped_plane.warp(self.arrow_starts, self.arrow_stops, mode=self.mode)
  84. T = warped_plane.transform
  85. mw = self.mesh1.clone().apply_transform(T).c('red4')
  86. a = Points(self.arrow_starts, r=10).apply_transform(T)
  87. b = Points(self.arrow_stops, r=10).apply_transform(T)
  88. T_inv = T.compute_inverse()
  89. self.dottedln = Lines(a,b, res=self.n).apply_transform(T_inv).point_size(5)
  90. self.msg1.text(self.instructions)
  91. self.msg2.text("Morphed output:")
  92. axes = Axes(warped_plane, xygrid=0, text_scale=0.6)
  93. self.plotter.at(1).clear()
  94. self.plotter.add_renderer_frame()
  95. self.plotter.add(self.mesh1.clone().c('grey4'), self.mesh2, self.msg2)
  96. self.plotter.add(warped_plane, axes, mw, self.dottedln)
  97. self.plotter.reset_camera().render()
  98. elif evt.keypress == 'g': ##------- generate intermediate shapes
  99. if not self.dottedln:
  100. return
  101. intermediates = []
  102. allpts = self.dottedln.vertices
  103. allpts = allpts.reshape(len(self.arrow_starts), self.n+1, 3)
  104. for i in range(self.n + 1):
  105. pi = allpts[:,i,:]
  106. m_nterp = self.mesh1.clone().warp(self.arrow_starts, pi, mode=self.mode)
  107. m_nterp.c('blue3').lw(1)
  108. intermediates.append(m_nterp)
  109. self.msg2.text("Morphed output + Interpolation:")
  110. self.plotter.at(1).add(intermediates).render()
  111. self.dottedln = None
  112. elif evt.keypress == 'c': ##------- clear all
  113. self.arrow_starts = []
  114. self.arrow_stops = []
  115. self.toggle = False
  116. self.dottedln = None
  117. self.msg1.text(self.instructions)
  118. self.msg2.text("[output will show here]")
  119. self.plotter.at(0).clear()
  120. self.plotter.add(self.plane1, self.msg1, self.mesh1, self.mesh2)
  121. self.plotter.at(1).clear().add_renderer_frame()
  122. self.plotter.add(self.plane2, self.msg2).render()
  123. ######################################################################################## MAIN
  124. if __name__ == "__main__":
  125. outlines = Assembly(dataurl + "timecourse1d.npy") # load a set of 2d shapes
  126. mesh1 = outlines[25]
  127. mesh2 = outlines[35].scale(1.3).shift(-2,0,0)
  128. morpher = Morpher(mesh1, mesh2, 10) # generate 10 intermediate outlines
  129. morpher.start()