Пример #1
0
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0:  #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = GroupedOperation()
            for i in range(count):
                if node.getParent() and node.getParent().callDecoration(
                        "isGroup"):
                    new_node = copy.deepcopy(
                        node.getParent())  #Copy the group node.
                    new_node.callDecoration("setConvexHull", None)

                    op.addOperation(
                        AddSceneNodeOperation(new_node,
                                              node.getParent().getParent()))
                else:
                    new_node = copy.deepcopy(node)
                    new_node.callDecoration("setConvexHull", None)
                    op.addOperation(
                        AddSceneNodeOperation(new_node, node.getParent()))

            op.push()
Пример #2
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 = AddSceneNodeOperation(node, parent)
        op.push()
        node.setPosition(position, CuraSceneNode.TransformSpace.World)

        Application.getInstance().getController().getScene().sceneChanged.emit(node)
Пример #3
0
    def _addShape(self, mesh_data: MeshData) -> None:
        application = CuraApplication.getInstance()
        global_stack = application.getGlobalContainerStack()
        if not global_stack:
            return

        node = CuraSceneNode()

        node.setMeshData(mesh_data)
        node.setSelectable(True)
        node.setName("SimpleShape" + str(id(mesh_data)))

        scene = self._controller.getScene()
        op = AddSceneNodeOperation(node, scene.getRoot())
        op.push()

        default_extruder_position = application.getMachineManager(
        ).defaultExtruderPosition
        default_extruder_id = global_stack.extruders[
            default_extruder_position].getId()
        node.callDecoration("setActiveExtruder", default_extruder_id)

        active_build_plate = application.getMultiBuildPlateModel(
        ).activeBuildPlate
        node.addDecorator(BuildPlateDecorator(active_build_plate))

        node.addDecorator(SliceableObjectDecorator())

        application.getController().getScene().sceneChanged.emit(node)
Пример #4
0
 def renderDuplicatedNode(self, node):
     if node.node.getParent() != self._scene.getRoot():
         parent = self.getDuplicatedNode(node.node.getParent())
     else:
         parent = self._scene.getRoot()
     op = AddSceneNodeOperation(node, parent)
     op.redo()
     node.update()
Пример #5
0
    def _onFileLoaded(self, job):
        node = job.getResult()
        if node != None:
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
            op.push()
    def _readMeshFinished(self, job):
        nodes = job.getResult()
        for node in nodes:
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self._scene.getRoot())
            op.push()
            self._scene.sceneChanged.emit(node)
Пример #7
0
 def _readMeshFinished(self, job):
     nodes = job.getResult()
     for node in nodes:
         node.setSelectable(True)
         node.setName(os.path.basename(job.getFileName()))
         # We need to prevent circular dependency, so do some just in time importing.
         from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
         op = AddSceneNodeOperation(node, self._application.getController().getScene().getRoot())
         op.push()
         self._application.getController().getScene().sceneChanged.emit(node)
Пример #8
0
    def _onFileLoaded(self, job):
        node = job.getResult()
        if node != None:
            self.fileLoaded.emit(job.getFileName())
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))
            op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
            op.push()

            self.getController().getScene().sceneChanged.emit(node) #Force scene change.
Пример #9
0
    def _readMeshFinished(self, job):
        node = job.getResult()
        if node != None:  
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self._scene.getRoot())
            op.push()

            self._scene.sceneChanged.emit(node)
Пример #10
0
    def _onFileLoaded(self, job):
        node = job.getResult()
        if node != None:
            self.setJobName(os.path.basename(job.getFileName()))
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))
            op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
            op.push()

            self.getController().getScene().sceneChanged.emit(node) #Force scene change.
Пример #11
0
    def _onFileLoaded(self, job):
        mesh = job.getResult()
        if mesh != None:
            node = SceneNode()

            node.setSelectable(True)
            node.setMeshData(mesh)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
            op.push()
