Ejemplo n.º 1
0
    def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
        """Reads a file using Trimesh.

        :param file_name: The file path. This is assumed to be one of the file
        types that Trimesh can read. It will not be checked again.
        :return: A scene node that contains the file's contents.
        """
        # CURA-6739
        # GLTF files are essentially JSON files. If you directly give a file name to trimesh.load(), it will
        # try to figure out the format, but for GLTF, it loads it as a binary file with flags "rb", and the json.load()
        # doesn't like it. For some reason, this seems to happen with 3.5.7, but not 3.7.1. Below is a workaround to
        # pass a file object that has been opened with "r" instead "rb" to load a GLTF file.
        if file_name.lower().endswith(".gltf"):
            mesh_or_scene = trimesh.load(open(file_name, "r",
                                              encoding="utf-8"),
                                         file_type="gltf")
        else:
            mesh_or_scene = trimesh.load(file_name)

        meshes = []  # type: List[Union[trimesh.Trimesh, trimesh.Scene, Any]]
        if isinstance(mesh_or_scene, trimesh.Trimesh):
            meshes = [mesh_or_scene]
        elif isinstance(mesh_or_scene, trimesh.Scene):
            meshes = [mesh for mesh in mesh_or_scene.geometry.values()]

        active_build_plate = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        nodes = []  # type: List[SceneNode]
        for mesh in meshes:
            if not isinstance(
                    mesh, trimesh.Trimesh
            ):  # Trimesh can also receive point clouds, 2D paths, 3D paths or metadata. Skip those.
                continue
            mesh.merge_vertices()
            mesh.remove_unreferenced_vertices()
            mesh.fix_normals()
            mesh_data = self._toMeshData(mesh, file_name)

            file_base_name = os.path.basename(file_name)
            new_node = CuraSceneNode()
            new_node.setMeshData(mesh_data)
            new_node.setSelectable(True)
            new_node.setName(file_base_name if len(
                meshes) == 1 else "{file_base_name} {counter}".format(
                    file_base_name=file_base_name, counter=str(len(nodes) +
                                                               1)))
            new_node.addDecorator(BuildPlateDecorator(active_build_plate))
            new_node.addDecorator(SliceableObjectDecorator())
            nodes.append(new_node)

        if len(nodes) == 1:
            return nodes[0]
        # Add all nodes to a group so they stay together.
        group_node = CuraSceneNode()
        group_node.addDecorator(GroupDecorator())
        group_node.addDecorator(ConvexHullDecorator())
        group_node.addDecorator(BuildPlateDecorator(active_build_plate))
        for node in nodes:
            node.setParent(group_node)
        return group_node
Ejemplo n.º 2
0
def convex_hull_decorator():
    with patch("cura.CuraApplication.CuraApplication.getInstance",
               MagicMock(return_value=mocked_application)):
        with patch("UM.Application.Application.getInstance",
                   MagicMock(return_value=mocked_application)):
            with patch(
                    "cura.Settings.ExtruderManager.ExtruderManager.getInstance"
            ):
                return ConvexHullDecorator()
Ejemplo n.º 3
0
    def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
        mesh_or_scene = trimesh.load(file_name)
        meshes = []  # type: List[Union[trimesh.Trimesh, trimesh.Scene, Any]]
        if isinstance(mesh_or_scene, trimesh.Trimesh):
            meshes = [mesh_or_scene]
        elif isinstance(mesh_or_scene, trimesh.Scene):
            meshes = [mesh for mesh in mesh_or_scene.geometry.values()]

        active_build_plate = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        nodes = []  # type: List[SceneNode]
        for mesh in meshes:
            if not isinstance(
                    mesh, trimesh.Trimesh
            ):  # Trimesh can also receive point clouds, 2D paths, 3D paths or metadata. Skip those.
                continue
            mesh.merge_vertices()
            mesh.remove_unreferenced_vertices()
            mesh.fix_normals()
            mesh_data = self._toMeshData(mesh)

            file_base_name = os.path.basename(file_name)
            new_node = CuraSceneNode()
            new_node.setMeshData(mesh_data)
            new_node.setSelectable(True)
            new_node.setName(file_base_name if len(
                meshes) == 1 else "{file_base_name} {counter}".format(
                    file_base_name=file_base_name, counter=str(len(nodes) +
                                                               1)))
            new_node.addDecorator(BuildPlateDecorator(active_build_plate))
            new_node.addDecorator(SliceableObjectDecorator())
            nodes.append(new_node)

        if len(nodes) == 1:
            return nodes[0]
        # Add all nodes to a group so they stay together.
        group_node = CuraSceneNode()
        group_node.addDecorator(GroupDecorator())
        group_node.addDecorator(ConvexHullDecorator())
        group_node.addDecorator(BuildPlateDecorator(active_build_plate))
        for node in nodes:
            node.setParent(group_node)
        return group_node
