Skip to content

Commit

Permalink
Merge branch 'CURA-10475_engineplugin' into CURA-10446_modify_gcode_path
Browse files Browse the repository at this point in the history
  • Loading branch information
casperlamboo committed Aug 11, 2023
2 parents 2240db9 + 0667230 commit c1acfee
Show file tree
Hide file tree
Showing 119 changed files with 673 additions and 75 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

[![Badge Test]][Test]   
[![Badge Conan]][Conan]   

![Badge Downloads]
<br>
<br>

Expand Down Expand Up @@ -84,6 +84,7 @@
[Badge Conan]: https://img.shields.io/github/workflow/status/Ultimaker/Cura/conan-package?style=for-the-badge&logoColor=white&labelColor=6185aa&color=4c6987&logo=Conan&label=Conan%20Package
[Badge Test]: https://img.shields.io/github/workflow/status/Ultimaker/Cura/unit-test?style=for-the-badge&logoColor=white&labelColor=4a999d&color=346c6e&logo=Codacy&label=Unit%20Test
[Badge Size]: https://img.shields.io/github/repo-size/ultimaker/cura?style=for-the-badge&logoColor=white&labelColor=715a97&color=584674&logo=GoogleAnalytics
[Badge Downloads]: https://img.shields.io/github/downloads-pre/Ultimaker/Cura/latest/total?style=for-the-badge


<!---------------------------------[ Buttons ]--------------------------------->
Expand Down
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,14 +468,14 @@ def deploy(self):
save(self, os.path.join(self._script_dir, f"activate_github_actions_version_env{ext}"), activate_github_actions_version_env)

self._generate_cura_version(os.path.join(self._site_packages, "cura"))
self._generate_about_versions(str(self._share_dir.joinpath("cura", "resources", "qml", "Dialogs")))

entitlements_file = "'{}'".format(Path(self.cpp_info.res_paths[2], "MacOS", "cura.entitlements"))
self._generate_pyinstaller_spec(location = self._base_dir,
entrypoint_location = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.bindirs[0], self.conan_data["pyinstaller"]["runinfo"]["entrypoint"])).replace("\\", "\\\\"),
icon_path = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.resdirs[2], self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"),
entitlements_file = entitlements_file if self.settings.os == "Macos" else "None")

self._generate_about_versions(os.path.join(self.source_folder, "resources", "qml", "Dialogs"))

def package(self):
copy(self, "cura_app.py", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.bindirs[0]))
Expand Down
32 changes: 26 additions & 6 deletions cura/BackendPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Optional, List

from UM.Logger import Logger
from UM.Message import Message
from UM.Settings.AdditionalSettingDefinitionAppender import AdditionalSettingDefinitionsAppender


Expand Down Expand Up @@ -57,13 +58,25 @@ def start(self) -> bool:
self._is_running = True
return True
except PermissionError:
Logger.log("e", f"Couldn't start backend_plugin [{self._plugin_id}]: No permission to execute process.")
Logger.log("e", f"Couldn't start EnginePlugin: {self._plugin_id} No permission to execute process.")
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
f"Couldn't start EnginePlugin: {self._plugin_id}\nNo permission to execute process."),
message_type = Message.MessageType.ERROR)
except FileNotFoundError:
Logger.logException("e", f"Unable to find backend_plugin executable [{self._plugin_id}]")
Logger.logException("e", f"Unable to find local EnginePlugin server executable for: {self._plugin_id}")
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
f"Unable to find local EnginePlugin server executable for: {self._plugin_id}"),
message_type = Message.MessageType.ERROR)
except BlockingIOError:
Logger.logException("e", f"Couldn't start backend_plugin [{self._plugin_id}]: Resource is temporarily unavailable")
Logger.logException("e", f"Couldn't start EnginePlugin: {self._plugin_id} Resource is temporarily unavailable")
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
f"Couldn't start EnginePlugin: {self._plugin_id}\nResource is temporarily unavailable"),
message_type = Message.MessageType.ERROR)
except OSError as e:
Logger.logException("e", f"Couldn't start backend_plugin [{self._plugin_id}]: Operating system is blocking it (antivirus?)")
Logger.logException("e", f"Couldn't start EnginePlugin {self._plugin_id} Operating system is blocking it (antivirus?)")
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
f"Couldn't start EnginePlugin: {self._plugin_id}\nOperating system is blocking it (antivirus?)"),
message_type = Message.MessageType.ERROR)
return False