Пример #12
0
 def _readMeshFinished(self, job):
     nodes = job.getResult()
     for node in nodes:
         node.setSelectable(True)
         node.setName(os.path.basename(job.getFileName()))
         # We need to prevent circular dependency, so do some just in time importing.
         from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
         op = AddSceneNodeOperation(
             node,
             self._application.getController().getScene().getRoot())
         op.push()
         self._application.getController().getScene().sceneChanged.emit(
             node)
    def _readWorkspaceFinished(self, job):
        # Add the loaded nodes to the scene.
        nodes = job.getResult()
        if nodes is not None:  # Job was not a failure.
            # Delete all old nodes.
            self._application.deleteAll()

            # Add the loaded nodes to the scene.
            nodes = job.getResult()
            for node in nodes:
                # We need to prevent circular dependency, so do some just in time importing.
                from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
                op = AddSceneNodeOperation(node, self._application.getController().getScene().getRoot())
                op.push()
                self._application.getController().getScene().sceneChanged.emit(node)
Пример #14
0
    def _replaceSceneNode(self, existing_node, trimeshes):
        name = existing_node.getName()
        file_name = existing_node.getMeshData().getFileName()
        transformation = existing_node.getWorldTransformation()
        parent = existing_node.getParent()
        extruder_id = existing_node.callDecoration("getActiveExtruder")
        build_plate = existing_node.callDecoration("getBuildPlateNumber")
        selected = Selection.isSelected(existing_node)

        op = GroupedOperation()
        op.addOperation(RemoveSceneNodeOperation(existing_node))

        for i, tri_node in enumerate(trimeshes):
            mesh_data = self._toMeshData(tri_node)

            new_node = CuraSceneNode()
            new_node.setSelectable(True)
            new_node.setMeshData(mesh_data)
            new_node.setName(name if i == 0 else "%s %d" % (name, i))
            new_node.callDecoration("setActiveExtruder", extruder_id)
            new_node.addDecorator(BuildPlateDecorator(build_plate))
            new_node.addDecorator(SliceableObjectDecorator())

            op.addOperation(AddSceneNodeOperation(new_node, parent))
            op.addOperation(
                SetTransformMatrixOperation(new_node, transformation))

            if selected:
                Selection.add(new_node)
        op.push()
Пример #15
0
def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"],
                                   build_volume: "BuildVolume",
                                   fixed_nodes: Optional[List["SceneNode"]] = None,
                                   factor = 10000,
                                   add_new_nodes_in_scene: bool = False)  -> Tuple[GroupedOperation, int]:
    scene_root = Application.getInstance().getController().getScene().getRoot()
    found_solution_for_all, node_items = findNodePlacement(nodes_to_arrange, build_volume, fixed_nodes, factor)

    not_fit_count = 0
    grouped_operation = GroupedOperation()
    for node, node_item in zip(nodes_to_arrange, node_items):
        if add_new_nodes_in_scene:
            grouped_operation.addOperation(AddSceneNodeOperation(node, scene_root))

        if node_item.binId() == 0:
            # We found a spot for it
            rotation_matrix = Matrix()
            rotation_matrix.setByRotationAxis(node_item.rotation(), Vector(0, -1, 0))
            grouped_operation.addOperation(RotateOperation(node, Quaternion.fromMatrix(rotation_matrix)))
            grouped_operation.addOperation(TranslateOperation(node, Vector(node_item.translation().x() / factor, 0,
                                                                           node_item.translation().y() / factor)))
        else:
            # We didn't find a spot
            grouped_operation.addOperation(
                TranslateOperation(node, Vector(200, node.getWorldPosition().y, -not_fit_count * 20), set_position = True))
            not_fit_count += 1

    return grouped_operation, not_fit_count
