Slicer 5.13
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
Loading...
Searching...
No Matches
ModelGlowDM.py
Go to the documentation of this file.
1"""
2This example shows how to create a glow around a model node using the SlicerLayerDisplayableManager module.
3
4It goes over the following concepts:
5 - Creates a unique display pipeline to set attach a VTK glow pass to a renderer
6 - Creates one glow display pipeline per 3D view for each created model nodes in the scene
7 - Register the pipeline creation mechanism
8 - Handles hover interaction: on mouse move, each pipeline reports whether the cursor is within its model's
9 bounding box and its distance to the model center. The closest matching pipeline gains focus and sets an
10 "IsSelected" attribute on its data node; losing focus (cursor moved to another model or left the window)
11 resets the attribute. The observed attribute change updates the glow actor visibility, so only the
12 hovered model glows.
13
14Usage:
15 This example is implemented as a scripted module and can be added as such to Slicer.
16 Once added, loading new model nodes will set their glow pass automatically.
17"""
18
19import sys
20import qt
21import random
22
23import slicer
24from slicer import (
25 vtkMRMLAbstractViewNode,
26 vtkMRMLInteractionEventData,
27 vtkMRMLModelNode,
28 vtkMRMLNode,
29 vtkMRMLScene,
30 vtkMRMLScriptedModuleNode,
31 vtkMRMLTransformNode,
32 vtkMRMLViewNode,
33)
34from slicer.ScriptedLoadableModule import ScriptedLoadableModule, ScriptedLoadableModuleWidget
35from vtk import (
36 VTK_OBJECT,
37 calldata_type,
38 vtkActor,
39 vtkCommand,
40 vtkGeneralTransform,
41 vtkMath,
42 vtkNamedColors,
43 vtkOutlineGlowPass,
44 vtkPolyDataMapper,
45 vtkRenderStepsPass,
46 vtkRenderer,
47 vtkSphereSource,
48 vtkTransformPolyDataFilter,
49)
50
51from LayerDMLib import ScriptedPipelineSceneConnector, vtkMRMLLayerDMScriptedPipeline
52
53
54class ModelGlowDM(ScriptedLoadableModule):
55 def __init__(self, parent):
56 ScriptedLoadableModule.__init__(self, parent)
57 self.parent.title = "Model Glow Pipeline Example"
58 self.parent.categories = ["Examples"]
59 self.parent.dependencies = []
60 self.parent.contributors = []
61 self.parent.helpText = ""
62 self.parent.acknowledgementText = ""
63
64 # At startup completed, the pipelines are connected to the scene.
65 # This allows the pipeline registration to be done automatically at loading time,
66 # without requiring the module widget to be opened first.
67 slicer.app.connect("startupCompleted()", _sceneConnector.connect)
68
69
70class ModelGlowDMWidget(ScriptedLoadableModuleWidget):
71 """In this example, the module's widget will allow us to create random sphere model nodes in the scene."""
72
73 def setup(self) -> None:
74 """
75 In the setup method, we create a widget with only two buttons:
76 - A "create sphere" button to create a random sphere in the scene
77 - A "Reset 3D views" button to reset the 3D view on the created spheres
78 """
79 # Re-connect the pipeline so that a module reload binds the reloaded pipeline classes.
80 # connect first removes any previous connection, so this is safe to call
81 # even though the pipeline was already connected at application startup.
82 _sceneConnector.connect()
83 ScriptedLoadableModuleWidget.setup(self)
84
85 widget = qt.QWidget()
86 layout = qt.QVBoxLayout(widget)
87
88 createSphereButton = qt.QPushButton("Create sphere")
89 createSphereButton.clicked.connect(self._onCreateSphereClicked)
90 layout.addWidget(createSphereButton)
91
92 reset3DView = qt.QPushButton("Reset 3D views")
93 reset3DView.clicked.connect(slicer.util.resetThreeDViews)
94 layout.addWidget(reset3DView)
95 layout.addStretch()
96
97 self.layout.addWidget(widget)
98
99 def cleanup(self) -> None:
100 """
101 Called when the module is about to be unloaded and by slicer.util.reloadScriptedModule before the widget is
102 rebuilt (module Reload button).
103
104 Here, we disconnect the pipelines from the scene (unregister the pipeline creator from the factory
105 singleton and remove the scene observers). Without this, the previously registered creator and
106 observers would keep running the pre-reload code.
107 """
108 _sceneConnector.disconnect()
109 ScriptedLoadableModuleWidget.cleanup(self)
110
111 @classmethod
113 """
114 Here we create the sphere models at random.
115
116 Note: Attaching the glow data to the model could be done in this type of methods if we wanted more control
117 on the creation logic.
118 """
119 # Create a sphere positioned at a random position
120 sphereSource = vtkSphereSource()
121 sphereSource.SetCenter(random.uniform(0, 10),
122 random.uniform(0, 10),
123 random.uniform(0, 10))
124 sphereSource.SetRadius(random.uniform(0.1, 1.0))
125 sphereSource.Update()
126
127 # Create the model node and set its polydata
128 modelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode")
129 modelNode.SetAndObservePolyData(sphereSource.GetOutput())
130 modelNode.CreateDefaultDisplayNodes()
131
132 # Set random color
133 displayNode = modelNode.GetDisplayNode()
134 modelNode.SetAndObserveDisplayNodeID(displayNode.GetID())
135 displayNode.SetColor(random.random(), random.random(), random.random())
136
137
138class _Pipeline(vtkMRMLLayerDMScriptedPipeline):
139 """
140 This class is a convenience abstract class for the glow pass pipelines.
141 It provides the following static methods:
142 - _CreatePipelineNode: Creates a new scripted node with a PipelineType string property containing the class type
143 This value is used to check if the pipeline should be created when a node is added to the scene.
144 - IsPipelineNode: Checks if a node is a scripted node and it contains the right PipelineType string property
145 - TryCreatePipeline: Creates new pipeline instances for views matching 3D views and PipelineType nodes.
146 """
147
148 @classmethod
149 def _CreatePipelineNode(cls) -> vtkMRMLScriptedModuleNode:
150 """
151 Creates a new scripted node with a PipelineType string property containing the class type.
152 This value is used to check if the pipeline should be created when a node is added to the scene.
153
154 In Python, inheritance of vtkMRMLDisplayNode is not possible to create new display node types.
155 To bypass this limitation and create display data that will be used in the pipelines, we use
156 vtkMRMLScriptedModuleNode instead.
157
158 vtkMRMLScriptedModuleNode can contain any pairs of string keys and string values.
159 We use this mechanism to store the type of pipeline we would like to create for the given node.
160
161 The actual choice of attribute is arbitrary in this example and the creation logic can be adapted in actual
162 application code.
163
164 Warning: Pipeline creation in the views is triggered by adding the node to the scene.
165 vtkMRMLScriptedModuleNode properties should be initialized prior to adding the nodes to the scene.
166 """
168 node.SetAttribute("PipelineType", cls._GetClassName())
169 return node
170
171 @classmethod
172 def IsPipelineNode(cls, node):
173 """
174 Returns True if the input vktMRMLNode is a scripted node and has the pipeline type attribute matching the
175 current pipeline class.
176 """
177 return isinstance(node, vtkMRMLScriptedModuleNode) and node.GetAttribute("PipelineType") == cls._GetClassName()
178
179 @classmethod
181 cls, viewNode: vtkMRMLAbstractViewNode, node: vtkMRMLNode,
182 ) -> vtkMRMLLayerDMScriptedPipeline | None:
183 """
184 Since we are creating pipelines for 3D views only, we check here if the view node is a ThreedView node and
185 if the node matches the current pipeline type (i.e. was created using the _CreatePipelineNode method).
186 """
187
188 if not cls.IsPipelineNode(node) or not isinstance(viewNode, vtkMRMLViewNode):
189 return None
190
191 return cls()
192
193 @classmethod
194 def _GetClassName(cls) -> str:
195 """
196 Convenience method to get the name of the current class.
197 This method will return the actual class name for inheriting classes.
198 """
199 return cls.__name__
200
201
203 """
204 The GlowDMPassPipeline is responsible for setting a glow render pass to the input renderer.
205 Since the pass only need to be set once, the following are used in this class:
206 - The data node is set to be a singleton data. This will make the data node persist in between scene clear.
207 - Setting and removing the render pass is done on renderer added / removed API calls.
208 - We set the pipeline to its own renderer (GetRenderOrder != 0) and we make it easy for other classes to know
209 the pipeline's render order by adding a convenience static method.
210
211 Note: This logic doesn't need to be split but is an example of possible implementation decoupling.
212 """
213
214 def __init__(self):
215 """
216 At creation, we call the super class initialization and create the VTK passes.
217 The VTK passes are then set during the API on renderer added / removed calls.
218 """
219 super().__init__()
220 self._basicPasses = vtkRenderStepsPass()
221 self._glowPass = vtkOutlineGlowPass()
222 self._glowPass.SetDelegatePass(self._basicPasses)
223
224 def OnRendererAdded(self, renderer: vtkRenderer) -> None:
225 """
226 Triggered when the pipeline is displayed on a new renderer.
227
228 When the renderer is added, we attach our glow pass.
229 Since we don't control the actual renderer used by the pipeline, this should be used systematically.
230 See also: self.GetRenderer()
231 """
232
233 if renderer is None:
234 return
235 renderer.SetPass(self._glowPass)
236
237 def OnRendererRemoved(self, renderer: vtkRenderer) -> None:
238 """
239 Triggered when the pipeline is removed from its previous renderer.
240
241 When the renderer is removed, we remove our glow pass and release the graphics resources it holds
242 (framebuffers, textures). Without the release, the GPU resources would leak when the pipeline is
243 deleted (for instance when it is recreated after a module reload).
244 Since we don't control the actual renderer used by the pipeline, this should be used systematically.
245 See also: self.GetRenderer()
246 """
247
248 if renderer is None:
249 return
250 renderer.SetPass(None)
251 if renderer.GetRenderWindow():
252 self._glowPass.ReleaseGraphicsResources(renderer.GetRenderWindow())
253
254 def GetRenderOrder(self) -> int:
255 """
256 Arbitrary render order number where the pipeline wants to be displayed.
257 Return 0 to be at the default order (main 3D Slicer pipelines)
258 Return larger values to be rendered on top of pipelines with lower render orders.
259
260 :return: default = 0. Here we use a helper static method to return the Glow pass render order and allow
261 pipelines that want to be rendered in this renderer to use it as well.
262 """
263 return self.GetGlowPassRenderOrder()
264
265 @classmethod
267 """Convenience static method to return the glow pass render order and be used by other classes."""
268 return 1
269
270 @classmethod
271 def EnsureGlowPass(cls, scene: vtkMRMLScene):
272 """
273 Convenience static method to add a singleton render glow pass data node to the scene.
274 The scene is passed in argument to avoid using the singleton slicer.mrmlScene which is not available in
275 trame-slicer.
276 """
277 if cls.SceneHasGlowPass(scene):
278 return
279
280 node = cls._CreatePipelineNode()
281 node.SetSingletonOn()
282 node.SetSingletonTag("RenderGlowPassPipeline")
283 scene.AddNode(node)
284
285 @classmethod
286 def SceneHasGlowPass(cls, scene: vtkMRMLScene) -> bool:
287 """
288 Convenience static method to check if the singleton glow pass data node is present in the scene.
289 The scene is passed in argument to avoid using the singleton slicer.mrmlScene which is not available in
290 trame-slicer.
291 """
292 return scene.GetSingletonNode("RenderGlowPassPipeline", "vtkMRMLScriptedModuleNode") is not None
293
294
296 """
297 The ModelGlowDMPipeline is responsible for the actual creation of actors (and mappers) that will be rendered
298 in the glow pass renderer.
299
300 In this pipeline, we do three things:
301 - Configure the rendering pipeline
302 - connect the pipeline reactivity to the scene observers
303 - connect the interaction events to make our model glow when the interaction is within the model's bounding box
304
305 Creation of the pipeline will be handled by our _Pipeline.TryCreatePipeline base methods and connected to the
306 factory in the registerPipeline method that we connected to the application load event.
307 """
308
309 def __init__(self):
310 """
311 In the pipeline creation, we create the different VTK objects.
312 Here, the mapper properties are static, but they could be set in the data node and be reactive.
313 """
314 super().__init__()
315
316 colors = vtkNamedColors()
317 self._glowMapper = vtkPolyDataMapper()
318 self._glowActor = vtkActor()
319 self._glowActor.SetMapper(self._glowMapper)
320 self._glowActor.GetProperty().SetColor(colors.GetColor3d("Magenta"))
321 self._glowActor.GetProperty().LightingOff()
322
323 # The two attributes below are used to connect observers on the modelNode and the modelTransform ModifiedEvent
324 # The vtkMRMLLayerDMScriptedPipeline base class provides convenience methods to simplify observers
325 # See also: OnUpdate
326 # See also: UpdateObservation
327 self._modelNode = None
328 self._modelTransform = None
329
330 # The attribute below is used to store the transformed polydata.
331 # Its bounding boxes will be used for user interaction.
332 self._polyData = None
333
334 def OnRendererAdded(self, renderer: vtkRenderer) -> None:
335 """
336 Triggered when the pipeline is displayed on a new renderer.
337 default behavior: does nothing.
338
339 Here, we add our actor to the input renderer.
340 If the pipeline renderer has changed, the pipeline's UpdateDisplay method will be triggered and in turn its
341 UpdateFromMRML method will be triggered.
342
343 Since we don't control the actual renderer used by the pipeline, this should be used systematically.
344 See also: self.GetRenderer()
345 """
346
347 if renderer is None or renderer.HasViewProp(self._glowActor):
348 return
349 renderer.AddViewProp(self._glowActor)
350
351 def OnRendererRemoved(self, renderer: vtkRenderer) -> None:
352 """
353 Triggered when the pipeline is removed from its previous renderer.
354 default behavior: does nothing.
355
356 Here, we add our actor to the input renderer.
357 If the pipeline renderer has changed, the pipeline's UpdateDisplay method will be triggered and in turn its
358 UpdateFromMRML method will be triggered.
359
360 Since we don't control the actual renderer used by the pipeline, this should be used systematically.
361 See also: self.GetRenderer()
362 """
363
364 if renderer is None or not renderer.HasViewProp(self._glowActor):
365 return
366 renderer.RemoveViewProp(self._glowActor)
367
368 def GetRenderOrder(self) -> int:
369 """
370 Arbitrary render order number where the pipeline wants to be displayed.
371 Here, we use the glow pass's render order to be in the same renderer (although we don't know which it will be)
372 """
373 return GlowDMPassPipeline.GetGlowPassRenderOrder()
374
375 def UpdateFromMRML(self):
376 """
377 Triggered by self.UpdateDisplay() calls:
378 - Called automatically at pipeline creation / add to the render window
379 - Called automatically when switching renderer
380 Override to update the representation of the pipeline in the different views.
381
382 See also: self.RequestRender()
383 default behavior: does nothing.
384
385 Here, we update the mapper connection to our modelNode PolyData and update the actor visibility to follow
386 the model's visibility.
387 Finally we ask for a rendering refresh.
388 """
391 self.RequestRender()
392
393 def OnUpdate(self, obj, eventId, callData):
394 """
395 Observer update callback.
396 Triggered when any object & events observed using UpdateObservation is triggered.
397
398 :param obj: vtkObject instance which triggered the callback
399 :param eventId: Event id which triggered the callback
400 :param callData: Optional observer call data. Use self.CastCallData(callData, vtkType) to convert to Python
401
402 Here, we want to update our model transform node observer if it has changed and trigger the pipeline's display
403 when either the view has changed (observed by default), the data node has changed (observed by default), or
404 our modelNode / transform nodes have changed (manually observed).
405 """
406
407 if obj == self._modelNode:
409
410 self.UpdateDisplay()
411
412 def SetDisplayNode(self, node):
413 """
414 Set the display node for the pipeline has changed (initialization).
415 default behavior: Stored and display node is observed for vtkCommand::ModifiedEvent.
416 See also: self.UpdateObservation(prevObj, newObj, eventIds)
417 See also: self.OnUpdate(obj, eventId, callData)
418
419 :param node: The new instance of display node for the pipeline
420 """
421 super().SetDisplayNode(node)
422 self._ObserveModelNode()
423
424 def CanProcessInteractionEvent(self, eventData: vtkMRMLInteractionEventData) -> tuple[bool, float]:
425 """
426 Should return true + distance2 to interaction if the pipeline can process the input event data.
427 :param eventData: The MRML event needing to be processed
428 :return: (bool, distance2) default = False, float_max
429
430 Here, we check if the event interaction is within the glow pass actor bounds and return the distance to it.
431
432 To avoid blocking camera interaction, we will also process mouse move events.
433 Of course we could also check for left click to go further with this widget.
434
435 Note: This is not an efficient way to check for interactions. A better way would be to delegate to the model
436 display node for picking events.
437 """
438 # Only process mouse move events to avoid blocking camera interaction on click / drag
439 isMouseMoveEvent = eventData.GetType() == vtkCommand.MouseMoveEvent
440
441 if not isMouseMoveEvent or not self._IsModelVisible() or self._polyData is None:
442 return False, sys.float_info.max
443
444 pos = eventData.GetWorldPosition()
445 glowActorBounds = self._polyData.GetBounds()
446 isInBounds = (
447 glowActorBounds[0] < pos[0] < glowActorBounds[1]
448 and glowActorBounds[2] < pos[1] < glowActorBounds[3]
449 and glowActorBounds[4] < pos[2] < glowActorBounds[5]
450 )
451 distance2 = vtkMath.Distance2BetweenPoints(pos, self._polyData.GetCenter())
452 return isInBounds, distance2
453
454 def ProcessInteractionEvent(self, eventData: vtkMRMLInteractionEventData) -> bool:
455 """
456 Triggered when the pipeline can process the interaction and is at the top of the priority list.
457 default behavior: does nothing and returns false.
458
459 :param eventData: The MRML event needing to be processed
460 :return: True if event was processed. False otherwise (default = false)
461
462 Here, the pipeline is the closest to the interaction. We modify our display property which will trigger the
463 rendering update.
464 """
465 if not self.GetDisplayNode():
466 return False
467
468 self.GetDisplayNode().SetAttribute("IsSelected", str(1))
469 return True
470
471 def LoseFocus(self, eventData: vtkMRMLInteractionEventData | None) -> None:
472 """
473 Triggered when the pipeline had focus (processed an interaction) and loses the focus (other pipeline
474 handled the new interaction or window leave event).
475 default behavior: does nothing.
476 :param eventData: Optional event data which triggered the lose focus
477
478 Here, the pipeline lost the previous interaction.
479 We make sure to restore the selection state.
480 """
481 super().LoseFocus(eventData)
482 if not self.GetDisplayNode():
483 return
484 self.GetDisplayNode().SetAttribute("IsSelected", str(0))
485
487 """
488 Convenience method to update the mapper connection.
489 Note: We apply the polydata transform manually here, but we could configure a transform pipeline and use
490 the model's polydata connection instead for a cleaner VTK implementation.
491 """
492 modelNode: vtkMRMLModelNode = self._GetModelNode()
493 self._polyData = self._TransformPolyData(modelNode.GetPolyData() if modelNode else None)
494 self._glowMapper.SetInputData(self._polyData)
495
496 def _TransformPolyData(self, polyData):
497 """Convenience method to update the transformed displayed polydata based on the current transform node."""
498 transformNode = self._modelNode.GetParentTransformNode() if self._modelNode else None
499 if transformNode is None:
500 return polyData
501 transformFilter = vtkTransformPolyDataFilter()
502 transform = vtkGeneralTransform()
503 transformNode.GetTransformToWorld(transform)
504 transformFilter.SetTransform(transform)
505 transformFilter.SetInputData(polyData)
506 transformFilter.Update()
507 return transformFilter.GetOutput()
508
510 """Convenience method to update the actor based on the model's visibility and the selection."""
511 # GetAttribute returns None when the attribute has never been set.
512 selectedAttribute = self.GetDisplayNode().GetAttribute("IsSelected") if self.GetDisplayNode() else None
513 isSelected = bool(int(selectedAttribute)) if selectedAttribute else False
514 self._glowActor.SetVisibility(self._IsModelVisible() and isSelected)
515
516 @classmethod
517 def CreateGlowNode(cls, modelNode: vtkMRMLModelNode, scene: vtkMRMLScene):
518 """
519 Convenience static method to create and add a new glow data node pointing to a model node and add it to the
520 scene.
521
522 Note: Here, we could add our new data node as a reference node for the modelNode instead of keeping each
523 separate. This would allow to iterate over the modelNode's references and find it instead of having
524 to iterate on the scene.
525 """
526
527 # Since our nodes can store key / value strings, we set a new key for the model node ID for the model this
528 # glow effect will be attached to.
529 # We also store a selection value to be able to update the model glow on user interaction.
530 # We could store other display properties here as well (or point to a dedicated display node storing more
531 # information)
532 node = cls._CreatePipelineNode()
533 node.SetAttribute("ModelNodeID", modelNode.GetID())
534 node.SetAttribute("IsSelected", str(0))
535 return scene.AddNode(node)
536
537 @classmethod
538 def RemoveGlowNode(cls, modelNode: vtkMRMLModelNode, scene: vtkMRMLScene):
539 """
540 Convenience static method to remove a glow node set on a given modelNode.
541 See also: CreateGlowNode
542
543 Note: This logic can be simplified if we attach our pipeline to the model node directly.
544 We would then iterate over node references to check if we have our pipeline node.
545 """
546 for node in slicer.util.getNodesByClass("vtkMRMLScriptedModuleNode", scene):
547 if cls._GetModelNodeID(node) == modelNode.GetID():
548 scene.RemoveNode(node)
549
550 def _IsModelVisible(self) -> bool:
551 """Convenience method to check if the pipeline's model node is visible."""
552 modelNode = self._GetModelNode()
553 if modelNode is None:
554 return False
555 return bool(modelNode.GetDisplayVisibility())
556
557 def _GetModelNode(self) -> vtkMRMLModelNode | None:
558 """
559 Convenience method to get the model node associated with the pipeline's data node.
560
561 Here, we use the following APIs:
562 - GetScene: This will return the scene on which the pipeline is attached
563 - GetDisplayNode: This will return the pipeline's data node instance
564 """
565 return self.GetScene().GetNodeByID(self._GetModelNodeID(self.GetDisplayNode()))
566
568 """
569 Convenience method to update the model and the model's transform node observers.
570
571 Here, we use the UpdateObservation method:
572 - UpdateObservation(vtkObject* prevObj, vtkObject* obj, const std::vector<unsigned long>& events) -> bool
573 - UpdateObservation(vtkObject* prevObj, vtkObject* obj, unsigned long event = vtkCommand::ModifiedEvent) -> bool
574
575 This method should be used to add an observer on VTK object.
576 By default, the object's modified event will be observed.
577 The method also supports lists of events.
578
579 On modify event, the class's self.OnUpdate method will be called.
580
581 Warning: prevObj is not mutated by this call. To update the pointer, a manual set is required after update.
582
583 See also: self.OnUpdate
584 """
585 if self._modelNode == self._GetModelNode():
586 return
587
588 self.UpdateObservation(self._modelNode, self._GetModelNode())
589 self._modelNode = self._GetModelNode()
591
593 """
594 Convenience method to update the model's transform node observer.
595
596 Note: Here we explicitly observe the transform modified event as modifying the transform doesn't trigger
597 its modified event.
598
599 See also: self._ObserveModelNode
600 """
601 transformNode = self._modelNode.GetParentTransformNode() if self._modelNode else None
602 if self._modelTransform == transformNode:
603 return
604
605 self.UpdateObservation(self._modelTransform, transformNode, vtkMRMLTransformNode.TransformModifiedEvent)
606 self._modelTransform = transformNode
607
608 @classmethod
609 def _GetModelNodeID(cls, node):
610 """Convenience method to get the model node ID attached to the input MRML node."""
611 if cls.IsPipelineNode(node):
612 return node.GetAttribute("ModelNodeID")
613 return ""
614
615
616class ModelGlowDMSceneConnector(ScriptedPipelineSceneConnector):
617 """
618 Connects this module's pipelines to the scene.
619
620 For pipelines to be created in our views, the pipeline factory needs to know about our pipeline classes.
621 When a node is added to the scene, the LayerDM orchestration queries the vtkMRMLLayerDMPipelineFactory
622 singleton to check if a pipeline can be created for the view node / node pair. The
623 ScriptedPipelineSceneConnector base class registers a creator which delegates to our pipeline classes'
624 TryCreatePipeline methods, and keeps track of what was connected so that the connection can be undone
625 (module unload) or replaced (module reload).
626
627 We also attach two scene observers to add our glow data node to model nodes when they are added and
628 garbage collect them on removal, and we create the glow pass data node so the glow pass pipeline is
629 created.
630
631 Note: Scene lifecycle are managed by the LayerDM library. If the view is newly created, its pipeline
632 manager will iterate over all the nodes in the scene to check if pipelines need to be created.
633 Similarly, when loading a scene, clearing a scene, the pipelines will be handled accordingly.
634 """
635
636 def getPipelines(self) -> list:
637 return [GlowDMPassPipeline, ModelGlowDMPipeline]
638
639 def getSceneObservers(self) -> list:
640 return [
641 (vtkMRMLScene.NodeAddedEvent, self.OnNodeAdded),
642 (vtkMRMLScene.NodeRemovedEvent, self.OnNodeRemoved),
643 ]
644
645 def onConnected(self) -> None:
646 """
647 Create our glow pass data node so that the glow pass pipeline is created.
648 Note: We use self.getScene() instead of the slicer.mrmlScene singleton so that the connector can be
649 used with an explicit scene (for instance in trame-slicer where the singleton is not available).
650 """
651 GlowDMPassPipeline.EnsureGlowPass(self.getScene())
652
653 @calldata_type(VTK_OBJECT)
654 def OnNodeAdded(self, _caller, _event, node):
655 """Attach a glow data node to model nodes when they are added to the scene."""
656 if isinstance(node, vtkMRMLModelNode):
657 ModelGlowDMPipeline.CreateGlowNode(node, self.getScene())
658
659 @calldata_type(VTK_OBJECT)
660 def OnNodeRemoved(self, _caller, _event, node):
661 """Garbage collect the glow data node when its model node is removed from the scene."""
662 if isinstance(node, vtkMRMLModelNode):
663 ModelGlowDMPipeline.RemoveGlowNode(node, self.getScene())
664
665
666_sceneConnector = ModelGlowDMSceneConnector()
bool SceneHasGlowPass(cls, vtkMRMLScene scene)
None OnRendererRemoved(self, vtkRenderer renderer)
EnsureGlowPass(cls, vtkMRMLScene scene)
None OnRendererAdded(self, vtkRenderer renderer)
vtkMRMLModelNode|None _GetModelNode(self)
None OnRendererRemoved(self, vtkRenderer renderer)
vtkMRMLModelNode|None _modelNode
None OnRendererAdded(self, vtkRenderer renderer)
RemoveGlowNode(cls, vtkMRMLModelNode modelNode, vtkMRMLScene scene)
OnUpdate(self, obj, eventId, callData)
tuple[bool, float] CanProcessInteractionEvent(self, vtkMRMLInteractionEventData eventData)
None LoseFocus(self, vtkMRMLInteractionEventData|None eventData)
CreateGlowNode(cls, vtkMRMLModelNode modelNode, vtkMRMLScene scene)
bool ProcessInteractionEvent(self, vtkMRMLInteractionEventData eventData)
__init__(self, parent)
IsPipelineNode(cls, node)
vtkMRMLScriptedModuleNode _CreatePipelineNode(cls)
vtkMRMLLayerDMScriptedPipeline|None TryCreatePipeline(cls, vtkMRMLAbstractViewNode viewNode, vtkMRMLNode node)