def createHullMesh(self, hull_points): mesh = MeshData() if len(hull_points) > 3: center = (hull_points.min(0) + hull_points.max(0)) / 2.0 mesh.addVertex(center[0], -0.1, center[1]) else: return None for point in hull_points: mesh.addVertex(point[0], -0.1, point[1]) indices = [] for i in range(len(hull_points) - 1): indices.append([0, i + 1, i + 2]) indices.append([0, mesh.getVertexCount() - 1, 1]) mesh.addIndices(numpy.array(indices, numpy.int32)) return mesh
def __init__(self, node, hull, parent = None): super().__init__(parent) self.setCalculateBoundingBox(False) self._material = None self._original_parent = parent self._inherit_orientation = False self._inherit_scale = False self._node = node self._node.transformationChanged.connect(self._onNodePositionChanged) self._node.parentChanged.connect(self._onNodeParentChanged) #self._onNodePositionChanged(self._node) self._hull = hull hull_points = self._hull.getPoints() center = (hull_points.min(0) + hull_points.max(0)) / 2.0 mesh = MeshData() mesh.addVertex(center[0], 0.1, center[1]) for point in hull_points: mesh.addVertex(point[0], 0.1, point[1]) indices = [] for i in range(len(hull_points) - 1): indices.append([0, i + 1, i + 2]) indices.append([0, mesh.getVertexCount() - 1, 1]) mesh.addIndices(numpy.array(indices, numpy.int32)) self.setMeshData(mesh)
def __init__(self, node, hull, parent = None): super().__init__(parent) self.setCalculateBoundingBox(False) self._material = None self._original_parent = parent self._inherit_orientation = False self._inherit_scale = False self._node = node self._node.transformationChanged.connect(self._onNodePositionChanged) self._node.parentChanged.connect(self._onNodeParentChanged) #self._onNodePositionChanged(self._node) self._hull = hull hull_points = self._hull.getPoints() mesh = MeshData() if len(hull_points) > 3: center = (hull_points.min(0) + hull_points.max(0)) / 2.0 mesh.addVertex(center[0], 0.1, center[1]) else: #Hull has not enough points return for point in hull_points: mesh.addVertex(point[0], 0.1, point[1]) indices = [] for i in range(len(hull_points) - 1): indices.append([0, i + 1, i + 2]) indices.append([0, mesh.getVertexCount() - 1, 1]) mesh.addIndices(numpy.array(indices, numpy.int32)) self.setMeshData(mesh)
# Copyright (c) 2015 Ultimaker B.V. # Uranium is released under the terms of the LGPLv3 or higher. from UM.Mesh.MeshData import MeshData @profile def calcNormals(mesh): mesh.calculateNormals() mesh = MeshData() mesh.reserveVertexCount(99999) for i in range(33333): mesh.addVertex(0, 1, 0) mesh.addVertex(1, 1, 0) mesh.addVertex(1, 0, 0) for i in range(100): calcNormals(mesh)
class SceneNode(SignalEmitter): class TransformSpace: Local = 1 Parent = 2 World = 3 def __init__(self, parent = None, name = ""): super().__init__() # Call super to make multiple inheritence work. self._children = [] self._mesh_data = None self._position = Vector() self._scale = Vector(1.0, 1.0, 1.0) self._mirror = Vector(1.0, 1.0, 1.0) self._orientation = Quaternion() self._transformation = None self._world_transformation = None self._derived_position = None self._derived_orientation = None self._derived_scale = None self._inherit_orientation = True self._inherit_scale = True self._parent = parent self._enabled = True self._selectable = False self._calculate_aabb = True self._aabb = None self._aabb_job = None self._visible = True self._name = name self._decorators = [] self._bounding_box_mesh = None self.boundingBoxChanged.connect(self.calculateBoundingBoxMesh) self.parentChanged.connect(self._onParentChanged) if parent: parent.addChild(self) def __deepcopy__(self, memo): copy = SceneNode() copy.translate(self.getPosition()) copy.setOrientation(self.getOrientation()) copy.setScale(self.getScale()) copy.setMeshData(deepcopy(self._mesh_data, memo)) copy.setVisible(deepcopy(self._visible, memo)) copy._selectable = deepcopy(self._selectable, memo) for decorator in self._decorators: copy.addDecorator(deepcopy(decorator, memo)) for child in self._children: copy.addChild(deepcopy(child, memo)) self.calculateBoundingBoxMesh() return copy def setCenterPosition(self, center): if self._mesh_data: m = Matrix() m.setByTranslation(-center) self._mesh_data = self._mesh_data.getTransformed(m) self._mesh_data.setCenterPosition(center) for child in self._children: child.setCenterPosition(center) ## \brief Get the parent of this node. If the node has no parent, it is the root node. # \returns SceneNode if it has a parent and None if it's the root node. def getParent(self): return self._parent def getBoundingBoxMesh(self): return self._bounding_box_mesh def calculateBoundingBoxMesh(self): if self._aabb: self._bounding_box_mesh = MeshData() rtf = self._aabb.maximum lbb = self._aabb.minimum self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) #Right - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) #Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) #Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) #Left - Bottom - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) #Left - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) #Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) #Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) #Right - Top - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) #Right - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) #Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) #Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) #Left - Bottom - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) #Left - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) #Right - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) #Right - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) #Right - Top - Back self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) #Right - Top - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) #Right - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) #Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) #Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) #Left - Bottom - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) #Left - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) #Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) #Right - Bottom - Back else: self._resetAABB() def _onParentChanged(self, node): for child in self.getChildren(): child.parentChanged.emit(self) decoratorsChanged = Signal() def addDecorator(self, decorator): decorator.setNode(self) self._decorators.append(decorator) self.decoratorsChanged.emit(self) def getDecorators(self): return self._decorators def getDecorator(self, dec_type): for decorator in self._decorators: if type(decorator) == dec_type: return decorator def removeDecorators(self): self._decorators = [] self.decoratorsChanged.emit(self) def removeDecorator(self, dec_type): for decorator in self._decorators: if type(decorator) == dec_type: self._decorators.remove(decorator) self.decoratorsChanged.emit(self) break def callDecoration(self, function, *args, **kwargs): for decorator in self._decorators: if hasattr(decorator, function): try: return getattr(decorator, function)(*args, **kwargs) except Exception as e: Logger.log("e", "Exception calling decoration %s: %s", str(function), str(e)) return None def hasDecoration(self, function): for decorator in self._decorators: if hasattr(decorator, function): return True return False def getName(self): return self._name def setName(self, name): self._name = name ## How many nodes is this node removed from the root def getDepth(self): if self._parent is None: return 0 return self._parent.getDepth() + 1 ## \brief Set the parent of this object # \param scene_node SceneNode that is the parent of this object. def setParent(self, scene_node): if self._parent: self._parent.removeChild(self) #self._parent = scene_node if scene_node: scene_node.addChild(self) ## Emitted whenever the parent changes. parentChanged = Signal() ## \brief Get the visibility of this node. The parents visibility overrides the visibility. # TODO: Let renderer actually use the visibility to decide wether to render or not. def isVisible(self): if self._parent != None and self._visible: return self._parent.isVisible() else: return self._visible def setVisible(self, visible): self._visible = visible ## \brief Get the (original) mesh data from the scene node/object. # \returns MeshData def getMeshData(self): return self._mesh_data ## \brief Get the transformed mesh data from the scene node/object, based on the transformation of scene nodes wrt root. # \returns MeshData def getMeshDataTransformed(self): #transformed_mesh = deepcopy(self._mesh_data) #transformed_mesh.transform(self.getWorldTransformation()) return self._mesh_data.getTransformed(self.getWorldTransformation()) ## \brief Set the mesh of this node/object # \param mesh_data MeshData object def setMeshData(self, mesh_data): if self._mesh_data: self._mesh_data.dataChanged.disconnect(self._onMeshDataChanged) self._mesh_data = mesh_data if self._mesh_data is not None: self._mesh_data.dataChanged.connect(self._onMeshDataChanged) self._resetAABB() self.meshDataChanged.emit(self) ## Emitted whenever the attached mesh data object changes. meshDataChanged = Signal() def _onMeshDataChanged(self): self.meshDataChanged.emit(self) ## \brief Add a child to this node and set it's parent as this node. # \params scene_node SceneNode to add. def addChild(self, scene_node): if scene_node not in self._children: scene_node.transformationChanged.connect(self.transformationChanged) scene_node.childrenChanged.connect(self.childrenChanged) scene_node.meshDataChanged.connect(self.meshDataChanged) self._children.append(scene_node) self._resetAABB() self.childrenChanged.emit(self) if not scene_node._parent is self: scene_node._parent = self scene_node._transformChanged() scene_node.parentChanged.emit(self) ## \brief remove a single child # \param child Scene node that needs to be removed. def removeChild(self, child): if child not in self._children: return child.transformationChanged.disconnect(self.transformationChanged) child.childrenChanged.disconnect(self.childrenChanged) child.meshDataChanged.disconnect(self.meshDataChanged) self._children.remove(child) child._parent = None child._transformChanged() child.parentChanged.emit(self) self.childrenChanged.emit(self) ## \brief Removes all children and its children's children. def removeAllChildren(self): for child in self._children: child.removeAllChildren() self.removeChild(child) self.childrenChanged.emit(self) ## \brief Get the list of direct children # \returns List of children def getChildren(self): return self._children def hasChildren(self): return True if self._children else False ## \brief Get list of all children (including it's children children children etc.) # \returns list ALl children in this 'tree' def getAllChildren(self): children = [] children.extend(self._children) for child in self._children: children.extend(child.getAllChildren()) return children ## \brief Emitted whenever the list of children of this object or any child object changes. # \param object The object that triggered the change. childrenChanged = Signal() ## \brief Computes and returns the transformation from world to local space. # \returns 4x4 transformation matrix def getWorldTransformation(self): if self._world_transformation is None: self._updateTransformation() return deepcopy(self._world_transformation) ## \brief Returns the local transformation with respect to its parent. (from parent to local) # \retuns transformation 4x4 (homogenous) matrix def getLocalTransformation(self): if self._transformation is None: self._updateTransformation() return deepcopy(self._transformation) ## Get the local orientation value. def getOrientation(self): return deepcopy(self._orientation) ## \brief Rotate the scene object (and thus its children) by given amount # # \param rotation \type{Quaternion} A quaternion indicating the amount of rotation. # \param transform_space The space relative to which to rotate. Can be any one of the constants in SceneNode::TransformSpace. def rotate(self, rotation, transform_space = TransformSpace.Local): if not self._enabled: return if transform_space == SceneNode.TransformSpace.Local: self._orientation = self._orientation * rotation elif transform_space == SceneNode.TransformSpace.Parent: self._orientation = rotation * self._orientation elif transform_space == SceneNode.TransformSpace.World: self._orientation = self._orientation * self._getDerivedOrientation().getInverse() * rotation * self._getDerivedOrientation() else: raise ValueError("Unknown transform space {0}".format(transform_space)) self._orientation.normalize() self._transformChanged() ## Set the local orientation of this scene node. # # \param orientation \type{Quaternion} The new orientation of this scene node. def setOrientation(self, orientation): if not self._enabled or orientation == self._orientation: return self._orientation = orientation self._orientation.normalize() self._transformChanged() ## Get the local scaling value. def getScale(self): return deepcopy(self._scale) ## Scale the scene object (and thus its children) by given amount # # \param scale \type{Vector} A Vector with three scale values # \param transform_space The space relative to which to scale. Can be any one of the constants in SceneNode::TransformSpace. def scale(self, scale, transform_space = TransformSpace.Local): if not self._enabled: return if transform_space == SceneNode.TransformSpace.Local: self._scale = self._scale.scale(scale) elif transform_space == SceneNode.TransformSpace.Parent: raise NotImplementedError() elif transform_space == SceneNode.TransformSpace.World: if self._parent: scale_change = Vector(1,1,1) - scale if scale_change.x < 0 or scale_change.y < 0 or scale_change.z < 0: direction = -1 else: direction = 1 # Hackish way to do this, but this seems to correctly scale the object. change_vector = self._scale.scale(self._getDerivedOrientation().getInverse().rotate(scale_change)) if change_vector.x < 0 and direction == 1: change_vector.setX(-change_vector.x) if change_vector.x > 0 and direction == -1: change_vector.setX(-change_vector.x) if change_vector.y < 0 and direction == 1: change_vector.setY(-change_vector.y) if change_vector.y > 0 and direction == -1: change_vector.setY(-change_vector.y) if change_vector.z < 0 and direction == 1: change_vector.setZ(-change_vector.z) if change_vector.z > 0 and direction == -1: change_vector.setZ(-change_vector.z) self._scale -= self._scale.scale(change_vector) else: raise ValueError("Unknown transform space {0}".format(transform_space)) self._transformChanged() ## Set the local scale value. # # \param scale \type{Vector} The new scale value of the scene node. def setScale(self, scale): if not self._enabled or scale == self._scale: return self._scale = scale self._transformChanged() ## Get the local mirror values. # # \return The mirror values as vector of scaling values. def getMirror(self): return deepcopy(self._mirror) ## Mirror the scene object (and thus its children) in the given directions. # # \param mirror \type{Vector} A vector of three scale values that is used # to mirror the node. # \param transform_space The space relative to which to scale. Can be any # one of the constants in SceneNode::TransformSpace. def mirror(self, mirror, transform_space = TransformSpace.Local): if not self._enabled: return if transform_space == SceneNode.TransformSpace.Local: self._mirror *= mirror elif transform_space == SceneNode.TransformSpace.Parent: self._mirror *= mirror elif transform_space == SceneNode.TransformSpace.World: self._mirror *= mirror else: raise ValueError("Unknown transform space {0}".format(transform_space)) self._transformChanged() ## Set the local mirror values. # # \param mirror \type{Vector} The new mirror values as scale multipliers. def setMirror(self, mirror): if not self._enabled or mirror == self._mirror: return self._mirror = mirror self._transformChanged() ## Get the local position. def getPosition(self): return deepcopy(self._position) ## Get the position of this scene node relative to the world. def getWorldPosition(self): if not self._derived_position: self._updateTransformation() return deepcopy(self._derived_position) ## Translate the scene object (and thus its children) by given amount. # # \param translation \type{Vector} The amount to translate by. # \param transform_space The space relative to which to translate. Can be any one of the constants in SceneNode::TransformSpace. def translate(self, translation, transform_space = TransformSpace.Local): if not self._enabled: return if transform_space == SceneNode.TransformSpace.Local: self._position += self._orientation.rotate(translation) elif transform_space == SceneNode.TransformSpace.Parent: self._position += translation elif transform_space == SceneNode.TransformSpace.World: if self._parent: self._position += (1.0 / self._parent._getDerivedScale()).scale(self._parent._getDerivedOrientation().getInverse().rotate(translation)) else: self._position += translation self._transformChanged() ## Set the local position value. # # \param position The new position value of the SceneNode. def setPosition(self, position): if not self._enabled or position == self._position: return self._position = position self._transformChanged() ## Signal. Emitted whenever the transformation of this object or any child object changes. # \param object The object that caused the change. transformationChanged = Signal() ## Rotate this scene node in such a way that it is looking at target. # # \param target \type{Vector} The target to look at. # \param up \type{Vector} The vector to consider up. Defaults to Vector.Unit_Y, i.e. (0, 1, 0). def lookAt(self, target, up = Vector.Unit_Y): if not self._enabled: return eye = self.getWorldPosition() f = (target - eye).normalize() up.normalize() s = f.cross(up).normalize() u = s.cross(f).normalize() m = Matrix([ [ s.x, u.x, -f.x, 0.0], [ s.y, u.y, -f.y, 0.0], [ s.z, u.z, -f.z, 0.0], [ 0.0, 0.0, 0.0, 1.0] ]) if self._parent: self._orientation = self._parent._getDerivedOrientation() * Quaternion.fromMatrix(m) else: self._orientation = Quaternion.fromMatrix(m) self._transformChanged() ## Can be overridden by child nodes if they need to perform special rendering. # If you need to handle rendering in a special way, for example for tool handles, # you can override this method and render the node. Return True to prevent the # view from rendering any attached mesh data. # # \param renderer The renderer object to use for rendering. # # \return False if the view should render this node, True if we handle our own rendering. def render(self, renderer): return False ## Get whether this SceneNode is enabled, that is, it can be modified in any way. def isEnabled(self): if self._parent != None and self._enabled: return self._parent.isEnabled() else: return self._enabled ## Set whether this SceneNode is enabled. # \param enable True if this object should be enabled, False if not. # \sa isEnabled def setEnabled(self, enable): self._enabled = enable ## Get whether this SceneNode can be selected. # # \note This will return false if isEnabled() returns false. def isSelectable(self): return self._enabled and self._selectable ## Set whether this SceneNode can be selected. # # \param select True if this SceneNode should be selectable, False if not. def setSelectable(self, select): self._selectable = select ## Get the bounding box of this node and its children. # # Note that the AABB is calculated in a separate thread. This method will return an invalid (size 0) AABB # while the calculation happens. def getBoundingBox(self): if self._aabb: return self._aabb if not self._aabb_job: self._resetAABB() return AxisAlignedBox() ## Set whether or not to calculate the bounding box for this node. # # \param calculate True if the bounding box should be calculated, False if not. def setCalculateBoundingBox(self, calculate): self._calculate_aabb = calculate boundingBoxChanged = Signal() ## private: def _getDerivedPosition(self): if not self._derived_position: self._updateTransformation() return self._derived_position def _getDerivedOrientation(self): if not self._derived_orientation: self._updateTransformation() # Sometimes the derived orientation can be None. # I've not been able to locate the cause of this, but this prevents it being an issue. if not self._derived_orientation: self._derived_orientation = Quaternion() return self._derived_orientation def _getDerivedScale(self): if not self._derived_scale: self._updateTransformation() return self._derived_scale def _transformChanged(self): self._resetAABB() self._transformation = None self._world_transformation = None self._derived_position = None self._derived_orientation = None self._derived_scale = None self.transformationChanged.emit(self) for child in self._children: child._transformChanged() def _updateTransformation(self): scale_and_mirror = self._scale * self._mirror self._transformation = Matrix.fromPositionOrientationScale(self._position, self._orientation, scale_and_mirror) if self._parent: parent_orientation = self._parent._getDerivedOrientation() if self._inherit_orientation: self._derived_orientation = parent_orientation * self._orientation else: self._derived_orientation = self._orientation # Sometimes the derived orientation can be None. # I've not been able to locate the cause of this, but this prevents it being an issue. if not self._derived_orientation: self._derived_orientation = Quaternion() parent_scale = self._parent._getDerivedScale() if self._inherit_scale: self._derived_scale = parent_scale.scale(scale_and_mirror) else: self._derived_scale = scale_and_mirror self._derived_position = parent_orientation.rotate(parent_scale.scale(self._position)) self._derived_position += self._parent._getDerivedPosition() self._world_transformation = Matrix.fromPositionOrientationScale(self._derived_position, self._derived_orientation, self._derived_scale) else: self._derived_position = self._position self._derived_orientation = self._orientation self._derived_scale = scale_and_mirror self._world_transformation = self._transformation def _resetAABB(self): if not self._calculate_aabb: return self._aabb = None if self._aabb_job: self._aabb_job.cancel() self._aabb_job = _CalculateAABBJob(self) self._aabb_job.start()
class SceneNode(SignalEmitter): class TransformSpace: Local = 1 Parent = 2 World = 3 def __init__(self, parent=None): super().__init__() # Call super to make multiple inheritence work. self._children = [] self._mesh_data = None self._position = Vector() self._scale = Vector(1.0, 1.0, 1.0) self._orientation = Quaternion() self._transformation = None self._world_transformation = None self._derived_position = None self._derived_orientation = None self._derived_scale = None self._inherit_orientation = True self._inherit_scale = True self._parent = parent self._enabled = True self._selectable = False self._calculate_aabb = True self._aabb = None self._aabb_job = None self._visible = True self._name = "" self._decorators = [] self._bounding_box_mesh = None self.boundingBoxChanged.connect(self.calculateBoundingBoxMesh) self.parentChanged.connect(self._onParentChanged) if parent: parent.addChild(self) def __deepcopy__(self, memo): copy = SceneNode() copy.translate(self.getPosition()) copy.setOrientation(self.getOrientation()) copy.setScale(self.getScale()) copy.setMeshData(deepcopy(self._mesh_data, memo)) copy.setVisible(deepcopy(self._visible, memo)) copy._selectable = deepcopy(self._selectable, memo) for decorator in self._decorators: copy.addDecorator(deepcopy(decorator, memo)) for child in self._children: copy.addChild(deepcopy(child, memo)) self.calculateBoundingBoxMesh() return copy def setCenterPosition(self, center): if self._mesh_data: m = Matrix() m.setByTranslation(-center) self._mesh_data = self._mesh_data.getTransformed(m) self._mesh_data.setCenterPosition(center) for child in self._children: child.setCenterPosition(center) ## \brief Get the parent of this node. If the node has no parent, it is the root node. # \returns SceneNode if it has a parent and None if it's the root node. def getParent(self): return self._parent def getBoundingBoxMesh(self): return self._bounding_box_mesh def calculateBoundingBoxMesh(self): if self._aabb: self._bounding_box_mesh = MeshData() rtf = self._aabb.maximum lbb = self._aabb.minimum self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) #Right - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) #Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) #Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) #Left - Bottom - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) #Left - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) #Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) #Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) #Right - Top - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) #Right - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) #Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) #Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) #Left - Bottom - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) #Left - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) #Right - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) #Right - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) #Right - Top - Back self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) #Right - Top - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) #Right - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) #Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) #Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) #Left - Bottom - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) #Left - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) #Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) #Right - Bottom - Back else: self._resetAABB() def _onParentChanged(self, node): for child in self.getChildren(): child.parentChanged.emit(self) decoratorsChanged = Signal() def addDecorator(self, decorator): decorator.setNode(self) self._decorators.append(decorator) self.decoratorsChanged.emit(self) def getDecorators(self): return self._decorators def getDecorator(self, dec_type): for decorator in self._decorators: if type(decorator) == dec_type: return decorator def removeDecorators(self): self._decorators = [] self.decoratorsChanged.emit(self) def removeDecorator(self, dec_type): for decorator in self._decorators: if type(decorator) == dec_type: self._decorators.remove(decorator) self.decoratorsChanged.emit(self) break def callDecoration(self, function, *args, **kwargs): for decorator in self._decorators: if hasattr(decorator, function): try: return getattr(decorator, function)(*args, **kwargs) except Exception as e: Logger.log("e", "Exception calling decoration %s: %s", str(function), str(e)) return None def hasDecoration(self, function): for decorator in self._decorators: if hasattr(decorator, function): return True return False def getName(self): return self._name def setName(self, name): self._name = name ## How many nodes is this node removed from the root def getDepth(self): if self._parent is None: return 0 return self._parent.getDepth() + 1 ## \brief Set the parent of this object # \param scene_node SceneNode that is the parent of this object. def setParent(self, scene_node): if self._parent: self._parent.removeChild(self) #self._parent = scene_node if scene_node: scene_node.addChild(self) ## Emitted whenever the parent changes. parentChanged = Signal() ## \brief Get the visibility of this node. The parents visibility overrides the visibility. # TODO: Let renderer actually use the visibility to decide wether to render or not. def isVisible(self): if self._parent != None and self._visible: return self._parent.isVisible() else: return self._visible def setVisible(self, visible): self._visible = visible ## \brief Get the (original) mesh data from the scene node/object. # \returns MeshData def getMeshData(self): return self._mesh_data ## \brief Get the transformed mesh data from the scene node/object, based on the transformation of scene nodes wrt root. # \returns MeshData def getMeshDataTransformed(self): #transformed_mesh = deepcopy(self._mesh_data) #transformed_mesh.transform(self.getWorldTransformation()) return self._mesh_data.getTransformed(self.getWorldTransformation()) ## \brief Set the mesh of this node/object # \param mesh_data MeshData object def setMeshData(self, mesh_data): if self._mesh_data: self._mesh_data.dataChanged.disconnect(self._onMeshDataChanged) self._mesh_data = mesh_data if self._mesh_data is not None: self._mesh_data.dataChanged.connect(self._onMeshDataChanged) self._resetAABB() self.meshDataChanged.emit(self) ## Emitted whenever the attached mesh data object changes. meshDataChanged = Signal() def _onMeshDataChanged(self): self.meshDataChanged.emit(self) ## \brief Add a child to this node and set it's parent as this node. # \params scene_node SceneNode to add. def addChild(self, scene_node): if scene_node not in self._children: scene_node.transformationChanged.connect( self.transformationChanged) scene_node.childrenChanged.connect(self.childrenChanged) scene_node.meshDataChanged.connect(self.meshDataChanged) self._children.append(scene_node) self._resetAABB() self.childrenChanged.emit(self) if not scene_node._parent is self: scene_node._parent = self scene_node._transformChanged() scene_node.parentChanged.emit(self) ## \brief remove a single child # \param child Scene node that needs to be removed. def removeChild(self, child): if child not in self._children: return child.transformationChanged.disconnect(self.transformationChanged) child.childrenChanged.disconnect(self.childrenChanged) child.meshDataChanged.disconnect(self.meshDataChanged) self._children.remove(child) child._parent = None child._transformChanged() child.parentChanged.emit(self) self.childrenChanged.emit(self) ## \brief Removes all children and its children's children. def removeAllChildren(self): for child in self._children: child.removeAllChildren() self.removeChild(child) self.childrenChanged.emit(self) ## \brief Get the list of direct children # \returns List of children def getChildren(self): return self._children def hasChildren(self): return True if self._children else False ## \brief Get list of all children (including it's children children children etc.) # \returns list ALl children in this 'tree' def getAllChildren(self): children = [] children.extend(self._children) for child in self._children: children.extend(child.getAllChildren()) return children ## \brief Emitted whenever the list of children of this object or any child object changes. # \param object The object that triggered the change. childrenChanged = Signal() ## \brief Computes and returns the transformation from world to local space. # \returns 4x4 transformation matrix def getWorldTransformation(self): if self._world_transformation is None: self._updateTransformation() return deepcopy(self._world_transformation) ## \brief Returns the local transformation with respect to its parent. (from parent to local) # \retuns transformation 4x4 (homogenous) matrix def getLocalTransformation(self): if self._transformation is None: self._updateTransformation() return deepcopy(self._transformation) ## Get the local orientation value. def getOrientation(self): return deepcopy(self._orientation) ## \brief Rotate the scene object (and thus its children) by given amount # # \param rotation \type{Quaternion} A quaternion indicating the amount of rotation. # \param transform_space The space relative to which to rotate. Can be any one of the constants in SceneNode::TransformSpace. def rotate(self, rotation, transform_space=TransformSpace.Local): if not self._enabled: return if transform_space == SceneNode.TransformSpace.Local: self._orientation = self._orientation * rotation elif transform_space == SceneNode.TransformSpace.Parent: self._orientation = rotation * self._orientation elif transform_space == SceneNode.TransformSpace.World: self._orientation = self._orientation * self._getDerivedOrientation( ).getInverse() * rotation * self._getDerivedOrientation() else: raise ValueError( "Unknown transform space {0}".format(transform_space)) self._orientation.normalize() self._transformChanged() ## Set the local orientation of this scene node. # # \param orientation \type{Quaternion} The new orientation of this scene node. def setOrientation(self, orientation): if not self._enabled or orientation == self._orientation: return self._orientation = orientation self._orientation.normalize() self._transformChanged() ## Get the local scaling value. def getScale(self): return deepcopy(self._scale) ## Scale the scene object (and thus its children) by given amount # # \param scale \type{Vector} A Vector with three scale values # \param transform_space The space relative to which to scale. Can be any one of the constants in SceneNode::TransformSpace. def scale(self, scale, transform_space=TransformSpace.Local): if not self._enabled: return if transform_space == SceneNode.TransformSpace.Local: self._scale = self._scale.scale(scale) elif transform_space == SceneNode.TransformSpace.Parent: raise NotImplementedError() elif transform_space == SceneNode.TransformSpace.World: if self._parent: scale_change = Vector(1, 1, 1) - scale if scale_change.x < 0 or scale_change.y < 0 or scale_change.z < 0: direction = -1 else: direction = 1 # Hackish way to do this, but this seems to correctly scale the object. change_vector = self._scale.scale( self._getDerivedOrientation().getInverse().rotate( scale_change)) if change_vector.x < 0 and direction == 1: change_vector.setX(-change_vector.x) if change_vector.x > 0 and direction == -1: change_vector.setX(-change_vector.x) if change_vector.y < 0 and direction == 1: change_vector.setY(-change_vector.y) if change_vector.y > 0 and direction == -1: change_vector.setY(-change_vector.y) if change_vector.z < 0 and direction == 1: change_vector.setZ(-change_vector.z) if change_vector.z > 0 and direction == -1: change_vector.setZ(-change_vector.z) self._scale -= self._scale.scale(change_vector) else: raise ValueError( "Unknown transform space {0}".format(transform_space)) self._transformChanged() ## Set the local scale value. # # \param scale \type{Vector} The new scale value of the scene node. def setScale(self, scale): if not self._enabled or scale == self._scale: return self._scale = scale self._transformChanged() ## Get the local position. def getPosition(self): return deepcopy(self._position) ## Get the position of this scene node relative to the world. def getWorldPosition(self): if not self._derived_position: self._updateTransformation() return deepcopy(self._derived_position) ## Translate the scene object (and thus its children) by given amount. # # \param translation \type{Vector} The amount to translate by. # \param transform_space The space relative to which to translate. Can be any one of the constants in SceneNode::TransformSpace. def translate(self, translation, transform_space=TransformSpace.Local): if not self._enabled: return if transform_space == SceneNode.TransformSpace.Local: self._position += self._orientation.rotate(translation) elif transform_space == SceneNode.TransformSpace.Parent: self._position += translation elif transform_space == SceneNode.TransformSpace.World: if self._parent: self._position += (1.0 / self._parent._getDerivedScale()).scale( self._parent._getDerivedOrientation( ).getInverse().rotate(translation)) else: self._position += translation self._transformChanged() ## Set the local position value. # # \param position The new position value of the SceneNode. def setPosition(self, position): if not self._enabled or position == self._position: return self._position = position self._transformChanged() ## Signal. Emitted whenever the transformation of this object or any child object changes. # \param object The object that caused the change. transformationChanged = Signal() ## Rotate this scene node in such a way that it is looking at target. # # \param target \type{Vector} The target to look at. # \param up \type{Vector} The vector to consider up. Defaults to Vector.Unit_Y, i.e. (0, 1, 0). def lookAt(self, target, up=Vector.Unit_Y): if not self._enabled: return eye = self.getWorldPosition() f = (target - eye).normalize() up.normalize() s = f.cross(up).normalize() u = s.cross(f).normalize() m = Matrix([[s.x, u.x, -f.x, 0.0], [s.y, u.y, -f.y, 0.0], [s.z, u.z, -f.z, 0.0], [0.0, 0.0, 0.0, 1.0]]) if self._parent: self._orientation = self._parent._getDerivedOrientation( ) * Quaternion.fromMatrix(m) else: self._orientation = Quaternion.fromMatrix(m) self._transformChanged() ## Can be overridden by child nodes if they need to perform special rendering. # If you need to handle rendering in a special way, for example for tool handles, # you can override this method and render the node. Return True to prevent the # view from rendering any attached mesh data. # # \param renderer The renderer object to use for rendering. # # \return False if the view should render this node, True if we handle our own rendering. def render(self, renderer): return False ## Get whether this SceneNode is enabled, that is, it can be modified in any way. def isEnabled(self): if self._parent != None and self._enabled: return self._parent.isEnabled() else: return self._enabled ## Set whether this SceneNode is enabled. # \param enable True if this object should be enabled, False if not. # \sa isEnabled def setEnabled(self, enable): self._enabled = enable ## Get whether this SceneNode can be selected. # # \note This will return false if isEnabled() returns false. def isSelectable(self): return self._enabled and self._selectable ## Set whether this SceneNode can be selected. # # \param select True if this SceneNode should be selectable, False if not. def setSelectable(self, select): self._selectable = select ## Get the bounding box of this node and its children. # # Note that the AABB is calculated in a separate thread. This method will return an invalid (size 0) AABB # while the calculation happens. def getBoundingBox(self): if self._aabb: return self._aabb if not self._aabb_job: self._resetAABB() return AxisAlignedBox() ## Set whether or not to calculate the bounding box for this node. # # \param calculate True if the bounding box should be calculated, False if not. def setCalculateBoundingBox(self, calculate): self._calculate_aabb = calculate boundingBoxChanged = Signal() ## private: def _getDerivedPosition(self): if not self._derived_position: self._updateTransformation() return self._derived_position def _getDerivedOrientation(self): if not self._derived_orientation: self._updateTransformation() # Sometimes the derived orientation can be None. # I've not been able to locate the cause of this, but this prevents it being an issue. if not self._derived_orientation: self._derived_orientation = Quaternion() return self._derived_orientation def _getDerivedScale(self): if not self._derived_scale: self._updateTransformation() return self._derived_scale def _transformChanged(self): self._resetAABB() self._transformation = None self._world_transformation = None self._derived_position = None self._derived_orientation = None self._derived_scale = None self.transformationChanged.emit(self) for child in self._children: child._transformChanged() def _updateTransformation(self): self._transformation = Matrix.fromPositionOrientationScale( self._position, self._orientation, self._scale) if self._parent: parent_orientation = self._parent._getDerivedOrientation() if self._inherit_orientation: self._derived_orientation = parent_orientation * self._orientation else: self._derived_orientation = self._orientation # Sometimes the derived orientation can be None. # I've not been able to locate the cause of this, but this prevents it being an issue. if not self._derived_orientation: self._derived_orientation = Quaternion() parent_scale = self._parent._getDerivedScale() if self._inherit_scale: self._derived_scale = parent_scale.scale(self._scale) else: self._derived_scale = self._scale self._derived_position = parent_orientation.rotate( parent_scale.scale(self._position)) self._derived_position += self._parent._getDerivedPosition() self._world_transformation = Matrix.fromPositionOrientationScale( self._derived_position, self._derived_orientation, self._derived_scale) else: self._derived_position = self._position self._derived_orientation = self._orientation self._derived_scale = self._scale self._world_transformation = self._transformation def _resetAABB(self): if not self._calculate_aabb: return self._aabb = None if self._aabb_job: self._aabb_job.cancel() self._aabb_job = _CalculateAABBJob(self) self._aabb_job.start()
class MeshBuilder: def __init__(self): self._mesh_data = MeshData() def getData(self): return self._mesh_data def addLine(self, v0, v1, **kwargs): self._mesh_data.addVertex(v0.x, v0.y, v0.z) self._mesh_data.addVertex(v1.x, v1.y, v1.z) color = kwargs.get("color", None) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addFace(self, v0, v1, v2, **kwargs): normal = kwargs.get("normal", None) if normal: self._mesh_data.addFaceWithNormals( v0.x, v0.y, v0.z, normal.x, normal.y, normal.z, v1.x, v1.y, v1.z, normal.x, normal.y, normal.z, v2.x, v2.y, v2.z, normal.x, normal.y, normal.z ) else: self._mesh_data.addFace(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z) color = kwargs.get("color", None) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 3, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addQuad(self, v0, v1, v2, v3, **kwargs): self.addFace(v0, v2, v1, color = kwargs.get("color"), normal = kwargs.get("normal") ) self.addFace(v0, v3, v2, color = kwargs.get("color"), normal = kwargs.get("normal") ) def addCube(self, **kwargs): width = kwargs["width"] height = kwargs["height"] depth = kwargs["depth"] center = kwargs.get("center", Vector(0, 0, 0)) minW = -width / 2 + center.x maxW = width / 2 + center.x minH = -height / 2 + center.y maxH = height / 2 + center.y minD = -depth / 2 + center.z maxD = depth / 2 + center.z start = self._mesh_data.getVertexCount() verts = numpy.asarray([ [minW, minH, maxD], [minW, maxH, maxD], [maxW, maxH, maxD], [maxW, minH, maxD], [minW, minH, minD], [minW, maxH, minD], [maxW, maxH, minD], [maxW, minH, minD], ], dtype=numpy.float32) self._mesh_data.addVertices(verts) indices = numpy.asarray([ [start, start + 2, start + 1], [start, start + 3, start + 2], [start + 3, start + 7, start + 6], [start + 3, start + 6, start + 2], [start + 7, start + 5, start + 6], [start + 7, start + 4, start + 5], [start + 4, start + 1, start + 5], [start + 4, start + 0, start + 1], [start + 1, start + 6, start + 5], [start + 1, start + 2, start + 6], [start + 0, start + 7, start + 3], [start + 0, start + 4, start + 7] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) color = kwargs.get("color", None) if color: vertex_count = self._mesh_data.getVertexCount() for i in range(1, 9): self._mesh_data.setVertexColor(vertex_count - i, color) def addArc(self, **kwargs): radius = kwargs["radius"] axis = kwargs["axis"] max_angle = kwargs.get("angle", math.pi * 2) center = kwargs.get("center", Vector(0, 0, 0)) sections = kwargs.get("sections", 32) color = kwargs.get("color", None) if axis == Vector.Unit_Y: start = axis.cross(Vector.Unit_X).normalize() * radius else: start = axis.cross(Vector.Unit_Y).normalize() * radius angle_increment = max_angle / sections angle = 0 point = start + center m = Matrix() while angle <= max_angle: self._mesh_data.addVertex(point.x, point.y, point.z) angle += angle_increment m.setByRotationAxis(angle, axis) point = start.multiply(m) + center self._mesh_data.addVertex(point.x, point.y, point.z) if color: self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) def addDonut(self, **kwargs): inner_radius = kwargs["inner_radius"] outer_radius = kwargs["outer_radius"] width = kwargs["width"] center = kwargs.get("center", Vector(0, 0, 0)) sections = kwargs.get("sections", 32) color = kwargs.get("color", None) angle = kwargs.get("angle", 0) axis = kwargs.get("axis", Vector.Unit_Y) vertices = [] indices = [] colors = [] start = self._mesh_data.getVertexCount() for i in range(sections): v1 = start + i * 3 v2 = v1 + 1 v3 = v1 + 2 v4 = v1 + 3 v5 = v1 + 4 v6 = v1 + 5 if i+1 >= sections: # connect the end to the start v4 = start v5 = start + 1 v6 = start + 2 theta = i * math.pi / (sections / 2) c = math.cos(theta) s = math.sin(theta) vertices.append( [inner_radius * c, inner_radius * s, 0] ) vertices.append( [outer_radius * c, outer_radius * s, width] ) vertices.append( [outer_radius * c, outer_radius * s, -width] ) indices.append( [v1, v4, v5] ) indices.append( [v2, v1, v5] ) indices.append( [v2, v5, v6] ) indices.append( [v3, v2, v6] ) indices.append( [v3, v6, v4] ) indices.append( [v1, v3, v4] ) if color: colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) matrix = Matrix() matrix.setByRotationAxis(angle, axis) vertices = numpy.asarray(vertices, dtype = numpy.float32) vertices = vertices.dot(matrix.getData()[0:3,0:3]) vertices[:] += center.getData() self._mesh_data.addVertices(vertices) self._mesh_data.addIndices(numpy.asarray(indices, dtype = numpy.int32)) self._mesh_data.addColors(numpy.asarray(colors, dtype = numpy.float32)) def addPyramid(self, **kwargs): width = kwargs["width"] height = kwargs["height"] depth = kwargs["depth"] angle = math.radians(kwargs.get("angle", 0)) axis = kwargs.get("axis", Vector.Unit_Y) center = kwargs.get("center", Vector(0, 0, 0)) minW = -width / 2 maxW = width / 2 minD = -depth / 2 maxD = depth / 2 start = self._mesh_data.getVertexCount() matrix = Matrix() matrix.setByRotationAxis(angle, axis) verts = numpy.asarray([ [minW, 0, maxD], [maxW, 0, maxD], [minW, 0, minD], [maxW, 0, minD], [0, height, 0] ], dtype=numpy.float32) verts = verts.dot(matrix.getData()[0:3,0:3]) verts[:] += center.getData() self._mesh_data.addVertices(verts) indices = numpy.asarray([ [start, start + 1, start + 4], [start + 1, start + 3, start + 4], [start + 3, start + 2, start + 4], [start + 2, start, start + 4], [start, start + 3, start + 1], [start, start + 2, start + 3] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) color = kwargs.get("color", None) if color: vertex_count = self._mesh_data.getVertexCount() for i in range(1, 6): self._mesh_data.setVertexColor(vertex_count - i, color)
class SceneNode(): class TransformSpace: Local = 1 Parent = 2 World = 3 ## Construct a scene node. # \param parent The parent of this node (if any). Only a root node should have None as a parent. # \param kwargs Keyword arguments. # Possible keywords: # - visible \type{bool} Is the SceneNode (and thus, all it's children) visible? Defaults to True # - name \type{string} Name of the SceneNode. Defaults to empty string. def __init__(self, parent=None, **kwargs): super().__init__() # Call super to make multiple inheritance work. self._children = [] self._mesh_data = None # Local transformation (from parent to local) self._transformation = Matrix() # Convenience "components" of the transformation self._position = Vector() self._scale = Vector(1.0, 1.0, 1.0) self._shear = Vector(0.0, 0.0, 0.0) self._orientation = Quaternion() # World transformation (from root to local) self._world_transformation = Matrix() # Convenience "components" of the world_transformation self._derived_position = Vector() self._derived_orientation = Quaternion() self._derived_scale = Vector() self._parent = parent self._enabled = True self._selectable = False self._calculate_aabb = True self._aabb = None self._original_aabb = None self._aabb_job = None self._visible = kwargs.get("visible", True) self._name = kwargs.get("name", "") self._decorators = [] ## Signals self.boundingBoxChanged.connect(self.calculateBoundingBoxMesh) self.parentChanged.connect(self._onParentChanged) if parent: parent.addChild(self) def __deepcopy__(self, memo): copy = SceneNode() copy.translate(self.getPosition()) copy.setOrientation(self.getOrientation()) copy.setScale(self.getScale()) copy.setMeshData(deepcopy(self._mesh_data, memo)) copy.setVisible(deepcopy(self._visible, memo)) copy._selectable = deepcopy(self._selectable, memo) for decorator in self._decorators: copy.addDecorator(deepcopy(decorator, memo)) for child in self._children: copy.addChild(deepcopy(child, memo)) self.calculateBoundingBoxMesh() return copy ## Set the center position of this node. # This is used to modify it's mesh data (and it's children) in such a way that they are centered. # In most cases this means that we use the center of mass as center (which most objects don't use) def setCenterPosition(self, center): if self._mesh_data: m = Matrix() m.setByTranslation(-center) self._mesh_data = self._mesh_data.getTransformed(m) self._mesh_data.setCenterPosition(center) for child in self._children: child.setCenterPosition(center) ## \brief Get the parent of this node. If the node has no parent, it is the root node. # \returns SceneNode if it has a parent and None if it's the root node. def getParent(self): return self._parent ## Get the MeshData of the bounding box # \returns \type{MeshData} Bounding box mesh. def getBoundingBoxMesh(self): return self._bounding_box_mesh ## (re)Calculate the bounding box mesh. def calculateBoundingBoxMesh(self): if self._aabb: self._bounding_box_mesh = MeshData() rtf = self._aabb.maximum lbb = self._aabb.minimum self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) # Right - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) # Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) # Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) # Left - Bottom - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) # Left - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) # Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) # Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) # Right - Top - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) # Right - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) # Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) # Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) # Left - Bottom - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) # Left - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) # Right - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) # Right - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) # Right - Top - Back self._bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z) # Right - Top - Front self._bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z) # Right - Top - Back self._bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z) # Left - Top - Front self._bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z) # Left - Top - Back self._bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z) # Left - Bottom - Front self._bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z) # Left - Bottom - Back self._bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z) # Right - Bottom - Front self._bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z) # Right - Bottom - Back else: self._resetAABB() ## Handler for the ParentChanged signal # \param node Node from which this event was triggered. def _onParentChanged(self, node): for child in self.getChildren(): child.parentChanged.emit(self) ## Signal for when a \type{SceneNodeDecorator} is added / removed. decoratorsChanged = Signal() ## Add a SceneNodeDecorator to this SceneNode. # \param \type{SceneNodeDecorator} decorator The decorator to add. # TODO: GetDecorator seems to imply that a scne node can only have a single decorator of a type, but we never enforce this. def addDecorator(self, decorator): decorator.setNode(self) self._decorators.append(decorator) self.decoratorsChanged.emit(self) ## Get all SceneNodeDecorators that decorate this SceneNode. # \return list of all SceneNodeDecorators. def getDecorators(self): return self._decorators ## Get SceneNodeDecorators by type. # \param dec_type type of decorator to return. def getDecorator(self, dec_type): for decorator in self._decorators: if type(decorator) == dec_type: return decorator ## Remove all decorators def removeDecorators(self): self._decorators = [] self.decoratorsChanged.emit(self) ## Remove decorator by type. # \param dec_type type of the decorator to remove. def removeDecorator(self, dec_type): for decorator in self._decorators: if type(decorator) == dec_type: self._decorators.remove(decorator) self.decoratorsChanged.emit(self) break ## Call a decoration of this SceneNode. # SceneNodeDecorators add Decorations, which are callable functions. # \param \type{string} function The function to be called. # \param *args # \param **kwargs def callDecoration(self, function, *args, **kwargs): for decorator in self._decorators: if hasattr(decorator, function): try: return getattr(decorator, function)(*args, **kwargs) except Exception as e: Logger.log("e", "Exception calling decoration %s: %s", str(function), str(e)) return None ## Does this SceneNode have a certain Decoration (as defined by a Decorator) # \param \type{string} function the function to check for. def hasDecoration(self, function): for decorator in self._decorators: if hasattr(decorator, function): return True return False def getName(self): return self._name def setName(self, name): self._name = name ## How many nodes is this node removed from the root? # \return |tupe{int} Steps from root (0 means it -is- the root). def getDepth(self): if self._parent is None: return 0 return self._parent.getDepth() + 1 ## \brief Set the parent of this object # \param scene_node SceneNode that is the parent of this object. def setParent(self, scene_node): if self._parent: self._parent.removeChild(self) if scene_node: scene_node.addChild(self) ## Emitted whenever the parent changes. parentChanged = Signal() ## \brief Get the visibility of this node. The parents visibility overrides the visibility. # TODO: Let renderer actually use the visibility to decide whether to render or not. def isVisible(self): if self._parent != None and self._visible: return self._parent.isVisible() else: return self._visible ## Set the visibility of this SceneNode. def setVisible(self, visible): self._visible = visible ## \brief Get the (original) mesh data from the scene node/object. # \returns MeshData def getMeshData(self): return self._mesh_data ## \brief Get the transformed mesh data from the scene node/object, based on the transformation of scene nodes wrt root. # \returns MeshData def getMeshDataTransformed(self): return self._mesh_data.getTransformed(self.getWorldTransformation()) ## \brief Set the mesh of this node/object # \param mesh_data MeshData object def setMeshData(self, mesh_data): if self._mesh_data: self._mesh_data.dataChanged.disconnect(self._onMeshDataChanged) self._mesh_data = mesh_data if self._mesh_data is not None: self._mesh_data.dataChanged.connect(self._onMeshDataChanged) self._resetAABB() self.meshDataChanged.emit(self) ## Emitted whenever the attached mesh data object changes. meshDataChanged = Signal() def _onMeshDataChanged(self): self.meshDataChanged.emit(self) ## \brief Add a child to this node and set it's parent as this node. # \params scene_node SceneNode to add. def addChild(self, scene_node): if scene_node not in self._children: scene_node.transformationChanged.connect( self.transformationChanged) scene_node.childrenChanged.connect(self.childrenChanged) scene_node.meshDataChanged.connect(self.meshDataChanged) self._children.append(scene_node) self._resetAABB() self.childrenChanged.emit(self) if not scene_node._parent is self: scene_node._parent = self scene_node._transformChanged() scene_node.parentChanged.emit(self) ## \brief remove a single child # \param child Scene node that needs to be removed. def removeChild(self, child): if child not in self._children: return child.transformationChanged.disconnect(self.transformationChanged) child.childrenChanged.disconnect(self.childrenChanged) child.meshDataChanged.disconnect(self.meshDataChanged) self._children.remove(child) child._parent = None child._transformChanged() child.parentChanged.emit(self) self.childrenChanged.emit(self) ## \brief Removes all children and its children's children. def removeAllChildren(self): for child in self._children: child.removeAllChildren() self.removeChild(child) self.childrenChanged.emit(self) ## \brief Get the list of direct children # \returns List of children def getChildren(self): return self._children def hasChildren(self): return True if self._children else False ## \brief Get list of all children (including it's children children children etc.) # \returns list ALl children in this 'tree' def getAllChildren(self): children = [] children.extend(self._children) for child in self._children: children.extend(child.getAllChildren()) return children ## \brief Emitted whenever the list of children of this object or any child object changes. # \param object The object that triggered the change. childrenChanged = Signal() ## \brief Computes and returns the transformation from world to local space. # \returns 4x4 transformation matrix def getWorldTransformation(self): if self._world_transformation is None: self._updateTransformation() return deepcopy(self._world_transformation) ## \brief Returns the local transformation with respect to its parent. (from parent to local) # \retuns transformation 4x4 (homogenous) matrix def getLocalTransformation(self): if self._transformation is None: self._updateTransformation() return deepcopy(self._transformation) def setTransformation(self, transformation): self._transformation = transformation self._transformChanged() ## Get the local orientation value. def getOrientation(self): return deepcopy(self._orientation) def getWorldOrientation(self): return deepcopy(self._derived_orientation) ## \brief Rotate the scene object (and thus its children) by given amount # # \param rotation \type{Quaternion} A quaternion indicating the amount of rotation. # \param transform_space The space relative to which to rotate. Can be any one of the constants in SceneNode::TransformSpace. def rotate(self, rotation, transform_space=TransformSpace.Local): if not self._enabled: return orientation_matrix = rotation.toMatrix() if transform_space == SceneNode.TransformSpace.Local: self._transformation.multiply(orientation_matrix) elif transform_space == SceneNode.TransformSpace.Parent: self._transformation.preMultiply(orientation_matrix) elif transform_space == SceneNode.TransformSpace.World: self._transformation.multiply( self._world_transformation.getInverse()) self._transformation.multiply(orientation_matrix) self._transformation.multiply(self._world_transformation) self._transformChanged() ## Set the local orientation of this scene node. # # \param orientation \type{Quaternion} The new orientation of this scene node. # \param transform_space The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace. def setOrientation(self, orientation, transform_space=TransformSpace.Local): if not self._enabled or orientation == self._orientation: return new_transform_matrix = Matrix() if transform_space == SceneNode.TransformSpace.Local: orientation_matrix = orientation.toMatrix() if transform_space == SceneNode.TransformSpace.World: if self.getWorldOrientation() == orientation: return new_orientation = orientation * ( self.getWorldOrientation() * self._orientation.getInverse()).getInverse() orientation_matrix = new_orientation.toMatrix() euler_angles = orientation_matrix.getEuler() new_transform_matrix.compose(scale=self._scale, angles=euler_angles, translate=self._position, shear=self._shear) self._transformation = new_transform_matrix self._transformChanged() ## Get the local scaling value. def getScale(self): return deepcopy(self._scale) def getWorldScale(self): return deepcopy(self._derived_scale) ## Scale the scene object (and thus its children) by given amount # # \param scale \type{Vector} A Vector with three scale values # \param transform_space The space relative to which to scale. Can be any one of the constants in SceneNode::TransformSpace. def scale(self, scale, transform_space=TransformSpace.Local): if not self._enabled: return scale_matrix = Matrix() scale_matrix.setByScaleVector(scale) if transform_space == SceneNode.TransformSpace.Local: self._transformation.multiply(scale_matrix) elif transform_space == SceneNode.TransformSpace.Parent: self._transformation.preMultiply(scale_matrix) elif transform_space == SceneNode.TransformSpace.World: self._transformation.multiply( self._world_transformation.getInverse()) self._transformation.multiply(scale_matrix) self._transformation.multiply(self._world_transformation) self._transformChanged() ## Set the local scale value. # # \param scale \type{Vector} The new scale value of the scene node. # \param transform_space The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace. def setScale(self, scale, transform_space=TransformSpace.Local): if not self._enabled or scale == self._scale: return if transform_space == SceneNode.TransformSpace.Local: self.scale(scale / self._scale, SceneNode.TransformSpace.Local) return if transform_space == SceneNode.TransformSpace.World: if self.getWorldScale() == scale: return self.scale(scale / self._scale, SceneNode.TransformSpace.World) ## Get the local position. def getPosition(self): return deepcopy(self._position) ## Get the position of this scene node relative to the world. def getWorldPosition(self): return deepcopy(self._derived_position) ## Translate the scene object (and thus its children) by given amount. # # \param translation \type{Vector} The amount to translate by. # \param transform_space The space relative to which to translate. Can be any one of the constants in SceneNode::TransformSpace. def translate(self, translation, transform_space=TransformSpace.Local): if not self._enabled: return translation_matrix = Matrix() translation_matrix.setByTranslation(translation) if transform_space == SceneNode.TransformSpace.Local: self._transformation.multiply(translation_matrix) elif transform_space == SceneNode.TransformSpace.Parent: self._transformation.preMultiply(translation_matrix) elif transform_space == SceneNode.TransformSpace.World: self._transformation.multiply( self._world_transformation.getInverse()) self._transformation.multiply(translation_matrix) self._transformation.multiply(self._world_transformation) self._transformChanged() ## Set the local position value. # # \param position The new position value of the SceneNode. # \param transform_space The space relative to which to rotate. Can be Local or World from SceneNode::TransformSpace. def setPosition(self, position, transform_space=TransformSpace.Local): if not self._enabled or position == self._position: return if transform_space == SceneNode.TransformSpace.Local: self.translate(position - self._position, SceneNode.TransformSpace.Parent) if transform_space == SceneNode.TransformSpace.World: if self.getWorldPosition() == position: return self.translate(position - self._position, SceneNode.TransformSpace.World) ## Signal. Emitted whenever the transformation of this object or any child object changes. # \param object The object that caused the change. transformationChanged = Signal() ## Rotate this scene node in such a way that it is looking at target. # # \param target \type{Vector} The target to look at. # \param up \type{Vector} The vector to consider up. Defaults to Vector.Unit_Y, i.e. (0, 1, 0). def lookAt(self, target, up=Vector.Unit_Y): if not self._enabled: return eye = self.getWorldPosition() f = (target - eye).normalize() up.normalize() s = f.cross(up).normalize() u = s.cross(f).normalize() m = Matrix([[s.x, u.x, -f.x, 0.0], [s.y, u.y, -f.y, 0.0], [s.z, u.z, -f.z, 0.0], [0.0, 0.0, 0.0, 1.0]]) self.setOrientation(Quaternion.fromMatrix(m)) ## Can be overridden by child nodes if they need to perform special rendering. # If you need to handle rendering in a special way, for example for tool handles, # you can override this method and render the node. Return True to prevent the # view from rendering any attached mesh data. # # \param renderer The renderer object to use for rendering. # # \return False if the view should render this node, True if we handle our own rendering. def render(self, renderer): return False ## Get whether this SceneNode is enabled, that is, it can be modified in any way. def isEnabled(self): if self._parent != None and self._enabled: return self._parent.isEnabled() else: return self._enabled ## Set whether this SceneNode is enabled. # \param enable True if this object should be enabled, False if not. # \sa isEnabled def setEnabled(self, enable): self._enabled = enable ## Get whether this SceneNode can be selected. # # \note This will return false if isEnabled() returns false. def isSelectable(self): return self._enabled and self._selectable ## Set whether this SceneNode can be selected. # # \param select True if this SceneNode should be selectable, False if not. def setSelectable(self, select): self._selectable = select ## Get the bounding box of this node and its children. # # Note that the AABB is calculated in a separate thread. This method will return an invalid (size 0) AABB # while the calculation happens. def getBoundingBox(self): if self._aabb: return self._aabb if not self._aabb_job: self._resetAABB() return AxisAlignedBox() ## Get the bounding box of this node and its children. Without taking any transformation into account def getOriginalBoundingBox(self): if self._original_aabb: return self._original_aabb if not self._aabb_job: self._resetAABB() return AxisAlignedBox() ## Set whether or not to calculate the bounding box for this node. # # \param calculate True if the bounding box should be calculated, False if not. def setCalculateBoundingBox(self, calculate): self._calculate_aabb = calculate boundingBoxChanged = Signal() ## private: def _transformChanged(self): self._updateTransformation() self._resetAABB() self.transformationChanged.emit(self) for child in self._children: child._transformChanged() def _updateTransformation(self): scale, shear, euler_angles, translation = self._transformation.decompose( ) self._position = translation self._scale = scale self._shear = shear orientation = Quaternion() euler_angle_matrix = Matrix() euler_angle_matrix.setByEuler(euler_angles.x, euler_angles.y, euler_angles.z) orientation.setByMatrix(euler_angle_matrix) self._orientation = orientation if self._parent: self._world_transformation = self._parent.getWorldTransformation( ).multiply(self._transformation, copy=True) else: self._world_transformation = self._transformation world_scale, world_shear, world_euler_angles, world_translation = self._world_transformation.decompose( ) self._derived_position = world_translation self._derived_scale = world_scale world_euler_angle_matrix = Matrix() world_euler_angle_matrix.setByEuler(world_euler_angles.x, world_euler_angles.y, world_euler_angles.z) self._derived_orientation.setByMatrix(world_euler_angle_matrix) world_scale, world_shear, world_euler_angles, world_translation = self._world_transformation.decompose( ) def _resetAABB(self): if not self._calculate_aabb: return self._aabb = None if self._aabb_job: self._aabb_job.cancel() self._aabb_job = _CalculateAABBJob(self) self._aabb_job.start()
def _rebuild(self): lines = MeshData() offset = 0 if self.YAxis in self._enabled_axis: lines.addVertex(0, 0, 0) lines.addVertex(0, 20, 0) lines.setVertexColor(offset, ToolHandle.YAxisColor) lines.setVertexColor(offset + 1, ToolHandle.YAxisColor) offset += 2 if self.XAxis in self._enabled_axis: lines.addVertex(0, 0, 0) lines.addVertex(20, 0, 0) lines.setVertexColor(offset, ToolHandle.XAxisColor) lines.setVertexColor(offset + 1, ToolHandle.XAxisColor) offset += 2 if self.ZAxis in self._enabled_axis: lines.addVertex(0, 0, 0) lines.addVertex(0, 0, 20) lines.setVertexColor(offset, ToolHandle.ZAxisColor) lines.setVertexColor(offset + 1, ToolHandle.ZAxisColor) offset += 2 self.setLineMesh(lines) mb = MeshBuilder() if self.YAxis in self._enabled_axis: mb.addPyramid( width = 2, height = 4, depth = 2, center = Vector(0, 20, 0), color = ToolHandle.YAxisColor ) if self.XAxis in self._enabled_axis: mb.addPyramid( width = 2, height = 4, depth = 2, center = Vector(20, 0, 0), color = ToolHandle.XAxisColor, axis = Vector.Unit_Z, angle = 90 ) if self.ZAxis in self._enabled_axis: mb.addPyramid( width = 2, height = 4, depth = 2, center = Vector(0, 0, 20), color = ToolHandle.ZAxisColor, axis = Vector.Unit_X, angle = -90 ) self.setSolidMesh(mb.getData()) mb = MeshBuilder() if self.YAxis in self._enabled_axis: mb.addCube( width = 4, height = 20, depth = 4, center = Vector(0, 10, 0), color = ToolHandle.YAxisColor ) mb.addPyramid( width = 4, height = 8, depth = 4, center = Vector(0, 20, 0), color = ToolHandle.YAxisColor ) if self.XAxis in self._enabled_axis: mb.addCube( width = 20, height = 4, depth = 4, center = Vector(10, 0, 0), color = ToolHandle.XAxisColor ) mb.addPyramid( width = 4, height = 8, depth = 4, center = Vector(20, 0, 0), color = ToolHandle.XAxisColor, axis = Vector.Unit_Z, angle = 90 ) if self.ZAxis in self._enabled_axis: mb.addCube( width = 4, height = 4, depth = 20, center = Vector(0, 0, 10), color = ToolHandle.ZAxisColor ) mb.addPyramid( width = 4, height = 8, depth = 4, center = Vector(0, 0, 20), color = ToolHandle.ZAxisColor, axis = Vector.Unit_X, angle = -90 ) self.setSelectionMesh(mb.getData())
class MeshBuilder: ## Creates a new MeshBuilder with an empty mesh. def __init__(self): self._mesh_data = MeshData() ## Gets the mesh that was built by this MeshBuilder. # # Note that this gets a reference to the mesh. Adding more primitives to # the MeshBuilder will cause the mesh returned by this function to change # as well. # # \return A mesh with all the primitives added to this MeshBuilder. def getData(self): return self._mesh_data ## Adds a 3-dimensional line to the mesh of this mesh builder. # # \param v0 One endpoint of the line to add. # \param v1 The other endpoint of the line to add. # \param color (Optional) The colour of the line, if any. If no colour is # provided, the colour is determined by the shader. def addLine(self, v0, v1, color = None): self._mesh_data.addVertex(v0.x, v0.y, v0.z) self._mesh_data.addVertex(v1.x, v1.y, v1.z) if color: #Add colours to the vertices, if we have them. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Adds a triangle to the mesh of this mesh builder. # # \param v0 The first corner of the triangle. # \param v1 The second corner of the triangle. # \param v2 The third corner of the triangle. # \param normal (Optional) The normal vector for the triangle. If no # normal vector is provided, it will be calculated automatically. # \param color (Optional) The colour for the triangle. If no colour is # provided, the colour is determined by the shader. def addFace(self, v0, v1, v2, normal = None, color = None): if normal: self._mesh_data.addFaceWithNormals( v0.x, v0.y, v0.z, normal.x, normal.y, normal.z, v1.x, v1.y, v1.z, normal.x, normal.y, normal.z, v2.x, v2.y, v2.z, normal.x, normal.y, normal.z ) else: self._mesh_data.addFace(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z) #Computes the normal by itself. if color: #Add colours to the vertices if we have them. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 3, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Add a quadrilateral to the mesh of this mesh builder. # # The quadrilateral will be constructed as two triangles. v0 and v2 are # the two vertices across the diagonal of the quadrilateral. # # \param v0 The first corner of the quadrilateral. # \param v1 The second corner of the quadrilateral. # \param v2 The third corner of the quadrilateral. # \param v3 The fourth corner of the quadrilateral. # \param normal (Optional) The normal vector for the quadrilateral. Both # triangles will get the same normal vector, if provided. If no normal # vector is provided, the normal vectors for both triangles are computed # automatically. # \param color (Optional) The colour for the quadrilateral. If no colour # is provided, the colour is determined by the shader. def addQuad(self, v0, v1, v2, v3, normal = None, color = None): self.addFace(v0, v2, v1, color = color, normal = normal ) self.addFace(v0, v3, v2, #v0 and v2 are shared with the other triangle! color = color, normal = normal ) ## Add a rectangular cuboid to the mesh of this mesh builder. # # A rectangular cuboid is a square block with arbitrary width, height and # depth. # # \param width The size of the rectangular cuboid in the X dimension. # \param height The size of the rectangular cuboid in the Y dimension. # \param depth The size of the rectangular cuboid in the Z dimension. # \param center (Optional) The position of the centre of the rectangular # cuboid in space. If not provided, the cuboid is placed at the coordinate # origin. # \param color (Optional) The colour for the rectangular cuboid. If no # colour is provided, the colour is determined by the shader. def addCube(self, width, height, depth, center = Vector(0, 0, 0), color = None): #Compute the actual positions of the planes. minW = -width / 2 + center.x maxW = width / 2 + center.x minH = -height / 2 + center.y maxH = height / 2 + center.y minD = -depth / 2 + center.z maxD = depth / 2 + center.z start = self._mesh_data.getVertexCount() verts = numpy.asarray([ #All 8 corners. [minW, minH, maxD], [minW, maxH, maxD], [maxW, maxH, maxD], [maxW, minH, maxD], [minW, minH, minD], [minW, maxH, minD], [maxW, maxH, minD], [maxW, minH, minD], ], dtype=numpy.float32) self._mesh_data.addVertices(verts) indices = numpy.asarray([ #All 6 quads (12 triangles). [start, start + 2, start + 1], [start, start + 3, start + 2], [start + 3, start + 7, start + 6], [start + 3, start + 6, start + 2], [start + 7, start + 5, start + 6], [start + 7, start + 4, start + 5], [start + 4, start + 1, start + 5], [start + 4, start + 0, start + 1], [start + 1, start + 6, start + 5], [start + 1, start + 2, start + 6], [start + 0, start + 7, start + 3], [start + 0, start + 4, start + 7] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) if color: #If we have a colour, add a colour to all of the vertices. vertex_count = self._mesh_data.getVertexCount() for i in range(1, 9): self._mesh_data.setVertexColor(vertex_count - i, color) ## Add an arc to the mesh of this mesh builder. # # An arc is a curve that is also a segment of a circle. # # \param radius The radius of the circle this arc is a segment of. # \param axis The axis perpendicular to the plane on which the arc lies. # \param angle (Optional) The length of the arc, in radians. If not # provided, the entire circle is used (2 pi). # \param center (Optional) The position of the centre of the arc in space. # If no position is provided, the arc is centred around the coordinate # origin. # \param sections (Optional) The resolution of the arc. The arc is # approximated by this number of line segments. # \param color (Optional) The colour for the arc. If no colour is # provided, the colour is determined by the shader. def addArc(self, radius, axis, angle = math.pi * 2, center = Vector(0, 0, 0), sections = 32, color = None): #We'll compute the vertices of the arc by computing an initial point and #rotating the initial point with a rotation matrix. if axis == Vector.Unit_Y: start = axis.cross(Vector.Unit_X).normalize() * radius else: start = axis.cross(Vector.Unit_Y).normalize() * radius angle_increment = angle / sections current_angle = 0 point = start + center m = Matrix() while current_angle <= angle: #Add each of the vertices. self._mesh_data.addVertex(point.x, point.y, point.z) current_angle += angle_increment m.setByRotationAxis(current_angle, axis) point = start.multiply(m) + center #Get the next vertex by rotating the start position with a matrix. self._mesh_data.addVertex(point.x, point.y, point.z) if color: #If we have a colour, add that colour to the new vertex. self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 2, color) self._mesh_data.setVertexColor(self._mesh_data.getVertexCount() - 1, color) ## Adds a torus to the mesh of this mesh builder. # # The torus is the shape of a doughnut. This doughnut is delicious and # moist, but not very healthy. # # \param inner_radius The radius of the hole inside the torus. Must be # smaller than outer_radius. # \param outer_radius The radius of the outside of the torus. Must be # larger than inner_radius. # \param width The radius of the torus in perpendicular direction to its # perimeter. This is the "thickness". # \param center (Optional) The position of the centre of the torus. If no # position is provided, the torus will be centred around the coordinate # origin. # \param sections (Optional) The resolution of the torus in the # circumference. The resolution of the intersection of the torus cannot be # changed. # \param color (Optional) The colour of the torus. If no colour is # provided, a colour will be determined by the shader. # \param angle (Optional) An angle of rotation to rotate the torus by, in # radians. # \param axis (Optional) An axis of rotation to rotate the torus around. # If no axis is provided and the angle of rotation is nonzero, the torus # will be rotated around the Y-axis. def addDonut(self, inner_radius, outer_radius, width, center = Vector(0, 0, 0), sections = 32, color = None, angle = 0, axis = Vector.Unit_Y): vertices = [] indices = [] colors = [] start = self._mesh_data.getVertexCount() #Starting index. for i in range(sections): v1 = start + i * 3 #Indices for each of the vertices we'll add for this section. v2 = v1 + 1 v3 = v1 + 2 v4 = v1 + 3 v5 = v1 + 4 v6 = v1 + 5 if i+1 >= sections: # connect the end to the start v4 = start v5 = start + 1 v6 = start + 2 theta = i * math.pi / (sections / 2) #Angle of this piece around torus perimeter. c = math.cos(theta) #X-coordinate around torus perimeter. s = math.sin(theta) #Y-coordinate around torus perimeter. #One vertex on the inside perimeter, two on the outside perimiter (up and down). vertices.append( [inner_radius * c, inner_radius * s, 0] ) vertices.append( [outer_radius * c, outer_radius * s, width] ) vertices.append( [outer_radius * c, outer_radius * s, -width] ) #Connect the vertices to the next segment. indices.append( [v1, v4, v5] ) indices.append( [v2, v1, v5] ) indices.append( [v2, v5, v6] ) indices.append( [v3, v2, v6] ) indices.append( [v3, v6, v4] ) indices.append( [v1, v3, v4] ) if color: #If we have a colour, add it to the vertices. colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) colors.append( [color.r, color.g, color.b, color.a] ) #Rotate the resulting torus around the specified axis. matrix = Matrix() matrix.setByRotationAxis(angle, axis) vertices = numpy.asarray(vertices, dtype = numpy.float32) vertices = vertices.dot(matrix.getData()[0:3, 0:3]) vertices[:] += center.getData() #And translate to the desired position. self._mesh_data.addVertices(vertices) self._mesh_data.addIndices(numpy.asarray(indices, dtype = numpy.int32)) self._mesh_data.addColors(numpy.asarray(colors, dtype = numpy.float32)) ## Adds a pyramid to the mesh of this mesh builder. # # \param width The width of the base of the pyramid. # \param height The height of the pyramid (from base to notch). # \param depth The depth of the base of the pyramid. # \param angle (Optional) An angle of rotation to rotate the pyramid by, # in degrees. # \param axis (Optional) An axis of rotation to rotate the pyramid around. # If no axis is provided and the angle of rotation is nonzero, the pyramid # will be rotated around the Y-axis. # \param center (Optional) The position of the centre of the base of the # pyramid. If not provided, the pyramid will be placed on the coordinate # origin. # \param color (Optional) The colour of the pyramid. If no colour is # provided, a colour will be determined by the shader. def addPyramid(self, width, height, depth, angle = 0, axis = Vector.Unit_Y, center = Vector(0, 0, 0), color = None): angle = math.radians(angle) minW = -width / 2 maxW = width / 2 minD = -depth / 2 maxD = depth / 2 start = self._mesh_data.getVertexCount() #Starting index. matrix = Matrix() matrix.setByRotationAxis(angle, axis) verts = numpy.asarray([ #All 5 vertices of the pyramid. [minW, 0, maxD], [maxW, 0, maxD], [minW, 0, minD], [maxW, 0, minD], [0, height, 0] ], dtype=numpy.float32) verts = verts.dot(matrix.getData()[0:3,0:3]) #Rotate the pyramid around the axis. verts[:] += center.getData() self._mesh_data.addVertices(verts) indices = numpy.asarray([ #Connect the vertices to each other (6 triangles). [start, start + 1, start + 4], #The four sides of the pyramid. [start + 1, start + 3, start + 4], [start + 3, start + 2, start + 4], [start + 2, start, start + 4], [start, start + 3, start + 1], #The base of the pyramid. [start, start + 2, start + 3] ], dtype=numpy.int32) self._mesh_data.addIndices(indices) if color: #If we have a colour, add the colour to each of the vertices. vertex_count = self._mesh_data.getVertexCount() for i in range(1, 6): self._mesh_data.setVertexColor(vertex_count - i, color)
def __init__(self, parent = None): super().__init__(parent) lines = MeshData() lines.addVertex(0, 0, 0) lines.addVertex(0, 20, 0) lines.addVertex(0, 0, 0) lines.addVertex(20, 0, 0) lines.addVertex(0, 0, 0) lines.addVertex(0, 0, 20) lines.setVertexColor(0, ToolHandle.YAxisColor) lines.setVertexColor(1, ToolHandle.YAxisColor) lines.setVertexColor(2, ToolHandle.XAxisColor) lines.setVertexColor(3, ToolHandle.XAxisColor) lines.setVertexColor(4, ToolHandle.ZAxisColor) lines.setVertexColor(5, ToolHandle.ZAxisColor) self.setLineMesh(lines) mb = MeshBuilder() mb.addCube( width = 2, height = 2, depth = 2, center = Vector(0, 0, 0), color = ToolHandle.AllAxisColor ) mb.addCube( width = 2, height = 2, depth = 2, center = Vector(0, 20, 0), color = ToolHandle.YAxisColor ) mb.addCube( width = 2, height = 2, depth = 2, center = Vector(20, 0, 0), color = ToolHandle.XAxisColor ) mb.addCube( width = 2, height = 2, depth = 2, center = Vector(0, 0, 20), color = ToolHandle.ZAxisColor ) self.setSolidMesh(mb.getData()) mb = MeshBuilder() mb.addCube( width = 4, height = 20, depth = 4, center = Vector(0, 10, 0), color = ToolHandle.YAxisColor ) mb.addCube( width = 4, height = 4, depth = 4, center = Vector(0, 20, 0), color = ToolHandle.YAxisColor ) mb.addCube( width = 20, height = 4, depth = 4, center = Vector(10, 0, 0), color = ToolHandle.XAxisColor ) mb.addCube( width = 4, height = 4, depth = 4, center = Vector(20, 0, 0), color = ToolHandle.XAxisColor ) mb.addCube( width = 4, height = 4, depth = 20, center = Vector(0, 0, 10), color = ToolHandle.ZAxisColor ) mb.addCube( width = 4, height = 4, depth = 4, center = Vector(0, 0, 20), color = ToolHandle.ZAxisColor ) mb.addCube( width = 8, height = 8, depth = 8, center = Vector(0, 0, 0), color = ToolHandle.AllAxisColor ) self.setSelectionMesh(mb.getData())
# Copyright (c) 2015 Ultimaker B.V. # Uranium is released under the terms of the AGPLv3 or higher. from UM.Mesh.MeshData import MeshData @profile def getByteArray(mesh): return mesh.getVerticesAsByteArray() mesh = MeshData() mesh.reserveVertexCount(10000) for i in range(10000): mesh.addVertex(0, 1, 0) for i in range(100): a = getByteArray(mesh)
class PathResultDecorator(SceneNodeDecorator): _move_color = Color(0, 0, 0, 128) _cut_color = Color(255, 0, 0, 255) _engrave_color = Color(0, 0, 255, 255) def __init__(self): super().__init__() self._paths = None def getCutPaths(self): return self._cut_paths def getEngravePaths(self): return self._engrave_paths def setPaths(self, engrave_paths, cut_paths): self._cut_paths = cut_paths self._engrave_paths = engrave_paths self._mesh = MeshData() last_point = None for path in engrave_paths.closed_paths: if last_point is not None: self._addMeshLine(last_point, path[0], self._move_color) last_point = path[0] for point in path[1:]: self._addMeshLine(last_point, point, self._engrave_color) last_point = point self._addMeshLine(last_point, path[0], self._engrave_color) last_point = path[0] for path in engrave_paths.open_paths: if last_point is not None: self._addMeshLine(last_point, path[0], self._move_color) last_point = path[0] for point in path[1:]: self._addMeshLine(last_point, point, self._engrave_color) last_point = point for path in cut_paths.closed_paths: if last_point is not None: self._addMeshLine(last_point, path[0], self._move_color) last_point = path[0] for point in path[1:]: self._addMeshLine(last_point, point, self._cut_color) last_point = point self._addMeshLine(last_point, path[0], self._cut_color) last_point = path[0] for path in cut_paths.open_paths: if last_point is not None: self._addMeshLine(last_point, path[0], self._move_color) last_point = path[0] for point in path[1:]: self._addMeshLine(last_point, point, self._cut_color) last_point = point self.getNode().setMeshData(self._mesh) def _addMeshLine(self, p0, p1, color): self._mesh.addVertex(p0[0] / Paths.SCALE, 0.0, p0[1] / Paths.SCALE) self._mesh.addVertex(p1[0] / Paths.SCALE, 0.0, p1[1] / Paths.SCALE) self._mesh.setVertexColor(self._mesh.getVertexCount() - 2, color) self._mesh.setVertexColor(self._mesh.getVertexCount() - 1, color)
class DXFReader(MeshReader): def __init__(self): super(DXFReader, self).__init__() self._supported_extensions = [".dxf"] self._dxf = None self._mesh = None def read(self, file_name): self._dxf = DXFObjectReader.DXFObjectReader(open(file_name, "rt")) self._mesh = MeshData() for obj in self._dxf: if obj.getName() == "SECTION": if obj.get(2) == "ENTITIES": self._handleEntities() elif obj.get(2) == "TABLES": self._handleTables() else: Logger.log("d", "DXF: Got unknown section: %s", obj.get(2)) for obj in self._dxf: if obj.getName() == "ENDSEC": break else: Logger.log("d", "DXF: %s", obj) elif obj.getName() == "EOF": pass else: Logger.log("e", "DXF: Unexpected object: %s", obj) #self._mesh.calculateNormals() node = SceneNode() node.setMeshData(self._mesh) return node def _handleTables(self): for obj in self._dxf: if obj.getName() == "ENDSEC": break elif obj.getName() == "TABLE": if obj.get(2) == "": pass else: Logger.log("d", "DXF: Got unknown table: %s", obj.get(2)) for obj in self._dxf: if obj.getName() == "ENDTAB": break else: Logger.log("d", "DXF: %s", obj) else: Logger.log("e", "DXF: Unexpected object in tables section: %s", obj) def _handleEntities(self): for obj in self._dxf: if obj.getName() == "ENDSEC": break elif obj.getName() == "LINE": self._addLine(obj.get(10), obj.get(20), obj.get(11), obj.get(21)) elif obj.getName() == "LWPOLYLINE": for n in range(0, obj.count(20) - 1): self._addLine(obj.get(10, n), obj.get(20, n), obj.get(10, n + 1), obj.get(20, n + 1)) elif obj.getName() == "ARC": cx = float(obj.get(10)) cy = float(obj.get(20)) r = float(obj.get(40)) a_start = float(obj.get(50)) a_end = float(obj.get(51)) if a_end < a_start: a_end += 360.0 steps = math.ceil(((2.0 * math.pi * r) * (a_end - a_start) / 360.0) / 0.5) for n in range(0, steps): a0 = a_start + (a_end - a_start) * n / steps a1 = a_start + (a_end - a_start) * (n + 1) / steps self._addLine( cx + math.cos(math.radians(a0)) * r, cy + math.sin(math.radians(a0)) * r, cx + math.cos(math.radians(a1)) * r, cy + math.sin(math.radians(a1)) * r ) elif obj.getName() == "CIRCLE": cx = float(obj.get(10)) cy = float(obj.get(20)) r = float(obj.get(40)) steps = math.ceil((2.0 * math.pi * r) / 0.5) for n in range(0, steps): a0 = 360.0 * n / steps a1 = 360.0 * (n + 1) / steps self._addLine( cx + math.cos(math.radians(a0)) * r, cy + math.sin(math.radians(a0)) * r, cx + math.cos(math.radians(a1)) * r, cy + math.sin(math.radians(a1)) * r ) elif obj.getName() == "ELLIPSE": cx = float(obj.get(10)) cy = float(obj.get(20)) emx = float(obj.get(11)) emy = float(obj.get(21)) r_major = math.sqrt(emx * emx + emy * emy) r_minor = r_major * float(obj.get(40)) a_start = math.degrees(float(obj.get(41))) a_end = math.degrees(float(obj.get(42))) if a_end < a_start: a_end += 360.0 steps = math.ceil(((2.0 * math.pi * r) * (a_end - a_start) / 360.0) / 0.5) for n in range(0, steps): a0 = a_start + (a_end - a_start) * n / steps a1 = a_start + (a_end - a_start) * (n + 1) / steps self._addLine( cx + math.cos(math.radians(a0)) * r_major, cy + math.sin(math.radians(a0)) * r_minor, cx + math.cos(math.radians(a1)) * r_major, cy + math.sin(math.radians(a1)) * r_minor ) elif obj.getName() == "SPLINE": nurbs = NURBS.NURBS(int(obj.get(71))) for n in range(0, obj.count(40)): nurbs.addKnot(float(obj.get(40, n))) for n in range(0, obj.count(10)): nurbs.addPoint(float(obj.get(10, n)), float(obj.get(20, n))) points = nurbs.calculate(2) distance = math.sqrt((points[0][0] - points[-1][0]) * (points[0][0] - points[-1][0]) + (points[0][1] - points[-1][1]) * (points[0][1] - points[-1][1])) if distance < 1.0: point_count = int(max(2, distance / 0.1)) elif distance < 5.0: point_count = int(max(2, distance / 0.3)) else: point_count = int(max(2, distance / 0.5)) points = nurbs.calculate(point_count) for n in range(0, len(points) - 1): self._addLine(points[n][0], points[n][1], points[n + 1][0], points[n + 1][1]) else: Logger.log("w", "DXF: Unknown entity: %s", str(obj)) def _addLine(self, x0, y0, x1, y1): x0 = float(x0) x1 = float(x1) y0 = -float(y0) y1 = -float(y1) if x0 == x1 and y0 == y1: return self._mesh.addVertex(x0, 0, y0) self._mesh.addVertex(x1, 0, y1) self._mesh.addVertex(x1, 1, y1) self._mesh.addVertex(x0, 1, y0) self._mesh.addVertex(x1, 1, y1) self._mesh.addVertex(x0, 0, y0)