Пример #16
0
    def run(self):
        status_message = Message(i18n_catalog.i18nc(
            "@info:status", "Multiplying and placing objects"),
                                 lifetime=0,
                                 dismissable=False,
                                 progress=0)
        status_message.show()
        scene = Application.getInstance().getController().getScene()
        node = scene.findObject(self._object_id)

        if not node and self._object_id != 0:  # Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        # If object is part of a group, multiply group
        current_node = node
        while current_node.getParent() and current_node.getParent(
        ).callDecoration("isGroup"):
            current_node = current_node.getParent()

        root = scene.getRoot()
        arranger = Arrange.create(scene_root=root)
        node_too_big = False
        if node.getBoundingBox().width < 300 or node.getBoundingBox(
        ).depth < 300:
            offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(
                current_node, min_offset=self._min_offset)
        else:
            node_too_big = True
        nodes = []
        found_solution_for_all = True
        for i in range(self._count):
            # We do place the nodes one by one, as we want to yield in between.
            if not node_too_big:
                node, solution_found = arranger.findNodePlacement(
                    current_node, offset_shape_arr, hull_shape_arr)
            if node_too_big or not solution_found:
                found_solution_for_all = False
                new_location = node.getPosition()
                new_location = new_location.set(z=100 - i * 20)
                node.setPosition(new_location)

            nodes.append(node)
            Job.yieldThread()
            status_message.setProgress((i + 1) / self._count * 100)

        if nodes:
            op = GroupedOperation()
            for new_node in nodes:
                op.addOperation(
                    AddSceneNodeOperation(new_node, current_node.getParent()))
            op.push()
        status_message.hide()

        if not found_solution_for_all:
            no_full_solution_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Unable to find a location within the build volume for all objects"
                ))
            no_full_solution_message.show()
Пример #17
0
    def multiplySelection(self, count: int) -> None:
        application = cura.CuraApplication.CuraApplication.getInstance()
        global_container_stack = application.getGlobalContainerStack()

        if not global_container_stack:
            return

        definition_container = global_container_stack.getBottom()
        if definition_container and definition_container.getId() not in [
                "blackbelt", "blackbeltvd"
        ]:
            # for all other printers do the normal multiply/arrange
            super().multiplySelection(count)
            return

        scene_root = application.getController().getScene().getRoot()

        current_nodes = []
        for node in DepthFirstIterator(
                scene_root
        ):  #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
            if node.callDecoration("isSliceable") or node.callDecoration(
                    "isGroup"):
                current_nodes.append(node)

        new_nodes = []  # type: List[CuraSceneNode]
        processed_nodes = []  # type: List[CuraSceneNode]

        active_build_plate = application.getMultiBuildPlateModel(
        ).activeBuildPlate

        for node in Selection.getAllSelectedObjects()[:]:
            # If object is part of a group, multiply group
            while node.getParent() and (
                    node.getParent().callDecoration("isGroup")
                    or node.getParent().callDecoration("isSliceable")):
                node = node.getParent()

            if node in processed_nodes:
                continue
            processed_nodes.append(node)

            for i in range(count):
                new_node = copy.deepcopy(node)

                new_node.callDecoration("setBuildPlateNumber",
                                        active_build_plate)
                for child in new_node.getChildren():
                    child.callDecoration("setBuildPlateNumber",
                                         active_build_plate)

                new_nodes.append(new_node)

        if new_nodes:
            op = GroupedOperation()
            for new_node in new_nodes:
                op.addOperation(AddSceneNodeOperation(new_node, scene_root))
            op.push()

        application.arrange(new_nodes, current_nodes)
Пример #18
0
    def _readMeshFinished(self, job):
        mesh = job.getResult()
        if mesh != None:
            if mesh.getType() is MeshType.pointcloud:  #Depending on the type we need a different node (as pointclouds are rendered differently)
                node = PointCloudNode()
            else:
                node = SceneNode()

            node.setSelectable(True)
            node.setMeshData(mesh)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self._scene.getRoot())
            op.push()

            self._scene.sceneChanged.emit(node)
Пример #19
0
    def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        node.setCalculateBoundingBox(True)
        mesh = self._createCube(10)
        node.setMeshData(mesh.build())
        node.calculateBoundingBoxMesh()

        active_build_plate = CuraApplication.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)

        CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
