From d6125b91298f9cfa5954e7340e1a39f89c1d1bf4 Mon Sep 17 00:00:00 2001 From: Forrest Date: Wed, 4 May 2022 16:37:26 -0400 Subject: [PATCH 1/7] chore: clean up example css and update vtk.js --- package-lock.json | 16 ++++++++-------- package.json | 4 ++-- usage/src/Geometry/CubeAxes.jsx | 2 +- usage/src/Geometry/CutterExample.jsx | 2 +- usage/src/Geometry/Glyph.jsx | 2 +- usage/src/Geometry/OBJViewer.jsx | 2 +- usage/src/Geometry/Picking.jsx | 2 +- usage/src/Geometry/PointCloud.jsx | 2 +- usage/src/Geometry/PolyDataViewer.jsx | 2 +- usage/src/Geometry/PolyDataWithData.jsx | 2 +- usage/src/Geometry/ProcessingPipeline.jsx | 2 +- usage/src/Geometry/SourceViewer.jsx | 2 +- usage/src/Geometry/TubeExample.jsx | 2 +- usage/src/Volume/DynamicRepUpdate.jsx | 4 ++-- usage/src/Volume/DynamicUpdate.jsx | 4 ++-- usage/src/Volume/SliceRendering.jsx | 6 +++--- usage/src/Volume/SyntheticVolumeRendering.jsx | 2 +- usage/src/Volume/VolumeRendering.jsx | 2 +- 18 files changed, 30 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a2ffc7..fca0bf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", - "@kitware/vtk.js": "^24.3.1", + "@kitware/vtk.js": "^24.7.1", "@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-commonjs": "17.0.0", "@rollup/plugin-eslint": "^8.0.1", @@ -46,7 +46,7 @@ "semantic-release": "17.3.1" }, "peerDependencies": { - "@kitware/vtk.js": "^24.3.1", + "@kitware/vtk.js": "^24.7.1", "react": "^16.0.0" } }, @@ -2128,9 +2128,9 @@ "dev": true }, "node_modules/@kitware/vtk.js": { - "version": "24.3.1", - "resolved": "https://registry.npmjs.org/@kitware/vtk.js/-/vtk.js-24.3.1.tgz", - "integrity": "sha512-SLDjDPohq1esh4MIYBqMQQYGu/Um4x5bbnuLyFo9l0bg1KmgLqC1IGnCtWecG5w0LR62JbSAHCcp/m9Zd4GaAw==", + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@kitware/vtk.js/-/vtk.js-24.7.1.tgz", + "integrity": "sha512-geOFGGpLFertWHu1oo23kScpd4fduBetOWtxFPWf1wlTHw/vxQNnp7+razqBIALOSYjFVftj8zplFAWW58cHhQ==", "dev": true, "dependencies": { "@babel/runtime": "7.16.7", @@ -16905,9 +16905,9 @@ "dev": true }, "@kitware/vtk.js": { - "version": "24.3.1", - "resolved": "https://registry.npmjs.org/@kitware/vtk.js/-/vtk.js-24.3.1.tgz", - "integrity": "sha512-SLDjDPohq1esh4MIYBqMQQYGu/Um4x5bbnuLyFo9l0bg1KmgLqC1IGnCtWecG5w0LR62JbSAHCcp/m9Zd4GaAw==", + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@kitware/vtk.js/-/vtk.js-24.7.1.tgz", + "integrity": "sha512-geOFGGpLFertWHu1oo23kScpd4fduBetOWtxFPWf1wlTHw/vxQNnp7+razqBIALOSYjFVftj8zplFAWW58cHhQ==", "dev": true, "requires": { "@babel/runtime": "7.16.7", diff --git a/package.json b/package.json index c7b69d3..ff2700b 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dev": "rollup ./src/index.js -c --watch" }, "peerDependencies": { - "@kitware/vtk.js": "^24.3.1", + "@kitware/vtk.js": "^24.7.1", "react": "^16.0.0" }, "devDependencies": { @@ -36,7 +36,7 @@ "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", - "@kitware/vtk.js": "^24.3.1", + "@kitware/vtk.js": "^24.7.1", "@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-commonjs": "17.0.0", "@rollup/plugin-eslint": "^8.0.1", diff --git a/usage/src/Geometry/CubeAxes.jsx b/usage/src/Geometry/CubeAxes.jsx index 530d3b8..cc4f90b 100644 --- a/usage/src/Geometry/CubeAxes.jsx +++ b/usage/src/Geometry/CubeAxes.jsx @@ -30,7 +30,7 @@ function Example(props) { zIndex: 1, }; return ( -
+
diff --git a/usage/src/Geometry/CutterExample.jsx b/usage/src/Geometry/CutterExample.jsx index fa654bc..cf0faa2 100644 --- a/usage/src/Geometry/CutterExample.jsx +++ b/usage/src/Geometry/CutterExample.jsx @@ -11,7 +11,7 @@ function Example(props) { }); return ( -
+
+
+
+
+    
+
+
+
+
diff --git a/usage/src/Geometry/TubeExample.jsx b/usage/src/Geometry/TubeExample.jsx index 5a0e06e..e4d3f9c 100644 --- a/usage/src/Geometry/TubeExample.jsx +++ b/usage/src/Geometry/TubeExample.jsx @@ -8,7 +8,7 @@ import { function Example(props) { return ( -
+
+
diff --git a/usage/src/Volume/DynamicUpdate.jsx b/usage/src/Volume/DynamicUpdate.jsx index b453a8b..7d109b6 100644 --- a/usage/src/Volume/DynamicUpdate.jsx +++ b/usage/src/Volume/DynamicUpdate.jsx @@ -76,7 +76,7 @@ function Example(props) { const colorLevel = fieldIdx ? 5 : 0.5; return ( -
+
diff --git a/usage/src/Volume/SliceRendering.jsx b/usage/src/Volume/SliceRendering.jsx index 7bf4b8e..b0c3d75 100644 --- a/usage/src/Volume/SliceRendering.jsx +++ b/usage/src/Volume/SliceRendering.jsx @@ -116,8 +116,8 @@ function Example(props) { const [useLookupTableScalarRange, setUseLookupTableScalarRange] = useState(false); return ( -
-
+
+
-
+
+
+
From 9b89bf8fe2a914e0c59a5b637595d352b92eba30 Mon Sep 17 00:00:00 2001 From: Forrest Date: Wed, 4 May 2022 16:40:08 -0400 Subject: [PATCH 2/7] feat(MultiViewRoot): add multi-view root support Changes the View export to be multi-view aware. The new MultiViewRoot component will convert View components into using renderers, rather than individual render windows. --- src/core/MultiViewRoot.js | 183 ++++++++++++++++++++++++++++++++++++++ src/core/View.js | 107 ++++++++-------------- src/core/ViewContainer.js | 170 +++++++++++++++++++++++++++++++++++ src/core/index.js | 5 +- src/index.js | 1 + 5 files changed, 396 insertions(+), 70 deletions(-) create mode 100644 src/core/MultiViewRoot.js create mode 100644 src/core/ViewContainer.js diff --git a/src/core/MultiViewRoot.js b/src/core/MultiViewRoot.js new file mode 100644 index 0000000..d540a50 --- /dev/null +++ b/src/core/MultiViewRoot.js @@ -0,0 +1,183 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import vtkRenderWindow from '@kitware/vtk.js/Rendering/Core/RenderWindow'; +import vtkRenderWindowInteractor from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor'; +import vtkOpenGLRenderWindow from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow'; + +// ---------------------------------------------------------------------------- +// Context to pass parent variables to children +// ---------------------------------------------------------------------------- + +export const MultiViewRootContext = React.createContext(null); + +export function removeKeys(props, propNames) { + const cleanedProps = { ...props }; + propNames.forEach((name) => { + delete cleanedProps[name]; + }); + return cleanedProps; +} + +// ---------------------------------------------------------------------------- +// Helper constants +// ---------------------------------------------------------------------------- + +const RENDERER_STYLE = { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + pointerEvents: 'none', +}; + +export default class MultiViewRoot extends Component { + constructor(props) { + super(props); + this.containerRef = React.createRef(); + + // Create vtk.js view + this.renderWindow = vtkRenderWindow.newInstance(); + this.interactor = null; + + this.renderWindowView = vtkOpenGLRenderWindow.newInstance(); + this.renderWindow.addView(this.renderWindowView); + + this.resizeObserver = new ResizeObserver((entries) => { + this.onResize(); + }); + + this.interactor = vtkRenderWindowInteractor.newInstance(); + this.interactor.setView(this.renderWindowView); + + this.onResize = this.onResize.bind(this); + + this.initialized = false; + } + + componentDidMount() { + // TODO support runtime toggling of this flag? + if (!this.props.disabled) { + const container = this.containerRef.current; + this.renderWindowView.setContainer(container); + + this.interactor.initialize(); + + this.resizeObserver.observe(container); + this.onResize(); + + this.initialized = true; + + this.update(this.props); + } + } + + componentDidUpdate(prevProps) { + this.update(this.props, prevProps); + } + + componentWillUnmount() { + if (this.initialized) { + // Stop size listening + this.resizeObserver.disconnect(); + + if (this.interactor.getContainer()) { + this.interactor.unbindEvents(); + } + + this.renderWindowView.setContainer(null); + } + + this.renderWindow.removeView(this.renderWindowView); + + this.interactor.delete(); + this.renderWindow.delete(); + this.renderWindowView.delete(); + + this.interactor = null; + this.renderWindow = null; + this.renderWindowView = null; + } + + render() { + const { id, children, style, disabled } = this.props; + + return ( +
+
+ + {children} + +
+ ); + } + + bindInteractorEvents(container) { + if (this.interactor) { + if (this.interactor.getContainer()) { + this.interactor.unbindEvents(); + } + if (container) { + this.interactor.bindEvents(container); + } + } + } + + onResize() { + const container = this.containerRef.current; + if (container) { + const devicePixelRatio = window.devicePixelRatio || 1; + const { width, height } = container.getBoundingClientRect(); + const w = Math.floor(width * devicePixelRatio); + const h = Math.floor(height * devicePixelRatio); + this.renderWindowView.setSize(Math.max(w, 10), Math.max(h, 10)); + this.renderWindow.render(); + } + } + + update(props, previous) { + const { triggerRender } = props; + // Allow to trigger method call from property change + if (previous && triggerRender !== previous.triggerRender) { + this.renderViewTimeout = setTimeout(this.renderWindow.render, 0); + } + } +} + +MultiViewRoot.defaultProps = { + triggerRender: 0, + disableRoot: false, +}; + +export const propTypes = { + /** + * The ID used to identify this component. + */ + id: PropTypes.string, + + /** + * Property use to trigger a render when changing. + */ + triggerRender: PropTypes.number, + + /** + * Disables or enables the multi-renderer root. + */ + disableRoot: PropTypes.bool, + + /** + * List of representation to show + */ + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node, + ]), +}; + +MultiViewRoot.propTypes = propTypes; diff --git a/src/core/View.js b/src/core/View.js index 8f29ce4..591aa51 100644 --- a/src/core/View.js +++ b/src/core/View.js @@ -7,12 +7,6 @@ import PropTypes from 'prop-types'; import { debounce } from '@kitware/vtk.js/macros.js'; -import vtkOpenGLRenderWindow from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow.js'; -import vtkRenderWindow from '@kitware/vtk.js/Rendering/Core/RenderWindow.js'; -import vtkRenderWindowInteractor from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor.js'; -import vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer.js'; -import vtkInteractorStyleManipulator from '@kitware/vtk.js/Interaction/Style/InteractorStyleManipulator.js'; - import vtkBoundingBox from '@kitware/vtk.js/Common/DataModel/BoundingBox.js'; import vtkCubeAxesActor from '@kitware/vtk.js/Rendering/Core/CubeAxesActor.js'; @@ -126,28 +120,21 @@ const RENDERER_STYLE = { * - `cameraParallelProjection`: false * - `showOrientationAxes`: true */ -export default class View extends Component { +class View extends Component { constructor(props) { super(props); this.containerRef = React.createRef(); // Create vtk.js view - this.renderWindow = vtkRenderWindow.newInstance(); - this.renderer = vtkRenderer.newInstance(); - this.renderWindow.addRenderer(this.renderer); + this.renderWindow = props.renderWindow; + this.renderer = props.renderer; this.camera = this.renderer.getActiveCamera(); - this.openglRenderWindow = vtkOpenGLRenderWindow.newInstance(); - this.renderWindow.addView(this.openglRenderWindow); + this.openglRenderWindow = props.renderWindowView; if (props.interactive) { - this.interactor = vtkRenderWindowInteractor.newInstance(); - this.interactor.setView(this.openglRenderWindow); - this.interactor.initialize(); - - // Interactor style - this.style = vtkInteractorStyleManipulator.newInstance(); - this.interactor.setInteractorStyle(this.style); + this.interactor = props.interactor; + this.style = props.interactorStyle; } // Create orientation widget @@ -155,8 +142,8 @@ export default class View extends Component { this.orientationWidget = vtkOrientationMarkerWidget.newInstance({ actor: this.axesActor, interactor: this.interactor, + parentRenderer: this.renderer, }); - this.orientationWidget.setEnabled(true); this.orientationWidget.setViewportCorner( vtkOrientationMarkerWidget.Corners.BOTTOM_LEFT ); @@ -221,6 +208,7 @@ export default class View extends Component { }; this.onEnter = () => { this.hasFocus = true; + this.interactor.setCurrentRenderer(this.renderer); }; this.onLeave = () => { this.hasFocus = false; @@ -345,7 +333,7 @@ export default class View extends Component { } getScreenEventPositionFor(source) { - const bounds = this.containerRef.current.getBoundingClientRect(); + const bounds = this.openglRenderWindow.getCanvas().getBoundingClientRect(); const [canvasWidth, canvasHeight] = this.openglRenderWindow.getSize(); const scaleX = canvasWidth / bounds.width; const scaleY = canvasHeight / bounds.height; @@ -357,6 +345,10 @@ export default class View extends Component { return position; } + onResize() { + this.props.onResize(); + } + render() { const { id, children, style, className } = this.props; @@ -372,36 +364,23 @@ export default class View extends Component { onMouseUp={this.onMouseUp} onMouseMove={this.onMouseMove} > -
-
- {children} -
+
+ {children}
); } - onResize() { - const container = this.containerRef.current; - if (container) { - const devicePixelRatio = window.devicePixelRatio || 1; - const { width, height } = container.getBoundingClientRect(); - const w = Math.floor(width * devicePixelRatio); - const h = Math.floor(height * devicePixelRatio); - this.openglRenderWindow.setSize(Math.max(w, 10), Math.max(h, 10)); - this.renderWindow.render(); - } - } - componentDidMount() { - const container = this.containerRef.current; - this.openglRenderWindow.setContainer(container); - if (this.props.interactive) { - this.interactor.bindEvents(container); - } + const container = this.props.containerRef.current; this.onResize(); this.resizeObserver.observe(container); - this.update(this.props); document.addEventListener('keyup', this.handleKey); + + // Assign the mouseDown event, we can't use the React event system + // because the mouseDown event is swallowed by other logic + container.addEventListener('mousedown', this.onMouseDown); + + this.update(this.props); this.resetCamera(); // Give a chance for the first layout to properly reset the camera @@ -423,34 +402,20 @@ export default class View extends Component { this.subscriptions.pop().unsubscribe(); } + const container = this.props.containerRef.current; + container.removeEventListener('mousedown', this.onMouseDown); + document.removeEventListener('keyup', this.handleKey); // Stop size listening this.resizeObserver.disconnect(); this.resizeObserver = null; - // Detatch from DOM - if (this.interactor) { - this.interactor.unbindEvents(); - } - this.openglRenderWindow.setContainer(null); - - // Free memory - this.renderWindow.removeRenderer(this.renderer); - this.renderWindow.removeView(this.openglRenderWindow); - - if (this.interactor) { - this.interactor.delete(); - this.interactor = null; - } + this.selector.delete(); + this.orientationWidget.delete(); - this.renderer.delete(); this.renderer = null; - - this.renderWindow.delete(); - this.renderWindow = null; - - this.openglRenderWindow.delete(); - this.openglRenderWindow = null; + this.selector = null; + this.orientationWidget = null; } update(props, previous) { @@ -527,11 +492,6 @@ export default class View extends Component { if (previous && triggerResetCamera !== previous.triggerResetCamera) { this.resetCameraTimeout = setTimeout(this.resetCamera, 0); } - - // Assign the mouseDown event, we can't use the React event system - // because the mouseDown event is swallowed by other logic - const canvas = this.openglRenderWindow.getCanvas(); - canvas.addEventListener('mousedown', this.onMouseDown); } resetCamera() { @@ -904,3 +864,12 @@ View.propTypes = { */ showOrientationAxes: PropTypes.bool, }; + +const ForwardedView = React.forwardRef((props, ref) => ( + +)); + +ForwardedView.defaultProps = View.defaultProps; +ForwardedView.propTypes = View.propTypes; + +export default ForwardedView; diff --git a/src/core/ViewContainer.js b/src/core/ViewContainer.js new file mode 100644 index 0000000..0f66604 --- /dev/null +++ b/src/core/ViewContainer.js @@ -0,0 +1,170 @@ +import React, { Component } from 'react'; + +import vtkOpenGLRenderWindow from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow.js'; +import vtkRenderWindow from '@kitware/vtk.js/Rendering/Core/RenderWindow.js'; +import vtkRenderWindowInteractor from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor.js'; +import vtkInteractorStyleManipulator from '@kitware/vtk.js/Interaction/Style/InteractorStyleManipulator.js'; +import vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer.js'; + +import View from './View'; +import { MultiViewRootContext } from './MultiViewRoot'; + +class ViewController extends Component { + constructor(props) { + super(props); + + this.interactorStyle = vtkInteractorStyleManipulator.newInstance(); + this.renderer = vtkRenderer.newInstance(); + this.containerRef = React.createRef(); + + if (props.root) { + this.renderWindow = props.root.renderWindow; + this.openglRenderWindow = props.root.renderWindowView; + this.interactor = props.root.interactor; + } else { + this.renderWindow = vtkRenderWindow.newInstance(); + this.openglRenderWindow = vtkOpenGLRenderWindow.newInstance(); + this.renderWindow.addView(this.openglRenderWindow); + + this.interactor = vtkRenderWindowInteractor.newInstance(); + if (props.interactive) { + this.interactor.setView(this.openglRenderWindow); + this.interactor.initialize(); + } + // this.interactor.setInteractorStyle(this.style); + } + + this.renderWindow.addRenderer(this.renderer); + + this.onEnter = this.onEnter.bind(this); + this.onResize = this.onResize.bind(this); + } + + componentDidMount() { + const container = this.containerRef.current; + container.addEventListener('pointerenter', this.onEnter); + + if (!this.props.root) { + this.openglRenderWindow.setContainer(container); + if (this.props.interactive) { + this.interactor.bindEvents(container); + } + this.interactor.setInteractorStyle(this.interactorStyle); + } + } + + componentWillUnmount() { + const container = this.containerRef.current; + container.removeEventListener('pointerenter', this.onEnter); + + // MultiViewRoot parent may delete the render window first in WillUnmount. + if (!this.renderWindow.isDeleted()) { + this.renderWindow.removeRenderer(this.renderer); + } + + if (this.props.root) { + this.bindInteractorEvents(null); + } else { + // Detatch from DOM + if (this.interactor.getContainer()) { + this.interactor.unbindEvents(); + } + this.openglRenderWindow.setContainer(null); + + if (!this.renderWindow.isDeleted()) { + this.renderWindow.removeView(this.openglRenderWindow); + this.renderWindow.delete(); + } + + this.interactor.delete(); + this.openglRenderWindow.delete(); + } + + this.interactorStyle.delete(); + this.renderer.delete(); + + this.interactorStyle = null; + this.interactor = null; + this.renderWindow = null; + this.openglRenderWindow = null; + } + + render() { + const filteredProps = { ...this.props }; + delete filteredProps.root; + + return ( + + ); + } + + bindInteractorEvents(el) { + const oldContainer = this.interactor.getContainer(); + if (oldContainer !== el) { + if (oldContainer) { + this.interactor.unbindEvents(); + } + if (el) { + this.interactor.bindEvents(el); + } + } + } + + onEnter() { + const container = this.containerRef.current; + if (this.props.root && container) { + this.bindInteractorEvents(container); + this.interactor.setInteractorStyle(this.interactorStyle); + } + } + + onResize() { + const container = this.containerRef.current; + if (container) { + if (this.props.root) { + const containerBox = container.getBoundingClientRect(); + const canvasBox = this.openglRenderWindow + .getCanvas() + .getBoundingClientRect(); + + // relative to canvas + const top = containerBox.top - canvasBox.top; + const left = containerBox.left - canvasBox.left; + + const xmin = left / canvasBox.width; + const xmax = (left + containerBox.width) / canvasBox.width; + const ymin = 1 - (top + containerBox.height) / canvasBox.height; + const ymax = 1 - top / canvasBox.height; + + this.renderer.setViewport(xmin, ymin, xmax, ymax); + } else { + const devicePixelRatio = window.devicePixelRatio || 1; + const { width, height } = container.getBoundingClientRect(); + const w = Math.floor(width * devicePixelRatio); + const h = Math.floor(height * devicePixelRatio); + this.openglRenderWindow.setSize(Math.max(w, 10), Math.max(h, 10)); + this.renderWindow.render(); + } + } + } +} + +ViewController.defaultProps = View.defaultProps; +ViewController.propTypes = View.propTypes; + +export default function ViewContainer(props) { + return ( + + {(root) => } + + ); +} diff --git a/src/core/index.js b/src/core/index.js index e646c07..bc371ed 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -5,7 +5,7 @@ import vtkPointData from './PointData'; import vtkPolyData from './PolyData'; import vtkReader from './Reader'; import vtkShareDataSet from './ShareDataSet'; -import vtkView from './View'; +import vtkView from './ViewContainer'; import vtkGeometryRepresentation from './GeometryRepresentation'; import vtkGeometry2DRepresentation from './Geometry2DRepresentation'; import vtkGlyphRepresentation from './GlyphRepresentation'; @@ -15,6 +15,7 @@ import vtkFieldData from './FieldData'; import vtkAlgorithm from './Algorithm'; import vtkCalculator from './Calculator'; import vtkCellData from './CellData'; +import vtkMultiViewRoot from './MultiViewRoot'; export const VolumeRepresentation = vtkVolumeRepresentation; export const SliceRepresentation = vtkSliceRepresentation; @@ -33,6 +34,7 @@ export const FieldData = vtkFieldData; export const Algorithm = vtkAlgorithm; export const Calculator = vtkCalculator; export const CellData = vtkCellData; +export const MultiViewRoot = vtkMultiViewRoot; export default { VolumeRepresentation: vtkVolumeRepresentation, @@ -52,4 +54,5 @@ export default { Algorithm: vtkAlgorithm, Calculator: vtkCalculator, CellData: vtkCellData, + MultiViewRoot: vtkMultiViewRoot, }; diff --git a/src/index.js b/src/index.js index c077c6c..9b99d12 100644 --- a/src/index.js +++ b/src/index.js @@ -31,6 +31,7 @@ export const FieldData = Core.FieldData; export const Algorithm = Core.Algorithm; export const Calculator = Core.Calculator; export const CellData = Core.CellData; +export const MultiViewRoot = Core.MultiViewRoot; // Representations export const PointCloudRepresentation = From 0b5807859acdb71170dbcc40f971dfbde1c78100 Mon Sep 17 00:00:00 2001 From: Forrest Date: Wed, 4 May 2022 16:41:11 -0400 Subject: [PATCH 3/7] docs(MultiView): add MultiView example --- usage/src/App.jsx | 3 +++ usage/src/MultiView.jsx | 55 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 usage/src/MultiView.jsx diff --git a/usage/src/App.jsx b/usage/src/App.jsx index 8401802..d41f264 100644 --- a/usage/src/App.jsx +++ b/usage/src/App.jsx @@ -17,6 +17,7 @@ const SyntheticVolumeRendering = lazy(() => ); const VolumeRendering = lazy(() => import('./Volume/VolumeRendering')); const DynamicUpdate = lazy(() => import('./Volume/DynamicUpdate')); +const MultiView = lazy(() => import('./MultiView')); const demos = [ 'Geometry/Picking', @@ -32,6 +33,7 @@ const demos = [ 'Volume/SyntheticVolumeRendering', 'Volume/VolumeRendering', 'Volume/DynamicUpdate', + 'MultiView', ]; function App() { @@ -93,6 +95,7 @@ function App() { )} {example === 'Volume/VolumeRendering' && } {example === 'Volume/DynamicUpdate' && } + {example === 'Multi' && }
diff --git a/usage/src/MultiView.jsx b/usage/src/MultiView.jsx new file mode 100644 index 0000000..32450af --- /dev/null +++ b/usage/src/MultiView.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import PointCloud from './Geometry/PointCloud'; +import Picking from './Geometry/Picking'; +import PolyDataWithData from './Geometry/PolyDataWithData'; +import ProcessingPipeline from './Geometry/ProcessingPipeline'; +import Cutter from './Geometry/CutterExample'; +import Glyph from './Geometry/Glyph'; + +import { MultiViewRoot } from 'react-vtk-js'; + +const points = []; +const scalars = []; +for (let i = 0; i < 1000; i++) { + scalars.push(Math.random()); + points.push(Math.random()); + points.push(Math.random()); + points.push(Math.random()); +} + +function Example(props) { + return ( + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); +} + +export default Example; From eccac3618fdfc3440245ed40c995a9ae62badce5 Mon Sep 17 00:00:00 2001 From: Forrest Date: Thu, 5 May 2022 10:19:18 -0400 Subject: [PATCH 4/7] fix(MultiViewRoot): fix disabled prop naming --- src/core/MultiViewRoot.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/MultiViewRoot.js b/src/core/MultiViewRoot.js index d540a50..c83f7bf 100644 --- a/src/core/MultiViewRoot.js +++ b/src/core/MultiViewRoot.js @@ -152,7 +152,7 @@ export default class MultiViewRoot extends Component { MultiViewRoot.defaultProps = { triggerRender: 0, - disableRoot: false, + disabled: false, }; export const propTypes = { @@ -169,7 +169,7 @@ export const propTypes = { /** * Disables or enables the multi-renderer root. */ - disableRoot: PropTypes.bool, + disabled: PropTypes.bool, /** * List of representation to show From 246b8eb73297507983d803aa2e188127c894d625 Mon Sep 17 00:00:00 2001 From: Forrest Date: Thu, 5 May 2022 13:29:29 -0400 Subject: [PATCH 5/7] fix(ViewContainer): use View's interactor style This allows users to change the view's style if they so please. --- src/core/View.js | 20 ++++++-------------- src/core/ViewContainer.js | 24 +++++++++++------------- usage/src/App.jsx | 2 +- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/core/View.js b/src/core/View.js index 591aa51..e2801a1 100644 --- a/src/core/View.js +++ b/src/core/View.js @@ -12,6 +12,7 @@ import vtkCubeAxesActor from '@kitware/vtk.js/Rendering/Core/CubeAxesActor.js'; import vtkAxesActor from '@kitware/vtk.js/Rendering/Core/AxesActor'; import vtkOrientationMarkerWidget from '@kitware/vtk.js/Interaction/Widgets/OrientationMarkerWidget'; +import vtkInteractorStyleManipulator from '@kitware/vtk.js/Interaction/Style/InteractorStyleManipulator.js'; // Style modes import vtkMouseCameraTrackballMultiRotateManipulator from '@kitware/vtk.js/Interaction/Manipulators/MouseCameraTrackballMultiRotateManipulator.js'; @@ -120,7 +121,7 @@ const RENDERER_STYLE = { * - `cameraParallelProjection`: false * - `showOrientationAxes`: true */ -class View extends Component { +export default class View extends Component { constructor(props) { super(props); this.containerRef = React.createRef(); @@ -134,7 +135,7 @@ class View extends Component { if (props.interactive) { this.interactor = props.interactor; - this.style = props.interactorStyle; + this.style = vtkInteractorStyleManipulator.newInstance(); } // Create orientation widget @@ -364,14 +365,14 @@ class View extends Component { onMouseUp={this.onMouseUp} onMouseMove={this.onMouseMove} > -
+
{children}
); } componentDidMount() { - const container = this.props.containerRef.current; + const container = this.containerRef.current; this.onResize(); this.resizeObserver.observe(container); document.addEventListener('keyup', this.handleKey); @@ -402,7 +403,7 @@ class View extends Component { this.subscriptions.pop().unsubscribe(); } - const container = this.props.containerRef.current; + const container = this.containerRef.current; container.removeEventListener('mousedown', this.onMouseDown); document.removeEventListener('keyup', this.handleKey); @@ -864,12 +865,3 @@ View.propTypes = { */ showOrientationAxes: PropTypes.bool, }; - -const ForwardedView = React.forwardRef((props, ref) => ( - -)); - -ForwardedView.defaultProps = View.defaultProps; -ForwardedView.propTypes = View.propTypes; - -export default ForwardedView; diff --git a/src/core/ViewContainer.js b/src/core/ViewContainer.js index 0f66604..0ed375a 100644 --- a/src/core/ViewContainer.js +++ b/src/core/ViewContainer.js @@ -3,7 +3,6 @@ import React, { Component } from 'react'; import vtkOpenGLRenderWindow from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow.js'; import vtkRenderWindow from '@kitware/vtk.js/Rendering/Core/RenderWindow.js'; import vtkRenderWindowInteractor from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor.js'; -import vtkInteractorStyleManipulator from '@kitware/vtk.js/Interaction/Style/InteractorStyleManipulator.js'; import vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer.js'; import View from './View'; @@ -13,9 +12,8 @@ class ViewController extends Component { constructor(props) { super(props); - this.interactorStyle = vtkInteractorStyleManipulator.newInstance(); this.renderer = vtkRenderer.newInstance(); - this.containerRef = React.createRef(); + this.viewRef = React.createRef(); if (props.root) { this.renderWindow = props.root.renderWindow; @@ -41,7 +39,8 @@ class ViewController extends Component { } componentDidMount() { - const container = this.containerRef.current; + const view = this.viewRef.current; + const container = view.containerRef.current; container.addEventListener('pointerenter', this.onEnter); if (!this.props.root) { @@ -49,12 +48,13 @@ class ViewController extends Component { if (this.props.interactive) { this.interactor.bindEvents(container); } - this.interactor.setInteractorStyle(this.interactorStyle); + this.interactor.setInteractorStyle(view.style); } } componentWillUnmount() { - const container = this.containerRef.current; + const view = this.viewRef.current; + const container = view.containerRef.current; container.removeEventListener('pointerenter', this.onEnter); // MultiViewRoot parent may delete the render window first in WillUnmount. @@ -80,10 +80,8 @@ class ViewController extends Component { this.openglRenderWindow.delete(); } - this.interactorStyle.delete(); this.renderer.delete(); - this.interactorStyle = null; this.interactor = null; this.renderWindow = null; this.openglRenderWindow = null; @@ -99,8 +97,7 @@ class ViewController extends Component { renderWindowView={this.openglRenderWindow} renderer={this.renderer} interactor={this.interactor} - interactorStyle={this.interactorStyle} - ref={this.containerRef} + ref={this.viewRef} onResize={this.onResize} {...filteredProps} /> @@ -120,15 +117,16 @@ class ViewController extends Component { } onEnter() { - const container = this.containerRef.current; + const view = this.viewRef.current; + const container = view?.containerRef.current; if (this.props.root && container) { this.bindInteractorEvents(container); - this.interactor.setInteractorStyle(this.interactorStyle); + this.interactor.setInteractorStyle(view.style); } } onResize() { - const container = this.containerRef.current; + const container = this.viewRef.current?.containerRef.current; if (container) { if (this.props.root) { const containerBox = container.getBoundingClientRect(); diff --git a/usage/src/App.jsx b/usage/src/App.jsx index d41f264..2d435b7 100644 --- a/usage/src/App.jsx +++ b/usage/src/App.jsx @@ -95,7 +95,7 @@ function App() { )} {example === 'Volume/VolumeRendering' && } {example === 'Volume/DynamicUpdate' && } - {example === 'Multi' && } + {example === 'MultiView' && }
From a2ec8e271438a0032331bea16545d05b61888070 Mon Sep 17 00:00:00 2001 From: Forrest Date: Thu, 5 May 2022 14:59:29 -0400 Subject: [PATCH 6/7] fix(View): support user-provided interactor styles --- src/core/View.js | 11 ++++++++++- src/core/ViewContainer.js | 17 +++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/core/View.js b/src/core/View.js index e2801a1..97c22be 100644 --- a/src/core/View.js +++ b/src/core/View.js @@ -135,7 +135,8 @@ export default class View extends Component { if (props.interactive) { this.interactor = props.interactor; - this.style = vtkInteractorStyleManipulator.newInstance(); + this.defaultStyle = vtkInteractorStyleManipulator.newInstance(); + this.style = this.defaultStyle; } // Create orientation widget @@ -192,6 +193,11 @@ export default class View extends Component { }; this.debouncedCubeBounds = debounce(this.updateCubeBounds, 50); + this.setInteractorStyle = (style) => { + this.style = style; + this.interactor.setInteractorStyle(style); + }; + // Internal functions this.hasFocus = false; this.handleKey = (e) => { @@ -413,7 +419,10 @@ export default class View extends Component { this.selector.delete(); this.orientationWidget.delete(); + this.defaultStyle.delete(); + this.defaultStyle = null; + this.style = null; this.renderer = null; this.selector = null; this.orientationWidget = null; diff --git a/src/core/ViewContainer.js b/src/core/ViewContainer.js index 0ed375a..4929a9d 100644 --- a/src/core/ViewContainer.js +++ b/src/core/ViewContainer.js @@ -22,23 +22,24 @@ class ViewController extends Component { } else { this.renderWindow = vtkRenderWindow.newInstance(); this.openglRenderWindow = vtkOpenGLRenderWindow.newInstance(); + } + + this.onEnter = this.onEnter.bind(this); + this.onResize = this.onResize.bind(this); + } + + componentDidMount() { + if (!this.props.root) { this.renderWindow.addView(this.openglRenderWindow); this.interactor = vtkRenderWindowInteractor.newInstance(); - if (props.interactive) { + if (this.props.interactive) { this.interactor.setView(this.openglRenderWindow); this.interactor.initialize(); } - // this.interactor.setInteractorStyle(this.style); } - this.renderWindow.addRenderer(this.renderer); - this.onEnter = this.onEnter.bind(this); - this.onResize = this.onResize.bind(this); - } - - componentDidMount() { const view = this.viewRef.current; const container = view.containerRef.current; container.addEventListener('pointerenter', this.onEnter); From 527d6c8f303ae6220add3c853be4bb486b2d416c Mon Sep 17 00:00:00 2001 From: Forrest Date: Thu, 5 May 2022 14:59:53 -0400 Subject: [PATCH 7/7] fix(View): conditionally set current renderer The current renderer should only be set if the container exists (which implies that the renderer is not deleted.) --- src/core/View.js | 1 - src/core/ViewContainer.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/View.js b/src/core/View.js index 97c22be..86503d0 100644 --- a/src/core/View.js +++ b/src/core/View.js @@ -215,7 +215,6 @@ export default class View extends Component { }; this.onEnter = () => { this.hasFocus = true; - this.interactor.setCurrentRenderer(this.renderer); }; this.onLeave = () => { this.hasFocus = false; diff --git a/src/core/ViewContainer.js b/src/core/ViewContainer.js index 4929a9d..90bee5b 100644 --- a/src/core/ViewContainer.js +++ b/src/core/ViewContainer.js @@ -122,6 +122,7 @@ class ViewController extends Component { const container = view?.containerRef.current; if (this.props.root && container) { this.bindInteractorEvents(container); + this.interactor.setCurrentRenderer(this.renderer); this.interactor.setInteractorStyle(view.style); } }