def stop(self) -> bool:
Expand All @@ -75,8 +88,15 @@ def stop(self) -> bool:
self._process.terminate()
return_code = self._process.wait()
self._is_running = False
Logger.log("d", f"Backend_plugin [{self._plugin_id}] was killed. Received return code {return_code}")
Logger.log("d", f"EnginePlugin: {self._plugin_id} was killed. Received return code {return_code}")
return True
except PermissionError:
Logger.log("e", "Unable to kill running engine. Access is denied.")
Logger.log("e", f"Unable to kill running EnginePlugin: {self._plugin_id} Access is denied.")
self._showMessage(self.catalog.i18nc("@info:plugin_failed",
f"Unable to kill running EnginePlugin: {self._plugin_id}\nAccess is denied."),
message_type = Message.MessageType.ERROR)
return False

def _showMessage(self, message: str, message_type: Message.MessageType = Message.MessageType.ERROR) -> None:
Message(message, title=self.catalog.i18nc("@info:title", "EnginePlugin"), message_type = message_type).show()

65 changes: 62 additions & 3 deletions cura/CuraActions.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2023 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher.

from PyQt6.QtCore import QObject, QUrl
from PyQt6.QtGui import QDesktopServices
from typing import List, cast

from PyQt6.QtCore import QObject, QUrl, QMimeData
from PyQt6.QtGui import QDesktopServices
from PyQt6.QtWidgets import QApplication

from UM.Event import CallFunctionEvent
from UM.FlameProfiler import pyqtSlot
from UM.Math.Vector import Vector
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.TranslateOperation import TranslateOperation
Expand All @@ -19,6 +22,7 @@
from cura.MultiplyObjectsJob import MultiplyObjectsJob
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Arranging.Nest2DArrange import createGroupOperationForArrange

from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOperation

Expand Down Expand Up @@ -181,5 +185,60 @@ def setBuildPlateForSelection(self, build_plate_nr: int) -> None:

Selection.clear()

@pyqtSlot()
def cut(self) -> None:
self.copy()
self.deleteSelection()

@pyqtSlot()
def copy(self) -> None:
mesh_writer = cura.CuraApplication.CuraApplication.getInstance().getMeshFileHandler().getWriter("3MFWriter")
if not mesh_writer:
Logger.log("e", "No 3MF writer found, unable to copy.")
return

# Get the selected nodes
selected_objects = Selection.getAllSelectedObjects()
# Serialize the nodes to a string
scene_string = mesh_writer.sceneNodesToString(selected_objects)
# Put the string on the clipboard
QApplication.clipboard().setText(scene_string)

@pyqtSlot()
def paste(self) -> None:
application = cura.CuraApplication.CuraApplication.getInstance()
mesh_reader = application.getMeshFileHandler().getReaderForFile(".3mf")
if not mesh_reader:
Logger.log("e", "No 3MF reader found, unable to paste.")
return

# Parse the scene from the clipboard
scene_string = QApplication.clipboard().text()

nodes = mesh_reader.stringToSceneNodes(scene_string)

if not nodes:
# Nothing to paste
return

# Find all fixed nodes, these are the nodes that should be avoided when arranging
fixed_nodes = []
root = application.getController().getScene().getRoot()
for node in DepthFirstIterator(root):
# Only count sliceable objects
if node.callDecoration("isSliceable"):
fixed_nodes.append(node)
# Add the new nodes to the scene, and arrange them
group_operation, not_fit_count = createGroupOperationForArrange(nodes, application.getBuildVolume(),
fixed_nodes, factor=10000,
add_new_nodes_in_scene=True)
group_operation.push()

# deselect currently selected nodes, and select the new nodes
for node in Selection.getAllSelectedObjects():
Selection.remove(node)
for node in nodes:
Selection.add(node)