Ejemplo n.º 4
0
    def _onChangeTimerFinished(self):
        if not self._enabled:
            return

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

        # Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the
        # same direction.
        transformed_nodes = []

        # We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A.
        # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
        nodes = list(BreadthFirstIterator(root))

        # Only check nodes inside build area.
        nodes = [
            node for node in nodes if (hasattr(node, "_outside_buildarea")
                                       and not node._outside_buildarea)
        ]

        random.shuffle(nodes)
        for node in nodes:
            if node is root or not isinstance(node, SceneNode) or isinstance(
                    node, DuplicatedNode) or node.getBoundingBox() is None:
                continue

            bbox = node.getBoundingBox()

            # Move it downwards if bottom is above platform
            move_vector = Vector()

            if Preferences.getInstance().getValue(
                    "physics/automatic_drop_down") and not (
                        node.getParent() and node.getParent().callDecoration(
                            "isGroup")) and node.isEnabled(
                            ):  #If an object is grouped, don't move it down
                z_offset = node.callDecoration(
                    "getZOffset") if node.getDecorator(
                        ZOffsetDecorator.ZOffsetDecorator) else 0
                move_vector = move_vector.set(y=-bbox.bottom + z_offset)

            # If there is no convex hull for the node, start calculating it and continue.
            if not node.getDecorator(ConvexHullDecorator):
                node.addDecorator(ConvexHullDecorator())

            # only push away objects if this node is a printing mesh
            if not node.callDecoration(
                    "isNonPrintingMesh") and Preferences.getInstance(
                    ).getValue("physics/automatic_push_free"
                               ) and type(node) != DuplicatedNode:
                # Check for collisions between convex hulls
                for other_node in BreadthFirstIterator(root):
                    # Ignore root, ourselves and anything that is not a normal SceneNode.
                    if other_node is root or not issubclass(
                            type(other_node), SceneNode
                    ) or other_node is node or other_node.callDecoration(
                            "getBuildPlateNumber") != node.callDecoration(
                                "getBuildPlateNumber"):
                        continue

                    # Ignore collisions of a group with it's own children
                    if other_node in node.getAllChildren(
                    ) or node in other_node.getAllChildren():
                        continue

                    # Ignore collisions within a group
                    if other_node.getParent() and node.getParent() and (
                            other_node.getParent().callDecoration("isGroup")
                            is not None
                            or node.getParent().callDecoration("isGroup")
                            is not None):
                        continue

                    # Ignore nodes that do not have the right properties set.
                    if not other_node.callDecoration(
                            "getConvexHull") or not other_node.getBoundingBox(
                            ):
                        continue

                    if other_node in transformed_nodes:
                        continue  # Other node is already moving, wait for next pass.

                    if other_node.callDecoration("isNonPrintingMesh"):
                        continue

                    overlap = (0, 0)  # Start loop with no overlap
                    current_overlap_checks = 0
                    # Continue to check the overlap until we no longer find one.
                    while overlap and current_overlap_checks < self._max_overlap_checks:
                        current_overlap_checks += 1
                        head_hull = node.callDecoration("getConvexHullHead")
                        if head_hull:  # One at a time intersection.
                            overlap = head_hull.translate(
                                move_vector.x,
                                move_vector.z).intersectsPolygon(
                                    other_node.callDecoration("getConvexHull"))
                            if not overlap:
                                other_head_hull = other_node.callDecoration(
                                    "getConvexHullHead")
                                if other_head_hull:
                                    overlap = node.callDecoration(
                                        "getConvexHull").translate(
                                            move_vector.x,
                                            move_vector.z).intersectsPolygon(
                                                other_head_hull)
                                    if overlap:
                                        # Moving ensured that overlap was still there. Try anew!
                                        move_vector = move_vector.set(
                                            x=move_vector.x +
                                            overlap[0] * self._move_factor,
                                            z=move_vector.z +
                                            overlap[1] * self._move_factor)
                            else:
                                # Moving ensured that overlap was still there. Try anew!
                                move_vector = move_vector.set(
                                    x=move_vector.x +
                                    overlap[0] * self._move_factor,
                                    z=move_vector.z +
                                    overlap[1] * self._move_factor)
                        else:
                            own_convex_hull = node.callDecoration(
                                "getConvexHull")
                            other_convex_hull = other_node.callDecoration(
                                "getConvexHull")
                            if own_convex_hull and other_convex_hull:
                                overlap = own_convex_hull.translate(
                                    move_vector.x,
                                    move_vector.z).intersectsPolygon(
                                        other_convex_hull)
                                if overlap:  # Moving ensured that overlap was still there. Try anew!
                                    temp_move_vector = move_vector.set(
                                        x=move_vector.x +
                                        overlap[0] * self._move_factor,
                                        z=move_vector.z +
                                        overlap[1] * self._move_factor)

                                    # if the distance between two models less than 2mm then try to find a new factor
                                    if abs(temp_move_vector.x - overlap[0]
                                           ) < self._minimum_gap and abs(
                                               temp_move_vector.y -
                                               overlap[1]) < self._minimum_gap:
                                        temp_x_factor = (
                                            abs(overlap[0]) + self._minimum_gap
                                        ) / overlap[0] if overlap[
                                            0] != 0 else 0  # find x move_factor, like (3.4 + 2) / 3.4 = 1.58
                                        temp_y_factor = (
                                            abs(overlap[1]) + self._minimum_gap
                                        ) / overlap[1] if overlap[
                                            1] != 0 else 0  # find y move_factor

                                        temp_scale_factor = temp_x_factor if abs(
                                            temp_x_factor) > abs(
                                                temp_y_factor
                                            ) else temp_y_factor

                                        move_vector = move_vector.set(
                                            x=move_vector.x +
                                            overlap[0] * temp_scale_factor,
                                            z=move_vector.z +
                                            overlap[1] * temp_scale_factor)
                                    else:
                                        move_vector = temp_move_vector
                            else:
                                # This can happen in some cases if the object is not yet done with being loaded.
                                # Simply waiting for the next tick seems to resolve this correctly.
                                overlap = None

            if not Vector.Null.equals(move_vector, epsilon=1e-5):
                transformed_nodes.append(node)
                op = PlatformPhysicsOperation.PlatformPhysicsOperation(
                    node, move_vector)
                op.push()

        # After moving, we have to evaluate the boundary checks for nodes
        build_volume = Application.getInstance().getBuildVolume()
        build_volume.updateNodeBoundaryCheck()
