Exemple #1
0
    def read(self, file_name):
        mesh_builder = MeshBuilder()
        scene_node = SceneNode()

        self.load_file(file_name, mesh_builder, _use_numpystl = use_numpystl)

        mesh = mesh_builder.build()

        if use_numpystl:
            verts = mesh.getVertices()
            # In some cases numpy stl reads incorrectly and the result is that the Z values are all 0
            # Add new error cases if you find them.
            if numpy.amin(verts[:, 1]) == numpy.amax(verts[:, 1]):
                # Something may have gone wrong in numpy stl, start over without numpy stl
                Logger.log("w", "All Z coordinates are the same using numpystl, trying again without numpy stl.")
                mesh_builder = MeshBuilder()
                self.load_file(file_name, mesh_builder, _use_numpystl = False)
                mesh = mesh_builder.build()

                verts = mesh.getVertices()
                if numpy.amin(verts[:, 1]) == numpy.amax(verts[:, 1]):
                    Logger.log("e", "All Z coordinates are still the same without numpy stl... let's hope for the best")

        if mesh_builder.getVertexCount() == 0:
            Logger.log("d", "File did not contain valid data, unable to read.")
            return None  # We didn't load anything.
        scene_node.setMeshData(mesh)
        Logger.log("d", "Loaded a mesh with %s vertices", mesh_builder.getVertexCount())

        return scene_node
    def buildMesh(self):
        #SOLIDMESH
        mb = MeshBuilder()

        mb.addDonut(
            inner_radius = self._inner_radius,
            outer_radius = self._outer_radius,
            width = self._line_width,
            color = self._z_axis_color
        )

        mb.addDonut(
            inner_radius = self._inner_radius,
            outer_radius = self._outer_radius,
            width = self._line_width,
            axis = Vector.Unit_X,
            angle = math.pi / 2,
            color = self._y_axis_color
        )

        mb.addDonut(
            inner_radius = self._inner_radius,
            outer_radius = self._outer_radius,
            width = self._line_width,
            axis = Vector.Unit_Y,
            angle = math.pi / 2,
            color = self._x_axis_color
        )
        self.setSolidMesh(mb.build())

        #SELECTIONMESH
        mb = MeshBuilder()

        mb.addDonut(
            inner_radius = self._active_inner_radius,
            outer_radius = self._active_outer_radius,
            width = self._active_line_width,
            color = ToolHandle.ZAxisSelectionColor
        )

        mb.addDonut(
            inner_radius = self._active_inner_radius,
            outer_radius = self._active_outer_radius,
            width = self._active_line_width,
            axis = Vector.Unit_X,
            angle = math.pi / 2,
            color = ToolHandle.YAxisSelectionColor
        )

        mb.addDonut(
            inner_radius = self._active_inner_radius,
            outer_radius = self._active_outer_radius,
            width = self._active_line_width,
            axis = Vector.Unit_Y,
            angle = math.pi / 2,
            color = ToolHandle.XAxisSelectionColor
        )

        self.setSelectionMesh(mb.build())
Exemple #3
0
    def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        mesh = MeshBuilder()
        mesh.addCube(10,10,10)
        node.setMeshData(mesh.build())

        active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
        node.addDecorator(BuildPlateDecorator(active_build_plate))
        node.addDecorator(SliceableObjectDecorator())

        stack = node.callDecoration("getStack") # created by SettingOverrideDecorator that is automatically added to CuraSceneNode
        settings = stack.getTop()

        definition = stack.getSettingDefinition("anti_overhang_mesh")
        new_instance = SettingInstance(definition, settings)
        new_instance.setProperty("value", True)
        new_instance.resetState()  # Ensure that the state is not seen as a user state.
        settings.addInstance(new_instance)

        op = GroupedOperation()
        # First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent
        op.addOperation(AddSceneNodeOperation(node, self._controller.getScene().getRoot()))
        op.addOperation(SetParentOperation(node, parent))
        op.push()
        node.setPosition(position, CuraSceneNode.TransformSpace.World)

        Application.getInstance().getController().getScene().sceneChanged.emit(node)
Exemple #4
0
def test_render():
    mocked_shader = MagicMock()
    with patch("UM.View.GL.OpenGL.OpenGL.getInstance"):
        render_batch = RenderBatch(mocked_shader)

    # Render without a camera shouldn't cause any effect.
    render_batch.render(None)
    assert mocked_shader.bind.call_count == 0

    # Rendering with a camera should cause the shader to be bound and released (even if the batch is empty)
    mocked_camera = MagicMock()
    mocked_camera.getWorldTransformation = MagicMock(return_value = Matrix())
    mocked_camera.getViewProjectionMatrix = MagicMock(return_value=Matrix())
    with patch("UM.View.GL.OpenGLContext.OpenGLContext.properties"):
        render_batch.render(mocked_camera)
    assert mocked_shader.bind.call_count == 1
    assert mocked_shader.release.call_count == 1

    # Actualy render with an item in the batch
    mb = MeshBuilder()
    mb.addPyramid(10, 10, 10, color=Color(0.0, 1.0, 0.0, 1.0))
    mb.calculateNormals()
    mesh_data = mb.build()
    render_batch.addItem(Matrix(), mesh_data, {})
    with patch("UM.View.GL.OpenGL.OpenGL.getInstance"):
        with patch("UM.View.GL.OpenGLContext.OpenGLContext.properties"):
            render_batch.render(mocked_camera)
    assert mocked_shader.bind.call_count == 2
    assert mocked_shader.release.call_count == 2
Exemple #5
0
    def __init__(self, node, hull, thickness, parent = None):
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._original_parent = parent

        # Color of the drawn convex hull
        if Application.getInstance().hasGui():
            self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
        else:
            self._color = Color(0, 0, 0)

        # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
        self._mesh_height = 0.1

        self._thickness = thickness

        # The node this mesh is "watching"
        self._node = node
        self._convex_hull_head_mesh = None

        self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
        self._onNodeDecoratorsChanged(self._node)

        self._hull = hull
        if self._hull:
            hull_mesh_builder = MeshBuilder()

            if hull_mesh_builder.addConvexPolygonExtrusion(
                self._hull.getPoints()[::-1],  # bottom layer is reversed
                self._mesh_height-thickness, self._mesh_height, color=self._color):

                hull_mesh = hull_mesh_builder.build()
                self.setMeshData(hull_mesh)
    def read(self, file_name):
        mesh_builder = MeshBuilder()
        scene_node = SceneNode()

        if use_numpystl:
            self._loadWithNumpySTL(file_name, mesh_builder)
        else:
            f = open(file_name, "rb")
            if not self._loadBinary(mesh_builder, f):
                f.close()
                f = open(file_name, "rt")
                try:
                    self._loadAscii(mesh_builder, f)
                except UnicodeDecodeError:
                    return None
                f.close()

            Job.yieldThread() # Yield somewhat to ensure the GUI has time to update a bit.

        mesh_builder.calculateNormals(fast = True)

        mesh = mesh_builder.build()
        Logger.log("d", "Loaded a mesh with %s vertices", mesh_builder.getVertexCount())
        scene_node.setMeshData(mesh)
        return scene_node
Exemple #7
0
    def _onNodeDecoratorsChanged(self, node):
        convex_hull_head = self._node.callDecoration("getConvexHullHead")
        if convex_hull_head:
            convex_hull_head_builder = MeshBuilder()
            convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height - self._thickness)
            self._convex_hull_head_mesh = convex_hull_head_builder.build()

        if not node:
            return
Exemple #8
0
    def read(self, file_name):
        try:
            self.defs = {}
            self.shapes = []
            
            tree = ET.parse(file_name)
            xml_root = tree.getroot()
            
            if xml_root.tag != "X3D":
                return None

            scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters            
            if xml_root[0].tag == "head":
                for head_node in xml_root[0]:
                    if head_node.tag == "unit" and head_node.attrib.get("category") == "length":
                        scale *= float(head_node.attrib["conversionFactor"])
                        break 
                xml_scene = xml_root[1]
            else:
                xml_scene = xml_root[0]
                
            if xml_scene.tag != "Scene":
                return None
            
            self.transform = Matrix()
            self.transform.setByScaleFactor(scale)
            self.index_base = 0
            
            # Traverse the scene tree, populate the shapes list
            self.processChildNodes(xml_scene)
            
            if self.shapes:
                builder = MeshBuilder()
                builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes]))
                builder.setIndices(numpy.concatenate([shape.faces for shape in self.shapes]))
                builder.calculateNormals()
                builder.setFileName(file_name)
                mesh_data = builder.build()

                # Manually try and get the extents of the mesh_data. This should prevent nasty NaN issues from
                # leaving the reader.
                mesh_data.getExtents()

                node = SceneNode()
                node.setMeshData(mesh_data)
                node.setSelectable(True)
                node.setName(file_name)

            else:
                return None
            
        except Exception:
            Logger.logException("e", "Exception in X3D reader")
            return None

        return node
def test_compute2DConvexHullMeshData(convex_hull_decorator):
    node = SceneNode()
    mb = MeshBuilder()
    mb.addCube(10,10,10)
    node.setMeshData(mb.build())

    convex_hull_decorator._getSettingProperty = MagicMock(return_value = 0)

    with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)):
        convex_hull_decorator.setNode(node)

    assert convex_hull_decorator._compute2DConvexHull() == Polygon([[5.0,-5.0], [-5.0,-5.0], [-5.0,5.0], [5.0,5.0]])
Exemple #10
0
def test_getExtents():
    # Create a cube mesh at position 0,0,0
    builder = MeshBuilder()
    builder.addCube(20, 20, 20)
    mesh_data = builder.build()

    extents = mesh_data.getExtents()
    assert extents.width == 20
    assert extents.height == 20
    assert extents.depth == 20

    assert extents.maximum == Vector(10, 10, 10)
    assert extents.minimum == Vector(-10, -10, -10)
Exemple #11
0
    def createHullMesh(self, hull_points):
        # Input checking.
        if len(hull_points) < 3:
            return None

        mesh_builder = MeshBuilder()
        point_first = Vector(hull_points[0][0], self._mesh_height, hull_points[0][1])
        point_previous = Vector(hull_points[1][0], self._mesh_height, hull_points[1][1])
        for point in hull_points[2:]:  # Add the faces in the order of a triangle fan.
            point_new = Vector(point[0], self._mesh_height, point[1])
            mesh_builder.addFace(point_first, point_previous, point_new, color = self._color)
            point_previous = point_new  # Prepare point_previous for the next triangle.

        return mesh_builder.build()
Exemple #12
0
    def run(self):
        layer_data = None
        for node in DepthFirstIterator(self._scene.getRoot()):
            layer_data = node.callDecoration("getLayerData")
            if layer_data:
                break

        if self._cancel or not layer_data:
            return

        layer_mesh = MeshBuilder()
        for i in range(self._solid_layers):
            layer_number = self._layer_number - i
            if layer_number < 0:
                continue

            try:
                layer = layer_data.getLayer(layer_number).createMesh()
            except Exception:
                Logger.logException("w", "An exception occurred while creating layer mesh.")
                return

            if not layer or layer.getVertices() is None:
                continue

            layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices())
            layer_mesh.addVertices(layer.getVertices())

            # Scale layer color by a brightness factor based on the current layer number
            # This will result in a range of 0.5 - 1.0 to multiply colors by.
            brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
            brightness[0, 3] = 1.0
            layer_mesh.addColors(layer.getColors() * brightness)

            if self._cancel:
                return

            Job.yieldThread()

        if self._cancel:
            return

        Job.yieldThread()
        jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
        if not jump_mesh or jump_mesh.getVertices() is None:
            jump_mesh = None

        self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
Exemple #13
0
def test_getExtentsTransposed():
    # Create a cube mesh at position 0,0,0
    builder = MeshBuilder()
    builder.addCube(20, 20, 20)
    mesh_data = builder.build()

    transformation_matrix = Matrix()
    transformation_matrix.setByTranslation(Vector(10, 10, 10))

    extents = mesh_data.getExtents(transformation_matrix)
    assert extents.width == 20
    assert extents.height == 20
    assert extents.depth == 20

    assert extents.maximum == Vector(20, 20, 20)
    assert extents.minimum == Vector(0, 0, 0)
Exemple #14
0
    def createMeshOrJumps(self, make_mesh):
        builder = MeshBuilder()
        
        line_count = 0
        if make_mesh:
            for polygon in self._polygons:
                line_count += polygon.meshLineCount
        else:
            for polygon in self._polygons:
                line_count += polygon.jumpCount

        # Reserve the neccesary space for the data upfront
        builder.reserveFaceAndVertexCount(2 * line_count, 4 * line_count)
        
        for polygon in self._polygons:
            # Filter out the types of lines we are not interesed in depending on whether we are drawing the mesh or the jumps.
            index_mask = numpy.logical_not(polygon.jumpMask) if make_mesh else polygon.jumpMask

            # Create an array with rows [p p+1] and only keep those we whant to draw based on make_mesh
            points = numpy.concatenate((polygon.data[:-1], polygon.data[1:]), 1)[index_mask.ravel()]
            # Line types of the points we want to draw
            line_types = polygon.types[index_mask]
            
            # Shift the z-axis according to previous implementation.
            if make_mesh:
                points[polygon.isInfillOrSkinType(line_types), 1::3] -= 0.01
            else:
                points[:, 1::3] += 0.01

            # Create an array with normals and tile 2 copies to match size of points variable
            normals = numpy.tile( polygon.getNormals()[index_mask.ravel()], (1, 2))

            # Scale all normals by the line width of the current line so we can easily offset.
            normals *= (polygon.lineWidths[index_mask.ravel()] / 2)

            # Create 4 points to draw each line segment, points +- normals results in 2 points each.
            # After this we reshape to one point per line.
            f_points = numpy.concatenate((points-normals, points+normals), 1).reshape((-1, 3))

            # __index_pattern defines which points to use to draw the two faces for each lines egment, the following linesegment is offset by 4
            f_indices = ( self.__index_pattern + numpy.arange(0, 4 * len(normals), 4, dtype=numpy.int32).reshape((-1, 1)) ).reshape((-1, 3))
            f_colors = numpy.repeat(polygon.mapLineTypeToColor(line_types), 4, 0)

            builder.addFacesWithColor(f_points, f_indices, f_colors)
        
        return builder.build()
Exemple #15
0
    def calculateBoundingBoxMesh(self):
        aabb = self.getBoundingBox()
        if aabb:
            bounding_box_mesh = MeshBuilder()
            rtf = aabb.maximum
            lbb = aabb.minimum

            bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z)  # Right - Top - Front
            bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z)  # Left - Top - Front

            bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z)  # Left - Top - Front
            bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z)  # Left - Bottom - Front

            bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z)  # Left - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z)  # Right - Bottom - Front

            bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z)  # Right - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z)  # Right - Top - Front

            bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z)  # Right - Top - Back
            bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z)  # Left - Top - Back

            bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z)  # Left - Top - Back
            bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z)  # Left - Bottom - Back

            bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z)  # Left - Bottom - Back
            bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z)  # Right - Bottom - Back

            bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z)  # Right - Bottom - Back
            bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z)  # Right - Top - Back

            bounding_box_mesh.addVertex(rtf.x, rtf.y, rtf.z)  # Right - Top - Front
            bounding_box_mesh.addVertex(rtf.x, rtf.y, lbb.z)  # Right - Top - Back

            bounding_box_mesh.addVertex(lbb.x, rtf.y, rtf.z)  # Left - Top - Front
            bounding_box_mesh.addVertex(lbb.x, rtf.y, lbb.z)  # Left - Top - Back

            bounding_box_mesh.addVertex(lbb.x, lbb.y, rtf.z)  # Left - Bottom - Front
            bounding_box_mesh.addVertex(lbb.x, lbb.y, lbb.z)  # Left - Bottom - Back

            bounding_box_mesh.addVertex(rtf.x, lbb.y, rtf.z)  # Right - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, lbb.y, lbb.z)  # Right - Bottom - Back

            self._bounding_box_mesh = bounding_box_mesh.build()
