123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- # Morph one shape into another interactively
- # (can work in 3d too! see example warp4b.py)
- #
- from vedo import Plotter, Axes, dataurl, Assembly, printc, merge
- from vedo.shapes import Text2D, Points, Lines, Arrows2D, Grid
- class Morpher:
- def __init__(self, mesh1, mesh2, n): ############################### init
- self.n = n # desired nr. of intermediate shapes
- self.mode = '2d'
- self.mesh1 = mesh1
- self.mesh2 = mesh2
- self.merged_meshes = merge(mesh1, mesh2)
- self.mesh1.lw(4).c('grey2').pickable(False)
- self.mesh2.lw(4).c('grey1').pickable(False)
- self.arrow_starts = []
- self.arrow_stops = []
- self.dottedln = None
- self.toggle = False
- self.instructions = (
- "Click to add arrows interactively on the left panel\n"
- "right-click to remove last arrow. Then press:\n"
- "- m to morph the plane\n"
- "- c to clear\n"
- "- g to generate interpolation"
- )
- self.msg1 = Text2D(self.instructions, pos='top-left', font="VictorMono", bg='g2', alpha=0.6)
- self.msg2 = Text2D('[output will show here]', pos='top-left', font="VictorMono")
- sz = self.merged_meshes.diagonal_size()
- self.plane1 = Grid(s=[sz,sz], res=[50,50]).pos(self.merged_meshes.center_of_mass())
- self.plane1.wireframe(False).alpha(1).linewidth(0.1).c('white').lc('grey5')
- self.plane2 = self.plane1.clone().pickable(False)
- self.plotter = Plotter(N=2, bg='light blue', size=(2000,1000), sharecam=0)
- self.plotter.add_callback('left click', self.onleftclick)
- self.plotter.add_callback('right click', self.onrightclick)
- self.plotter.add_callback('key press', self.onkeypress)
- def start(self): ################################################ show stuff
- paxes = Axes(self.plane1, xygrid=0, text_scale=0.6)
- self.plotter.at(0).show(self.plane1, paxes, self.msg1, self.mesh1, self.mesh2)
- self.plotter.at(1).show(self.plane2, self.msg2, mode='image')
- if len(self.arrow_starts)>0:
- self.draw(True)
- self.draw(False)
- self.msg1.text(self.instructions)
- self.plotter.show(interactive=True, zoom=1.3).close()
- def draw(self, toggle=None): #################################### update scene
- if toggle is None:
- toggle = self.toggle
- if toggle:
- self.msg1.text("Choose start point or press:\nm to morph the shapes\ng to interpolate")
- self.plotter.at(0).remove("displacementArrows")
- if len(self.arrow_starts)==0: return
- arrows = Arrows2D(self.arrow_starts, self.arrow_stops).c('red4')
- arrows.name = "displacementArrows"
- self.plotter.add(arrows)
- else:
- self.msg1.text("Click to choose an end point")
- self.plotter.at(0).remove("displacementPoints")
- points = Points(self.arrow_starts).ps(15).c('green3',0.5)
- points.name = "displacementPoints"
- self.plotter.add(points)
- def onleftclick(self, evt): ############################################ add points
- msh = evt.object
- if not msh or msh.name!="Grid": return
- pt = self.merged_meshes.closest_point(evt.picked3d) # get the closest pt on the line
- self.arrow_stops.append(pt) if self.toggle else self.arrow_starts.append(pt)
- self.draw()
- self.toggle = not self.toggle
- def onrightclick(self, evt): ######################################## remove points
- if not self.arrow_starts: return
- self.arrow_starts.pop()
- if not self.toggle:
- self.arrow_stops.pop()
- self.plotter.at(0).clear().add_renderer_frame()
- self.plotter.add([self.plane1, self.msg1, self.mesh1, self.mesh2])
- self.draw(False)
- self.draw(True)
- def onkeypress(self, evt): ###################################### MORPH & GENERATE
- if evt.keypress == 'm': ##--------- morph mesh1 based on the existing arrows
- if len(self.arrow_starts) != len(self.arrow_stops):
- printc("You must select your end point first!", c='y')
- return
- warped_plane = self.plane1.clone().pickable(False)
- warped_plane.warp(self.arrow_starts, self.arrow_stops, mode=self.mode)
- T = warped_plane.transform
- mw = self.mesh1.clone().apply_transform(T).c('red4')
- a = Points(self.arrow_starts, r=10).apply_transform(T)
- b = Points(self.arrow_stops, r=10).apply_transform(T)
- T_inv = T.compute_inverse()
- self.dottedln = Lines(a,b, res=self.n).apply_transform(T_inv).point_size(5)
- self.msg1.text(self.instructions)
- self.msg2.text("Morphed output:")
- axes = Axes(warped_plane, xygrid=0, text_scale=0.6)
- self.plotter.at(1).clear()
- self.plotter.add_renderer_frame()
- self.plotter.add(self.mesh1.clone().c('grey4'), self.mesh2, self.msg2)
- self.plotter.add(warped_plane, axes, mw, self.dottedln)
- self.plotter.reset_camera().render()
- elif evt.keypress == 'g': ##------- generate intermediate shapes
- if not self.dottedln:
- return
- intermediates = []
- allpts = self.dottedln.vertices
- allpts = allpts.reshape(len(self.arrow_starts), self.n+1, 3)
- for i in range(self.n + 1):
- pi = allpts[:,i,:]
- m_nterp = self.mesh1.clone().warp(self.arrow_starts, pi, mode=self.mode)
- m_nterp.c('blue3').lw(1)
- intermediates.append(m_nterp)
- self.msg2.text("Morphed output + Interpolation:")
- self.plotter.at(1).add(intermediates).render()
- self.dottedln = None
- elif evt.keypress == 'c': ##------- clear all
- self.arrow_starts = []
- self.arrow_stops = []
- self.toggle = False
- self.dottedln = None
- self.msg1.text(self.instructions)
- self.msg2.text("[output will show here]")
- self.plotter.at(0).clear()
- self.plotter.add(self.plane1, self.msg1, self.mesh1, self.mesh2)
- self.plotter.at(1).clear().add_renderer_frame()
- self.plotter.add(self.plane2, self.msg2).render()
- ######################################################################################## MAIN
- if __name__ == "__main__":
- outlines = Assembly(dataurl + "timecourse1d.npy") # load a set of 2d shapes
- mesh1 = outlines[25]
- mesh2 = outlines[35].scale(1.3).shift(-2,0,0)
- morpher = Morpher(mesh1, mesh2, 10) # generate 10 intermediate outlines
- morpher.start()
|