예제 #1
0
    def setOrientation(self,
                       orientation: Quaternion,
                       transform_space: int = TransformSpace.Local):
        if not self._enabled or orientation == self._orientation:
            return

        new_transform_matrix = Matrix()
        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()
        else:  # Local
            orientation_matrix = 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()
예제 #2
0
    def setOrientation(self,
                       orientation: Quaternion,
                       transform_space: int = TransformSpace.Local) -> None:
        """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.
        """

        if not self._enabled or orientation == self._orientation:
            return

        if transform_space == SceneNode.TransformSpace.World:
            if self.getWorldOrientation() == orientation:
                return
            new_orientation = orientation * (
                self.getWorldOrientation() *
                self._orientation.getInverse()).invert()
            orientation_matrix = new_orientation.toMatrix()
        else:  # Local
            orientation_matrix = orientation.toMatrix()

        euler_angles = orientation_matrix.getEuler()
        new_transform_matrix = Matrix()
        new_transform_matrix.compose(scale=self._scale,
                                     angles=euler_angles,
                                     translate=self._position,
                                     shear=self._shear)
        self._transformation = new_transform_matrix
        self._transformChanged()
예제 #3
0
def createMockedSelectionWithScale(scale):
    selection = MagicMock(name="mocked_selection")
    node = MagicMock(name="mocked_node")

    matrix = Matrix()
    matrix.compose(scale=scale)
    node.getWorldTransformation = MagicMock(return_value=matrix)
    node.getScale = MagicMock(return_value=scale)
    selection.hasSelection = MagicMock(return_value=True)
    selection.getSelectedObject = MagicMock(return_value=node)
    return selection
예제 #4
0
    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()