def test_compute2DConvexHullMeshDataGrouped(convex_hull_decorator):
    parent_node = SceneNode()
    parent_node.addDecorator(GroupDecorator())
    node = SceneNode()
    parent_node.addChild(node)

    mb = MeshBuilder()
    mb.addCube(10, 10, 10)
    node.setMeshData(mb.build())

    convex_hull_decorator._getSettingProperty = MagicMock(return_value=0)

    with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)):
        convex_hull_decorator.setNode(parent_node)
        with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance"):
            copied_decorator = copy.deepcopy(convex_hull_decorator)
            copied_decorator._getSettingProperty = MagicMock(return_value=0)
        node.addDecorator(copied_decorator)
    assert convex_hull_decorator._compute2DConvexHull() == Polygon([[-5.0,5.0], [5.0,5.0], [5.0,-5.0], [-5.0,-5.0]])
Exemple #17
0
    def setMeshDataFromPywimTriangles(self,
                                      face: pywim.geom.tri.Face,
                                      axis: pywim.geom.Vector = None):

        if len(face.triangles) == 0:
            return

        self.face = face
        self.axis = axis

        mb = MeshBuilder()

        for tri in self.face.triangles:
            mb.addFace(tri.v1, tri.v2, tri.v3)

        mb.calculateNormals()

        self.setMeshData(mb.build())

        self._setupTools()
Exemple #18
0
    def createHullMesh(self, hull_points):
        # Input checking.
        if len(hull_points) < 3:
            return None

        mesh_builder = MeshBuilder()
        point_first = Vector(hull_points[0][0], self._mesh_height,
                             hull_points[0][1])
        point_previous = Vector(hull_points[1][0], self._mesh_height,
                                hull_points[1][1])
        for point in hull_points[
                2:]:  # Add the faces in the order of a triangle fan.
            point_new = Vector(point[0], self._mesh_height, point[1])
            mesh_builder.addFace(point_first,
                                 point_previous,
                                 point_new,
                                 color=self._color)
            point_previous = point_new  # Prepare point_previous for the next triangle.

        return mesh_builder.build()
Exemple #19
0
    def test_setCenterPosition(self):
        node = SceneNode()
        child_node = SceneNode()
        node.addChild(child_node)
        child_node.setCenterPosition = MagicMock()

        builder = MeshBuilder()
        builder.addVertex(10, 20, 20)
        node.setMeshData(builder.build())

        node.setCenterPosition(Vector(-10, 0, 0))

        transformed_mesh = node.getMeshData()

        transformed_vertex = transformed_mesh.getVertices()[0]
        assert transformed_vertex[0] == 20
        assert transformed_vertex[1] == 20
        assert transformed_vertex[2] == 20

        child_node.setCenterPosition.assert_called_once_with(Vector(-10, 0, 0))
Exemple #20
0
    def drawSelection(self):
        if self._tri is None:
            return

        ph = self._connector.propertyHandler
        if ph._selection_mode is SelectionMode.AnchorMode:
            self.setFace(ph._anchoredTris)
        else:
            self.setFace(ph._loadedTris)

        #  Construct Edges using MeshBuilder Cubes
        mb = MeshBuilder()

        for tri in self._tri:
            mb.addFace(tri.v1, tri.v2, tri.v3, color=self._selected_color)

        if self._connector.propertyHandler._selection_mode == SelectionMode.LoadMode:
            self.paintArrow(self._tri, mb)

        #  Add to Cura Scene
        self.setSolidMesh(mb.build())
Exemple #21
0
    def __init__(self, node: SceneNode, hull: Optional[Polygon], thickness: float, parent: Optional[SceneNode] = None) -> None:
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._original_parent = parent

        # Color of the drawn convex hull
        if not Application.getInstance().getIsHeadLess():
            theme = QtApplication.getInstance().getTheme()
            if theme:
                self._color = Color(*theme.getColor("convex_hull").getRgb())
            else:
                self._color = Color(0, 0, 0)
        else:
            self._color = Color(0, 0, 0)

        # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
        self._mesh_height = 0.1

        self._thickness = thickness

        # The node this mesh is "watching"
        self._node = node
        self._convex_hull_head_mesh = None

        self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
        self._onNodeDecoratorsChanged(self._node)

        self._hull = hull
        if self._hull:
            hull_mesh_builder = MeshBuilder()

            if hull_mesh_builder.addConvexPolygonExtrusion(
                self._hull.getPoints()[::-1],  # bottom layer is reversed
                self._mesh_height-thickness, self._mesh_height, color=self._color):

                hull_mesh = hull_mesh_builder.build()
                self.setMeshData(hull_mesh)
Exemple #22
0
    def _createEraserMesh(self):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        mesh = MeshBuilder()
        mesh.addCube(10, 10, 10)
        node.setMeshData(mesh.build())

        active_build_plate = Application.getInstance().getBuildPlateModel(
        ).activeBuildPlate

        node.addDecorator(SettingOverrideDecorator())
        node.addDecorator(BuildPlateDecorator(active_build_plate))
        node.addDecorator(SliceableObjectDecorator())

        stack = node.callDecoration(
            "getStack"
        )  #Don't try to get the active extruder since it may be None anyway.
        if not stack:
            node.addDecorator(SettingOverrideDecorator())
            stack = node.callDecoration("getStack")

        settings = stack.getTop()

        if not (settings.getInstance("anti_overhang_mesh")
                and settings.getProperty("anti_overhang_mesh", "value")):
            definition = stack.getSettingDefinition("anti_overhang_mesh")
            new_instance = SettingInstance(definition, settings)
            new_instance.setProperty("value", True)
            new_instance.resetState(
            )  # Ensure that the state is not seen as a user state.
            settings.addInstance(new_instance)

        scene = self._controller.getScene()
        op = AddSceneNodeOperation(node, scene.getRoot())
        op.push()
        Application.getInstance().getController().getScene().sceneChanged.emit(
            node)
Exemple #23
0
def test_compute2DConvexHullMeshDataGrouped(convex_hull_decorator):
    parent_node = SceneNode()
    parent_node.addDecorator(GroupDecorator())
    node = SceneNode()
    parent_node.addChild(node)

    mb = MeshBuilder()
    mb.addCube(10, 10, 10)
    node.setMeshData(mb.build())

    convex_hull_decorator._getSettingProperty = MagicMock(return_value=0)

    with patch("UM.Application.Application.getInstance",
               MagicMock(return_value=mocked_application)):
        convex_hull_decorator.setNode(parent_node)
        with patch(
                "cura.Settings.ExtruderManager.ExtruderManager.getInstance"):
            copied_decorator = copy.deepcopy(convex_hull_decorator)
            copied_decorator._getSettingProperty = MagicMock(return_value=0)
        node.addDecorator(copied_decorator)
    assert convex_hull_decorator._compute2DConvexHull() == Polygon(
        [[-5.0, 5.0], [5.0, 5.0], [5.0, -5.0], [-5.0, -5.0]])
Exemple #24
0
    def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        mesh = MeshBuilder()
        mesh.addCube(10, 10, 10)
        node.setMeshData(mesh.build())
        node.setPosition(position)

        active_build_plate = Application.getInstance().getMultiBuildPlateModel(
        ).activeBuildPlate

        node.addDecorator(SettingOverrideDecorator())
        node.addDecorator(BuildPlateDecorator(active_build_plate))
        node.addDecorator(SliceableObjectDecorator())

        stack = node.callDecoration(
            "getStack")  # created by SettingOverrideDecorator
        settings = stack.getTop()

        definition = stack.getSettingDefinition("anti_overhang_mesh")
        new_instance = SettingInstance(definition, settings)
        new_instance.setProperty("value", True)
        new_instance.resetState(
        )  # Ensure that the state is not seen as a user state.
        settings.addInstance(new_instance)

        root = self._controller.getScene().getRoot()

        op = GroupedOperation()
        # First add the node to the scene, so it gets the expected transform
        op.addOperation(AddSceneNodeOperation(node, root))
        op.addOperation(SetParentOperation(node, parent))
        op.push()

        Application.getInstance().getController().getScene().sceneChanged.emit(
            node)
    def _createSupportMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        mesh = MeshBuilder()
        mesh.addCube(5, 5, 5)
        node.setMeshData(mesh.build())

        active_build_plate = Application.getInstance().getMultiBuildPlateModel(
        ).activeBuildPlate
        node.addDecorator(BuildPlateDecorator(active_build_plate))
        node.addDecorator(SliceableObjectDecorator())

        stack = node.callDecoration(
            "getStack"
        )  # created by SettingOverrideDecorator that is automatically added to CuraSceneNode
        settings = stack.getTop()

        definition = stack.getSettingDefinition("support_mesh")
        new_instance = SettingInstance(definition, settings)
        new_instance.setProperty("value", True)
        new_instance.resetState(
        )  # Ensure that the state is not seen as a user state.
        settings.addInstance(new_instance)

        op = GroupedOperation()
        # First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent
        op.addOperation(
            AddSceneNodeOperation(node,
                                  self._controller.getScene().getRoot()))
        op.addOperation(SetParentOperation(node, parent))
        op.push()
        node.setPosition(position, CuraSceneNode.TransformSpace.World)

        Application.getInstance().getController().getScene().sceneChanged.emit(
            node)
Exemple #26
0
    def createMeshOrJumps(self, make_mesh):
        builder = MeshBuilder()

        for polygon in self._polygons:
            if make_mesh and (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
                continue
            if not make_mesh and not (polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType):
                continue

            poly_color = polygon.getColor()

            points = numpy.copy(polygon.data)
            if polygon.type == LayerPolygon.InfillType or polygon.type == LayerPolygon.SkinType or polygon.type == LayerPolygon.SupportInfillType:
                points[:,1] -= 0.01
            if polygon.type == LayerPolygon.MoveCombingType or polygon.type == LayerPolygon.MoveRetractionType:
                points[:,1] += 0.01

            normals = polygon.getNormals()

            # Scale all by the line width of the polygon so we can easily offset.
            normals *= (polygon.lineWidth / 2)

            #TODO: Use numpy magic to perform the vertex creation to speed up things.
            for i in range(len(points)):
                start = points[i - 1]
                end = points[i]

                normal = normals[i - 1]

                point1 = Vector(data = start - normal)
                point2 = Vector(data = start + normal)
                point3 = Vector(data = end + normal)
                point4 = Vector(data = end - normal)

                builder.addQuad(point1, point2, point3, point4, color = poly_color)

        return builder.build()
Exemple #27
0
    def __init__(self, node, hull, thickness, parent=None):
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._shader = None

        self._original_parent = parent

        # Color of the drawn convex hull
        self._color = None

        # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
        self._mesh_height = 0.1

        self._thickness = thickness

        # The node this mesh is "watching"
        self._node = node
        self._convex_hull_head_mesh = None

        self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
        self._onNodeDecoratorsChanged(self._node)

        self._hull = hull
        if self._hull:
            hull_mesh_builder = MeshBuilder()

            if hull_mesh_builder.addConvexPolygonExtrusion(
                    self._hull.getPoints()[::-1],  # bottom layer is reversed
                    self._mesh_height - thickness,
                    self._mesh_height,
                    color=self._color):

                hull_mesh = hull_mesh_builder.build()
                self.setMeshData(hull_mesh)
Exemple #28
0
 def _getAxisMesh(self, node):
     mb = MeshBuilder()
     mb.addCube(
         width=self.axis_width,
         height=self.axis_height,
         depth=self.axis_width,
         color=self.YAxisColor,
         center = Vector(0, self.axis_height / 2, 0)
     )
     mb.addCube(
         width=self.axis_width,
         height=self.axis_width,
         depth=self.axis_height,
         color=self.ZAxisColor,
         center=Vector(0, 0, self.axis_height / 2)
     )
     mb.addCube(
         width=self.axis_height,
         height=self.axis_width,
         depth=self.axis_width,
         color=self.XAxisColor,
         center=Vector(self.axis_height / 2, 0, 0)
     )
     return mb.build().getTransformed(node.getWorldTransformation())
Exemple #29
0
    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
Exemple #30
0
    def read(self, file_name):
        result = SceneNode()
        # The base object of 3mf is a zipped archive.
        archive = zipfile.ZipFile(file_name, "r")
        try:
            root = ET.parse(archive.open("3D/3dmodel.model"))

            # There can be multiple objects, try to load all of them.
            objects = root.findall("./3mf:resources/3mf:object", self._namespaces)
            if len(objects) == 0:
                Logger.log("w", "No objects found in 3MF file %s, either the file is corrupt or you are using an outdated format", file_name)
                return None

            for entry in objects:
                mesh_builder = MeshBuilder()
                node = SceneNode()
                vertex_list = []
                #for vertex in entry.mesh.vertices.vertex:
                for vertex in entry.findall(".//3mf:vertex", self._namespaces):
                    vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
                    Job.yieldThread()

                triangles = entry.findall(".//3mf:triangle", self._namespaces)
                mesh_builder.reserveFaceCount(len(triangles))

                #for triangle in object.mesh.triangles.triangle:
                for triangle in triangles:
                    v1 = int(triangle.get("v1"))
                    v2 = int(triangle.get("v2"))
                    v3 = int(triangle.get("v3"))

                    mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2],
                                                 vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2],
                                                 vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2])

                    Job.yieldThread()

                # Rotate the model; We use a different coordinate frame.
                rotation = Matrix()
                rotation.setByRotationAxis(-0.5 * math.pi, Vector(1,0,0))

                #TODO: We currently do not check for normals and simply recalculate them.
                mesh_builder.calculateNormals()

                node.setMeshData(mesh_builder.build().getTransformed(rotation))
                node.setSelectable(True)

                transformations = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(entry.get("id")), self._namespaces)
                transformation = transformations[0] if transformations else None
                if transformation is not None and transformation.get("transform"):
                    splitted_transformation = transformation.get("transform").split()
                    ## Transformation is saved as:
                    ## M00 M01 M02 0.0
                    ## M10 M11 M12 0.0
                    ## M20 M21 M22 0.0
                    ## M30 M31 M32 1.0
                    ## We switch the row & cols as that is how everyone else uses matrices!
                    temp_mat = Matrix()
                    # Rotation & Scale
                    temp_mat._data[0,0] = splitted_transformation[0]
                    temp_mat._data[1,0] = splitted_transformation[1]
                    temp_mat._data[2,0] = splitted_transformation[2]
                    temp_mat._data[0,1] = splitted_transformation[3]
                    temp_mat._data[1,1] = splitted_transformation[4]
                    temp_mat._data[2,1] = splitted_transformation[5]
                    temp_mat._data[0,2] = splitted_transformation[6]
                    temp_mat._data[1,2] = splitted_transformation[7]
                    temp_mat._data[2,2] = splitted_transformation[8]

                    # Translation
                    temp_mat._data[0,3] = splitted_transformation[9]
                    temp_mat._data[1,3] = splitted_transformation[10]
                    temp_mat._data[2,3] = splitted_transformation[11]

                    node.setTransformation(temp_mat)

                result.addChild(node)

                Job.yieldThread()

            #If there is more then one object, group them.
            if len(objects) > 1:
                group_decorator = GroupDecorator()
                result.addDecorator(group_decorator)
        except Exception as e:
            Logger.log("e" ,"exception occured in 3mf reader: %s" , e)

        return result  