Ejemplo n.º 5
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
Ejemplo n.º 6
0
    def _read(self, file_name):
        base_name = os.path.basename(file_name)
        try:
            zipped_file = zipfile.ZipFile(file_name)
            xml_document = zipped_file.read(zipped_file.namelist()[0])
            zipped_file.close()
        except zipfile.BadZipfile:
            raw_file = open(file_name, "r")
            xml_document = raw_file.read()
            raw_file.close()

        try:
            amf_document = ET.fromstring(xml_document)
        except ET.ParseError:
            Logger.log("e", "Could not parse XML in file %s" % base_name)
            return None

        if "unit" in amf_document.attrib:
            unit = amf_document.attrib["unit"].lower()
        else:
            unit = "millimeter"
        if unit == "millimeter":
            scale = 1.0
        elif unit == "meter":
            scale = 1000.0
        elif unit == "inch":
            scale = 25.4
        elif unit == "feet":
            scale = 304.8
        elif unit == "micron":
            scale = 0.001
        else:
            Logger.log("w",
                       "Unknown unit in amf: %s. Using mm instead." % unit)
            scale = 1.0

        nodes = []
        for amf_object in amf_document.iter("object"):
            for amf_mesh in amf_object.iter("mesh"):
                amf_mesh_vertices = []
                for vertices in amf_mesh.iter("vertices"):
                    for vertex in vertices.iter("vertex"):
                        for coordinates in vertex.iter("coordinates"):
                            v = [0.0, 0.0, 0.0]
                            for t in coordinates:
                                if t.tag == "x":
                                    v[0] = float(t.text) * scale
                                elif t.tag == "y":
                                    v[2] = -float(t.text) * scale
                                elif t.tag == "z":
                                    v[1] = float(t.text) * scale
                            amf_mesh_vertices.append(v)
                if not amf_mesh_vertices:
                    continue

                indices = []
                for volume in amf_mesh.iter("volume"):
                    for triangle in volume.iter("triangle"):
                        f = [0, 0, 0]
                        for t in triangle:
                            if t.tag == "v1":
                                f[0] = int(t.text)
                            elif t.tag == "v2":
                                f[1] = int(t.text)
                            elif t.tag == "v3":
                                f[2] = int(t.text)
                        indices.append(f)

                    mesh = trimesh.base.Trimesh(
                        vertices=numpy.array(amf_mesh_vertices,
                                             dtype=numpy.float32),
                        faces=numpy.array(indices, dtype=numpy.int32))
                    mesh.merge_vertices()
                    mesh.remove_unreferenced_vertices()
                    mesh.fix_normals()
                    mesh_data = self._toMeshData(mesh, file_name)

                    new_node = CuraSceneNode()
                    new_node.setSelectable(True)
                    new_node.setMeshData(mesh_data)
                    new_node.setName(base_name if len(nodes) ==
                                     0 else "%s %d" % (base_name, len(nodes)))
                    new_node.addDecorator(
                        BuildPlateDecorator(CuraApplication.getInstance(
                        ).getMultiBuildPlateModel().activeBuildPlate))
                    new_node.addDecorator(SliceableObjectDecorator())

                    nodes.append(new_node)

        if not nodes:
            Logger.log("e", "No meshes in file %s" % base_name)
            return None

        if len(nodes) == 1:
            return nodes[0]

        # Add all scenenodes to a group so they stay together
        group_node = CuraSceneNode()
        group_node.addDecorator(GroupDecorator())
        group_node.addDecorator(ConvexHullDecorator())
        group_node.addDecorator(
            BuildPlateDecorator(CuraApplication.getInstance().
                                getMultiBuildPlateModel().activeBuildPlate))

        for node in nodes:
            node.setParent(group_node)

        return group_node