Пример #20
0
    def _replaceSceneNode(self, existing_node, trimeshes) -> None:
        name = existing_node.getName()
        file_name = existing_node.getMeshData().getFileName()
        transformation = existing_node.getWorldTransformation()
        parent = existing_node.getParent()
        extruder_id = existing_node.callDecoration("getActiveExtruder")
        build_plate = existing_node.callDecoration("getBuildPlateNumber")
        selected = Selection.isSelected(existing_node)

        children = existing_node.getChildren()
        new_nodes = []

        op = GroupedOperation()
        op.addOperation(RemoveSceneNodeOperation(existing_node))

        for i, tri_node in enumerate(trimeshes):
            mesh_data = self._toMeshData(tri_node, file_name)

            new_node = CuraSceneNode()
            new_node.setSelectable(True)
            new_node.setMeshData(mesh_data)
            new_node.setName(name if i==0 else "%s %d" % (name, i))
            new_node.callDecoration("setActiveExtruder", extruder_id)
            new_node.addDecorator(BuildPlateDecorator(build_plate))
            new_node.addDecorator(SliceableObjectDecorator())

            op.addOperation(AddSceneNodeOperation(new_node, parent))
            op.addOperation(SetTransformMatrixOperation(new_node, transformation))

            new_nodes.append(new_node)

            if selected:
                Selection.add(new_node)

        for child in children:
            mesh_data = child.getMeshData()
            if not mesh_data:
                continue
            child_bounding_box = mesh_data.getTransformed(child.getWorldTransformation()).getExtents()
            if not child_bounding_box:
                continue
            new_parent = None
            for potential_parent in new_nodes:
                parent_mesh_data = potential_parent.getMeshData()
                if not parent_mesh_data:
                    continue
                parent_bounding_box = parent_mesh_data.getTransformed(potential_parent.getWorldTransformation()).getExtents()
                if not parent_bounding_box:
                    continue
                intersection = child_bounding_box.intersectsBox(parent_bounding_box)
                if intersection != AxisAlignedBox.IntersectionResult.NoIntersection:
                    new_parent = potential_parent
                    break
            if not new_parent:
                new_parent = new_nodes[0]
            op.addOperation(SetParentOperationSimplified(child, new_parent))

        op.push()
Пример #21
0
    def _readWorkspaceFinished(self, job: ReadFileJob) -> None:
        # Add the loaded nodes to the scene.
        result = job.getResult()
        if isinstance(result, tuple):
            nodes, metadata = result
        else:
            nodes = result
            metadata = {}

        if nodes is not None:  # Job was not a failure.
            self._application.resetWorkspace()
            for node in nodes:
                # We need to prevent circular dependency, so do some just in time importing.
                from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
                op = AddSceneNodeOperation(node, self._application.getController().getScene().getRoot())
                op.push()

            self._application.getWorkspaceMetadataStorage().setAllData(metadata)
            self._application.workspaceLoaded.emit(cast(WorkspaceReader, self.workspace_reader).workspaceName())
Пример #22
0
    def _createEraserMesh(self):
        node = CuraSceneNode()

        node.setName("Eraser")
        node.setSelectable(True)
        mesh = MeshBuilder()
        mesh.addCube(10, 10, 10)
        node.setMeshData(mesh.build())
        # Place the cube in the platform. Do it manually so it works if the "automatic drop models" is OFF
        move_vector = Vector(0, 5, 0)
        node.setPosition(move_vector)

        active_build_plate = Application.getInstance().getMultiBuildPlateModel(
        ).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)
