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 + "'>"