Exemple #31
0
    def _createNodeFromObject(self, object, name=""):
        node = SceneNode()
        node.setName(name)
        mesh_builder = MeshBuilder()
        vertex_list = []

        components = object.find(".//3mf:components", self._namespaces)
        if components:
            for component in components:
                id = component.get("objectid")
                new_object = self._root.find(
                    "./3mf:resources/3mf:object[@id='{0}']".format(id),
                    self._namespaces)
                new_node = self._createNodeFromObject(
                    new_object, self._base_name + "_" + str(id))
                node.addChild(new_node)
                transform = component.get("transform")
                if transform is not None:
                    new_node.setTransformation(
                        self._createMatrixFromTransformationString(transform))

        # for vertex in entry.mesh.vertices.vertex:
        for vertex in object.findall(".//3mf:vertex", self._namespaces):
            vertex_list.append(
                [vertex.get("x"),
                 vertex.get("y"),
                 vertex.get("z")])
            Job.yieldThread()

        xml_settings = list(object.findall(".//cura:setting",
                                           self._namespaces))

        # Add the setting override decorator, so we can add settings to this node.
        if xml_settings:
            node.addDecorator(SettingOverrideDecorator())

            global_container_stack = Application.getInstance(
            ).getGlobalContainerStack()
            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                multi_extrusion = global_container_stack.getProperty(
                    "machine_extruder_count", "value") > 1
                # Ensure that all extruder data is reset
                if not multi_extrusion:
                    default_stack_id = global_container_stack.getId()
                else:
                    default_stack = ExtruderManager.getInstance(
                    ).getExtruderStack(0)
                    if default_stack:
                        default_stack_id = default_stack.getId()
                    else:
                        default_stack_id = global_container_stack.getId()
                node.callDecoration("setActiveExtruder", default_stack_id)

                # Get the definition & set it
                definition = QualityManager.getInstance(
                ).getParentMachineDefinition(
                    global_container_stack.getBottom())
                node.callDecoration("getStack").getTop().setDefinition(
                    definition)

            setting_container = node.callDecoration("getStack").getTop()
            for setting in xml_settings:
                setting_key = setting.get("key")
                setting_value = setting.text

                # Extruder_nr is a special case.
                if setting_key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance(
                    ).getExtruderStack(int(setting_value))
                    if extruder_stack:
                        node.callDecoration("setActiveExtruder",
                                            extruder_stack.getId())
                    else:
                        Logger.log("w",
                                   "Unable to find extruder in position %s",
                                   setting_value)
                    continue
                setting_container.setProperty(setting_key, "value",
                                              setting_value)

        if len(node.getChildren()) > 0:
            group_decorator = GroupDecorator()
            node.addDecorator(group_decorator)

        triangles = object.findall(".//3mf:triangle", self._namespaces)
        mesh_builder.reserveFaceCount(len(triangles))

        for triangle in triangles:
            v1 = int(triangle.get("v1"))
            v2 = int(triangle.get("v2"))
            v3 = int(triangle.get("v3"))

            mesh_builder.addFaceByPoints(
                vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2],
                vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2],
                vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2])

            Job.yieldThread()

        # TODO: We currently do not check for normals and simply recalculate them.
        mesh_builder.calculateNormals(fast=True)
        mesh_builder.setFileName(name)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            node.setMeshData(mesh_data)

        node.setSelectable(True)
        return node
Exemple #32
0
    def buildMesh(self):
        mb = MeshBuilder()

        #SOLIDMESH
        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, self._handle_position, 0),
            color = self._y_axis_color
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, -self._handle_position, 0),
            color = self._y_axis_color,
            axis = Vector.Unit_X,
            angle = 180
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(self._handle_position, 0, 0),
            color = self._x_axis_color,
            axis = Vector.Unit_Z,
            angle = 90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(-self._handle_position, 0, 0),
            color = self._x_axis_color,
            axis = Vector.Unit_Z,
            angle = -90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, 0, -self._handle_position),
            color = self._z_axis_color,
            axis = Vector.Unit_X,
            angle = 90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, 0, self._handle_position),
            color = self._z_axis_color,
            axis = Vector.Unit_X,
            angle = -90
        )

        self.setSolidMesh(mb.build())

        #SELECTIONMESH
        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, self._handle_position, 0),
            color = ToolHandle.YAxisSelectionColor
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, -self._handle_position, 0),
            color = ToolHandle.YAxisSelectionColor,
            axis = Vector.Unit_X,
            angle = 180
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(self._handle_position, 0, 0),
            color = ToolHandle.XAxisSelectionColor,
            axis = Vector.Unit_Z,
            angle = 90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(-self._handle_position, 0, 0),
            color = ToolHandle.XAxisSelectionColor,
            axis = Vector.Unit_Z,
            angle = -90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, 0, -self._handle_position),
            color = ToolHandle.ZAxisSelectionColor,
            axis = Vector.Unit_X,
            angle = 90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, 0, self._handle_position),
            color = ToolHandle.ZAxisSelectionColor,
            axis = Vector.Unit_X,
            angle = -90
        )

        self.setSelectionMesh(mb.build())
    def read(self, file_name):
        scene_node = None

        extension = os.path.splitext(file_name)[1]
        if extension.lower() in self._supported_extensions:
            vertex_list = []
            normal_list = []
            uv_list = []
            face_list = []
            scene_node = SceneNode()

            mesh_builder = MeshBuilder()
            mesh_builder.setFileName(file_name)
            f = open(file_name, "rt")
            for line in f:
                parts = line.split()
                if len(parts) < 1:
                    continue
                if parts[0] == "v":
                    vertex_list.append([float(parts[1]), float(parts[3]), -float(parts[2])])
                if parts[0] == "vn":
                    normal_list.append([float(parts[1]), float(parts[3]), -float(parts[2])])
                if parts[0] == "vt":
                    uv_list.append([float(parts[1]), float(parts[2])])
                if parts[0] == "f":
                    parts = [i for i in map(lambda p: p.split("/"), parts)]
                    for idx in range(1, len(parts)-2):
                        data = [int(parts[1][0]), int(parts[idx+1][0]), int(parts[idx+2][0])]
                        if len(parts[1]) > 2:
                            data += [int(parts[1][2]), int(parts[idx+1][2]), int(parts[idx+2][2])]

                            if parts[1][1] and parts[idx+1][1] and parts[idx+2][1]:
                                data += [int(parts[1][1]), int(parts[idx+1][1]), int(parts[idx+2][1])]
                        face_list.append(data)
                Job.yieldThread()
            f.close()

            mesh_builder.reserveVertexCount(3 * len(face_list))
            num_vertices = len(vertex_list)
            num_normals = len(normal_list)

            for face in face_list:
                # Substract 1 from index, as obj starts counting at 1 instead of 0
                i = face[0] - 1
                j = face[1] - 1
                k = face[2] - 1

                if len(face) > 3:
                    ni = face[3] - 1
                    nj = face[4] - 1
                    nk = face[5] - 1
                else:
                    ni = -1
                    nj = -1
                    nk = -1

                if len(face) > 6:
                    ui = face[6] - 1
                    uj = face[7] - 1
                    uk = face[8] - 1
                else:
                    ui = -1
                    uj = -1
                    uk = -1

                #TODO: improve this handling, this can cause weird errors (negative indexes are relative indexes, and are not properly handled)
                if i < 0 or i >= num_vertices:
                    i = 0
                if j < 0 or j >= num_vertices:
                    j = 0
                if k < 0 or k >= num_vertices:
                    k = 0
                if ni != -1 and nj != -1 and nk != -1:
                    mesh_builder.addFaceWithNormals(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], normal_list[ni][0], normal_list[ni][1], normal_list[ni][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], normal_list[nj][0], normal_list[nj][1], normal_list[nj][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2],normal_list[nk][0], normal_list[nk][1], normal_list[nk][2])
                else:
                    mesh_builder.addFaceByPoints(vertex_list[i][0], vertex_list[i][1], vertex_list[i][2], vertex_list[j][0], vertex_list[j][1], vertex_list[j][2], vertex_list[k][0], vertex_list[k][1], vertex_list[k][2])

                if ui != -1:
                    mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 3, uv_list[ui][0], uv_list[ui][1])

                if uj != -1:
                    mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 2, uv_list[uj][0], uv_list[uj][1])

                if uk != -1:
                    mesh_builder.setVertexUVCoordinates(mesh_builder.getVertexCount() - 1, uv_list[uk][0], uv_list[uk][1])

                Job.yieldThread()
            if not mesh_builder.hasNormals():
                mesh_builder.calculateNormals(fast = True)
            scene_node.setMeshData(mesh_builder.build())

        return scene_node
Exemple #34
0
    def setNode(self, node):
        super().setNode(node)

        aabb = node.getBoundingBox()
        if not aabb.isValid():
            return

        if not self._body:
            self._body = ode.Body(self._world)
            self._body.setMaxAngularSpeed(0)
            mass = ode.Mass()
            mass.setBox(5.0, Helpers.toODE(aabb.width),
                        Helpers.toODE(aabb.height), Helpers.toODE(aabb.depth))
            self._body.setMass(mass)

        if not self._geom:
            if node.getMeshData():
                scale_matrix = Matrix()
                scale_matrix.setByScaleFactor(1.01)
                mesh = node.getMeshData().getTransformed(scale_matrix)

                self._trimesh = ode.TriMeshData()

                debug_builder = MeshBuilder()

                vertices = mesh.getVertices()
                indices = mesh.getConvexHull().simplices

                _fixWindingOrder(vertices, indices, debug_builder)

                self._trimesh.build(vertices / Helpers.ScaleFactor, indices)

                self._geom = ode.GeomTriMesh(self._trimesh, self._space)

                mb = MeshBuilder()

                for i in range(self._geom.getTriangleCount()):
                    tri = self._geom.getTriangle(i)

                    v0 = Helpers.fromODE(tri[0])
                    v1 = Helpers.fromODE(tri[1])
                    v2 = Helpers.fromODE(tri[2])

                    mb.addFace(v0=v0,
                               v1=v1,
                               v2=v2,
                               color=Color(1.0, 0.0, 0.0, 0.5))

                chn = SceneNode(node)
                chn.setMeshData(mb.build())

                def _renderConvexHull(renderer):
                    renderer.queueNode(chn, transparent=True)
                    return True

                chn.render = _renderConvexHull

                n = SceneNode(node)
                n.setMeshData(debug_builder.build())

                def _renderNormals(renderer):
                    renderer.queueNode(n, mode=1, overlay=True)
                    return True

                n.render = _renderNormals
            else:
                self._geom = ode.GeomBox(self._space,
                                         lengths=(Helpers.toODE(aabb.width),
                                                  Helpers.toODE(aabb.height),
                                                  Helpers.toODE(aabb.depth)))

            self._geom.setBody(self._body)

        self._body.setPosition(Helpers.toODE(node.getWorldPosition()))

        node.transformationChanged.connect(self._onTransformationChanged)
Exemple #35
0
    def beginRendering(self):
        # Convenience setup
        scene = self.getController().getScene()
        renderer = self.getRenderer()

        if not self._shader:
            self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
            self._shader.setUniformValue("u_color", Color(32, 32, 32, 170))

        for node in DepthFirstIterator(scene.getRoot()):
            if not node.render(renderer):
                # For now we only render nodes that indicate that they need rendering.
                if node.getMeshData():
                    # Render origin of this node.
                    renderer.queueNode(scene.getRoot(), mesh = self._getAxisMesh(node), transparent = True)

                    # Render transparent MeshData
                    renderer.queueNode(node, shader = self._shader, transparent = True)

                    billboard_node = self._ensureNodeHasBillboard(node)

                    # Update the displayed data on the billboard.
                    data = self._matrixToHtml(node.getLocalTransformation())
                    billboard_node.setDisplayData({"name": node.getName(), "matrix": data, "depth": node.getDepth(), "parent_name": node.getParent().getName(), "has_mesh": "True"})

                # Handle group nodes
                if node.callDecoration("isGroup"):
                    # Render origin of this node.
                    renderer.queueNode(scene.getRoot(), mesh=self._getAxisMesh(node), transparent = True)
                    # Render bounding box of this node
                    renderer.queueNode(scene.getRoot(), mesh=node.getBoundingBoxMesh(), mode=Renderer.RenderLines)

                    billboard_node = self._ensureNodeHasBillboard(node)

                    # Update the displayed data on the billboard.
                    data = self._matrixToHtml(node.getLocalTransformation())
                    billboard_node.setDisplayData({"name": node.getName(), "matrix": data, "depth": node.getDepth(), "parent_name": node.getParent().getName(), "has_mesh": node.getMeshData() is not None})

                # We sometimes have nodes that are not groups, but have children. Also draw them
                elif not node.getMeshData() and len(node.getChildren()) != 0:
                    # Render origin of this node.
                    renderer.queueNode(scene.getRoot(), mesh=self._getAxisMesh(node), transparent = True)

                    billboard_node = self._ensureNodeHasBillboard(node)

                    # Update the displayed data on the billboard.
                    data = self._matrixToHtml(node.getLocalTransformation())
                    parent_name = node.getParent().getName() if node.getParent() else ""
                    billboard_node.setDisplayData({"name": node.getName(), "matrix": data, "depth": node.getDepth(), "parent_name": parent_name, "has_mesh": "False"})
                    bounding_box = node.getBoundingBox()
                    if bounding_box:
                        mesh_builder = MeshBuilder()
                        mesh_builder.addCube(
                            width=bounding_box.width,
                            height=bounding_box.height,
                            depth=bounding_box.depth,
                            center=bounding_box.center,
                            color=Color(0.0, 0.0, 1.0, 1.0)
                        )
                        mesh = mesh_builder.build()
                        renderer.queueNode(scene.getRoot(), mesh=mesh, mode=Renderer.RenderLines)
    def __init__(self, parent = None):
        super().__init__(parent)
        self._inner_radius = 40
        self._outer_radius = 40.5
        self._line_width = 0.5
        self._active_inner_radius = 37
        self._active_outer_radius = 44
        self._active_line_width = 3

        #SOLIDMESH
        mb = MeshBuilder()

        mb.addDonut(
            inner_radius = self._inner_radius,
            outer_radius = self._outer_radius,
            width = self._line_width,
            color = ToolHandle.ZAxisColor
        )

        mb.addDonut(
            inner_radius = self._inner_radius,
            outer_radius = self._outer_radius,
            width = self._line_width,
            axis = Vector.Unit_X,
            angle = math.pi / 2,
            color = ToolHandle.YAxisColor
        )

        mb.addDonut(
            inner_radius = self._inner_radius,
            outer_radius = self._outer_radius,
            width = self._line_width,
            axis = Vector.Unit_Y,
            angle = math.pi / 2,
            color = ToolHandle.XAxisColor
        )
        self.setSolidMesh(mb.build())

        #SELECTIONMESH
        mb = MeshBuilder()

        mb.addDonut(
            inner_radius = self._active_inner_radius,
            outer_radius = self._active_outer_radius,
            width = self._active_line_width,
            color = ToolHandle.ZAxisColor
        )

        mb.addDonut(
            inner_radius = self._active_inner_radius,
            outer_radius = self._active_outer_radius,
            width = self._active_line_width,
            axis = Vector.Unit_X,
            angle = math.pi / 2,
            color = ToolHandle.YAxisColor
        )

        mb.addDonut(
            inner_radius = self._active_inner_radius,
            outer_radius = self._active_outer_radius,
            width = self._active_line_width,
            axis = Vector.Unit_Y,
            angle = math.pi / 2,
            color = ToolHandle.XAxisColor
        )

        self.setSelectionMesh(mb.build())