Пример #23
0
    def subdivide(self):
        if Selection.getCount() != 2:
            Logger.log(
                "w",
                i18n_catalog.i18n(
                    "Cannot subdivide: number of selected objects is not equal 2. Plane and object need to be selected. Current selected objects: %i"
                ) % Selection.getCount())
            return
        object1 = Selection.getSelectedObject(0)
        object2 = Selection.getSelectedObject(1)
        if type(object1) is SceneNode and type(object2) is Plane:
            obj = object1
            plane = object2
        elif type(object2) is SceneNode and type(object1) is Plane:
            obj = object2
            plane = object1
        else:
            Logger.log(
                "w",
                i18n_catalog.i18n(
                    "Cannot subdivide: object and plane need to be selected. Current selection: %s and %s"
                ) % (str(object1), str(object2)))
            return

        result = self._subdivide(obj, plane)
        if type(result) is tuple:
            operation = GroupedOperation()
            operation.addOperation(RemoveSceneNodeOperation(plane))
            operation.addOperation(RemoveSceneNodeOperation(obj))
            operation.addOperation(
                AddSceneNodeOperation(result[0], obj.getParent()))
            operation.addOperation(
                TranslateOperation(result[0], obj.getPosition()))
            if len(result) == 2:
                operation.addOperation(
                    AddSceneNodeOperation(result[1], obj.getParent()))
                operation.addOperation(
                    TranslateOperation(result[1], obj.getPosition()))
            operation.push()
        else:
            Logger.log("w",
                       i18n_catalog.i18n("Cannot subdivide: Internal error"))
    def _addShape(self, name, mesh_data: MeshData, ext_pos=0) -> None:
        application = CuraApplication.getInstance()
        global_stack = application.getGlobalContainerStack()
        if not global_stack:
            return

        node = CuraSceneNode()

        node.setMeshData(mesh_data)
        node.setSelectable(True)
        if len(name) == 0:
            node.setName("TestPart" + str(id(mesh_data)))
        else:
            node.setName(str(name))

        scene = self._controller.getScene()
        op = AddSceneNodeOperation(node, scene.getRoot())
        op.push()

        extruder_nr = len(global_stack.extruders)
        # Logger.log("d", "extruder_nr= %d", extruder_nr)
        # default_extruder_position  : <class 'str'>
        if ext_pos > 0 and ext_pos <= extruder_nr:
            default_extruder_position = str(ext_pos - 1)
        else:
            default_extruder_position = application.getMachineManager(
            ).defaultExtruderPosition
        # Logger.log("d", "default_extruder_position= %s", type(default_extruder_position))
        # default_extruder_id = global_stack.extruders[default_extruder_position].getId()
        default_extruder_id = global_stack.extruders[
            default_extruder_position].getId()
        # Logger.log("d", "default_extruder_id= %s", default_extruder_id)
        node.callDecoration("setActiveExtruder", default_extruder_id)

        active_build_plate = application.getMultiBuildPlateModel(
        ).activeBuildPlate
        node.addDecorator(BuildPlateDecorator(active_build_plate))

        node.addDecorator(SliceableObjectDecorator())

        application.getController().getScene().sceneChanged.emit(node)
Пример #25
0
def arrange(nodes_to_arrange: List["SceneNode"],
            build_volume: "BuildVolume",
            fixed_nodes: Optional[List["SceneNode"]] = None,
            factor=10000,
            add_new_nodes_in_scene: bool = False) -> bool:
    """
    Find placement for a set of scene nodes, and move them by using a single grouped operation.
    :param nodes_to_arrange: The list of nodes that need to be moved.
    :param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this.
    :param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
                        are placed.
    :param factor: The library that we use is int based. This factor defines how accuracte we want it to be.
    :param add_new_nodes_in_scene: Whether to create new scene nodes before applying the transformations and rotations

    :return: found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects
    """
    scene_root = Application.getInstance().getController().getScene().getRoot()
    found_solution_for_all, node_items = findNodePlacement(
        nodes_to_arrange, build_volume, fixed_nodes, factor)

    not_fit_count = 0
    grouped_operation = GroupedOperation()
    for node, node_item in zip(nodes_to_arrange, node_items):
        if add_new_nodes_in_scene:
            grouped_operation.addOperation(
                AddSceneNodeOperation(node, scene_root))

        if node_item.binId() == 0:
            # We found a spot for it
            rotation_matrix = Matrix()
            rotation_matrix.setByRotationAxis(node_item.rotation(),
                                              Vector(0, -1, 0))
            grouped_operation.addOperation(
                RotateOperation(node, Quaternion.fromMatrix(rotation_matrix)))
            grouped_operation.addOperation(
                TranslateOperation(
                    node,
                    Vector(node_item.translation().x() / factor, 0,
                           node_item.translation().y() / factor)))
        else:
            # We didn't find a spot
            grouped_operation.addOperation(
                TranslateOperation(node,
                                   Vector(200,
                                          node.getWorldPosition().y,
                                          -not_fit_count * 20),
                                   set_position=True))
            not_fit_count += 1
    grouped_operation.push()

    return found_solution_for_all