예제 #5
0
class SetTransformOperation(Operation.Operation):
    ##  Creates the transform operation.
    #
    #   Careful! No real input checking is done by this function. If you'd
    #   provide other transformations than respectively translation, orientation
    #   and scale in place for the translation, orientation and scale matrices,
    #   it could get confused.
    #
    #   \param node The scene node to transform.
    #   \param translation A translation matrix to move the node with.
    #   \param orientation An orientation matrix to rotate the node with.
    #   \param scale A scaling matrix to resize the node with.
    def __init__(self,
                 node,
                 translation=None,
                 orientation=None,
                 scale=None,
                 shear=None,
                 mirror=None):
        super().__init__()
        self._node = node

        self._old_translation = node.getPosition()
        self._old_orientation = node.getOrientation()
        self._old_scale = node.getScale()
        self._old_shear = node.getShear()
        self._old_transformation = node.getWorldTransformation()

        if translation:
            self._new_translation = translation
        else:
            self._new_translation = node.getPosition()

        if orientation:
            self._new_orientation = orientation
        else:
            self._new_orientation = node.getOrientation()

        if scale:
            self._new_scale = scale
        else:
            self._new_scale = node.getScale()

        if shear:
            self._new_shear = shear
        else:
            self._new_shear = node.getShear()

        if mirror:
            self._new_mirror = mirror
        else:
            # Scale will either be negative or positive. If it's negative, we need to use the inverse mirror.
            if self._node.getScale().x < 0:
                self._new_mirror = Vector(-node.getMirror().x,
                                          -node.getMirror().y,
                                          -node.getMirror().z)
            else:
                self._new_mirror = node.getMirror()
        self._new_transformation = Matrix()

        euler_orientation = self._new_orientation.toMatrix().getEuler()

        self._new_transformation.compose(scale=self._new_scale,
                                         shear=self._new_shear,
                                         angles=euler_orientation,
                                         translate=self._new_translation,
                                         mirror=self._new_mirror)

    ##  Undoes the transformation, restoring the node to the old state.
    def undo(self):
        self._node.setTransformation(self._old_transformation)

    ##  Re-applies the transformation after it has been undone.
    def redo(self):
        self._node.setTransformation(self._new_transformation)

    ##  Merges this operation with another TransformOperation.
    #
    #   This prevents the user from having to undo multiple operations if they
    #   were not his operations.
    #
    #   You should ONLY merge this operation with an older operation. It is NOT
    #   symmetric.
    #
    #   \param other The older operation with which to merge this operation.
    #   \return A combination of the two operations, or False if the merge
    #   failed.
    def mergeWith(self, other):
        if type(other) is not SetTransformOperation:
            return False
        if other._node != self._node:  # Must be on the same node.
            return False

        op = SetTransformOperation(self._node)
        op._old_transformation = other._old_transformation
        op._new_transformation = self._new_transformation
        return op

    ##  Returns a programmer-readable representation of this operation.
    #
    #   A programmer-readable representation of this operation.
    def __repr__(self):
        return "SetTransformOperation(node = {0})".format(self._node)

    def LOG_MATRIX(self, str_matrix_name, matrix):
        Logger.log(
            "d",
            "\n ................................................................... "
        )

        Logger.log("d", "\n %s: ", str_matrix_name)
        if (matrix != None):
            Logger.log("d", "%d  %d  %d  %d", matrix.at(0, 0), matrix.at(0, 1),
                       matrix.at(0, 2), matrix.at(0, 3))
            Logger.log("d", "%d  %d  %d  %d", matrix.at(1, 0), matrix.at(1, 1),
                       matrix.at(1, 2), matrix.at(1, 3))
            Logger.log("d", "%d  %d  %d  %d", matrix.at(2, 0), matrix.at(2, 1),
                       matrix.at(2, 2), matrix.at(2, 3))
            Logger.log("d", "%d  %d  %d  %d", matrix.at(3, 0), matrix.at(3, 1),
                       matrix.at(3, 2), matrix.at(3, 3))
        else:
            Logger.log("d", "\n %s in None ", str_matrix_name)

        Logger.log(
            "d",
            "................................................................... \n"
        )

    def LOG_QUATERNION(self, str_quaternion_name, quaternion):
        Logger.log(
            "d",
            "\n ................................................................... "
        )
        Logger.log("d", "\n %s: ", str_quaternion_name)
        Logger.log("d", "%d  %d  %d  %d",
                   quaternion.toMatrix().at(0, 0),
                   quaternion.toMatrix().at(0, 1),
                   quaternion.toMatrix().at(0, 2),
                   quaternion.toMatrix().at(0, 3))
        Logger.log("d", "%d  %d  %d  %d",
                   quaternion.toMatrix().at(1, 0),
                   quaternion.toMatrix().at(1, 1),
                   quaternion.toMatrix().at(1, 2),
                   quaternion.toMatrix().at(1, 3))
        Logger.log("d", "%d  %d  %d  %d",
                   quaternion.toMatrix().at(2, 0),
                   quaternion.toMatrix().at(2, 1),
                   quaternion.toMatrix().at(2, 2),
                   quaternion.toMatrix().at(2, 3))
        Logger.log("d", "%d  %d  %d  %d",
                   quaternion.toMatrix().at(3, 0),
                   quaternion.toMatrix().at(3, 1),
                   quaternion.toMatrix().at(3, 2),
                   quaternion.toMatrix().at(3, 3))
        Logger.log(
            "d",
            "................................................................... \n"
        )

    def LOG_VECTOR(self, str_vector_name, vector):
        Logger.log(
            "d",
            "\n ................................................................... "
        )
        Logger.log("d", "\n %s: ", str_vector_name)
        Logger.log("d", "%d  %d  %d", vector.x, vector.y, vector.z)
        Logger.log(
            "d",
            "................................................................... \n"
        )