Exemple #37
0
    def buildMesh(self):
        mb = MeshBuilder()

        #SOLIDMESH -> LINES
        if self.YAxis in self._enabled_axis:
            mb.addCube(width=self._line_width,
                       height=self._line_length,
                       depth=self._line_width,
                       center=Vector(0, self._handle_position / 2, 0),
                       color=self._y_axis_color)
        if self.XAxis in self._enabled_axis:
            mb.addCube(width=self._line_length,
                       height=self._line_width,
                       depth=self._line_width,
                       center=Vector(self._handle_position / 2, 0, 0),
                       color=self._x_axis_color)

        if self.ZAxis in self._enabled_axis:
            mb.addCube(width=self._line_width,
                       height=self._line_width,
                       depth=self._line_length,
                       center=Vector(0, 0, self._handle_position / 2),
                       color=self._z_axis_color)

        #SOLIDMESH -> HANDLES
        if self.YAxis in self._enabled_axis:
            mb.addPyramid(width=self._handle_width,
                          height=self._handle_height,
                          depth=self._handle_width,
                          center=Vector(0, self._handle_position, 0),
                          color=self._y_axis_color)

        if self.XAxis in self._enabled_axis:
            mb.addPyramid(width=self._handle_width,
                          height=self._handle_height,
                          depth=self._handle_width,
                          center=Vector(self._handle_position, 0, 0),
                          color=self._x_axis_color,
                          axis=Vector.Unit_Z,
                          angle=90)

        if self.ZAxis in self._enabled_axis:
            mb.addPyramid(width=self._handle_width,
                          height=self._handle_height,
                          depth=self._handle_width,
                          center=Vector(0, 0, self._handle_position),
                          color=self._z_axis_color,
                          axis=Vector.Unit_X,
                          angle=-90)

        self.setSolidMesh(mb.build())

        mb = MeshBuilder()
        #SELECTIONMESH -> LINES
        if self.YAxis in self._enabled_axis:
            mb.addCube(width=self._active_line_width,
                       height=self._active_line_length,
                       depth=self._active_line_width,
                       center=Vector(0, self._active_handle_position / 2, 0),
                       color=self._y_axis_color)
        if self.XAxis in self._enabled_axis:
            mb.addCube(width=self._active_line_length,
                       height=self._active_line_width,
                       depth=self._active_line_width,
                       center=Vector(self._active_handle_position / 2, 0, 0),
                       color=self._x_axis_color)

        if self.ZAxis in self._enabled_axis:
            mb.addCube(width=self._active_line_width,
                       height=self._active_line_width,
                       depth=self._active_line_length,
                       center=Vector(0, 0, self._active_handle_position / 2),
                       color=self._z_axis_color)

        #SELECTIONMESH -> HANDLES
        mb.addCube(width=self._active_handle_width,
                   height=self._active_handle_width,
                   depth=self._active_handle_width,
                   center=Vector(0, 0, 0),
                   color=ToolHandle.AllAxisSelectionColor)

        mb.addCube(width=self._active_handle_width,
                   height=self._active_handle_width,
                   depth=self._active_handle_width,
                   center=Vector(0, self._active_handle_position, 0),
                   color=ToolHandle.YAxisSelectionColor)

        mb.addCube(width=self._active_handle_width,
                   height=self._active_handle_width,
                   depth=self._active_handle_width,
                   center=Vector(self._active_handle_position, 0, 0),
                   color=ToolHandle.XAxisSelectionColor)

        mb.addCube(width=self._active_handle_width,
                   height=self._active_handle_width,
                   depth=self._active_handle_width,
                   center=Vector(0, 0, self._active_handle_position),
                   color=ToolHandle.ZAxisSelectionColor)
        self.setSelectionMesh(mb.build())
    def __init__(self, parent = None):
        super().__init__(parent)
        self._handle_width = 8
        self._handle_height = 14
        self._handle_position = 20

        mb = MeshBuilder()

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, self._handle_position, 0),
            color = ToolHandle.YAxisColor
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, -self._handle_position, 0),
            color = ToolHandle.YAxisColor,
            axis = Vector.Unit_X,
            angle = 180
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(self._handle_position, 0, 0),
            color = ToolHandle.XAxisColor,
            axis = Vector.Unit_Z,
            angle = 90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(-self._handle_position, 0, 0),
            color = ToolHandle.XAxisColor,
            axis = Vector.Unit_Z,
            angle = -90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, 0, -self._handle_position),
            color = ToolHandle.ZAxisColor,
            axis = Vector.Unit_X,
            angle = 90
        )

        mb.addPyramid(
            width = self._handle_width,
            height = self._handle_height,
            depth = self._handle_width,
            center = Vector(0, 0, self._handle_position),
            color = ToolHandle.ZAxisColor,
            axis = Vector.Unit_X,
            angle = -90
        )
        mesh = mb.build()
        self.setSolidMesh(mesh)
        self.setSelectionMesh(mesh)
Exemple #39
0
    def _createNodeFromObject(self, object, name = ""):
        node = SceneNode()
        node.setName(name)
        mesh_builder = MeshBuilder()
        vertex_list = []

        components = object.find(".//3mf:components", self._namespaces)
        if components:
            for component in components:
                id = component.get("objectid")
                new_object = self._root.find("./3mf:resources/3mf:object[@id='{0}']".format(id), self._namespaces)
                new_node = self._createNodeFromObject(new_object, self._base_name + "_" + str(id))
                node.addChild(new_node)
                transform = component.get("transform")
                if transform is not None:
                    new_node.setTransformation(self._createMatrixFromTransformationString(transform))

        # for vertex in entry.mesh.vertices.vertex:
        for vertex in object.findall(".//3mf:vertex", self._namespaces):
            vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")])
            Job.yieldThread()

        xml_settings = list(object.findall(".//cura:setting", self._namespaces))

        # Add the setting override decorator, so we can add settings to this node.
        if xml_settings:
            node.addDecorator(SettingOverrideDecorator())

            global_container_stack = Application.getInstance().getGlobalContainerStack()
            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
                # Ensure that all extruder data is reset
                if not multi_extrusion:
                    default_stack_id = global_container_stack.getId()
                else:
                    default_stack = ExtruderManager.getInstance().getExtruderStack(0)
                    if default_stack:
                        default_stack_id = default_stack.getId()
                    else:
                        default_stack_id = global_container_stack.getId()
                node.callDecoration("setActiveExtruder", default_stack_id)

                # Get the definition & set it
                definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())
                node.callDecoration("getStack").getTop().setDefinition(definition)

            setting_container = node.callDecoration("getStack").getTop()
            for setting in xml_settings:
                setting_key = setting.get("key")
                setting_value = setting.text

                # Extruder_nr is a special case.
                if setting_key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value))
                    if extruder_stack:
                        node.callDecoration("setActiveExtruder", extruder_stack.getId())
                    else:
                        Logger.log("w", "Unable to find extruder in position %s", setting_value)
                    continue
                setting_container.setProperty(setting_key,"value", setting_value)

        if len(node.getChildren()) > 0:
            group_decorator = GroupDecorator()
            node.addDecorator(group_decorator)

        triangles = object.findall(".//3mf:triangle", self._namespaces)
        mesh_builder.reserveFaceCount(len(triangles))

        for triangle in triangles:
            v1 = int(triangle.get("v1"))
            v2 = int(triangle.get("v2"))
            v3 = int(triangle.get("v3"))

            mesh_builder.addFaceByPoints(vertex_list[v1][0], vertex_list[v1][1], vertex_list[v1][2],
                                         vertex_list[v2][0], vertex_list[v2][1], vertex_list[v2][2],
                                         vertex_list[v3][0], vertex_list[v3][1], vertex_list[v3][2])

            Job.yieldThread()

        # TODO: We currently do not check for normals and simply recalculate them.
        mesh_builder.calculateNormals()
        mesh_builder.setFileName(name)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            node.setMeshData(mesh_data)

        node.setSelectable(True)
        return node
Exemple #40
0
    def buildMesh(self):
        #SOLIDMESH -> LINES
        mb = MeshBuilder()

        mb.addCube(width=self._line_width,
                   height=self._line_length,
                   depth=self._line_width,
                   center=Vector(0, self._handle_position / 2, 0),
                   color=self._y_axis_color)
        mb.addCube(width=self._line_length,
                   height=self._line_width,
                   depth=self._line_width,
                   center=Vector(self._handle_position / 2, 0, 0),
                   color=self._x_axis_color)

        mb.addCube(width=self._line_width,
                   height=self._line_width,
                   depth=self._line_length,
                   center=Vector(0, 0, self._handle_position / 2),
                   color=self._z_axis_color)

        #SOLIDMESH -> HANDLES
        mb.addCube(width=self._handle_width,
                   height=self._handle_width,
                   depth=self._handle_width,
                   center=Vector(0, 0, 0),
                   color=self._all_axis_color)

        mb.addCube(width=self._handle_width,
                   height=self._handle_width,
                   depth=self._handle_width,
                   center=Vector(0, self._handle_position, 0),
                   color=self._y_axis_color)

        mb.addCube(width=self._handle_width,
                   height=self._handle_width,
                   depth=self._handle_width,
                   center=Vector(self._handle_position, 0, 0),
                   color=self._x_axis_color)

        mb.addCube(width=self._handle_width,
                   height=self._handle_width,
                   depth=self._handle_width,
                   center=Vector(0, 0, self._handle_position),
                   color=self._z_axis_color)
        self.setSolidMesh(mb.build())

        #SELECTIONMESH -> LINES
        mb = MeshBuilder()
        mb.addCube(width=self._active_line_width,
                   height=self._active_line_length,
                   depth=self._active_line_width,
                   center=Vector(0, self._active_handle_position / 2, 0),
                   color=ToolHandle.YAxisSelectionColor)

        mb.addCube(width=self._active_line_length,
                   height=self._active_line_width,
                   depth=self._active_line_width,
                   center=Vector(self._active_handle_position / 2, 0, 0),
                   color=ToolHandle.XAxisSelectionColor)

        mb.addCube(width=self._active_line_width,
                   height=self._active_line_width,
                   depth=self._active_line_length,
                   center=Vector(0, 0, self._active_handle_position / 2),
                   color=ToolHandle.ZAxisSelectionColor)

        #SELECTIONMESH -> HANDLES
        mb.addCube(width=self._active_handle_width,
                   height=self._active_handle_width,
                   depth=self._active_handle_width,
                   center=Vector(0, 0, 0),
                   color=ToolHandle.AllAxisSelectionColor)

        mb.addCube(width=self._active_handle_width,
                   height=self._active_handle_width,
                   depth=self._active_handle_width,
                   center=Vector(0, self._active_handle_position, 0),
                   color=ToolHandle.YAxisSelectionColor)

        mb.addCube(width=self._active_handle_width,
                   height=self._active_handle_width,
                   depth=self._active_handle_width,
                   center=Vector(self._active_handle_position, 0, 0),
                   color=ToolHandle.XAxisSelectionColor)

        mb.addCube(width=self._active_handle_width,
                   height=self._active_handle_width,
                   depth=self._active_handle_width,
                   center=Vector(0, 0, self._active_handle_position),
                   color=ToolHandle.ZAxisSelectionColor)

        self.setSelectionMesh(mb.build())
    def __init__(self, parent = None):
        super().__init__(parent)

        self._line_width = 0.5
        self._line_length= 40
        self._handle_position = 40
        self._handle_width = 4

        self._active_line_width = 0.8
        self._active_line_length = 40
        self._active_handle_position = 40
        self._active_handle_width = 15

        #SOLIDMESH -> LINES
        mb = MeshBuilder()

        mb.addCube(
            width = self._line_width,
            height = self._line_length,
            depth = self._line_width,
            center = Vector(0, self._handle_position/2, 0),
            color = ToolHandle.YAxisColor
        )
        mb.addCube(
            width = self._line_length,
            height = self._line_width,
            depth = self._line_width,
            center = Vector(self._handle_position/2, 0, 0),
            color = ToolHandle.XAxisColor
        )

        mb.addCube(
            width = self._line_width,
            height = self._line_width,
            depth = self._line_length,
            center = Vector(0, 0, self._handle_position/2),
            color = ToolHandle.ZAxisColor
        )

        #SOLIDMESH -> HANDLES
        mb.addCube(
            width = self._handle_width,
            height = self._handle_width,
            depth = self._handle_width,
            center = Vector(0, 0, 0),
            color = ToolHandle.AllAxisColor
        )

        mb.addCube(
            width = self._handle_width,
            height = self._handle_width,
            depth = self._handle_width,
            center = Vector(0, self._handle_position, 0),
            color = ToolHandle.YAxisColor
        )

        mb.addCube(
            width = self._handle_width,
            height = self._handle_width,
            depth = self._handle_width,
            center = Vector(self._handle_position, 0, 0),
            color = ToolHandle.XAxisColor
        )

        mb.addCube(
            width = self._handle_width,
            height = self._handle_width,
            depth = self._handle_width,
            center = Vector(0, 0, self._handle_position),
            color = ToolHandle.ZAxisColor
        )
        self.setSolidMesh(mb.build())

        #SELECTIONMESH -> LINES
        mb = MeshBuilder()
        mb.addCube(
            width = self._active_line_width,
            height = self._active_line_length,
            depth = self._active_line_width,
            center = Vector(0, self._active_handle_position/2, 0),
            color = ToolHandle.YAxisColor
        )

        mb.addCube(
            width = self._active_line_length,
            height = self._active_line_width,
            depth = self._active_line_width,
            center = Vector(self._active_handle_position/2, 0, 0),
            color = ToolHandle.XAxisColor
        )

        mb.addCube(
            width = self._active_line_width,
            height = self._active_line_width,
            depth = self._active_line_length,
            center = Vector(0, 0, self._active_handle_position/2),
            color = ToolHandle.ZAxisColor
        )

        #SELECTIONMESH -> HANDLES
        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, 0, 0),
            color = ToolHandle.AllAxisColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, self._active_handle_position, 0),
            color = ToolHandle.YAxisColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(self._active_handle_position, 0, 0),
            color = ToolHandle.XAxisColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, 0, self._active_handle_position),
            color = ToolHandle.ZAxisColor
        )

        self.setSelectionMesh(mb.build())
Exemple #42
0
    def read(self, file_name):
        scene_node = None

        extension = os.path.splitext(file_name)[1]
        if extension.lower() in self._supported_extensions:
            vertex_list = []
            normal_list = []
            uv_list = []
            face_list = []
            scene_node = SceneNode()

            mesh_builder = MeshBuilder()
            mesh_builder.setFileName(file_name)
            f = open(file_name, "rt")
            for line in f:
                parts = line.split()
                if len(parts) < 1:
                    continue
                if parts[0] == "v":
                    vertex_list.append(
                        [float(parts[1]),
                         float(parts[3]), -float(parts[2])])
                if parts[0] == "vn":
                    normal_list.append(
                        [float(parts[1]),
                         float(parts[3]), -float(parts[2])])
                if parts[0] == "vt":
                    uv_list.append([float(parts[1]), float(parts[2])])
                if parts[0] == "f":
                    parts = [i for i in map(lambda p: p.split("/"), parts)]
                    for idx in range(1, len(parts) - 2):
                        data = [
                            int(parts[1][0]),
                            int(parts[idx + 1][0]),
                            int(parts[idx + 2][0])
                        ]
                        if len(parts[1]) > 2:
                            data += [
                                int(parts[1][2]),
                                int(parts[idx + 1][2]),
                                int(parts[idx + 2][2])
                            ]

                            if parts[1][1] and parts[idx +
                                                     1][1] and parts[idx +
                                                                     2][1]:
                                data += [
                                    int(parts[1][1]),
                                    int(parts[idx + 1][1]),
                                    int(parts[idx + 2][1])
                                ]
                        face_list.append(data)
                Job.yieldThread()
            f.close()

            mesh_builder.reserveVertexCount(3 * len(face_list))
            num_vertices = len(vertex_list)
            num_normals = len(normal_list)

            for face in face_list:
                # Substract 1 from index, as obj starts counting at 1 instead of 0
                i = face[0] - 1
                j = face[1] - 1
                k = face[2] - 1

                if len(face) > 3:
                    ni = face[3] - 1
                    nj = face[4] - 1
                    nk = face[5] - 1
                else:
                    ni = -1
                    nj = -1
                    nk = -1

                if len(face) > 6:
                    ui = face[6] - 1
                    uj = face[7] - 1
                    uk = face[8] - 1
                else:
                    ui = -1
                    uj = -1
                    uk = -1

                #TODO: improve this handling, this can cause weird errors (negative indexes are relative indexes, and are not properly handled)
                if i < 0 or i >= num_vertices:
                    i = 0
                if j < 0 or j >= num_vertices:
                    j = 0
                if k < 0 or k >= num_vertices:
                    k = 0
                if ni != -1 and nj != -1 and nk != -1:
                    mesh_builder.addFaceWithNormals(
                        vertex_list[i][0], vertex_list[i][1],
                        vertex_list[i][2], normal_list[ni][0],
                        normal_list[ni][1], normal_list[ni][2],
                        vertex_list[j][0], vertex_list[j][1],
                        vertex_list[j][2], normal_list[nj][0],
                        normal_list[nj][1], normal_list[nj][2],
                        vertex_list[k][0], vertex_list[k][1],
                        vertex_list[k][2], normal_list[nk][0],
                        normal_list[nk][1], normal_list[nk][2])
                else:
                    mesh_builder.addFaceByPoints(
                        vertex_list[i][0], vertex_list[i][1],
                        vertex_list[i][2], vertex_list[j][0],
                        vertex_list[j][1], vertex_list[j][2],
                        vertex_list[k][0], vertex_list[k][1],
                        vertex_list[k][2])

                if ui != -1:
                    mesh_builder.setVertexUVCoordinates(
                        mesh_builder.getVertexCount() - 3, uv_list[ui][0],
                        uv_list[ui][1])

                if uj != -1:
                    mesh_builder.setVertexUVCoordinates(
                        mesh_builder.getVertexCount() - 2, uv_list[uj][0],
                        uv_list[uj][1])

                if uk != -1:
                    mesh_builder.setVertexUVCoordinates(
                        mesh_builder.getVertexCount() - 1, uv_list[uk][0],
                        uv_list[uk][1])

                Job.yieldThread()
            if not mesh_builder.hasNormals():
                mesh_builder.calculateNormals(fast=True)
            scene_node.setMeshData(mesh_builder.build())

        return scene_node
    def buildMesh(self):
        mb = MeshBuilder()

        #SOLIDMESH -> LINES
        if self.YAxis in self._enabled_axis:
            mb.addCube(
                width = self._line_width,
                height = self._line_length,
                depth = self._line_width,
                center = Vector(0, self._handle_position/2, 0),
                color = self._y_axis_color
            )
        if self.XAxis in self._enabled_axis:
            mb.addCube(
                width = self._line_length,
                height = self._line_width,
                depth = self._line_width,
                center = Vector(self._handle_position/2, 0, 0),
                color = self._x_axis_color
            )

        if self.ZAxis in self._enabled_axis:
            mb.addCube(
                width = self._line_width,
                height = self._line_width,
                depth = self._line_length,
                center = Vector(0, 0, self._handle_position/2),
                color = self._z_axis_color
            )

        #SOLIDMESH -> HANDLES
        if self.YAxis in self._enabled_axis:
            mb.addPyramid(
                width = self._handle_width,
                height = self._handle_height,
                depth = self._handle_width,
                center = Vector(0, self._handle_position, 0),
                color = self._y_axis_color
            )

        if self.XAxis in self._enabled_axis:
            mb.addPyramid(
                width = self._handle_width,
                height = self._handle_height,
                depth = self._handle_width,
                center = Vector(self._handle_position, 0, 0),
                color = self._x_axis_color,
                axis = Vector.Unit_Z,
                angle = 90
            )

        if self.ZAxis in self._enabled_axis:
            mb.addPyramid(
                width = self._handle_width,
                height = self._handle_height,
                depth = self._handle_width,
                center = Vector(0, 0, self._handle_position),
                color = self._z_axis_color,
                axis = Vector.Unit_X,
                angle = -90
            )

        self.setSolidMesh(mb.build())

        mb = MeshBuilder()
        #SELECTIONMESH -> LINES
        if self.YAxis in self._enabled_axis:
            mb.addCube(
                width = self._active_line_width,
                height = self._active_line_length,
                depth = self._active_line_width,
                center = Vector(0, self._active_handle_position/2, 0),
                color = self._y_axis_color
            )
        if self.XAxis in self._enabled_axis:
            mb.addCube(
                width = self._active_line_length,
                height = self._active_line_width,
                depth = self._active_line_width,
                center = Vector(self._active_handle_position/2, 0, 0),
                color = self._x_axis_color
            )

        if self.ZAxis in self._enabled_axis:
            mb.addCube(
                width = self._active_line_width,
                height = self._active_line_width,
                depth = self._active_line_length,
                center = Vector(0, 0, self._active_handle_position/2),
                color = self._z_axis_color
            )

        #SELECTIONMESH -> HANDLES
        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, 0, 0),
            color = ToolHandle.AllAxisSelectionColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, self._active_handle_position, 0),
            color = ToolHandle.YAxisSelectionColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(self._active_handle_position, 0, 0),
            color = ToolHandle.XAxisSelectionColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, 0, self._active_handle_position),
            color = ToolHandle.ZAxisSelectionColor
        )
        self.setSelectionMesh(mb.build())
