1# Copyright (c) 2019 Ultimaker B.V.
2# Uranium is released under the terms of the LGPLv3 or higher.
3
4from copy import deepcopy
5from typing import Any, Dict, List, Optional, cast
6
7import numpy
8
9from UM.Logger import Logger
10from UM.Math.AxisAlignedBox import AxisAlignedBox
11from UM.Math.Matrix import Matrix
12from UM.Math.Quaternion import Quaternion
13from UM.Math.Vector import Vector
14from UM.Mesh.MeshBuilder import MeshBuilder
15from UM.Mesh.MeshData import MeshData
16from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
17from UM.Signal import Signal, signalemitter
18
19
20@signalemitter
21class SceneNode:
22    """A scene node object.
23
24    These objects can hold a mesh and multiple children. Each node has a transformation matrix
25    that maps it it's parents space to the local space (it's inverse maps local space to parent).
26
27    SceneNodes can be "Decorated" by adding SceneNodeDecorator objects.
28    These decorators can add functionality to scene nodes.
29    :sa SceneNodeDecorator
30    :todo Add unit testing
31    """
32
33    class TransformSpace:
34        Local = 1 #type: int
35        Parent = 2 #type: int
36        World = 3 #type: int
37
38    def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", node_id: str = "") -> None:
39        """Construct a scene node.
40
41        :param parent: The parent of this node (if any). Only a root node should have None as a parent.
42        :param visible: Is the SceneNode (and thus, all its children) visible?
43        :param name: Name of the SceneNode.
44        """
45
46        super().__init__()  # Call super to make multiple inheritance work.
47
48        self._children = []     # type: List[SceneNode]
49        self._mesh_data = None  # type: Optional[MeshData]
50
51        # Local transformation (from parent to local)
52        self._transformation = Matrix()  # type: Matrix
53
54        # Convenience "components" of the transformation
55        self._position = Vector()  # type: Vector
56        self._scale = Vector(1.0, 1.0, 1.0)  # type: Vector
57        self._shear = Vector(0.0, 0.0, 0.0)  # type: Vector
58        self._mirror = Vector(1.0, 1.0, 1.0)  # type: Vector
59        self._orientation = Quaternion()  # type: Quaternion
60
61        # World transformation (from root to local)
62        self._world_transformation = Matrix()  # type: Matrix
63
64        # This is used for rendering. Since we don't want to recompute it every time, we cache it in the node
65        self._cached_normal_matrix = Matrix()
66
67        # Convenience "components" of the world_transformation
68        self._derived_position = Vector()  # type: Vector
69        self._derived_orientation = Quaternion()  # type: Quaternion
70        self._derived_scale = Vector()  # type: Vector
71
72        self._parent = parent  # type: Optional[SceneNode]
73
74        # Can this SceneNode be modified in any way?
75        self._enabled = True  # type: bool
76        # Can this SceneNode be selected in any way?
77        self._selectable = False  # type: bool
78
79        # Should the AxisAlignedBoundingBox be re-calculated?
80        self._calculate_aabb = True  # type: bool
81
82        # The AxisAligned bounding box.
83        self._aabb = None  # type: Optional[AxisAlignedBox]
84        self._bounding_box_mesh = None  # type: Optional[MeshData]
85
86        self._visible = visible  # type: bool
87        self._name = name  # type: str
88        self._id = node_id  # type: str
89        self._decorators = []  # type: List[SceneNodeDecorator]
90
91        # Store custom settings to be compatible with Savitar SceneNode
92        self._settings = {}  # type: Dict[str, Any]
93
94        ## Signals
95        self.parentChanged.connect(self._onParentChanged)
96
97        if parent:
98            parent.addChild(self)
99
100    def __deepcopy__(self, memo: Dict[int, object]) -> "SceneNode":
101        copy = self.__class__()
102        copy.setTransformation(self.getLocalTransformation())
103        copy.setMeshData(self._mesh_data)
104        copy._visible = cast(bool, deepcopy(self._visible, memo))
105        copy._selectable = cast(bool, deepcopy(self._selectable, memo))
106        copy._name = cast(str, deepcopy(self._name, memo))
107        for decorator in self._decorators:
108            copy.addDecorator(cast(SceneNodeDecorator, deepcopy(decorator, memo)))
109
110        for child in self._children:
111            copy.addChild(cast(SceneNode, deepcopy(child, memo)))
112        self.calculateBoundingBoxMesh()
113        return copy
114
115    def setCenterPosition(self, center: Vector) -> None:
116        """Set the center position of this node.
117
118        This is used to modify it's mesh data (and it's children) in such a way that they are centered.
119        In most cases this means that we use the center of mass as center (which most objects don't use)
120        """
121
122        if self._mesh_data:
123            m = Matrix()
124            m.setByTranslation(-center)
125            self._mesh_data = self._mesh_data.getTransformed(m).set(center_position=center)
126        for child in self._children:
127            child.setCenterPosition(center)
128
129    def getParent(self) -> Optional["SceneNode"]:
130        """Get the parent of this node.
131
132        If the node has no parent, it is the root node.
133
134        :returns: SceneNode if it has a parent and None if it's the root node.
135        """
136
137        return self._parent
138
139    def getMirror(self) -> Vector:
140        return self._mirror
141
142    def setMirror(self, vector) -> None:
143        self._mirror = vector
144
145    def getBoundingBoxMesh(self) -> Optional[MeshData]:
146        """Get the MeshData of the bounding box
147
148        :returns: :type{MeshData} Bounding box mesh.
149        """
150
151        if self._bounding_box_mesh is None:
152            self.calculateBoundingBoxMesh()
153        return self._bounding_box_mesh
154
155    def calculateBoundingBoxMesh(self) -> None:
156        """(re)Calculate the bounding box mesh."""
157
158        aabb = self.getBoundingBox()
159        if aabb:
160            bounding_box_mesh = MeshBuilder()
161            rtf = aabb.maximum
162            lbb = aabb.minimum
163
164            bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z)  # Right - Top - Front
165            bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z)  # Left - Top - Front
166
167            bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z)  # Left - Top - Front
168            bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z)  # Left - Bottom - Front
169
170            bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z)  # Left - Bottom - Front
171            bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z)  # Right - Bottom - Front
172
173            bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z)  # Right - Bottom - Front
174            bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z)  # Right - Top - Front
175
176            bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z)  # Right - Top - Back
177            bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z)  # Left - Top - Back
178
179            bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z)  # Left - Top - Back
180            bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z)  # Left - Bottom - Back
181
182            bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z)  # Left - Bottom - Back
183            bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z)  # Right - Bottom - Back
184
185            bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z)  # Right - Bottom - Back
186            bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z)  # Right - Top - Back
187
188            bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z)  # Right - Top - Front
189            bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z)  # Right - Top - Back
190
191            bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z)  # Left - Top - Front
192            bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z)  # Left - Top - Back
193
194            bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z)  # Left - Bottom - Front
195            bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z)  # Left - Bottom - Back
196
197            bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z)  # Right - Bottom - Front
198            bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z)  # Right - Bottom - Back
199
200            self._bounding_box_mesh = bounding_box_mesh.build()
201
202    def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool:
203        """Return if the provided bbox collides with the bbox of this SceneNode"""
204
205        bbox = self.getBoundingBox()
206        if bbox is not None:
207            if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
208                return True
209
210        return False
211
212    def _onParentChanged(self, node: Optional["SceneNode"]) -> None:
213        """Handler for the ParentChanged signal
214        :param node: Node from which this event was triggered.
215        """
216
217        for child in self.getChildren():
218            child.parentChanged.emit(self)
219
220    decoratorsChanged = Signal()
221    """Signal for when a :type{SceneNodeDecorator} is added / removed."""
222
223    def addDecorator(self, decorator: SceneNodeDecorator) -> None:
224        """Add a SceneNodeDecorator to this SceneNode.
225
226        :param decorator: The decorator to add.
227        """
228
229        if type(decorator) in [type(dec) for dec in self._decorators]:
230            Logger.log("w", "Unable to add the same decorator type (%s) to a SceneNode twice.", type(decorator))
231            return
232        try:
233            decorator.setNode(self)
234        except AttributeError:
235            Logger.logException("e", "Unable to add decorator.")
236            return
237        self._decorators.append(decorator)
238        self.decoratorsChanged.emit(self)
239
240    def getDecorators(self) -> List[SceneNodeDecorator]:
241        """Get all SceneNodeDecorators that decorate this SceneNode.
242
243        :return: list of all SceneNodeDecorators.
244        """
245
246        return self._decorators
247
248    def getDecorator(self, dec_type: type) -> Optional[SceneNodeDecorator]:
249        """Get SceneNodeDecorators by type.
250
251        :param dec_type: type of decorator to return.
252        """
253
254        for decorator in self._decorators:
255            if type(decorator) == dec_type:
256                return decorator
257        return None
258
259    def removeDecorators(self):
260        """Remove all decorators"""
261
262        for decorator in self._decorators:
263            decorator.clear()
264        self._decorators = []
265        self.decoratorsChanged.emit(self)
266
267    def removeDecorator(self, dec_type: type) -> None:
268        """Remove decorator by type.
269
270        :param dec_type: type of the decorator to remove.
271        """
272
273        for decorator in self._decorators:
274            if type(decorator) == dec_type:
275                decorator.clear()
276                self._decorators.remove(decorator)
277                self.decoratorsChanged.emit(self)
278                break
279
280    def callDecoration(self, function: str, *args, **kwargs) -> Any:
281        """Call a decoration of this SceneNode.
282
283        SceneNodeDecorators add Decorations, which are callable functions.
284        :param function: The function to be called.
285        :param *args
286        :param **kwargs
287        """
288
289        for decorator in self._decorators:
290            if hasattr(decorator, function):
291                try:
292                    return getattr(decorator, function)(*args, **kwargs)
293                except Exception as e:
294                    Logger.logException("e", "Exception calling decoration %s: %s", str(function), str(e))
295                    return None
296
297    def hasDecoration(self, function: str) -> bool:
298        """Does this SceneNode have a certain Decoration (as defined by a Decorator)
299        :param :type{string} function the function to check for.
300        """
301
302        for decorator in self._decorators:
303            if hasattr(decorator, function):
304                return True
305        return False
306
307    def getName(self) -> str:
308        return self._name
309
310    def setName(self, name: str) -> None:
311        self._name = name
312
313    def getId(self) -> str:
314        return self._id
315
316    def setId(self, node_id: str) -> None:
317        self._id = node_id
318
319    def getDepth(self) -> int:
320        """How many nodes is this node removed from the root?
321
322        :return: Steps from root (0 means it -is- the root).
323        """
324
325        if self._parent is None:
326            return 0
327        return self._parent.getDepth() + 1
328
329    def setParent(self, scene_node: Optional["SceneNode"]) -> None:
330        """:brief Set the parent of this object
331
332        :param scene_node: SceneNode that is the parent of this object.
333        """
334
335        if self._parent:
336            self._parent.removeChild(self)
337
338        if scene_node:
339            scene_node.addChild(self)
340
341    parentChanged = Signal()
342    """Emitted whenever the parent changes."""
343
344    def isVisible(self) -> bool:
345        """Get the visibility of this node.
346        The parents visibility overrides the visibility.
347        TODO: Let renderer actually use the visibility to decide whether to render or not.
348        """
349
350        if self._parent is not None and self._visible:
351            return self._parent.isVisible()
352        else:
353            return self._visible
354
355    def setVisible(self, visible: bool) -> None:
356        """Set the visibility of this SceneNode."""
357
358        self._visible = visible
359
360    def getMeshData(self) -> Optional[MeshData]:
361        """Get the (original) mesh data from the scene node/object.
362
363        :returns: MeshData
364        """
365
366        return self._mesh_data
367
368    def getMeshDataTransformed(self) -> Optional[MeshData]:
369        """Get the transformed mesh data from the scene node/object, based on the transformation of scene nodes wrt root.
370
371        If this node is a group, it will recursively concatenate all child nodes/objects.
372        :returns: MeshData
373        """
374
375        return MeshData(vertices = self.getMeshDataTransformedVertices(), normals = self.getMeshDataTransformedNormals())
376
377    def getMeshDataTransformedVertices(self) -> numpy.ndarray:
378        """Get the transformed vertices from this scene node/object, based on the transformation of scene nodes wrt root.
379
380        If this node is a group, it will recursively concatenate all child nodes/objects.
381        :return: numpy.ndarray
382        """
383
384        transformed_vertices = None
385        if self.callDecoration("isGroup"):
386            for child in self._children:
387                tv = child.getMeshDataTransformedVertices()
388                if transformed_vertices is None:
389                    transformed_vertices = tv
390                else:
391                    transformed_vertices = numpy.concatenate((transformed_vertices, tv), axis = 0)
392        else:
393            if self._mesh_data:
394                transformed_vertices = self._mesh_data.getTransformed(self.getWorldTransformation(copy=False)).getVertices()
395        return transformed_vertices
396
397    def getMeshDataTransformedNormals(self) -> numpy.ndarray:
398        """Get the transformed normals from this scene node/object, based on the transformation of scene nodes wrt root.
399
400        If this node is a group, it will recursively concatenate all child nodes/objects.
401        :return: numpy.ndarray
402        """
403
404        transformed_normals = None
405        if self.callDecoration("isGroup"):
406            for child in self._children:
407                tv = child.getMeshDataTransformedNormals()
408                if transformed_normals is None:
409                    transformed_normals = tv
410                else:
411                    transformed_normals = numpy.concatenate((transformed_normals, tv), axis = 0)
412        else:
413            if self._mesh_data:
414                transformed_normals = self._mesh_data.getTransformed(self.getWorldTransformation(copy = False)).getNormals()
415        return transformed_normals
416
417    def setMeshData(self, mesh_data: Optional[MeshData]) -> None:
418        """Set the mesh of this node/object
419
420        :param mesh_data: MeshData object
421        """
422
423        self._mesh_data = mesh_data
424        self._resetAABB()
425        self.meshDataChanged.emit(self)
426
427    meshDataChanged = Signal()
428    """Emitted whenever the attached mesh data object changes."""
429
430    def _onMeshDataChanged(self) -> None:
431        self.meshDataChanged.emit(self)
432
433    def addChild(self, scene_node: "SceneNode") -> None:
434        """Add a child to this node and set it's parent as this node.
435
436        :params scene_node SceneNode to add.
437        """
438
439        if scene_node in self._children:
440            return
441
442        scene_node.transformationChanged.connect(self.transformationChanged)
443        scene_node.childrenChanged.connect(self.childrenChanged)
444        scene_node.meshDataChanged.connect(self.meshDataChanged)
445
446        self._children.append(scene_node)
447        self._resetAABB()
448        self.childrenChanged.emit(self)
449
450        if not scene_node._parent is self:
451            scene_node._parent = self
452            scene_node._transformChanged()
453            scene_node.parentChanged.emit(self)
454
455    def removeChild(self, child: "SceneNode") -> None:
456        """remove a single child
457
458        :param child: Scene node that needs to be removed.
459        """
460
461        if child not in self._children:
462            return
463
464        child.transformationChanged.disconnect(self.transformationChanged)
465        child.childrenChanged.disconnect(self.childrenChanged)
466        child.meshDataChanged.disconnect(self.meshDataChanged)
467
468        self._children.remove(child)
469        child._parent = None
470        child._transformChanged()
471        child.parentChanged.emit(self)
472
473        self._resetAABB()
474        self.childrenChanged.emit(self)
475
476    def removeAllChildren(self) -> None:
477        """Removes all children and its children's children."""
478
479        for child in self._children:
480            child.removeAllChildren()
481            self.removeChild(child)
482
483        self.childrenChanged.emit(self)
484
485    def getChildren(self) -> List["SceneNode"]:
486        """Get the list of direct children
487
488        :returns: List of children
489        """
490
491        return self._children
492
493    def hasChildren(self) -> bool:
494        return True if self._children else False
495
496    def getAllChildren(self) -> List["SceneNode"]:
497        """Get list of all children (including it's children children children etc.)
498
499        :returns: list ALl children in this 'tree'
500        """
501
502        children = []
503        children.extend(self._children)
504        for child in self._children:
505            children.extend(child.getAllChildren())
506        return children
507
508    childrenChanged = Signal()
509    """Emitted whenever the list of children of this object or any child object changes.
510
511    :param object: The object that triggered the change.
512    """
513
514    def _updateCachedNormalMatrix(self) -> None:
515        self._cached_normal_matrix = Matrix(self.getWorldTransformation(copy=False).getData())
516        self._cached_normal_matrix.setRow(3, [0, 0, 0, 1])
517        self._cached_normal_matrix.setColumn(3, [0, 0, 0, 1])
518        self._cached_normal_matrix.invert()
519        self._cached_normal_matrix.transpose()
520
521    def getCachedNormalMatrix(self) -> Matrix:
522        if self._cached_normal_matrix is None:
523            self._updateCachedNormalMatrix()
524        return self._cached_normal_matrix
525
526    def getWorldTransformation(self, copy = True) -> Matrix:
527        """Computes and returns the transformation from world to local space.
528
529        :returns: 4x4 transformation matrix
530        """
531
532        if self._world_transformation is None:
533            self._updateWorldTransformation()
534        if copy:
535            return self._world_transformation.copy()
536        return self._world_transformation
537
538    def getLocalTransformation(self, copy = True) -> Matrix:
539        """Returns the local transformation with respect to its parent. (from parent to local)
540
541        :returns transformation 4x4 (homogeneous) matrix
542        """
543
544        if self._transformation is None:
545            self._updateLocalTransformation()
546        if copy:
547            return self._transformation.copy()
548        return self._transformation
549
550    def setTransformation(self, transformation: Matrix):
551        self._transformation = transformation.copy() # Make a copy to ensure we never change the given transformation
552        self._transformChanged()
553
554    def getOrientation(self) -> Quaternion:
555        """Get the local orientation value."""
556
557        return deepcopy(self._orientation)
558
559    def getWorldOrientation(self) -> Quaternion:
560        return deepcopy(self._derived_orientation)
561
562    def rotate(self, rotation: Quaternion, transform_space: int = TransformSpace.Local) -> None:
563        """Rotate the scene object (and thus its children) by given amount
564
565        :param rotation: :type{Quaternion} A quaternion indicating the amount of rotation.
566        :param transform_space: The space relative to which to rotate. Can be any one of the constants in SceneNode::TransformSpace.
567        """
568
569        if not self._enabled:
570            return
571
572        orientation_matrix = rotation.toMatrix()
573        if transform_space == SceneNode.TransformSpace.Local:
574            self._transformation.multiply(orientation_matrix)
575        elif transform_space == SceneNode.TransformSpace.Parent:
576            self._transformation.preMultiply(orientation_matrix)
577        elif transform_space == SceneNode.TransformSpace.World:
578            self._transformation.multiply(self._world_transformation.getInverse())
579            self._transformation.multiply(orientation_matrix)
580            self._transformation.multiply(self._world_transformation)
581
582        self._transformChanged()
583
584    def setOrientation(self, orientation: Quaternion, transform_space: int = TransformSpace.Local) -> None:
585        """Set the local orientation of this scene node.
586
587        :param orientation: :type{Quaternion} The new orientation of this scene node.
588        :param transform_space: The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace.
589        """
590
591        if not self._enabled or orientation == self._orientation:
592            return
593
594        if transform_space == SceneNode.TransformSpace.World:
595            if self.getWorldOrientation() == orientation:
596                return
597            new_orientation = orientation * (self.getWorldOrientation() * self._orientation.getInverse()).invert()
598            orientation_matrix = new_orientation.toMatrix()
599        else:  # Local
600            orientation_matrix = orientation.toMatrix()
601
602        euler_angles = orientation_matrix.getEuler()
603        new_transform_matrix = Matrix()
604        new_transform_matrix.compose(scale = self._scale, angles = euler_angles, translate = self._position, shear = self._shear)
605        self._transformation = new_transform_matrix
606        self._transformChanged()
607
608    def getScale(self) -> Vector:
609        """Get the local scaling value."""
610
611        return self._scale
612
613    def getWorldScale(self) -> Vector:
614        return self._derived_scale
615
616    def scale(self, scale: Vector, transform_space: int = TransformSpace.Local) -> None:
617        """Scale the scene object (and thus its children) by given amount
618
619        :param scale: :type{Vector} A Vector with three scale values
620        :param transform_space: The space relative to which to scale. Can be any one of the constants in SceneNode::TransformSpace.
621        """
622
623        if not self._enabled:
624            return
625
626        scale_matrix = Matrix()
627        scale_matrix.setByScaleVector(scale)
628        if transform_space == SceneNode.TransformSpace.Local:
629            self._transformation.multiply(scale_matrix)
630        elif transform_space == SceneNode.TransformSpace.Parent:
631            self._transformation.preMultiply(scale_matrix)
632        elif transform_space == SceneNode.TransformSpace.World:
633            self._transformation.multiply(self._world_transformation.getInverse())
634            self._transformation.multiply(scale_matrix)
635            self._transformation.multiply(self._world_transformation)
636
637        self._transformChanged()
638
639    def setScale(self, scale: Vector, transform_space: int = TransformSpace.Local) -> None:
640        """Set the local scale value.
641
642        :param scale: :type{Vector} The new scale value of the scene node.
643        :param transform_space: The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace.
644        """
645
646        if not self._enabled or scale == self._scale:
647            return
648        if transform_space == SceneNode.TransformSpace.Local:
649            self.scale(scale / self._scale, SceneNode.TransformSpace.Local)
650            return
651        if transform_space == SceneNode.TransformSpace.World:
652            if self.getWorldScale() == scale:
653                return
654
655            self.scale(scale / self._scale, SceneNode.TransformSpace.World)
656
657    def getPosition(self) -> Vector:
658        """Get the local position."""
659
660        return self._position
661
662    def getWorldPosition(self) -> Vector:
663        """Get the position of this scene node relative to the world."""
664
665        return self._derived_position
666
667    def translate(self, translation: Vector, transform_space: int = TransformSpace.Local) -> None:
668        """Translate the scene object (and thus its children) by given amount.
669
670        :param translation: :type{Vector} The amount to translate by.
671        :param transform_space: The space relative to which to translate. Can be any one of the constants in SceneNode::TransformSpace.
672        """
673
674        if not self._enabled:
675            return
676        translation_matrix = Matrix()
677        translation_matrix.setByTranslation(translation)
678        if transform_space == SceneNode.TransformSpace.Local:
679            self._transformation.multiply(translation_matrix)
680        elif transform_space == SceneNode.TransformSpace.Parent:
681            self._transformation.preMultiply(translation_matrix)
682        elif transform_space == SceneNode.TransformSpace.World:
683            world_transformation = self._world_transformation.copy()
684            self._transformation.multiply(self._world_transformation.getInverse())
685            self._transformation.multiply(translation_matrix)
686            self._transformation.multiply(world_transformation)
687        self._transformChanged()
688
689    def setPosition(self, position: Vector, transform_space: int = TransformSpace.Local) -> None:
690        """Set the local position value.
691
692        :param position: The new position value of the SceneNode.
693        :param transform_space: The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace.
694        """
695
696        if not self._enabled or position == self._position:
697            return
698        if transform_space == SceneNode.TransformSpace.Local:
699            self.translate(position - self._position, SceneNode.TransformSpace.Parent)
700        if transform_space == SceneNode.TransformSpace.World:
701            if self.getWorldPosition() == position:
702                return
703            self.translate(position - self._derived_position, SceneNode.TransformSpace.World)
704
705    transformationChanged = Signal()
706    """Signal. Emitted whenever the transformation of this object or any child object changes.
707    :param object: The object that caused the change.
708    """
709
710    def lookAt(self, target: Vector, up: Vector = Vector.Unit_Y) -> None:
711        """Rotate this scene node in such a way that it is looking at target.
712
713        :param target: :type{Vector} The target to look at.
714        :param up: :type{Vector} The vector to consider up. Defaults to Vector.Unit_Y, i.e. (0, 1, 0).
715        """
716
717        if not self._enabled:
718            return
719
720        eye = self.getWorldPosition()
721        f = (target - eye).normalized()
722        up = up.normalized()
723        s = f.cross(up).normalized()
724        u = s.cross(f).normalized()
725
726        m = Matrix([
727            [ s.x,  u.x,  -f.x, 0.0],
728            [ s.y,  u.y,  -f.y, 0.0],
729            [ s.z,  u.z,  -f.z, 0.0],
730            [ 0.0,  0.0,  0.0,  1.0]
731        ])
732
733        self.setOrientation(Quaternion.fromMatrix(m))
734
735    def render(self, renderer) -> bool:
736        """Can be overridden by child nodes if they need to perform special rendering.
737        If you need to handle rendering in a special way, for example for tool handles,
738        you can override this method and render the node. Return True to prevent the
739        view from rendering any attached mesh data.
740
741        :param renderer: The renderer object to use for rendering.
742
743        :return: False if the view should render this node, True if we handle our own rendering.
744        """
745
746        return False
747
748    def isEnabled(self) -> bool:
749        """Get whether this SceneNode is enabled, that is, it can be modified in any way."""
750
751        if self._parent is not None and self._enabled:
752            return self._parent.isEnabled()
753        else:
754            return self._enabled
755
756    def setEnabled(self, enable: bool) -> None:
757        """Set whether this SceneNode is enabled.
758
759        :param enable: True if this object should be enabled, False if not.
760        :sa isEnabled
761        """
762
763        self._enabled = enable
764
765    def isSelectable(self) -> bool:
766        """Get whether this SceneNode can be selected.
767
768        :note This will return false if isEnabled() returns false.
769        """
770
771        return self._enabled and self._selectable
772
773    def setSelectable(self, select: bool) -> None:
774        """Set whether this SceneNode can be selected.
775
776        :param select: True if this SceneNode should be selectable, False if not.
777        """
778
779        self._selectable = select
780
781    def getBoundingBox(self) -> Optional[AxisAlignedBox]:
782        """Get the bounding box of this node and its children."""
783
784        if not self._calculate_aabb:
785            return None
786        if self._aabb is None:
787            self._calculateAABB()
788        return self._aabb
789
790    def setCalculateBoundingBox(self, calculate: bool) -> None:
791        """Set whether or not to calculate the bounding box for this node.
792
793        :param calculate: True if the bounding box should be calculated, False if not.
794        """
795
796        self._calculate_aabb = calculate
797
798    boundingBoxChanged = Signal()
799
800    def getShear(self) -> Vector:
801        return self._shear
802
803    def getSetting(self, key: str, default_value: str = "") -> str:
804        return self._settings.get(key, default_value)
805
806    def setSetting(self, key: str, value: str) -> None:
807        self._settings[key] = value
808
809    def invertNormals(self) -> None:
810        for child in self._children:
811            child.invertNormals()
812        if self._mesh_data:
813            self._mesh_data.invertNormals()
814
815    def _transformChanged(self) -> None:
816        self._updateTransformation()
817        self._resetAABB()
818        self.transformationChanged.emit(self)
819
820        for child in self._children:
821            child._transformChanged()
822
823    def _updateLocalTransformation(self) -> None:
824        self._position, euler_angle_matrix, self._scale, self._shear = self._transformation.decompose()
825
826        self._orientation.setByMatrix(euler_angle_matrix)
827
828    def _updateWorldTransformation(self) -> None:
829        if self._parent:
830            self._world_transformation = self._parent.getWorldTransformation().multiply(self._transformation)
831        else:
832            self._world_transformation = self._transformation
833
834        self._derived_position, world_euler_angle_matrix, self._derived_scale, world_shear = self._world_transformation.decompose()
835        self._derived_orientation.setByMatrix(world_euler_angle_matrix)
836
837    def _updateTransformation(self) -> None:
838        self._updateLocalTransformation()
839        self._updateWorldTransformation()
840        self._updateCachedNormalMatrix()
841
842    def _resetAABB(self) -> None:
843        if not self._calculate_aabb:
844            return
845        self._aabb = None
846        self._bounding_box_mesh = None
847        if self._parent:
848            self._parent._resetAABB()
849        self.boundingBoxChanged.emit()
850
851    def _calculateAABB(self) -> None:
852        if self._mesh_data:
853            aabb = self._mesh_data.getExtents(self.getWorldTransformation(copy = False))
854        else:  # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
855            position = self.getWorldPosition()
856            aabb = AxisAlignedBox(minimum = position, maximum = position)
857
858        for child in self._children:
859            if aabb is None:
860                aabb = child.getBoundingBox()
861            else:
862                aabb = aabb + child.getBoundingBox()
863        self._aabb = aabb
864
865    def __str__(self) -> str:
866        """String output for debugging."""
867
868        name = self._name if self._name != "" else hex(id(self))
869        return "<" + self.__class__.__qualname__ + " object: '" + name + "'>"