Пример #26
0
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = GroupedOperation()
            for i in range(count):
                new_node = SceneNode()
                new_node.setMeshData(node.getMeshData())
                new_node.setScale(node.getScale())
                new_node.translate(Vector((i + 1) * node.getBoundingBox().width, 0, 0))
                new_node.setSelectable(True)
                op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
            op.push()
def test_SimpleRedoUndo():
    node = SceneNode()
    parent_node = SceneNode()
    operation = AddSceneNodeOperation(node, parent_node)
    operation.redo()

    assert node.getParent() == parent_node

    operation.undo()

    assert node.getParent() is None
Пример #28
0
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0:  # Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            current_node = node
            # Find the topmost group
            while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
                current_node = current_node.getParent()

            op = GroupedOperation()
            for _ in range(count):
                new_node = copy.deepcopy(current_node)
                op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
            op.push()
def test_UndoRedoWithSelection():
    node = SceneNode()
    parent_node = SceneNode()
    Selection.add(node)

    operation = AddSceneNodeOperation(node, parent_node)
    operation.undo()

    assert not Selection.isSelected(node)

    operation.redo()
    assert Selection.isSelected(node)
Пример #30
0
    def _createSupportMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()
        node.setSelectable(True)
        
        if self._SupportType == 'cylinder':
            height = position.y
            node.setName("CustomSupportCylinder")
            mesh = self._createCylinder(self._SupportSize,22.5,height)
            node_position = Vector(position.x,position.y,position.z)
        else:
            node.setName("CustomSupportCube")
            height = position.y-self._SupportSize/2+self._SupportSize*0.1
            mesh =  self._createCube(self._SupportSize,height)
            node_position = Vector(position.x,position.y-self._SupportSize/2+self._SupportSize*0.1,position.z)
        node.setMeshData(mesh.build())

        active_build_plate = CuraApplication.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()

        for key in ["support_mesh", "support_mesh_drop_down"]:
            definition = stack.getSettingDefinition(key)
            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 support 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(node_position, CuraSceneNode.TransformSpace.World)

        CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
Пример #31
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)
Пример #32
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)
Пример #33
0
    def run(self):
        status_message = Message(i18n_catalog.i18nc(
            "@info:status", "Multiplying and placing objects"),
                                 lifetime=0,
                                 dismissable=False,
                                 progress=0)
        status_message.show()
        scene = Application.getInstance().getController().getScene()

        total_progress = len(self._objects) * self._count
        current_progress = 0

        root = scene.getRoot()
        arranger = Arrange.create(scene_root=root)
        nodes = []
        for node in self._objects:
            # If object is part of a group, multiply group
            current_node = node
            while current_node.getParent() and current_node.getParent(
            ).callDecoration("isGroup"):
                current_node = current_node.getParent()

            node_too_big = False
            if node.getBoundingBox().width < 300 or node.getBoundingBox(
            ).depth < 300:
                offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(
                    current_node, min_offset=self._min_offset)
            else:
                node_too_big = True

            found_solution_for_all = True
            for i in range(self._count):
                # We do place the nodes one by one, as we want to yield in between.
                if not node_too_big:
                    node, solution_found = arranger.findNodePlacement(
                        current_node, offset_shape_arr, hull_shape_arr)
                if node_too_big or not solution_found:
                    found_solution_for_all = False
                    new_location = node.getPosition()
                    new_location = new_location.set(z=100 - i * 20)
                    node.setPosition(new_location)

                nodes.append(node)
                current_progress += 1
                status_message.setProgress(
                    (current_progress / total_progress) * 100)
                Job.yieldThread()

            Job.yieldThread()

        if nodes:
            op = GroupedOperation()
            for new_node in nodes:
                print_mode_enabled = Application.getInstance(
                ).getGlobalContainerStack().getProperty(
                    "print_mode", "enabled")
                if print_mode_enabled:
                    node_dup = DuplicatedNode(new_node)
                    op.addOperation(
                        AddNodesOperation(node_dup, current_node.getParent()))
                else:
                    op.addOperation(
                        AddSceneNodeOperation(new_node,
                                              current_node.getParent()))
            op.push()
        status_message.hide()

        if not found_solution_for_all:
            no_full_solution_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Unable to find a location within the build volume for all objects"
                ))
            no_full_solution_message.show()