Exemple #44
0
    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

        mb = MeshBuilder()

        # Outline 'cube' of the build volume
        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())

        mb = MeshBuilder()
        mb.addQuad(
            Vector(min_w, min_h - 0.2, min_d),
            Vector(max_w, min_h - 0.2, min_d),
            Vector(max_w, min_h - 0.2, max_d),
            Vector(min_w, min_h - 0.2, max_d)
        )

        for n in range(0, 6):
            v = mb.getVertex(n)
            mb.setVertexUVCoordinates(n, v[0], v[2])
        self._grid_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._prime_tower_area:
            mb = MeshBuilder()
            color = Color(1.0, 0.0, 0.0, 0.5)
            points = self._prime_tower_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._prime_tower_area_mesh = mb.build()
        else:
            self._prime_tower_area_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 = 0.0

        container_stack = Application.getInstance().getGlobalContainerStack()
        if container_stack:
            bed_adhesion_size = self._getBedAdhesionSize(container_stack)

        # 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
Exemple #45
0
    def _generateSceneNode(self, file_name, xz_size, peak_height, base_height,
                           blur_iterations, max_size, lighter_is_higher,
                           use_transparency_model, transmittance_1mm):
        scene_node = SceneNode()

        mesh = MeshBuilder()

        img = QImage(file_name)

        if img.isNull():
            Logger.log("e", "Image is corrupt.")
            return None

        width = max(img.width(), 2)
        height = max(img.height(), 2)
        aspect = height / width

        if img.width() < 2 or img.height() < 2:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        base_height = max(base_height, 0)
        peak_height = max(peak_height, -base_height)

        xz_size = max(xz_size, 1)
        scale_vector = Vector(xz_size, peak_height, xz_size)

        if width > height:
            scale_vector = scale_vector.set(z=scale_vector.z * aspect)
        elif height > width:
            scale_vector = scale_vector.set(x=scale_vector.x / aspect)

        if width > max_size or height > max_size:
            scale_factor = max_size / width
            if height > width:
                scale_factor = max_size / height

            width = int(max(round(width * scale_factor), 2))
            height = int(max(round(height * scale_factor), 2))
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        width_minus_one = width - 1
        height_minus_one = height - 1

        Job.yieldThread()

        texel_width = 1.0 / (width_minus_one) * scale_vector.x
        texel_height = 1.0 / (height_minus_one) * scale_vector.z

        height_data = numpy.zeros((height, width), dtype=numpy.float32)

        for x in range(0, width):
            for y in range(0, height):
                qrgb = img.pixel(x, y)
                if use_transparency_model:
                    height_data[y, x] = (
                        0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) +
                        0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) +
                        0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2))
                else:
                    height_data[y, x] = (
                        0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) +
                        0.072187 * qBlue(qrgb)
                    ) / 255  # fast computation ignoring gamma and degamma

        Job.yieldThread()

        if lighter_is_higher == use_transparency_model:
            height_data = 1 - height_data

        for _ in range(0, blur_iterations):
            copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode="edge")

            height_data += copy[1:-1, 2:]
            height_data += copy[1:-1, :-2]
            height_data += copy[2:, 1:-1]
            height_data += copy[:-2, 1:-1]

            height_data += copy[2:, 2:]
            height_data += copy[:-2, 2:]
            height_data += copy[2:, :-2]
            height_data += copy[:-2, :-2]

            height_data /= 9

            Job.yieldThread()

        if use_transparency_model:
            divisor = 1.0 / math.log(
                transmittance_1mm / 100.0
            )  # log-base doesn't matter here. Precompute this value for faster computation of each pixel.
            min_luminance = (transmittance_1mm / 100.0)**(peak_height -
                                                          base_height)
            for (y, x) in numpy.ndindex(height_data.shape):
                mapped_luminance = min_luminance + (
                    1.0 - min_luminance) * height_data[y, x]
                height_data[y, x] = base_height + divisor * math.log(
                    mapped_luminance
                )  # use same base as a couple lines above this
        else:
            height_data *= scale_vector.y
            height_data += base_height

        if img.hasAlphaChannel():
            for x in range(0, width):
                for y in range(0, height):
                    height_data[y, x] *= qAlpha(img.pixel(x, y)) / 255.0

        heightmap_face_count = 2 * height_minus_one * width_minus_one
        total_face_count = heightmap_face_count + (width_minus_one * 2) * (
            height_minus_one * 2) + 2

        mesh.reserveFaceCount(total_face_count)

        # initialize to texel space vertex offsets.
        # 6 is for 6 vertices for each texel quad.
        heightmap_vertices = numpy.zeros(
            (width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32)
        heightmap_vertices = heightmap_vertices + numpy.array(
            [[[0, base_height, 0], [0, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, 0], [0, base_height, 0]]],
            dtype=numpy.float32)

        offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width - 1]
        offsetsx = numpy.array(offsetsx, numpy.float32).reshape(
            -1, 1) * texel_width
        offsetsz = numpy.array(offsetsz, numpy.float32).reshape(
            -1, 1) * texel_height

        # offsets for each texel quad
        heightmap_vertex_offsets = numpy.concatenate([
            offsetsx,
            numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]),
                        dtype=numpy.float32), offsetsz
        ], 1)
        heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(
            -1, 6, 3)

        # apply height data to y values
        heightmap_vertices[:, 0,
                           1] = heightmap_vertices[:, 5,
                                                   1] = height_data[:-1, :
                                                                    -1].reshape(
                                                                        -1)
        heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
        heightmap_vertices[:, 2,
                           1] = heightmap_vertices[:, 3, 1] = height_data[
                               1:, 1:].reshape(-1)
        heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)

        heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count *
                                                    3],
                                        dtype=numpy.int32).reshape(-1, 3)

        mesh._vertices[0:(heightmap_vertices.size //
                          3), :] = heightmap_vertices.reshape(-1, 3)
        mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices

        mesh._vertex_count = heightmap_vertices.size // 3
        mesh._face_count = heightmap_indices.size // 3

        geo_width = width_minus_one * texel_width
        geo_height = height_minus_one * texel_height

        # bottom
        mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0,
                             geo_height)
        mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0,
                             0)

        # north and south walls
        for n in range(0, width_minus_one):
            x = n * texel_width
            nx = (n + 1) * texel_width

            hn0 = height_data[0, n]
            hn1 = height_data[0, n + 1]

            hs0 = height_data[height_minus_one, n]
            hs1 = height_data[height_minus_one, n + 1]

            mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0)
            mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0)

            mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1,
                                 geo_height)
            mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0,
                                 geo_height)

        # west and east walls
        for n in range(0, height_minus_one):
            y = n * texel_height
            ny = (n + 1) * texel_height

            hw0 = height_data[n, 0]
            hw1 = height_data[n + 1, 0]

            he0 = height_data[n, width_minus_one]
            he1 = height_data[n + 1, width_minus_one]

            mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny)
            mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y)

            mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width,
                                 he1, ny)
            mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y,
                                 geo_width, 0, y)

        mesh.calculateNormals(fast=True)

        scene_node.setMeshData(mesh.build())

        return scene_node
Exemple #46
0
    def _generateSceneNode(self, file_name, xz_size, peak_height, base_height,
                           blur_iterations, max_size, lighter_is_higher):
        scene_node = SceneNode()

        mesh = MeshBuilder()

        img = QImage(file_name)

        if img.isNull():
            Logger.log("e", "Image is corrupt.")
            return None

        width = max(img.width(), 2)
        height = max(img.height(), 2)
        aspect = height / width

        if img.width() < 2 or img.height() < 2:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        base_height = max(base_height, 0)
        peak_height = max(peak_height, -base_height)

        xz_size = max(xz_size, 1)
        scale_vector = Vector(xz_size, peak_height, xz_size)

        if width > height:
            scale_vector = scale_vector.set(z=scale_vector.z * aspect)
        elif height > width:
            scale_vector = scale_vector.set(x=scale_vector.x / aspect)

        if width > max_size or height > max_size:
            scale_factor = max_size / width
            if height > width:
                scale_factor = max_size / height

            width = int(max(round(width * scale_factor), 2))
            height = int(max(round(height * scale_factor), 2))
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        width_minus_one = width - 1
        height_minus_one = height - 1

        Job.yieldThread()

        texel_width = 1.0 / (width_minus_one) * scale_vector.x
        texel_height = 1.0 / (height_minus_one) * scale_vector.z

        height_data = numpy.zeros((height, width), dtype=numpy.float32)

        for x in range(0, width):
            for y in range(0, height):
                qrgb = img.pixel(x, y)
                avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 *
                                                                        255)
                height_data[y, x] = avg

        Job.yieldThread()

        if not lighter_is_higher:
            height_data = 1 - height_data

        for _ in range(0, blur_iterations):
            copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode="edge")

            height_data += copy[1:-1, 2:]
            height_data += copy[1:-1, :-2]
            height_data += copy[2:, 1:-1]
            height_data += copy[:-2, 1:-1]

            height_data += copy[2:, 2:]
            height_data += copy[:-2, 2:]
            height_data += copy[2:, :-2]
            height_data += copy[:-2, :-2]

            height_data /= 9

            Job.yieldThread()

        height_data *= scale_vector.y
        height_data += base_height

        heightmap_face_count = 2 * height_minus_one * width_minus_one
        total_face_count = heightmap_face_count + (width_minus_one * 2) * (
            height_minus_one * 2) + 2

        mesh.reserveFaceCount(total_face_count)

        # initialize to texel space vertex offsets.
        # 6 is for 6 vertices for each texel quad.
        heightmap_vertices = numpy.zeros(
            (width_minus_one * height_minus_one, 6, 3), dtype=numpy.float32)
        heightmap_vertices = heightmap_vertices + numpy.array(
            [[[0, base_height, 0], [0, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, texel_height],
              [texel_width, base_height, 0], [0, base_height, 0]]],
            dtype=numpy.float32)

        offsetsz, offsetsx = numpy.mgrid[0:height_minus_one, 0:width - 1]
        offsetsx = numpy.array(offsetsx, numpy.float32).reshape(
            -1, 1) * texel_width
        offsetsz = numpy.array(offsetsz, numpy.float32).reshape(
            -1, 1) * texel_height

        # offsets for each texel quad
        heightmap_vertex_offsets = numpy.concatenate([
            offsetsx,
            numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]),
                        dtype=numpy.float32), offsetsz
        ], 1)
        heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(
            -1, 6, 3)

        # apply height data to y values
        heightmap_vertices[:, 0,
                           1] = heightmap_vertices[:, 5,
                                                   1] = height_data[:-1, :
                                                                    -1].reshape(
                                                                        -1)
        heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
        heightmap_vertices[:, 2,
                           1] = heightmap_vertices[:, 3, 1] = height_data[
                               1:, 1:].reshape(-1)
        heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)

        heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count *
                                                    3],
                                        dtype=numpy.int32).reshape(-1, 3)

        mesh._vertices[0:(heightmap_vertices.size //
                          3), :] = heightmap_vertices.reshape(-1, 3)
        mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices

        mesh._vertex_count = heightmap_vertices.size // 3
        mesh._face_count = heightmap_indices.size // 3

        geo_width = width_minus_one * texel_width
        geo_height = height_minus_one * texel_height

        # bottom
        mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0,
                             geo_height)
        mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0,
                             0)

        # north and south walls
        for n in range(0, width_minus_one):
            x = n * texel_width
            nx = (n + 1) * texel_width

            hn0 = height_data[0, n]
            hn1 = height_data[0, n + 1]

            hs0 = height_data[height_minus_one, n]
            hs1 = height_data[height_minus_one, n + 1]

            mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0)
            mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0)

            mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1,
                                 geo_height)
            mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0,
                                 geo_height)

        # west and east walls
        for n in range(0, height_minus_one):
            y = n * texel_height
            ny = (n + 1) * texel_height

            hw0 = height_data[n, 0]
            hw1 = height_data[n + 1, 0]

            he0 = height_data[n, width_minus_one]
            he1 = height_data[n + 1, width_minus_one]

            mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny)
            mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y)

            mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width,
                                 he1, ny)
            mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y,
                                 geo_width, 0, y)

        mesh.calculateNormals(fast=True)

        scene_node.setMeshData(mesh.build())

        return scene_node