예제 #6
0
파일: BuildVolume.py 프로젝트: mifga/Cura
    def rebuild(self):
        if not self._width or not self._height or not self._depth:
            return

        min_w = -self._width / 2
        max_w = self._width / 2
        min_h = 0.0
        max_h = self._height
        min_d = -self._depth / 2
        max_d = self._depth / 2

        z_fight_distance = 0.2 # Distance between buildplate and disallowed area meshes to prevent z-fighting

        if self._shape != "elliptic":
            # Outline 'cube' of the build volume
            mb = MeshBuilder()
            mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)

            mb.addLine(Vector(min_w, min_h, max_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(min_w, min_h, max_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(min_w, max_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(max_w, min_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)

            mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, min_h, max_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor)
            mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor)

            self.setMeshData(mb.build())

            # Build plate grid mesh
            mb = MeshBuilder()
            mb.addQuad(
                Vector(min_w, min_h - z_fight_distance, min_d),
                Vector(max_w, min_h - z_fight_distance, min_d),
                Vector(max_w, min_h - z_fight_distance, max_d),
                Vector(min_w, min_h - z_fight_distance, max_d)
            )

            for n in range(0, 6):
                v = mb.getVertex(n)
                mb.setVertexUVCoordinates(n, v[0], v[2])
            self._grid_mesh = mb.build()

        else:
            # Bottom and top 'ellipse' of the build volume
            aspect = 1.0
            scale_matrix = Matrix()
            if self._width != 0:
                # Scale circular meshes by aspect ratio if width != height
                aspect = self._height / self._width
                scale_matrix.compose(scale = Vector(1, 1, aspect))
            mb = MeshBuilder()
            mb.addArc(max_w, Vector.Unit_Y, center = (0, min_h - z_fight_distance, 0), color = self.VolumeOutlineColor)
            mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0),  color = self.VolumeOutlineColor)
            self.setMeshData(mb.build().getTransformed(scale_matrix))

            # Build plate grid mesh
            mb = MeshBuilder()
            mb.addVertex(0, min_h - z_fight_distance, 0)
            mb.addArc(max_w, Vector.Unit_Y, center = Vector(0, min_h - z_fight_distance, 0))
            sections = mb.getVertexCount() - 1 # Center point is not an arc section
            indices = []
            for n in range(0, sections - 1):
                indices.append([0, n + 2, n + 1])
            mb.addIndices(numpy.asarray(indices, dtype = numpy.int32))
            mb.calculateNormals()

            for n in range(0, mb.getVertexCount()):
                v = mb.getVertex(n)
                mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
            self._grid_mesh = mb.build().getTransformed(scale_matrix)

        # Indication of the machine origin
        if self._global_container_stack.getProperty("machine_center_is_zero", "value"):
            origin = (Vector(min_w, min_h, min_d) + Vector(max_w, min_h, max_d)) / 2
        else:
            origin = Vector(min_w, min_h, max_d)

        mb = MeshBuilder()
        mb.addCube(
            width = self._origin_line_length,
            height = self._origin_line_width,
            depth = self._origin_line_width,
            center = origin + Vector(self._origin_line_length / 2, 0, 0),
            color = self.XAxisColor
        )
        mb.addCube(
            width = self._origin_line_width,
            height = self._origin_line_length,
            depth = self._origin_line_width,
            center = origin + Vector(0, self._origin_line_length / 2, 0),
            color = self.YAxisColor
        )
        mb.addCube(
            width = self._origin_line_width,
            height = self._origin_line_width,
            depth = self._origin_line_length,
            center = origin - Vector(0, 0, self._origin_line_length / 2),
            color = self.ZAxisColor
        )
        self._origin_mesh = mb.build()

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._disallowed_areas:
            mb = MeshBuilder()
            color = Color(0.0, 0.0, 0.0, 0.15)
            for polygon in self._disallowed_areas:
                points = polygon.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color = color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds.
                # This is a very nasty hack. This pretty much only works for UM machines.
                # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
                if numpy.min(points[:, 1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
                    size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                else:
                    size = 0
                disallowed_area_size = max(size, disallowed_area_size)

            self._disallowed_area_mesh = mb.build()
        else:
            self._disallowed_area_mesh = None

        if self._error_areas:
            mb = MeshBuilder()
            for error_area in self._error_areas:
                color = Color(1.0, 0.0, 0.0, 0.5)
                points = error_area.getPoints()
                first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
                               self._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
                                        self._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
                                       self._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point
            self._error_mesh = mb.build()
        else:
            self._error_mesh = None

        self._volume_aabb = AxisAlignedBox(
            minimum = Vector(min_w, min_h - 1.0, min_d),
            maximum = Vector(max_w, max_h - self._raft_thickness, max_d))

        bed_adhesion_size = self._getEdgeDisallowedSize()

        # As this works better for UM machines, we only add the disallowed_area_size for the z direction.
        # This is probably wrong in all other cases. TODO!
        # The +1 and -1 is added as there is always a bit of extra room required to work properly.
        scale_to_max_bounds = AxisAlignedBox(
            minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + disallowed_area_size - bed_adhesion_size + 1),
            maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness, max_d - disallowed_area_size + bed_adhesion_size - 1)
        )

        Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