Пример #34
0
    def run(self):
        status_message = Message(
            i18n_catalog.i18nc("@info:status",
                               "Multiplying and placing objects"),
            lifetime=0,
            dismissable=False,
            progress=0,
            title=i18n_catalog.i18nc("@info:title", "Placing Object"))
        status_message.show()
        scene = Application.getInstance().getController().getScene()

        total_progress = len(self._objects) * self._count
        current_progress = 0

        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        machine_width = global_container_stack.getProperty(
            "machine_width", "value")
        machine_depth = global_container_stack.getProperty(
            "machine_depth", "value")

        root = scene.getRoot()
        scale = 0.5
        arranger = Arrange.create(x=machine_width,
                                  y=machine_depth,
                                  scene_root=root,
                                  scale=scale,
                                  min_offset=self._min_offset)
        processed_nodes = []
        nodes = []

        not_fit_count = 0

        for node in self._objects:
            # If object is part of a group, multiply group
            current_node = node
            while current_node.getParent() and (
                    current_node.getParent().callDecoration("isGroup")
                    or current_node.getParent().callDecoration("isSliceable")):
                current_node = current_node.getParent()

            if current_node in processed_nodes:
                continue
            processed_nodes.append(current_node)

            node_too_big = False
            if node.getBoundingBox(
            ).width < machine_width or node.getBoundingBox(
            ).depth < machine_depth:
                offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(
                    current_node, min_offset=self._min_offset, scale=scale)
            else:
                node_too_big = True

            found_solution_for_all = True
            arranger.resetLastPriority()
            for i in range(self._count):
                # We do place the nodes one by one, as we want to yield in between.
                new_node = copy.deepcopy(node)
                solution_found = False
                if not node_too_big:
                    solution_found = arranger.findNodePlacement(
                        new_node, offset_shape_arr, hull_shape_arr)

                if node_too_big or not solution_found:
                    found_solution_for_all = False
                    new_location = new_node.getPosition()
                    new_location = new_location.set(z=-not_fit_count * 20)
                    new_node.setPosition(new_location)
                    not_fit_count += 1

                # Same build plate
                build_plate_number = current_node.callDecoration(
                    "getBuildPlateNumber")
                new_node.callDecoration("setBuildPlateNumber",
                                        build_plate_number)
                for child in new_node.getChildren():
                    child.callDecoration("setBuildPlateNumber",
                                         build_plate_number)

                nodes.append(new_node)
                current_progress += 1
                status_message.setProgress(
                    (current_progress / total_progress) * 100)
                Job.yieldThread()

            Job.yieldThread()

        if nodes:
            op = GroupedOperation()
            for new_node in nodes:
                op.addOperation(
                    AddSceneNodeOperation(new_node, current_node.getParent()))
            op.push()
        status_message.hide()

        if not found_solution_for_all:
            no_full_solution_message = Message(i18n_catalog.i18nc(
                "@info:status",
                "Unable to find a location within the build volume for all objects"
            ),
                                               title=i18n_catalog.i18nc(
                                                   "@info:title",
                                                   "Placing Object"))
            no_full_solution_message.show()