Exemple #47
0
    def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
        scene_node = SceneNode()

        mesh = MeshBuilder()

        img = QImage(file_name)

        if img.isNull():
            Logger.log("e", "Image is corrupt.")
            return None

        width = max(img.width(), 2)
        height = max(img.height(), 2)
        aspect = height / width

        if img.width() < 2 or img.height() < 2:
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        base_height = max(base_height, 0)
        peak_height = max(peak_height, -base_height)

        xz_size = max(xz_size, 1)
        scale_vector = Vector(xz_size, peak_height, xz_size)

        if width > height:
            scale_vector = scale_vector.set(z=scale_vector.z * aspect)
        elif height > width:
            scale_vector = scale_vector.set(x=scale_vector.x / aspect)

        if width > max_size or height > max_size:
            scale_factor = max_size / width
            if height > width:
                scale_factor = max_size / height

            width = int(max(round(width * scale_factor), 2))
            height = int(max(round(height * scale_factor), 2))
            img = img.scaled(width, height, Qt.IgnoreAspectRatio)

        width_minus_one = width - 1
        height_minus_one = height - 1

        Job.yieldThread()

        texel_width = 1.0 / (width_minus_one) * scale_vector.x
        texel_height = 1.0 / (height_minus_one) * scale_vector.z

        height_data = numpy.zeros((height, width), dtype=numpy.float32)

        for x in range(0, width):
            for y in range(0, height):
                qrgb = img.pixel(x, y)
                avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255)
                height_data[y, x] = avg

        Job.yieldThread()

        if image_color_invert:
            height_data = 1 - height_data

        for _ in range(0, blur_iterations):
            copy = numpy.pad(height_data, ((1, 1), (1, 1)), mode= "edge")

            height_data += copy[1:-1, 2:]
            height_data += copy[1:-1, :-2]
            height_data += copy[2:, 1:-1]
            height_data += copy[:-2, 1:-1]

            height_data += copy[2:, 2:]
            height_data += copy[:-2, 2:]
            height_data += copy[2:, :-2]
            height_data += copy[:-2, :-2]

            height_data /= 9

            Job.yieldThread()

        height_data *= scale_vector.y
        height_data += base_height

        heightmap_face_count = 2 * height_minus_one * width_minus_one
        total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2

        mesh.reserveFaceCount(total_face_count)

        # initialize to texel space vertex offsets.
        # 6 is for 6 vertices for each texel quad.
        heightmap_vertices = numpy.zeros((width_minus_one * height_minus_one, 6, 3), dtype = numpy.float32)
        heightmap_vertices = heightmap_vertices + numpy.array([[
            [0, base_height, 0],
            [0, base_height, texel_height],
            [texel_width, base_height, texel_height],
            [texel_width, base_height, texel_height],
            [texel_width, base_height, 0],
            [0, base_height, 0]
        ]], dtype = numpy.float32)

        offsetsz, offsetsx = numpy.mgrid[0: height_minus_one, 0: width - 1]
        offsetsx = numpy.array(offsetsx, numpy.float32).reshape(-1, 1) * texel_width
        offsetsz = numpy.array(offsetsz, numpy.float32).reshape(-1, 1) * texel_height

        # offsets for each texel quad
        heightmap_vertex_offsets = numpy.concatenate([offsetsx, numpy.zeros((offsetsx.shape[0], offsetsx.shape[1]), dtype=numpy.float32), offsetsz], 1)
        heightmap_vertices += heightmap_vertex_offsets.repeat(6, 0).reshape(-1, 6, 3)

        # apply height data to y values
        heightmap_vertices[:, 0, 1] = heightmap_vertices[:, 5, 1] = height_data[:-1, :-1].reshape(-1)
        heightmap_vertices[:, 1, 1] = height_data[1:, :-1].reshape(-1)
        heightmap_vertices[:, 2, 1] = heightmap_vertices[:, 3, 1] = height_data[1:, 1:].reshape(-1)
        heightmap_vertices[:, 4, 1] = height_data[:-1, 1:].reshape(-1)

        heightmap_indices = numpy.array(numpy.mgrid[0:heightmap_face_count * 3], dtype=numpy.int32).reshape(-1, 3)

        mesh._vertices[0:(heightmap_vertices.size // 3), :] = heightmap_vertices.reshape(-1, 3)
        mesh._indices[0:(heightmap_indices.size // 3), :] = heightmap_indices

        mesh._vertex_count = heightmap_vertices.size // 3
        mesh._face_count = heightmap_indices.size // 3

        geo_width = width_minus_one * texel_width
        geo_height = height_minus_one * texel_height

        # bottom
        mesh.addFaceByPoints(0, 0, 0, 0, 0, geo_height, geo_width, 0, geo_height)
        mesh.addFaceByPoints(geo_width, 0, geo_height, geo_width, 0, 0, 0, 0, 0)

        # north and south walls
        for n in range(0, width_minus_one):
            x = n * texel_width
            nx = (n + 1) * texel_width

            hn0 = height_data[0, n]
            hn1 = height_data[0, n + 1]

            hs0 = height_data[height_minus_one, n]
            hs1 = height_data[height_minus_one, n + 1]

            mesh.addFaceByPoints(x, 0, 0, nx, 0, 0, nx, hn1, 0)
            mesh.addFaceByPoints(nx, hn1, 0, x, hn0, 0, x, 0, 0)

            mesh.addFaceByPoints(x, 0, geo_height, nx, 0, geo_height, nx, hs1, geo_height)
            mesh.addFaceByPoints(nx, hs1, geo_height, x, hs0, geo_height, x, 0, geo_height)

        # west and east walls
        for n in range(0, height_minus_one):
            y = n * texel_height
            ny = (n + 1) * texel_height

            hw0 = height_data[n, 0]
            hw1 = height_data[n + 1, 0]

            he0 = height_data[n, width_minus_one]
            he1 = height_data[n + 1, width_minus_one]

            mesh.addFaceByPoints(0, 0, y, 0, 0, ny, 0, hw1, ny)
            mesh.addFaceByPoints(0, hw1, ny, 0, hw0, y, 0, 0, y)

            mesh.addFaceByPoints(geo_width, 0, y, geo_width, 0, ny, geo_width, he1, ny)
            mesh.addFaceByPoints(geo_width, he1, ny, geo_width, he0, y, geo_width, 0, y)

        mesh.calculateNormals(fast=True)

        scene_node.setMeshData(mesh.build())

        return scene_node
    def buildMesh(self):
        #SOLIDMESH
        mb = MeshBuilder()

        mb.addDonut(inner_radius=self._inner_radius,
                    outer_radius=self._outer_radius,
                    width=self._line_width,
                    color=self._z_axis_color)

        mb.addDonut(inner_radius=self._inner_radius,
                    outer_radius=self._outer_radius,
                    width=self._line_width,
                    axis=Vector.Unit_X,
                    angle=math.pi / 2,
                    color=self._y_axis_color)

        mb.addDonut(inner_radius=self._inner_radius,
                    outer_radius=self._outer_radius,
                    width=self._line_width,
                    axis=Vector.Unit_Y,
                    angle=math.pi / 2,
                    color=self._x_axis_color)

        mb.addPyramid(width=self._handle_width,
                      height=self._handle_height,
                      depth=self._handle_width,
                      center=Vector(0, self._handle_offset_a,
                                    -self._handle_offset_b),
                      color=self._x_axis_color,
                      axis=Vector.Unit_X,
                      angle=90 + self._angle_offset)
        mb.addPyramid(width=self._handle_width,
                      height=self._handle_height,
                      depth=self._handle_width,
                      center=Vector(0, self._handle_offset_a,
                                    self._handle_offset_b),
                      color=self._x_axis_color,
                      axis=Vector.Unit_X,
                      angle=-90 - self._angle_offset)

        mb.addPyramid(width=self._handle_width,
                      height=self._handle_height,
                      depth=self._handle_width,
                      center=Vector(self._handle_offset_b, 0,
                                    self._handle_offset_a),
                      color=self._y_axis_color,
                      axis=Vector.Unit_Z,
                      angle=90 - self._angle_offset)
        mb.addPyramid(width=self._handle_width,
                      height=self._handle_height,
                      depth=self._handle_width,
                      center=Vector(-self._handle_offset_b, 0,
                                    self._handle_offset_a),
                      color=self._y_axis_color,
                      axis=Vector.Unit_Z,
                      angle=-90 + self._angle_offset)

        mb.addPyramid(width=self._handle_width,
                      height=self._handle_height,
                      depth=self._handle_width,
                      center=Vector(self._handle_offset_a,
                                    self._handle_offset_b, 0),
                      color=self._z_axis_color,
                      axis=Vector.Unit_Z,
                      angle=-self._angle_offset)
        mb.addPyramid(width=self._handle_width,
                      height=self._handle_height,
                      depth=self._handle_width,
                      center=Vector(self._handle_offset_a,
                                    -self._handle_offset_b, 0),
                      color=self._z_axis_color,
                      axis=Vector.Unit_Z,
                      angle=180 + self._angle_offset)

        self.setSolidMesh(mb.build())

        #SELECTIONMESH
        mb = MeshBuilder()

        mb.addDonut(inner_radius=self._active_inner_radius,
                    outer_radius=self._active_outer_radius,
                    width=self._active_line_width,
                    color=ToolHandle.ZAxisSelectionColor)

        mb.addDonut(inner_radius=self._active_inner_radius,
                    outer_radius=self._active_outer_radius,
                    width=self._active_line_width,
                    axis=Vector.Unit_X,
                    angle=math.pi / 2,
                    color=ToolHandle.YAxisSelectionColor)

        mb.addDonut(inner_radius=self._active_inner_radius,
                    outer_radius=self._active_outer_radius,
                    width=self._active_line_width,
                    axis=Vector.Unit_Y,
                    angle=math.pi / 2,
                    color=ToolHandle.XAxisSelectionColor)

        mb.addPyramid(width=self._active_handle_width,
                      height=self._active_handle_height,
                      depth=self._active_handle_width,
                      center=Vector(0, self._handle_offset_a,
                                    self._handle_offset_b),
                      color=self._extra_widgets_color_map[
                          self.ExtraWidgets.XPositive90.value],
                      axis=Vector.Unit_X,
                      angle=-90 - self._angle_offset)
        mb.addPyramid(width=self._active_handle_width,
                      height=self._active_handle_height,
                      depth=self._active_handle_width,
                      center=Vector(0, self._handle_offset_a,
                                    -self._handle_offset_b),
                      color=self._extra_widgets_color_map[
                          self.ExtraWidgets.XNegative90.value],
                      axis=Vector.Unit_X,
                      angle=90 + self._angle_offset)

        mb.addPyramid(width=self._active_handle_width,
                      height=self._active_handle_height,
                      depth=self._active_handle_width,
                      center=Vector(self._handle_offset_b, 0,
                                    self._handle_offset_a),
                      color=self._extra_widgets_color_map[
                          self.ExtraWidgets.YPositive90.value],
                      axis=Vector.Unit_Z,
                      angle=90 - self._angle_offset)
        mb.addPyramid(width=self._active_handle_width,
                      height=self._active_handle_height,
                      depth=self._active_handle_width,
                      center=Vector(-self._handle_offset_b, 0,
                                    self._handle_offset_a),
                      color=self._extra_widgets_color_map[
                          self.ExtraWidgets.YNegative90.value],
                      axis=Vector.Unit_Z,
                      angle=-90 + self._angle_offset)

        mb.addPyramid(width=self._active_handle_width,
                      height=self._active_handle_height,
                      depth=self._active_handle_width,
                      center=Vector(self._handle_offset_a,
                                    self._handle_offset_b, 0),
                      color=self._extra_widgets_color_map[
                          self.ExtraWidgets.ZPositive90.value],
                      axis=Vector.Unit_Z,
                      angle=-self._angle_offset)
        mb.addPyramid(width=self._active_handle_width,
                      height=self._active_handle_height,
                      depth=self._active_handle_width,
                      center=Vector(self._handle_offset_a,
                                    -self._handle_offset_b, 0),
                      color=self._extra_widgets_color_map[
                          self.ExtraWidgets.ZNegative90.value],
                      axis=Vector.Unit_Z,
                      angle=180 + self._angle_offset)

        self.setSelectionMesh(mb.build())
Exemple #49
0
    def calculateBoundingBoxMesh(self):
        aabb = self.getBoundingBox()
        if aabb:
            bounding_box_mesh = MeshBuilder()
            rtf = aabb.maximum
            lbb = aabb.minimum

            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        rtf.z)  # Right - Top - Front
            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        rtf.z)  # Left - Top - Front

            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        rtf.z)  # Left - Top - Front
            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        rtf.z)  # Left - Bottom - Front

            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        rtf.z)  # Left - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        rtf.z)  # Right - Bottom - Front

            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        rtf.z)  # Right - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        rtf.z)  # Right - Top - Front

            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        lbb.z)  # Right - Top - Back
            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        lbb.z)  # Left - Top - Back

            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        lbb.z)  # Left - Top - Back
            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        lbb.z)  # Left - Bottom - Back

            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        lbb.z)  # Left - Bottom - Back
            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        lbb.z)  # Right - Bottom - Back

            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        lbb.z)  # Right - Bottom - Back
            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        lbb.z)  # Right - Top - Back

            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        rtf.z)  # Right - Top - Front
            bounding_box_mesh.addVertex(rtf.x, rtf.y,
                                        lbb.z)  # Right - Top - Back

            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        rtf.z)  # Left - Top - Front
            bounding_box_mesh.addVertex(lbb.x, rtf.y,
                                        lbb.z)  # Left - Top - Back

            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        rtf.z)  # Left - Bottom - Front
            bounding_box_mesh.addVertex(lbb.x, lbb.y,
                                        lbb.z)  # Left - Bottom - Back

            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        rtf.z)  # Right - Bottom - Front
            bounding_box_mesh.addVertex(rtf.x, lbb.y,
                                        lbb.z)  # Right - Bottom - Back

            self._bounding_box_mesh = bounding_box_mesh.build()
Exemple #50
0
    def _convertSavitarNodeToUMNode(
            self,
            savitar_node: Savitar.SceneNode,
            file_name: str = "") -> Optional[SceneNode]:
        node_name = savitar_node.getName()
        node_id = savitar_node.getId()
        if node_name == "":
            if file_name != "":
                node_name = os.path.basename(file_name)
            else:
                node_name = "Object {}".format(node_id)

        active_build_plate = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate

        um_node = CuraSceneNode()  # This adds a SettingOverrideDecorator
        um_node.addDecorator(BuildPlateDecorator(active_build_plate))
        um_node.setName(node_name)
        um_node.setId(node_id)
        transformation = self._createMatrixFromTransformationString(
            savitar_node.getTransformation())
        um_node.setTransformation(transformation)
        mesh_builder = MeshBuilder()

        data = numpy.fromstring(
            savitar_node.getMeshData().getFlatVerticesAsBytes(),
            dtype=numpy.float32)

        vertices = numpy.resize(data, (int(data.size / 3), 3))
        mesh_builder.setVertices(vertices)
        mesh_builder.calculateNormals(fast=True)
        if file_name:
            # The filename is used to give the user the option to reload the file if it is changed on disk
            # It is only set for the root node of the 3mf file
            mesh_builder.setFileName(file_name)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            um_node.setMeshData(mesh_data)

        for child in savitar_node.getChildren():
            child_node = self._convertSavitarNodeToUMNode(child)
            if child_node:
                um_node.addChild(child_node)

        if um_node.getMeshData() is None and len(um_node.getChildren()) == 0:
            return None

        settings = savitar_node.getSettings()

        # Add the setting override decorator, so we can add settings to this node.
        if settings:
            global_container_stack = CuraApplication.getInstance(
            ).getGlobalContainerStack()

            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                default_stack = ExtruderManager.getInstance().getExtruderStack(
                    0)

                if default_stack:
                    um_node.callDecoration("setActiveExtruder",
                                           default_stack.getId())

                # Get the definition & set it
                definition_id = ContainerTree.getInstance().machines[
                    global_container_stack.definition.getId(
                    )].quality_definition
                um_node.callDecoration("getStack").getTop().setDefinition(
                    definition_id)

            setting_container = um_node.callDecoration("getStack").getTop()

            for key in settings:
                setting_value = settings[key]

                # Extruder_nr is a special case.
                if key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance(
                    ).getExtruderStack(int(setting_value))
                    if extruder_stack:
                        um_node.callDecoration("setActiveExtruder",
                                               extruder_stack.getId())
                    else:
                        Logger.log("w",
                                   "Unable to find extruder in position %s",
                                   setting_value)
                    continue
                setting_container.setProperty(key, "value", setting_value)

        if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None:
            group_decorator = GroupDecorator()
            um_node.addDecorator(group_decorator)
        um_node.setSelectable(True)
        if um_node.getMeshData():
            # Assuming that all nodes with mesh data are printable objects
            # affects (auto) slicing
            sliceable_decorator = SliceableObjectDecorator()
            um_node.addDecorator(sliceable_decorator)
        return um_node