예제 #7
0
class SetTransformOperation(Operation.Operation):
    ##  Creates the transform operation.
    #
    #   Careful! No real input checking is done by this function. If you'd
    #   provide other transformations than respectively translation, orientation
    #   and scale in place for the translation, orientation and scale matrices,
    #   it could get confused.
    #
    #   \param node The scene node to transform.
    #   \param translation A translation matrix to move the node with.
    #   \param orientation An orientation matrix to rotate the node with.
    #   \param scale A scaling matrix to resize the node with.
    def __init__(self, node, translation = None, orientation = None, scale = None, shear = None, mirror = None):
        super().__init__()
        self._node = node

        self._old_translation = node.getPosition()
        self._old_orientation = node.getOrientation()
        self._old_scale = node.getScale()
        self._old_shear = node.getShear()
        self._old_transformation = node.getWorldTransformation()

        if translation:
            self._new_translation = translation
        else:
            self._new_translation = node.getPosition()

        if orientation:
            self._new_orientation = orientation
        else:
            self._new_orientation = node.getOrientation()

        if scale:
            self._new_scale = scale
        else:
            self._new_scale = node.getScale()

        if shear:
            self._new_shear = shear
        else:
            self._new_shear = node.getShear()

        if mirror:
            self._new_mirror = mirror
        else:
            # Scale will either be negative or positive. If it's negative, we need to use the inverse mirror.
            if self._node.getScale().x < 0:
                self._new_mirror = Vector(-node.getMirror().x, -node.getMirror().y, -node.getMirror().z)
            else:
                self._new_mirror = node.getMirror()

        self._new_transformation = Matrix()

        euler_orientation = self._new_orientation.toMatrix().getEuler()
        self._new_transformation.compose(scale = self._new_scale, shear = self._new_shear, angles = euler_orientation, translate = self._new_translation, mirror = self._new_mirror)

    ##  Undoes the transformation, restoring the node to the old state.
    def undo(self):
        self._node.setTransformation(self._old_transformation)

    ##  Re-applies the transformation after it has been undone.
    def redo(self):
        self._node.setTransformation(self._new_transformation)

    ##  Merges this operation with another TransformOperation.
    #
    #   This prevents the user from having to undo multiple operations if they
    #   were not his operations.
    #
    #   You should ONLY merge this operation with an older operation. It is NOT
    #   symmetric.
    #
    #   \param other The older operation with which to merge this operation.
    #   \return A combination of the two operations, or False if the merge
    #   failed.
    def mergeWith(self, other):
        if type(other) is not SetTransformOperation:
            return False
        if other._node != self._node: # Must be on the same node.
            return False

        op = SetTransformOperation(self._node)
        op._old_transformation = other._old_transformation
        op._new_transformation = self._new_transformation
        return op

    ##  Returns a programmer-readable representation of this operation.
    #
    #   A programmer-readable representation of this operation.
    def __repr__(self):
        return "SetTransformOperation(node = {0})".format(self._node)