def _openUrl(self, url: QUrl) -> None:
QDesktopServices.openUrl(url)
29 changes: 23 additions & 6 deletions plugins/3MFReader/ThreeMFReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def __init__(self) -> None:
def emptyFileHintSet(self) -> bool:
return self._empty_project

def _createMatrixFromTransformationString(self, transformation: str) -> Matrix:
@staticmethod
def _createMatrixFromTransformationString(transformation: str) -> Matrix:
if transformation == "":
return Matrix()

Expand Down Expand Up @@ -90,7 +91,8 @@ def _createMatrixFromTransformationString(self, transformation: str) -> Matrix:

return temp_mat

def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]:
@staticmethod
def _convertSavitarNodeToUMNode(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.
Expand Down Expand Up @@ -119,7 +121,7 @@ def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode, file_name
pass
um_node.setName(node_name)
um_node.setId(node_id)
transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
transformation = ThreeMFReader._createMatrixFromTransformationString(savitar_node.getTransformation())
um_node.setTransformation(transformation)
mesh_builder = MeshBuilder()

Expand All @@ -138,7 +140,7 @@ def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode, file_name
um_node.setMeshData(mesh_data)

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

Expand Down Expand Up @@ -214,7 +216,7 @@ def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
CuraApplication.getInstance().getController().getScene().setMetaDataEntry(key, value)

for node in scene_3mf.getSceneNodes():
um_node = self._convertSavitarNodeToUMNode(node, file_name)
um_node = ThreeMFReader._convertSavitarNodeToUMNode(node, file_name)
if um_node is None:
continue

Expand Down Expand Up @@ -300,8 +302,23 @@ def _getScaleFromUnit(self, unit: Optional[str]) -> Vector:
if unit is None:
unit = "millimeter"
elif unit not in conversion_to_mm:
Logger.log("w", "Unrecognised unit {unit} used. Assuming mm instead.".format(unit = unit))
Logger.log("w", "Unrecognised unit {unit} used. Assuming mm instead.".format(unit=unit))
unit = "millimeter"

scale = conversion_to_mm[unit]
return Vector(scale, scale, scale)

@staticmethod
def stringToSceneNodes(scene_string: str) -> List[SceneNode]:
parser = Savitar.ThreeMFParser()
scene = parser.parse(scene_string)

# Convert the scene to scene nodes
nodes = []
for savitar_node in scene.getSceneNodes():
scene_node = ThreeMFReader._convertSavitarNodeToUMNode(savitar_node, "file_name")
if scene_node is None:
continue
nodes.append(scene_node)

return nodes
26 changes: 19 additions & 7 deletions plugins/3MFWriter/ThreeMFWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ def __init__(self):
"cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10"
}

self._unit_matrix_string = self._convertMatrixToString(Matrix())
self._unit_matrix_string = ThreeMFWriter._convertMatrixToString(Matrix())
self._archive: Optional[zipfile.ZipFile] = None
self._store_archive = False

def _convertMatrixToString(self, matrix):
@staticmethod
def _convertMatrixToString(matrix):
result = ""
result += str(matrix._data[0, 0]) + " "
result += str(matrix._data[1, 0]) + " "
Expand All @@ -85,7 +86,8 @@ def setStoreArchive(self, store_archive):
"""
self._store_archive = store_archive

def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()):
@staticmethod
def _convertUMNodeToSavitarNode(um_node, transformation=Matrix()):
"""Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
:returns: Uranium Scene node.
Expand All @@ -102,7 +104,7 @@ def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()):

node_matrix = um_node.getLocalTransformation()

matrix_string = self._convertMatrixToString(node_matrix.preMultiply(transformation))
matrix_string = ThreeMFWriter._convertMatrixToString(node_matrix.preMultiply(transformation))

savitar_node.setTransformation(matrix_string)
mesh_data = um_node.getMeshData()
Expand Down Expand Up @@ -135,7 +137,7 @@ def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()):
# only save the nodes on the active build plate
if child_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr:
continue
savitar_child_node = self._convertUMNodeToSavitarNode(child_node)
savitar_child_node = ThreeMFWriter._convertUMNodeToSavitarNode(child_node)
if savitar_child_node is not None:
savitar_node.addChild(savitar_child_node)