Ejemplo n.º 7
0
    def _readMeshFinished(self, job):
        Logger.log("d", "read mesh finisihed!")

        ### START PATCH: detect belt printer
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return

        is_belt_printer = self._preferences.getValue("BeltPlugin/on_plugin")
        ### END PATCH

        nodes = job.getResult()
        file_name = job.getFileName()
        file_name_lower = file_name.lower()
        file_extension = file_name_lower.split(".")[-1]
        self._application._currently_loading_files.remove(file_name)

        self._application.fileLoaded.emit(file_name)
        target_build_plate = self._application.getMultiBuildPlateModel(
        ).activeBuildPlate

        root = self._application.getController().getScene().getRoot()
        fixed_nodes = []
        for node_ in DepthFirstIterator(root):
            if node_.callDecoration("isSliceable") and node_.callDecoration(
                    "getBuildPlateNumber") == target_build_plate:
                fixed_nodes.append(node_)
        global_container_stack = self._application.getGlobalContainerStack()
        machine_width = global_container_stack.getProperty(
            "machine_width", "value")
        machine_depth = global_container_stack.getProperty(
            "machine_depth", "value")
        arranger = Arrange.create(x=machine_width,
                                  y=machine_depth,
                                  fixed_nodes=fixed_nodes)
        min_offset = 8
        default_extruder_position = self._application.getMachineManager(
        ).defaultExtruderPosition
        default_extruder_id = self._application._global_container_stack.extruders[
            default_extruder_position].getId()

        select_models_on_load = self._application.getPreferences().getValue(
            "cura/select_models_on_load")

        for original_node in nodes:

            # Create a CuraSceneNode just if the original node is not that type
            if isinstance(original_node, CuraSceneNode):
                node = original_node
            else:
                node = CuraSceneNode()
                node.setMeshData(original_node.getMeshData())

                #Setting meshdata does not apply scaling.
                if (original_node.getScale() != Vector(1.0, 1.0, 1.0)):
                    node.scale(original_node.getScale())

            node.setSelectable(True)
            node.setName(os.path.basename(file_name))
            self._application.getBuildVolume().checkBoundsAndUpdate(node)

            is_non_sliceable = "." + file_extension in self._application._non_sliceable_extensions

            if is_non_sliceable:
                self._application.callLater(
                    lambda: self._application.getController().setActiveView(
                        "SimulationView"))

                block_slicing_decorator = BlockSlicingDecorator()
                node.addDecorator(block_slicing_decorator)
            else:
                sliceable_decorator = SliceableObjectDecorator()
                node.addDecorator(sliceable_decorator)

            scene = self._application.getController().getScene()

            # If there is no convex hull for the node, start calculating it and continue.
            if not node.getDecorator(ConvexHullDecorator):
                node.addDecorator(ConvexHullDecorator())
            for child in node.getAllChildren():
                if not child.getDecorator(ConvexHullDecorator):
                    child.addDecorator(ConvexHullDecorator())

            ### START PATCH: don't do standard arrange on load for belt printers
            ###              but place in a line instead
            if is_belt_printer:
                half_node_depth = node.getBoundingBox().depth / 2
                build_plate_empty = True
                leading_edge = self._application.getBuildVolume(
                ).getBoundingBox().front

                for existing_node in DepthFirstIterator(root):
                    if (not issubclass(type(existing_node), CuraSceneNode) or
                        (not existing_node.getMeshData()
                         and not existing_node.callDecoration("getLayerData"))
                            or
                        (existing_node.callDecoration("getBuildPlateNumber") !=
                         target_build_plate)):

                        continue

                    build_plate_empty = False
                    leading_edge = min(leading_edge,
                                       existing_node.getBoundingBox().back)

                if not build_plate_empty or leading_edge < half_node_depth:
                    node.setPosition(
                        Vector(
                            0, 0, leading_edge - half_node_depth -
                            self._margin_between_models))

            if file_extension != "3mf" and not is_belt_printer:
                ### END PATCH
                if node.callDecoration("isSliceable"):
                    # Only check position if it's not already blatantly obvious that it won't fit.
                    if node.getBoundingBox(
                    ) is None or self._application._volume.getBoundingBox(
                    ) is None or node.getBoundingBox(
                    ).width < self._application._volume.getBoundingBox(
                    ).width or node.getBoundingBox(
                    ).depth < self._application._volume.getBoundingBox().depth:
                        # Find node location
                        offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(
                            node, min_offset=min_offset)

                        # If a model is to small then it will not contain any points
                        if offset_shape_arr is None and hull_shape_arr is None:
                            Message(self._application._i18n_catalog.i18nc(
                                "@info:status",
                                "The selected model was too small to load."),
                                    title=self._application._i18n_catalog.
                                    i18nc("@info:title", "Warning")).show()
                            return

                        # Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
                        arranger.findNodePlacement(node,
                                                   offset_shape_arr,
                                                   hull_shape_arr,
                                                   step=10)

            # This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
            # of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if
            # the BuildPlateDecorator exists or not and always set the correct build plate number.
            build_plate_decorator = node.getDecorator(BuildPlateDecorator)
            if build_plate_decorator is None:
                build_plate_decorator = BuildPlateDecorator(target_build_plate)
                node.addDecorator(build_plate_decorator)
            build_plate_decorator.setBuildPlateNumber(target_build_plate)

            op = AddSceneNodeOperation(node, scene.getRoot())
            op.push()

            node.callDecoration("setActiveExtruder", default_extruder_id)
            scene.sceneChanged.emit(node)

            if select_models_on_load:
                Selection.add(node)

        self._application.fileCompleted.emit(file_name)