Пример #35
0
    def _createSupportMesh(self, parent: CuraSceneNode, position: Vector):
        node = CuraSceneNode()

        node.setName("RoundTab")
            
        node.setSelectable(True)
        
        # long=Support Height
        _long=position.y

        # get layer_height_0 used to define pastille height
        _id_ex=0
        
        # This function can be triggered in the middle of a machine change, so do not proceed if the machine change
        # has not done yet.
        global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
        #extruder = global_container_stack.extruderList[int(_id_ex)] 
        extruder_stack = CuraApplication.getInstance().getExtruderManager().getActiveExtruderStacks()[0]        
        _layer_h_i = extruder_stack.getProperty("layer_height_0", "value")
        _layer_height = extruder_stack.getProperty("layer_height", "value")
        _line_w = extruder_stack.getProperty("line_width", "value")
        # Logger.log('d', 'layer_height_0 : ' + str(_layer_h_i))
        _layer_h = (_layer_h_i * 1.2) + (_layer_height * (self._Nb_Layer -1) )
        _line_w = _line_w * 1.2 
        
        if self._AsCapsule:
             # Capsule creation Diameter , Increment angle 4°, length, layer_height_0*1.2 , line_width
            mesh = self._createCapsule(self._UseSize,4,_long,_layer_h,_line_w)       
        else:
            # Cylinder creation Diameter , Increment angle 4°, length, layer_height_0*1.2
            mesh = self._createPastille(self._UseSize,4,_long,_layer_h)
        
        node.setMeshData(mesh.build())

        active_build_plate = CuraApplication.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()

        # support_mesh type
        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)

        definition = stack.getSettingDefinition("support_mesh_drop_down")
        new_instance = SettingInstance(definition, settings)
        new_instance.setProperty("value", False)
        new_instance.resetState()  # Ensure that the state is not seen as a user state.
        settings.addInstance(new_instance)
 
        if self._AsCapsule:
            s_p = global_container_stack.getProperty("support_type", "value")
            if s_p ==  'buildplate' :
                Message(text = "Info modification current profile support_type parameter\nNew value : everywhere", title = catalog.i18nc("@info:title", "Warning ! Tab Anti Warping")).show()
                Logger.log('d', 'support_type different : ' + str(s_p))
                # Define support_type=everywhere
                global_container_stack.setProperty("support_type", "value", 'everywhere')
                
            
        # Define support_xy_distance
        definition = stack.getSettingDefinition("support_xy_distance")
        new_instance = SettingInstance(definition, settings)
        new_instance.setProperty("value", self._UseOffset)
        # new_instance.resetState()  # Ensure that the state is not seen as a user state.
        settings.addInstance(new_instance)

        # Fix some settings in Cura to get a better result
        id_ex=0
        global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
        extruder_stack = CuraApplication.getInstance().getExtruderManager().getActiveExtruderStacks()[0]
        #extruder = global_container_stack.extruderList[int(id_ex)]    
        
        # hop to fix it in a futur release
        # https://github.com/Ultimaker/Cura/issues/9882
        # if self.Major < 5 or ( self.Major == 5 and self.Minor < 1 ) :
        _xy_distance = extruder_stack.getProperty("support_xy_distance", "value")
        if self._UseOffset !=  _xy_distance :
            _msg = "New value : %8.3f" % (self._UseOffset) 
            Message(text = "Info modification current profile support_xy_distance parameter\nNew value : %8.3f" % (self._UseOffset), title = catalog.i18nc("@info:title", "Warning ! Tab Anti Warping")).show()
            Logger.log('d', 'support_xy_distance different : ' + str(_xy_distance))
            # Define support_xy_distance
            extruder_stack.setProperty("support_xy_distance", "value", self._UseOffset)
 
        if self._Nb_Layer >1 :
            s_p = int(extruder_stack.getProperty("support_infill_rate", "value"))
            Logger.log('d', 'support_infill_rate actual : ' + str(s_p))
            if s_p < 99 :
                Message(text = "Info modification current profile support_infill_rate parameter\nNew value : 100%", title = catalog.i18nc("@info:title", "Warning ! Tab Anti Warping")).show()
                Logger.log('d', 'support_infill_rate different : ' + str(s_p))
                # Define support_infill_rate=100%
                extruder_stack.setProperty("support_infill_rate", "value", 100)
                
        
        
        op = GroupedOperation()
        # First add node to the scene at the correct position/scale, before parenting, so the support 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)

        CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)