Exemple #51
0
    def __init__(self,
                 node: SceneNode,
                 hull: Optional[Polygon],
                 thickness: float,
                 parent: Optional[SceneNode] = None) -> None:
        """Convex hull node is a special type of scene node that is used to display an area, to indicate the

        location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
        then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded
        to represent the raft as well.
        """
        super().__init__(parent)

        self.setCalculateBoundingBox(False)

        self._original_parent = parent

        # Color of the drawn convex hull
        if not Application.getInstance().getIsHeadLess():
            theme = QtApplication.getInstance().getTheme()
            if theme:
                self._color = Color(*theme.getColor("convex_hull").getRgb())
            else:
                self._color = Color(0, 0, 0)
        else:
            self._color = Color(0, 0, 0)

        # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
        self._mesh_height = 0.1

        self._thickness = thickness

        # The node this mesh is "watching"
        self._node = node
        # Area of the head + fans for display as a shadow on the buildplate
        self._convex_hull_head_mesh = None  # type: Optional[MeshData]

        self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged)
        self._onNodeDecoratorsChanged(self._node)

        self._hull = hull
        if self._hull:
            hull_mesh_builder = MeshBuilder()
            if self._thickness == 0:
                if hull_mesh_builder.addConvexPolygon(
                        self._hull.getPoints()[::],  # bottom layer is reversed
                        self._mesh_height,
                        color=self._color):
                    hull_mesh_builder.resetNormals()

                    hull_mesh = hull_mesh_builder.build()
                    self.setMeshData(hull_mesh)
            else:
                if hull_mesh_builder.addConvexPolygonExtrusion(
                        self._hull.getPoints()
                    [::-1],  # bottom layer is reversed
                        self._mesh_height - thickness,
                        self._mesh_height,
                        color=self._color):
                    hull_mesh_builder.resetNormals()
                    hull_mesh = hull_mesh_builder.build()
                    self.setMeshData(hull_mesh)
Exemple #52
0
    def _constructSupport(self, buffer: QImage) -> None:
        depth_pass = PickingPass(
            buffer.width(), buffer.height()
        )  #Instead of using the picking pass to pick for us, we need to bulk-pick digits so do this in Numpy.
        depth_pass.render()
        depth_image = depth_pass.getOutput()
        camera = CuraApplication.getInstance().getController().getScene(
        ).getActiveCamera()

        #to_support = qimage2ndarray.raw_view(buffer)
        #to_support= _qimageview(_qt.QImage(buffer))
        to_support = self._raw_view(buffer)

        #depth = qimage2ndarray.recarray_view(depth_image)
        depth = self._recarray_view(depth_image)

        depth.a = 0  #Discard alpha channel.
        depth = depth.view(dtype=_np.int32).astype(
            _np.float32
        ) / 1000  #Conflate the R, G and B channels to one 24-bit (cast to 32) float. Divide by 1000 to get mm.
        support_positions_2d = _np.array(
            _np.where(_np.bitwise_and(to_support == 255, depth < 16777))
        )  #All the 2D coordinates on the screen where we want support. The 16777 is for points that don't land on a model.
        support_depths = _np.take(
            depth, support_positions_2d[0, :] * depth.shape[1] +
            support_positions_2d[1, :])  #The depth at those pixels.
        support_positions_2d = support_positions_2d.transpose(
        )  #We want rows with pixels, not columns with pixels.
        if len(support_positions_2d) == 0:
            Logger.log(
                "i",
                "Support was not drawn on the surface of any objects. Not creating support."
            )
            return
        support_positions_2d[:, [0, 1]] = support_positions_2d[:, [
            1, 0
        ]]  #Swap columns to get OpenGL's coordinate system.
        camera_viewport = _np.array(
            [camera.getViewportWidth(),
             camera.getViewportHeight()])
        support_positions_2d = support_positions_2d * 2.0 / camera_viewport - 1.0  #Scale to view coordinates (range -1 to 1).
        inverted_projection = _np.linalg.inv(
            camera.getProjectionMatrix().getData())
        transformation = camera.getWorldTransformation().getData()
        transformation[:,
                       1] = -transformation[:,
                                            1]  #Invert Z to get OpenGL's coordinate system.

        #For each pixel, get the near and far plane.
        near = _np.ndarray((support_positions_2d.shape[0], 4))
        near.fill(1)
        near[0:support_positions_2d.shape[0],
             0:support_positions_2d.shape[1]] = support_positions_2d
        near[:, 2].fill(-1)
        near = _np.dot(inverted_projection, near.transpose())
        near = _np.dot(transformation, near)
        near = near[0:3] / near[3]
        far = _np.ndarray((support_positions_2d.shape[0], 4))
        far.fill(1)
        far[0:support_positions_2d.shape[0],
            0:support_positions_2d.shape[1]] = support_positions_2d
        far = _np.dot(inverted_projection, far.transpose())
        far = _np.dot(transformation, far)
        far = far[0:3] / far[3]

        #Direction is from near plane pixel to far plane pixel, normalised.
        direction = near - far
        direction /= _np.linalg.norm(direction, axis=0)

        #Final position is in the direction of the pixel, moving with <depth> mm away from the camera position.
        support_positions_3d = (
            support_depths - 1
        ) * direction  #We want the support to appear just before the surface, not behind the surface, so - 1.
        support_positions_3d = support_positions_3d.transpose()
        camera_position_data = camera.getPosition().getData()
        support_positions_3d = support_positions_3d + camera_position_data

        #Create the vertices for the 3D mesh.
        #This mesh consists of a diamond-shape for each position that we traced.
        n = support_positions_3d.shape[0]
        Logger.log(
            "i",
            "Adding support in {num_pixels} locations.".format(num_pixels=n))
        vertices = support_positions_3d.copy().astype(_np.float32)
        vertices = _np.resize(vertices,
                              (n * 6, support_positions_3d.shape[1]
                               ))  #Resize will repeat all coordinates 6 times.
        #For each position, create a diamond shape around the position with 6 vertices.
        vertices[
            n * 0:n * 1,
            0] -= support_depths * 0.001 * self.globule_size  #First corner (-x, +y).
        vertices[n * 0:n * 1, 2] += support_depths * 0.001 * self.globule_size
        vertices[
            n * 1:n * 2,
            0] += support_depths * 0.001 * self.globule_size  #Second corner (+x, +y).
        vertices[n * 1:n * 2, 2] += support_depths * 0.001 * self.globule_size
        vertices[
            n * 2:n * 3,
            0] -= support_depths * 0.001 * self.globule_size  #Third corner (-x, -y).
        vertices[n * 2:n * 3, 2] -= support_depths * 0.001 * self.globule_size
        vertices[
            n * 3:n * 4,
            0] += support_depths * 0.001 * self.globule_size  #Fourth corner (+x, -y)
        vertices[n * 3:n * 4, 2] -= support_depths * 0.001 * self.globule_size
        vertices[n * 4:n * 5,
                 1] += support_depths * 0.001 * self.globule_size  #Top side.
        vertices[
            n * 5:n * 6,
            1] -= support_depths * 0.001 * self.globule_size  #Bottom side.

        #Create the faces of the diamond.
        indices = _np.arange(n, dtype=_np.int32)
        indices = _np.kron(indices, _np.ones(
            (3, 1))).astype(_np.int32).transpose()
        indices = _np.resize(
            indices, (n * 8, 3)
        )  #Creates 8 triangles using 3 times the same vertex, for each position: [[0, 0, 0], [1, 1, 1], ... , [0, 0, 0], [1, 1, 1], ... ]

        #indices[n * 0: n * 1, 0] += n * 0 #First corner.
        indices[n * 0:n * 1, 1] += n * 1  #Second corner.
        indices[n * 0:n * 1, 2] += n * 4  #Top side.

        indices[n * 1:n * 2, 0] += n * 1  #Second corner.
        indices[n * 1:n * 2, 1] += n * 3  #Fourth corner.
        indices[n * 1:n * 2, 2] += n * 4  #Top side.

        indices[n * 2:n * 3, 0] += n * 3  #Fourth corner.
        indices[n * 2:n * 3, 1] += n * 2  #Third corner.
        indices[n * 2:n * 3, 2] += n * 4  #Top side.

        indices[n * 3:n * 4, 0] += n * 2  #Third corner.
        #indices[n * 3: n * 4, 1] += n * 0 #First corner.
        indices[n * 3:n * 4, 2] += n * 4  #Top side.

        indices[n * 4:n * 5, 0] += n * 1  #Second corner.
        #indices[n * 4: n * 5, 1] += n * 0 #First corner.
        indices[n * 4:n * 5, 2] += n * 5  #Bottom side.

        indices[n * 5:n * 6, 0] += n * 3  #Fourth corner.
        indices[n * 5:n * 6, 1] += n * 1  #Second corner.
        indices[n * 5:n * 6, 2] += n * 5  #Bottom side.

        indices[n * 6:n * 7, 0] += n * 2  #Third corner.
        indices[n * 6:n * 7, 1] += n * 3  #Fourth corner.
        indices[n * 6:n * 7, 2] += n * 5  #Bottom side.

        #indices[n * 7: n * 8, 0] += n * 0 #First corner.
        indices[n * 7:n * 8, 1] += n * 2  #Third corner.
        indices[n * 7:n * 8, 2] += n * 5  #Bottom side.

        builder = MeshBuilder()
        builder.addVertices(vertices)
        builder.addIndices(indices)

        #Create the scene node.
        scene = CuraApplication.getInstance().getController().getScene()
        new_node = CuraSceneNode(parent=scene.getRoot(), name="BrushSupport")
        new_node.setSelectable(False)
        new_node.setMeshData(builder.build())
        new_node.addDecorator(
            BuildPlateDecorator(CuraApplication.getInstance().
                                getMultiBuildPlateModel().activeBuildPlate))
        new_node.addDecorator(SliceableObjectDecorator())
        operation = GroupedOperation()

        #Figure out which mesh this piece of support belongs to.
        #TODO: You can draw support in one stroke over multiple meshes. The support would belong to an arbitrary one of these.
        selection_pass = CuraApplication.getInstance().getRenderer(
        ).getRenderPass("selection")
        parent_id = selection_pass.getIdAtPosition(
            support_positions_2d[0][0], support_positions_2d[0]
            [1])  #Find the selection under the first support pixel.
        parent_node = scene.getRoot()
        if not parent_id:
            Logger.log("d", "Can't link custom support to any scene node.")
        else:
            for node in BreadthFirstIterator(scene.getRoot()):
                if id(node) == parent_id:
                    parent_node = node
                    break

        #Add the appropriate per-object settings.
        stack = new_node.callDecoration(
            "getStack"
        )  #Created by SettingOverrideDecorator that is automatically added to CuraSceneNode.
        settings = stack.getTop()
        support_mesh_instance = SettingInstance(
            stack.getSettingDefinition("support_mesh"), settings)
        support_mesh_instance.setProperty("value", True)
        support_mesh_instance.resetState()
        settings.addInstance(support_mesh_instance)
        drop_down_instance = SettingInstance(
            stack.getSettingDefinition("support_mesh_drop_down"), settings)
        drop_down_instance.setProperty("value", True)
        drop_down_instance.resetState()
        settings.addInstance(drop_down_instance)

        #Add the scene node to the scene (and allow for undo).
        operation.addOperation(
            AddSceneNodeOperation(new_node, scene.getRoot())
        )  #Set the parent to root initially, then change the parent, so that we don't have to alter the transformation.
        operation.addOperation(SetParentOperation(new_node, parent_node))
        operation.push()

        scene.sceneChanged.emit(new_node)
Exemple #53
0
    def _convertSavitarNodeToUMNode(self, savitar_node):
        um_node = SceneNode()
        transformation = self._createMatrixFromTransformationString(
            savitar_node.getTransformation())
        um_node.setTransformation(transformation)
        mesh_builder = MeshBuilder()

        data = numpy.fromstring(
            savitar_node.getMeshData().getFlatVerticesAsBytes(),
            dtype=numpy.float32)

        vertices = numpy.resize(data, (int(data.size / 3), 3))
        mesh_builder.setVertices(vertices)
        mesh_builder.calculateNormals(fast=True)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            um_node.setMeshData(mesh_data)

        for child in savitar_node.getChildren():
            child_node = self._convertSavitarNodeToUMNode(child)
            if child_node:
                um_node.addChild(child_node)

        if um_node.getMeshData() is None and len(um_node.getChildren()) == 0:
            return None

        settings = savitar_node.getSettings()

        # Add the setting override decorator, so we can add settings to this node.
        if settings:
            um_node.addDecorator(SettingOverrideDecorator())

            global_container_stack = Application.getInstance(
            ).getGlobalContainerStack()

            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                default_stack = ExtruderManager.getInstance().getExtruderStack(
                    0)

                if default_stack:
                    um_node.callDecoration("setActiveExtruder",
                                           default_stack.getId())

                # Get the definition & set it
                definition = QualityManager.getInstance(
                ).getParentMachineDefinition(
                    global_container_stack.getBottom())
                um_node.callDecoration("getStack").getTop().setDefinition(
                    definition.getId())

            setting_container = um_node.callDecoration("getStack").getTop()

            for key in settings:
                setting_value = settings[key]

                # Extruder_nr is a special case.
                if key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance(
                    ).getExtruderStack(int(setting_value))
                    if extruder_stack:
                        um_node.callDecoration("setActiveExtruder",
                                               extruder_stack.getId())
                    else:
                        Logger.log("w",
                                   "Unable to find extruder in position %s",
                                   setting_value)
                    continue
                setting_container.setProperty(key, "value", setting_value)

        if len(um_node.getChildren()) > 0:
            group_decorator = GroupDecorator()
            um_node.addDecorator(group_decorator)
        um_node.setSelectable(True)
        if um_node.getMeshData():
            # Assuming that all nodes with mesh data are printable objects
            # affects (auto) slicing
            sliceable_decorator = SliceableObjectDecorator()
            um_node.addDecorator(sliceable_decorator)
        return um_node
    def updateSceneFromOptimizationResult(
            self, analysis: pywim.smartslice.result.Analysis):

        type_map = {
            'int': int,
            'float': float,
            'str': str,
            'enum': str,
            'bool': bool
        }

        our_only_node = getPrintableNodes()[0]
        active_extruder = getNodeActiveExtruder(our_only_node)

        # TODO - Move this into a common class or function to apply an am.Config to GlobalStack/ExtruderStack
        if analysis.print_config.infill:

            infill_density = analysis.print_config.infill.density
            infill_pattern = analysis.print_config.infill.pattern

            if infill_pattern is None or infill_pattern == pywim.am.InfillType.unknown:
                infill_pattern = pywim.am.InfillType.grid

            infill_pattern_name = SmartSliceJobHandler.INFILL_SMARTSLICE_CURA[
                infill_pattern]

            extruder_dict = {
                "wall_line_count": analysis.print_config.walls,
                "top_layers": analysis.print_config.top_layers,
                "bottom_layers": analysis.print_config.bottom_layers,
                "infill_sparse_density": analysis.print_config.infill.density,
                "infill_pattern": infill_pattern_name
            }

            Logger.log("d",
                       "Optimized extruder settings: {}".format(extruder_dict))

            for key, value in extruder_dict.items():
                if value is not None:
                    property_type = type_map.get(
                        active_extruder.getProperty(key, "type"))
                    if property_type:
                        active_extruder.setProperty(key,
                                                    "value",
                                                    property_type(value),
                                                    set_from_cache=True)
                        active_extruder.setProperty(key,
                                                    "state",
                                                    InstanceState.User,
                                                    set_from_cache=True)

            Application.getInstance().getMachineManager(
            ).forceUpdateAllSettings()
            self.optimizationResultAppliedToScene.emit()

        # Remove any modifier meshes which are present from a previous result
        mod_meshes = getModifierMeshes()
        if len(mod_meshes) > 0:
            for node in mod_meshes:
                node.addDecorator(SmartSliceRemovedDecorator())
                our_only_node.removeChild(node)
            Application.getInstance().getController().getScene(
            ).sceneChanged.emit(node)

        # Add in the new modifier meshes
        for modifier_mesh in analysis.modifier_meshes:
            # Building the scene node
            modifier_mesh_node = CuraSceneNode()
            modifier_mesh_node.setName("SmartSliceMeshModifier")
            modifier_mesh_node.setSelectable(True)
            modifier_mesh_node.setCalculateBoundingBox(True)

            # Use the data from the SmartSlice engine to translate / rotate / scale the mod mesh
            modifier_mesh_node.setTransformation(
                Matrix(modifier_mesh.transform))

            # Building the mesh

            # # Preparing the data from pywim for MeshBuilder
            modifier_mesh_vertices = [[v.x, v.y, v.z]
                                      for v in modifier_mesh.vertices]
            modifier_mesh_indices = [[triangle.v1, triangle.v2, triangle.v3]
                                     for triangle in modifier_mesh.triangles]

            # Doing the actual build
            modifier_mesh_data = MeshBuilder()
            modifier_mesh_data.setVertices(
                numpy.asarray(modifier_mesh_vertices, dtype=numpy.float32))
            modifier_mesh_data.setIndices(
                numpy.asarray(modifier_mesh_indices, dtype=numpy.int32))
            modifier_mesh_data.calculateNormals()

            modifier_mesh_node.setMeshData(modifier_mesh_data.build())
            modifier_mesh_node.calculateBoundingBoxMesh()

            active_build_plate = Application.getInstance(
            ).getMultiBuildPlateModel().activeBuildPlate
            modifier_mesh_node.addDecorator(
                BuildPlateDecorator(active_build_plate))
            modifier_mesh_node.addDecorator(SliceableObjectDecorator())
            modifier_mesh_node.addDecorator(SmartSliceAddedDecorator())

            bottom = modifier_mesh_node.getBoundingBox().bottom

            z_offset_decorator = ZOffsetDecorator()
            z_offset_decorator.setZOffset(bottom)
            modifier_mesh_node.addDecorator(z_offset_decorator)

            stack = modifier_mesh_node.callDecoration("getStack")
            settings = stack.getTop()

            modifier_mesh_node_infill_pattern = SmartSliceJobHandler.INFILL_SMARTSLICE_CURA[
                modifier_mesh.print_config.infill.pattern]
            definition_dict = {
                "infill_mesh": True,
                "infill_pattern": modifier_mesh_node_infill_pattern,
                "infill_sparse_density":
                modifier_mesh.print_config.infill.density,
                "wall_line_count": modifier_mesh.print_config.walls,
                "top_layers": modifier_mesh.print_config.top_layers,
                "bottom_layers": modifier_mesh.print_config.bottom_layers,
            }
            Logger.log(
                "d",
                "Optimized modifier mesh settings: {}".format(definition_dict))

            for key, value in definition_dict.items():
                if value is not None:
                    definition = stack.getSettingDefinition(key)
                    property_type = type_map.get(stack.getProperty(
                        key, "type"))
                    if property_type:
                        new_instance = SettingInstance(definition, settings)

                        new_instance.setProperty("value", property_type(value))

                        new_instance.resetState(
                        )  # Ensure that the state is not seen as a user state.
                        settings.addInstance(new_instance)

            our_only_node.addChild(modifier_mesh_node)

            # emit changes and connect error tracker
            Application.getInstance().getController().getScene(
            ).sceneChanged.emit(modifier_mesh_node)
