|
| 1 | +# Should import classes from vtkmodules |
| 2 | +# but as an example we use vtk for simplicity |
| 3 | +import vtk |
| 4 | + |
| 5 | +from pathlib import Path |
| 6 | + |
| 7 | +from trame.app import TrameApp |
| 8 | +from trame.decorators import change |
| 9 | +from trame.ui.vuetify3 import SinglePageLayout |
| 10 | +from trame.widgets import vuetify3, vtklocal, client |
| 11 | + |
| 12 | +DATA_PATH = str(Path(__file__).with_name("star-fighter.vtp").resolve()) |
| 13 | + |
| 14 | + |
| 15 | +class Clip(TrameApp): |
| 16 | + def __init__(self, server=None): |
| 17 | + super().__init__(server) |
| 18 | + self._setup_vtk() |
| 19 | + self._build_ui() |
| 20 | + |
| 21 | + def _setup_vtk(self): |
| 22 | + renderer = vtk.vtkRenderer() |
| 23 | + renderWindow = vtk.vtkRenderWindow() |
| 24 | + renderWindow.AddRenderer(renderer) |
| 25 | + renderWindow.OffScreenRenderingOn() |
| 26 | + |
| 27 | + renderWindowInteractor = vtk.vtkRenderWindowInteractor() |
| 28 | + renderWindowInteractor.SetRenderWindow(renderWindow) |
| 29 | + renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera() |
| 30 | + |
| 31 | + reader = vtk.vtkXMLPolyDataReader(file_name=DATA_PATH) |
| 32 | + bounds = reader().GetBounds() |
| 33 | + |
| 34 | + plane = vtk.vtkPlane( |
| 35 | + origin=( |
| 36 | + 0.5 * (bounds[0] + bounds[1]), |
| 37 | + 0.5 * (bounds[2] + bounds[3]), |
| 38 | + 0.5 * (bounds[4] + bounds[5]), |
| 39 | + ), |
| 40 | + ) |
| 41 | + mapper = vtk.vtkPolyDataMapper() |
| 42 | + mapper.AddClippingPlane(plane) |
| 43 | + reader >> mapper |
| 44 | + |
| 45 | + actor = vtk.vtkActor(mapper=mapper) |
| 46 | + renderer.AddActor(actor) |
| 47 | + |
| 48 | + renderer.ResetCamera() |
| 49 | + renderWindow.Render() |
| 50 | + |
| 51 | + # region widget |
| 52 | + # Widget setup |
| 53 | + rep = vtk.vtkImplicitPlaneRepresentation( |
| 54 | + place_factor=1.25, |
| 55 | + outline_translation=False, |
| 56 | + ) |
| 57 | + rep.DrawPlaneOff() |
| 58 | + rep.PlaceWidget(bounds) |
| 59 | + rep.normal = plane.normal |
| 60 | + rep.origin = plane.origin |
| 61 | + |
| 62 | + plane_widget = vtk.vtkImplicitPlaneWidget2( |
| 63 | + interactor=renderWindowInteractor, representation=rep |
| 64 | + ) |
| 65 | + plane_widget.On() |
| 66 | + # endregion widget |
| 67 | + |
| 68 | + self.plane = plane |
| 69 | + self.render_window = renderWindow |
| 70 | + self.actor = actor |
| 71 | + self.mapper = mapper |
| 72 | + self.widget = plane_widget |
| 73 | + self.widget_rep = rep |
| 74 | + |
| 75 | + def _build_ui(self): |
| 76 | + with SinglePageLayout(self.server) as layout: |
| 77 | + self.ui = layout |
| 78 | + |
| 79 | + layout.title.set_text("WASM Widget") |
| 80 | + layout.icon.click = self.ctrl.view_reset_camera |
| 81 | + layout.toolbar.density = "compact" |
| 82 | + |
| 83 | + with layout.content: |
| 84 | + # from trame.widgets.vtk import VtkRemoteView |
| 85 | + # with VtkRemoteView(self.render_window) as view: |
| 86 | + # self.ctrl.view_update = view.update |
| 87 | + # self.ctrl.view_reset_camera = view.reset_camera |
| 88 | + |
| 89 | + # def update_plane(*_): |
| 90 | + # self.plane.SetOrigin(self.widget_rep.GetOrigin()) |
| 91 | + # self.plane.SetNormal(self.widget_rep.GetNormal()) |
| 92 | + # self.ctrl.view_update() |
| 93 | + |
| 94 | + # self.widget.AddObserver("InteractionEvent", update_plane) |
| 95 | + with vtklocal.LocalView( |
| 96 | + self.render_window, |
| 97 | + throttle_rate=20, |
| 98 | + ctx_name="wasm_view", |
| 99 | + updated="utils.get('setupWidget')()", |
| 100 | + ) as view: |
| 101 | + self.ctrl.view_update = view.update_throttle |
| 102 | + self.ctrl.view_reset_camera = view.reset_camera |
| 103 | + |
| 104 | + client.Script(f""" |
| 105 | + function setupWidget() {{ |
| 106 | + const widget = trame.refs.{self.ctx.wasm_view.ref_name}.getVtkObject({self.ctx.wasm_view.register_vtk_object(self.widget)}); |
| 107 | + const plane = trame.refs.{self.ctx.wasm_view.ref_name}.getVtkObject({self.ctx.wasm_view.register_vtk_object(self.plane)}); |
| 108 | + widget.observe("InteractionEvent", () => {{ |
| 109 | + plane.origin = widget.widgetRepresentation.origin; |
| 110 | + plane.normal = widget.widgetRepresentation.normal; |
| 111 | + }}); |
| 112 | + console.log("widget", widget.id); |
| 113 | + console.log("plane", plane.id); |
| 114 | + }} |
| 115 | + window.setupWidget = setupWidget; |
| 116 | + """) |
| 117 | + |
| 118 | + |
| 119 | +def main(): |
| 120 | + # region export |
| 121 | + import sys |
| 122 | + |
| 123 | + app = Clip() |
| 124 | + if "--export" in sys.argv: |
| 125 | + app.ctx.wasm_view.save("star-fighter2.wazex") |
| 126 | + |
| 127 | + app.server.start() |
| 128 | + # endregion export |
| 129 | + |
| 130 | + |
| 131 | +if __name__ == "__main__": |
| 132 | + main() |
0 commit comments