예제 #8
0
    def _rebuild(self):
        if not self._build_volume._width or not self._build_volume._height or not self._build_volume._depth:
            return

        if not self._build_volume._engine_ready:
            return

        if not self._build_volume._volume_outline_color:
            theme = Application.getInstance().getTheme()
            self._build_volume._volume_outline_color = Color(
                *theme.getColor("volume_outline").getRgb())
            self._build_volume._x_axis_color = Color(
                *theme.getColor("x_axis").getRgb())
            self._build_volume._y_axis_color = Color(
                *theme.getColor("y_axis").getRgb())
            self._build_volume._z_axis_color = Color(
                *theme.getColor("z_axis").getRgb())
            self._build_volume._disallowed_area_color = Color(
                *theme.getColor("disallowed_area").getRgb())
            self._build_volume._error_area_color = Color(
                *theme.getColor("error_area").getRgb())

        ### START PATCH
        # Get a dict from the machine metadata optionally overriding the build volume
        # Note that CuraEngine is blissfully unaware of this; it is just what the user is shown in Cura
        limit_buildvolume = self._build_volume._global_container_stack.getMetaDataEntry(
            "limit_buildvolume", {})
        if not isinstance(limit_buildvolume, dict):
            limit_buildvolume = {}

        min_w = limit_buildvolume.get("width",
                                      {}).get("minimum",
                                              -self._build_volume._width / 2)
        max_w = limit_buildvolume.get("width",
                                      {}).get("maximum",
                                              self._build_volume._width / 2)
        min_h = limit_buildvolume.get("height", {}).get("minimum", 0.0)
        max_h = limit_buildvolume.get("height",
                                      {}).get("maximum",
                                              self._build_volume._height)
        min_d = limit_buildvolume.get("depth",
                                      {}).get("minimum",
                                              -self._build_volume._depth / 2)
        max_d = limit_buildvolume.get("depth",
                                      {}).get("maximum",
                                              self._build_volume._depth / 2)
        ### END PATCH

        z_fight_distance = 0.2  # Distance between buildplate and disallowed area meshes to prevent z-fighting

        if self._build_volume._shape != "elliptic":
            # Outline 'cube' of the build volume
            mb = MeshBuilder()
            mb.addLine(Vector(min_w, min_h, min_d),
                       Vector(max_w, min_h, min_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(min_w, min_h, min_d),
                       Vector(min_w, max_h, min_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(min_w, max_h, min_d),
                       Vector(max_w, max_h, min_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(max_w, min_h, min_d),
                       Vector(max_w, max_h, min_d),
                       color=self._build_volume._volume_outline_color)

            mb.addLine(Vector(min_w, min_h, max_d),
                       Vector(max_w, min_h, max_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(min_w, min_h, max_d),
                       Vector(min_w, max_h, max_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(min_w, max_h, max_d),
                       Vector(max_w, max_h, max_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(max_w, min_h, max_d),
                       Vector(max_w, max_h, max_d),
                       color=self._build_volume._volume_outline_color)

            mb.addLine(Vector(min_w, min_h, min_d),
                       Vector(min_w, min_h, max_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(max_w, min_h, min_d),
                       Vector(max_w, min_h, max_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(min_w, max_h, min_d),
                       Vector(min_w, max_h, max_d),
                       color=self._build_volume._volume_outline_color)
            mb.addLine(Vector(max_w, max_h, min_d),
                       Vector(max_w, max_h, max_d),
                       color=self._build_volume._volume_outline_color)

            self._build_volume.setMeshData(mb.build())

            # Build plate grid mesh
            mb = MeshBuilder()
            mb.addQuad(Vector(min_w, min_h - z_fight_distance, min_d),
                       Vector(max_w, min_h - z_fight_distance, min_d),
                       Vector(max_w, min_h - z_fight_distance, max_d),
                       Vector(min_w, min_h - z_fight_distance, max_d))

            for n in range(0, 6):
                v = mb.getVertex(n)
                mb.setVertexUVCoordinates(n, v[0], v[2])
            self._build_volume._grid_mesh = mb.build()

        else:
            # Bottom and top 'ellipse' of the build volume
            aspect = 1.0
            scale_matrix = Matrix()
            if self._build_volume._width != 0:
                # Scale circular meshes by aspect ratio if width != height
                aspect = self._build_volume._depth / self._build_volume._width
                scale_matrix.compose(scale=Vector(1, 1, aspect))
            mb = MeshBuilder()
            mb.addArc(max_w,
                      Vector.Unit_Y,
                      center=(0, min_h - z_fight_distance, 0),
                      color=self._build_volume._volume_outline_color)
            mb.addArc(max_w,
                      Vector.Unit_Y,
                      center=(0, max_h, 0),
                      color=self._build_volume._volume_outline_color)
            self._build_volume.setMeshData(
                mb.build().getTransformed(scale_matrix))

            # Build plate grid mesh
            mb = MeshBuilder()
            mb.addVertex(0, min_h - z_fight_distance, 0)
            mb.addArc(max_w,
                      Vector.Unit_Y,
                      center=Vector(0, min_h - z_fight_distance, 0))
            sections = mb.getVertexCount(
            ) - 1  # Center point is not an arc section
            indices = []
            for n in range(0, sections - 1):
                indices.append([0, n + 2, n + 1])
            mb.addIndices(numpy.asarray(indices, dtype=numpy.int32))
            mb.calculateNormals()

            for n in range(0, mb.getVertexCount()):
                v = mb.getVertex(n)
                mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
            self._build_volume._grid_mesh = mb.build().getTransformed(
                scale_matrix)

        # Indication of the machine origin
        if self._build_volume._global_container_stack.getProperty(
                "machine_center_is_zero", "value"):
            origin = (Vector(min_w, min_h, min_d) +
                      Vector(max_w, min_h, max_d)) / 2
        else:
            origin = Vector(min_w, min_h, max_d)

        mb = MeshBuilder()
        mb.addCube(width=self._build_volume._origin_line_length,
                   height=self._build_volume._origin_line_width,
                   depth=self._build_volume._origin_line_width,
                   center=origin +
                   Vector(self._build_volume._origin_line_length / 2, 0, 0),
                   color=self._build_volume._x_axis_color)
        mb.addCube(width=self._build_volume._origin_line_width,
                   height=self._build_volume._origin_line_length,
                   depth=self._build_volume._origin_line_width,
                   center=origin +
                   Vector(0, self._build_volume._origin_line_length / 2, 0),
                   color=self._build_volume._y_axis_color)
        mb.addCube(width=self._build_volume._origin_line_width,
                   height=self._build_volume._origin_line_width,
                   depth=self._build_volume._origin_line_length,
                   center=origin -
                   Vector(0, 0, self._build_volume._origin_line_length / 2),
                   color=self._build_volume._z_axis_color)
        self._build_volume._origin_mesh = mb.build()

        disallowed_area_height = 0.1
        disallowed_area_size = 0
        if self._build_volume._disallowed_areas:
            mb = MeshBuilder()
            color = self._build_volume._disallowed_area_color
            for polygon in self._build_volume._disallowed_areas:
                points = polygon.getPoints()
                if len(points) == 0:
                    continue

                first = Vector(
                    self._build_volume._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._build_volume._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(
                    self._build_volume._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._build_volume._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(
                        self._build_volume._clamp(point[0], min_w, max_w),
                        disallowed_area_height,
                        self._build_volume._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point

                # Find the largest disallowed area to exclude it from the maximum scale bounds.
                # This is a very nasty hack. This pretty much only works for UM machines.
                # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
                if numpy.min(
                        points[:, 1]
                ) >= 0:  # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
                    size = abs(
                        numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
                else:
                    size = 0
                disallowed_area_size = max(size, disallowed_area_size)

            self._build_volume._disallowed_area_mesh = mb.build()
        else:
            self._build_volume._disallowed_area_mesh = None

        if self._build_volume._error_areas:
            mb = MeshBuilder()
            for error_area in self._build_volume._error_areas:
                color = self._build_volume._error_area_color
                points = error_area.getPoints()
                first = Vector(
                    self._build_volume._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._build_volume._clamp(points[0][1], min_d, max_d))
                previous_point = Vector(
                    self._build_volume._clamp(points[0][0], min_w, max_w),
                    disallowed_area_height,
                    self._build_volume._clamp(points[0][1], min_d, max_d))
                for point in points:
                    new_point = Vector(
                        self._build_volume._clamp(point[0], min_w, max_w),
                        disallowed_area_height,
                        self._build_volume._clamp(point[1], min_d, max_d))
                    mb.addFace(first, previous_point, new_point, color=color)
                    previous_point = new_point
            self._build_volume._error_mesh = mb.build()
        else:
            self._build_volume._error_mesh = None

        self._build_volume._volume_aabb = AxisAlignedBox(
            minimum=Vector(min_w, min_h - 1.0, min_d),
            maximum=Vector(
                max_w, max_h - self._build_volume._raft_thickness -
                self._build_volume._extra_z_clearance, max_d))

        bed_adhesion_size = self._build_volume.getEdgeDisallowedSize()

        # As this works better for UM machines, we only add the disallowed_area_size for the z direction.
        # This is probably wrong in all other cases. TODO!
        # The +1 and -1 is added as there is always a bit of extra room required to work properly.
        scale_to_max_bounds = AxisAlignedBox(
            minimum=Vector(
                min_w + bed_adhesion_size + 1, min_h,
                min_d + disallowed_area_size - bed_adhesion_size + 1),
            maximum=Vector(
                max_w - bed_adhesion_size - 1,
                max_h - self._build_volume._raft_thickness -
                self._build_volume._extra_z_clearance,
                max_d - disallowed_area_size + bed_adhesion_size - 1))

        Application.getInstance().getController().getScene(
        )._maximum_bounds = scale_to_max_bounds

        self._build_volume.updateNodeBoundaryCheck()
class SetTransformOperation(Operation.Operation):
    ##  Creates the transform operation.
    #
    #   Careful! No real input checking is done by this function. If you'd
    #   provide other transformations than respectively translation, orientation
    #   and scale in place for the translation, orientation and scale matrices,
    #   it could get confused.
    #
    #   \param node The scene node to transform.
    #   \param translation A translation matrix to move the node with.
    #   \param orientation An orientation matrix to rotate the node with.
    #   \param scale A scaling matrix to resize the node with.
    def __init__(self, node, translation = None, orientation = None, scale = None, shear = None, mirror = None):
        super().__init__()
        self._node = node

        self._old_translation = node.getPosition()
        self._old_orientation = node.getOrientation()
        self._old_scale = node.getScale()
        self._old_shear = node.getShear()
        self._old_transformation = node.getWorldTransformation()

        if translation:
            self._new_translation = translation
        else:
            self._new_translation = node.getPosition()

        if orientation:
            self._new_orientation = orientation
        else:
            self._new_orientation = node.getOrientation()

        if scale:
            self._new_scale = scale
        else:
            self._new_scale = node.getScale()

        if shear:
            self._new_shear = shear
        else:
            self._new_shear = node.getShear()

        self._new_mirror = Vector(1, 1, 1)
        self._new_transformation = Matrix()

        euler_orientation = self._new_orientation.toMatrix().getEuler()
        self._new_transformation.compose(scale = self._new_scale, shear = self._new_shear, angles = euler_orientation, translate = self._new_translation, mirror = self._new_mirror)

    ##  Undoes the transformation, restoring the node to the old state.
    def undo(self):
        self._node.setTransformation(self._old_transformation)

    ##  Re-applies the transformation after it has been undone.
    def redo(self):
        self._node.setTransformation(self._new_transformation)

    ##  Merges this operation with another TransformOperation.
    #
    #   This prevents the user from having to undo multiple operations if they
    #   were not his operations.
    #
    #   You should ONLY merge this operation with an older operation. It is NOT
    #   symmetric.
    #
    #   \param other The older operation with which to merge this operation.
    #   \return A combination of the two operations, or False if the merge
    #   failed.
    def mergeWith(self, other):
        if type(other) is not SetTransformOperation:
            return False
        if other._node != self._node: # Must be on the same node.
            return False

        op = SetTransformOperation(self._node)
        op._old_transformation = other._old_transformation
        op._new_transformation = self._new_transformation
        return op

    ##  Returns a programmer-readable representation of this operation.
    #
    #   A programmer-readable representation of this operation.
    def __repr__(self):
        return "SetTransformOp.(node={0})".format(self._node)