Exemple #55
0
    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

        mb = MeshBuilder()

        # Outline 'cube' of the build volume
        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())

        mb = MeshBuilder()
        mb.addQuad(Vector(min_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, min_d),
                   Vector(max_w, min_h - 0.2, max_d),
                   Vector(min_w, min_h - 0.2, max_d))

        for n in range(0, 6):
            v = mb.getVertex(n)
            mb.setVertexUVCoordinates(n, v[0], v[2])
        self._grid_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
Exemple #56
0
    def buildMesh(self):
        #SOLIDMESH -> LINES
        mb = MeshBuilder()

        mb.addCube(
            width = self._line_width,
            height = self._line_length,
            depth = self._line_width,
            center = Vector(0, self._handle_position/2, 0),
            color = self._y_axis_color
        )
        mb.addCube(
            width = self._line_length,
            height = self._line_width,
            depth = self._line_width,
            center = Vector(self._handle_position/2, 0, 0),
            color = self._x_axis_color
        )

        mb.addCube(
            width = self._line_width,
            height = self._line_width,
            depth = self._line_length,
            center = Vector(0, 0, self._handle_position/2),
            color = self._z_axis_color
        )

        #SOLIDMESH -> HANDLES
        mb.addCube(
            width = self._handle_width,
            height = self._handle_width,
            depth = self._handle_width,
            center = Vector(0, 0, 0),
            color = self._all_axis_color
        )

        mb.addCube(
            width = self._handle_width,
            height = self._handle_width,
            depth = self._handle_width,
            center = Vector(0, self._handle_position, 0),
            color = self._y_axis_color
        )

        mb.addCube(
            width = self._handle_width,
            height = self._handle_width,
            depth = self._handle_width,
            center = Vector(self._handle_position, 0, 0),
            color = self._x_axis_color
        )

        mb.addCube(
            width = self._handle_width,
            height = self._handle_width,
            depth = self._handle_width,
            center = Vector(0, 0, self._handle_position),
            color = self._z_axis_color
        )
        self.setSolidMesh(mb.build())

        #SELECTIONMESH -> LINES
        mb = MeshBuilder()
        mb.addCube(
            width = self._active_line_width,
            height = self._active_line_length,
            depth = self._active_line_width,
            center = Vector(0, self._active_handle_position/2, 0),
            color = ToolHandle.YAxisSelectionColor
        )

        mb.addCube(
            width = self._active_line_length,
            height = self._active_line_width,
            depth = self._active_line_width,
            center = Vector(self._active_handle_position/2, 0, 0),
            color = ToolHandle.XAxisSelectionColor
        )

        mb.addCube(
            width = self._active_line_width,
            height = self._active_line_width,
            depth = self._active_line_length,
            center = Vector(0, 0, self._active_handle_position/2),
            color = ToolHandle.ZAxisSelectionColor
        )

        #SELECTIONMESH -> HANDLES
        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, 0, 0),
            color = ToolHandle.AllAxisSelectionColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, self._active_handle_position, 0),
            color = ToolHandle.YAxisSelectionColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(self._active_handle_position, 0, 0),
            color = ToolHandle.XAxisSelectionColor
        )

        mb.addCube(
            width = self._active_handle_width,
            height = self._active_handle_width,
            depth = self._active_handle_width,
            center = Vector(0, 0, self._active_handle_position),
            color = ToolHandle.ZAxisSelectionColor
        )

        self.setSelectionMesh(mb.build())
Exemple #57
0
    def _convertSavitarNodeToUMNode(
            self,
            savitar_node: Savitar.SceneNode,
            file_name: str = "") -> Optional[SceneNode]:
        """Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.

        :returns: Scene node.
        """
        try:
            node_name = savitar_node.getName()
            node_id = savitar_node.getId()
        except AttributeError:
            Logger.log(
                "e",
                "Outdated version of libSavitar detected! Please update to the newest version!"
            )
            node_name = ""
            node_id = ""

        if node_name == "":
            if file_name != "":
                node_name = os.path.basename(file_name)
            else:
                node_name = "Object {}".format(node_id)

        active_build_plate = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate

        um_node = CuraSceneNode()  # This adds a SettingOverrideDecorator
        um_node.addDecorator(BuildPlateDecorator(active_build_plate))
        try:
            um_node.addDecorator(ConvexHullDecorator())
        except:
            pass
        um_node.setName(node_name)
        um_node.setId(node_id)
        transformation = self._createMatrixFromTransformationString(
            savitar_node.getTransformation())
        um_node.setTransformation(transformation)
        mesh_builder = MeshBuilder()

        data = numpy.fromstring(
            savitar_node.getMeshData().getFlatVerticesAsBytes(),
            dtype=numpy.float32)

        vertices = numpy.resize(data, (int(data.size / 3), 3))
        mesh_builder.setVertices(vertices)
        mesh_builder.calculateNormals(fast=True)
        if file_name:
            # The filename is used to give the user the option to reload the file if it is changed on disk
            # It is only set for the root node of the 3mf file
            mesh_builder.setFileName(file_name)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            um_node.setMeshData(mesh_data)

        for child in savitar_node.getChildren():
            child_node = self._convertSavitarNodeToUMNode(child)
            if child_node:
                um_node.addChild(child_node)

        if um_node.getMeshData() is None and len(um_node.getChildren()) == 0:
            return None

        settings = savitar_node.getSettings()

        # Add the setting override decorator, so we can add settings to this node.
        if settings:
            global_container_stack = CuraApplication.getInstance(
            ).getGlobalContainerStack()

            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                default_stack = ExtruderManager.getInstance().getExtruderStack(
                    0)

                if default_stack:
                    um_node.callDecoration("setActiveExtruder",
                                           default_stack.getId())

                # Get the definition & set it
                definition_id = ContainerTree.getInstance().machines[
                    global_container_stack.definition.getId(
                    )].quality_definition
                um_node.callDecoration("getStack").getTop().setDefinition(
                    definition_id)

            setting_container = um_node.callDecoration("getStack").getTop()
            known_setting_keys = um_node.callDecoration(
                "getStack").getAllKeys()
            for key in settings:
                setting_value = settings[key].value

                # Extruder_nr is a special case.
                if key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance(
                    ).getExtruderStack(int(setting_value))
                    if extruder_stack:
                        um_node.callDecoration("setActiveExtruder",
                                               extruder_stack.getId())
                    else:
                        Logger.log("w",
                                   "Unable to find extruder in position %s",
                                   setting_value)
                    continue
                if key in known_setting_keys:
                    setting_container.setProperty(key, "value", setting_value)
                else:
                    um_node.metadata[key] = settings[key]

        if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None:
            if len(um_node.getAllChildren()) == 1:
                # We don't want groups of one, so move the node up one "level"
                child_node = um_node.getChildren()[0]
                parent_transformation = um_node.getLocalTransformation()
                child_transformation = child_node.getLocalTransformation()
                child_node.setTransformation(
                    parent_transformation.multiply(child_transformation))
                um_node = cast(CuraSceneNode, um_node.getChildren()[0])
            else:
                group_decorator = GroupDecorator()
                um_node.addDecorator(group_decorator)
        um_node.setSelectable(True)
        if um_node.getMeshData():
            # Assuming that all nodes with mesh data are printable objects
            # affects (auto) slicing
            sliceable_decorator = SliceableObjectDecorator()
            um_node.addDecorator(sliceable_decorator)
        return um_node
Exemple #58
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()
Exemple #59
0
    def _convertSavitarNodeToUMNode(self, savitar_node):
        self._object_count += 1
        node_name = "Object %s" % self._object_count

        active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate

        um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
        um_node.addDecorator(BuildPlateDecorator(active_build_plate))
        um_node.setName(node_name)
        transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
        um_node.setTransformation(transformation)
        mesh_builder = MeshBuilder()

        data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32)

        vertices = numpy.resize(data, (int(data.size / 3), 3))
        mesh_builder.setVertices(vertices)
        mesh_builder.calculateNormals(fast=True)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            um_node.setMeshData(mesh_data)

        for child in savitar_node.getChildren():
            child_node = self._convertSavitarNodeToUMNode(child)
            if child_node:
                um_node.addChild(child_node)

        if um_node.getMeshData() is None and len(um_node.getChildren()) == 0:
            return None

        settings = savitar_node.getSettings()

        # Add the setting override decorator, so we can add settings to this node.
        if settings:
            global_container_stack = Application.getInstance().getGlobalContainerStack()

            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                default_stack = ExtruderManager.getInstance().getExtruderStack(0)

                if default_stack:
                    um_node.callDecoration("setActiveExtruder", default_stack.getId())

                # Get the definition & set it
                definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition)
                um_node.callDecoration("getStack").getTop().setDefinition(definition_id)

            setting_container = um_node.callDecoration("getStack").getTop()

            for key in settings:
                setting_value = settings[key]

                # Extruder_nr is a special case.
                if key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value))
                    if extruder_stack:
                        um_node.callDecoration("setActiveExtruder", extruder_stack.getId())
                    else:
                        Logger.log("w", "Unable to find extruder in position %s", setting_value)
                    continue
                setting_container.setProperty(key, "value", setting_value)

        if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None:
            group_decorator = GroupDecorator()
            um_node.addDecorator(group_decorator)
        um_node.setSelectable(True)
        if um_node.getMeshData():
            # Assuming that all nodes with mesh data are printable objects
            # affects (auto) slicing
            sliceable_decorator = SliceableObjectDecorator()
            um_node.addDecorator(sliceable_decorator)
        return um_node
Exemple #60
0
    def _read(self, file_name):
        scene_node = None

        extension = os.path.splitext(file_name)[1]
        if extension.lower() in self._supported_extensions:
            vertex_list = []
            normal_list = []
            uv_list = []
            face_list = []
            scene_node = SceneNode()

            mesh_builder = MeshBuilder()
            mesh_builder.setFileName(file_name)
            previous_line_parts = []
            f = open(file_name, "rt", encoding="utf-8")
            for line in f:
                parts = previous_line_parts + line.split()
                previous_line_parts = []
                if len(parts) < 1:
                    continue
                if parts[-1] == "\\":
                    del parts[-1]
                    previous_line_parts = parts
                    continue
                if parts[0] == "f":
                    parts = [i for i in map(lambda p: p.split("/"), parts)]
                    for idx in range(1, len(parts) - 2):
                        data = self._toAbsoluteIndex(len(vertex_list), [
                            int(parts[1][0]),
                            int(parts[idx + 1][0]),
                            int(parts[idx + 2][0])
                        ])

                        if len(parts[1]) > 1 and parts[1][1] and parts[
                                idx + 1][1] and parts[idx + 2][1]:
                            data += self._toAbsoluteIndex(
                                len(normal_list), [
                                    int(parts[1][1]),
                                    int(parts[idx + 1][1]),
                                    int(parts[idx + 2][1])
                                ])
                        else:
                            data += [0, 0, 0]

                        if len(parts[1]) > 2:
                            data += self._toAbsoluteIndex(
                                len(uv_list), [
                                    int(parts[1][2]),
                                    int(parts[idx + 1][2]),
                                    int(parts[idx + 2][2])
                                ])
                        else:
                            data += [0, 0, 0]

                        face_list.append(data)
                elif parts[0] == "v":
                    vertex_list.append(
                        [float(parts[1]),
                         float(parts[3]), -float(parts[2])])
                elif parts[0] == "vn":
                    normal_list.append(
                        [float(parts[1]),
                         float(parts[3]), -float(parts[2])])
                elif parts[0] == "vt":
                    uv_list.append([float(parts[1]), float(parts[2])])
                Job.yieldThread()
            f.close()

            mesh_builder.reserveVertexCount(3 * len(face_list))
            num_vertices = len(vertex_list)

            for face in face_list:
                # Substract 1 from index, as obj starts counting at 1 instead of 0
                i = face[0] - 1
                j = face[1] - 1
                k = face[2] - 1

                ui = face[3] - 1
                uj = face[4] - 1
                uk = face[5] - 1

                ni = face[6] - 1
                nj = face[7] - 1
                nk = face[8] - 1

                if i < 0 or i >= num_vertices:
                    i = 0
                if j < 0 or j >= num_vertices:
                    j = 0
                if k < 0 or k >= num_vertices:
                    k = 0
                if ni != -1 and nj != -1 and nk != -1:
                    mesh_builder.addFaceWithNormals(
                        vertex_list[i][0], vertex_list[i][1],
                        vertex_list[i][2], normal_list[ni][0],
                        normal_list[ni][1], normal_list[ni][2],
                        vertex_list[j][0], vertex_list[j][1],
                        vertex_list[j][2], normal_list[nj][0],
                        normal_list[nj][1], normal_list[nj][2],
                        vertex_list[k][0], vertex_list[k][1],
                        vertex_list[k][2], normal_list[nk][0],
                        normal_list[nk][1], normal_list[nk][2])
                else:
                    mesh_builder.addFaceByPoints(
                        vertex_list[i][0], vertex_list[i][1],
                        vertex_list[i][2], vertex_list[j][0],
                        vertex_list[j][1], vertex_list[j][2],
                        vertex_list[k][0], vertex_list[k][1],
                        vertex_list[k][2])

                if ui != -1 and len(uv_list) > ui:
                    mesh_builder.setVertexUVCoordinates(
                        mesh_builder.getVertexCount() - 3, uv_list[ui][0],
                        uv_list[ui][1])

                if uj != -1 and len(uv_list) > uj:
                    mesh_builder.setVertexUVCoordinates(
                        mesh_builder.getVertexCount() - 2, uv_list[uj][0],
                        uv_list[uj][1])

                if uk != -1 and len(uv_list) > uk:
                    mesh_builder.setVertexUVCoordinates(
                        mesh_builder.getVertexCount() - 1, uv_list[uk][0],
                        uv_list[uk][1])

                Job.yieldThread()
            if not mesh_builder.hasNormals():
                mesh_builder.calculateNormals(fast=True)

            # make sure that the mesh data is not empty
            if mesh_builder.getVertexCount() == 0:
                Logger.log("d",
                           "File did not contain valid data, unable to read.")
                return None  # We didn't load anything.

            scene_node.setMeshData(mesh_builder.build())

        return scene_node