Expand Down Expand Up @@ -225,7 +227,7 @@ def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode) -> bool:
for node in nodes:
if node == root_node:
for root_child in node.getChildren():
savitar_node = self._convertUMNodeToSavitarNode(root_child, transformation_matrix)
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(root_child, transformation_matrix)
if savitar_node:
savitar_scene.addSceneNode(savitar_node)
else:
Expand Down Expand Up @@ -369,9 +371,19 @@ def _createSnapshot(self):
Logger.log("w", "Can't create snapshot when renderer not initialized.")
return None
try:
snapshot = Snapshot.snapshot(width = 300, height = 300)
snapshot = Snapshot.snapshot(width=300, height=300)
except:
Logger.logException("w", "Failed to create snapshot image")
return None

return snapshot

@staticmethod
def sceneNodesToString(scene_nodes: [SceneNode]) -> str:
savitar_scene = Savitar.Scene()
for scene_node in scene_nodes:
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node)
savitar_scene.addSceneNode(savitar_node)
parser = Savitar.ThreeMFParser()
scene_string = parser.sceneToString(savitar_scene)
return scene_string
2 changes: 1 addition & 1 deletion plugins/CuraEngineBackend/CuraEngineBackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def startPlugins(self) -> None:
backend_plugins = CuraApplication.getInstance().getBackendPlugins()
for backend_plugin in backend_plugins:
if backend_plugin.isRunning():
continue
backend_plugin.stop()
# Set the port to prevent plugins from using the same one.
backend_plugin.setPort(self._last_backend_plugin_port)
self._last_backend_plugin_port += 1
Expand Down
29 changes: 29 additions & 0 deletions resources/definitions/anycubic_kobra_plus.def.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"version": 2,
"name": "Anycubic Kobra Plus",
"inherits": "fdmprinter",
"metadata":
{
"visible": true,
"author": "Jordon Brooks",
"manufacturer": "Anycubic",
"file_formats": "text/x-gcode",
"has_machine_quality": true,
"has_materials": true,
"machine_extruder_trains": { "0": "anycubic_kobra_plus_extruder_0" },
"preferred_material": "generic_pla",
"preferred_quality_type": "normal",
"quality_definition": "anycubic_kobra_plus"
},
"overrides":
{
"machine_depth": { "default_value": 302 },
"machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG28 X0 Y0\nM84\nM355 S0; led off" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_heated_bed": { "default_value": true },
"machine_height": { "default_value": 352 },
"machine_name": { "default_value": "Anycubic Kobra Plus" },
"machine_start_gcode": { "default_value": "G28 ;Home\nG1 Z15.0 F6000 ;Move the platform down 15mm\n;Prime the extruder\nG92 E0\nM355 S1; Turn LED on\n; Add Custom purge lines\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X1.0 Y30 Z0.3 F5000.0 ; Move to start position\nG1 X1.0 Y100.0 Z0.3 F1500.0 E15 ; Draw the first line\nG1 X1.3 Y100.0 Z0.3 F5000.0 ; Move to side a little\nG1 X1.3 Y30 Z0.3 F1500.0 E30 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 E-2 F500 ; Retract a little \nG1 X50 F500 ; wipe away from the filament line\nG1 X100 F9000 ; Quickly wipe away from the filament line\nG1 Z5.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\n; End custom purge lines" },
"machine_width": { "default_value": 302 }
}
}
2 changes: 1 addition & 1 deletion resources/definitions/fdmprinter.def.json
Original file line number Diff line number Diff line change
Expand Up @@ -5921,7 +5921,7 @@
"maximum_value_warning": "skirt_brim_line_width",
"enabled": "resolveOrValue('adhesion_type') == 'brim'",
"limit_to_extruder": "skirt_brim_extruder_nr",
"settable_per_mesh": true,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"brim_replaces_support":
Expand Down
16 changes: 16 additions & 0 deletions resources/extruders/anycubic_kobra_plus_extruder_0.def.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": 2,
"name": "Extruder 1",
"inherits": "fdmextruder",
"metadata":
{
"machine": "anycubic_kobra_plus",
"position": "0"
},
"overrides":
{
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 }
}
}
Loading

0 comments on commit c1acfee

Please sign in to comment.