mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-05 21:13:58 -06:00
Merge branch 'main' into CURA-11078_python_312_spike
This commit is contained in:
commit
052c37ae6f
291 changed files with 76894 additions and 67230 deletions
|
@ -1259,7 +1259,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
available_intent_category_list = IntentManager.getInstance().currentAvailableIntentCategories()
|
||||
if self._intent_category_to_apply is not None and self._intent_category_to_apply in available_intent_category_list:
|
||||
machine_manager.setIntentByCategory(self._intent_category_to_apply)
|
||||
|
||||
else:
|
||||
# if no intent is provided, reset to the default (balanced) intent
|
||||
machine_manager.resetIntents()
|
||||
# Notify everything/one that is to notify about changes.
|
||||
global_stack.containersChanged.emit(global_stack.getTop())
|
||||
|
||||
|
|
|
@ -35,10 +35,12 @@ class WorkspaceDialog(QObject):
|
|||
self._qml_url = "WorkspaceDialog.qml"
|
||||
self._lock = threading.Lock()
|
||||
self._default_strategy = None
|
||||
self._result = {"machine": self._default_strategy,
|
||||
"quality_changes": self._default_strategy,
|
||||
"definition_changes": self._default_strategy,
|
||||
"material": self._default_strategy}
|
||||
self._result = {
|
||||
"machine": self._default_strategy,
|
||||
"quality_changes": self._default_strategy,
|
||||
"definition_changes": self._default_strategy,
|
||||
"material": self._default_strategy,
|
||||
}
|
||||
self._override_machine = None
|
||||
self._visible = False
|
||||
self.showDialogSignal.connect(self.__show)
|
||||
|
@ -347,10 +349,12 @@ class WorkspaceDialog(QObject):
|
|||
if threading.current_thread() != threading.main_thread():
|
||||
self._lock.acquire()
|
||||
# Reset the result
|
||||
self._result = {"machine": self._default_strategy,
|
||||
"quality_changes": self._default_strategy,
|
||||
"definition_changes": self._default_strategy,
|
||||
"material": self._default_strategy}
|
||||
self._result = {
|
||||
"machine": self._default_strategy,
|
||||
"quality_changes": self._default_strategy,
|
||||
"definition_changes": self._default_strategy,
|
||||
"material": self._default_strategy,
|
||||
}
|
||||
self._visible = True
|
||||
self.showDialogSignal.emit()
|
||||
|
||||
|
|
|
@ -208,12 +208,14 @@ Item
|
|||
anchors.rightMargin: UM.Theme.getSize("thin_margin").height
|
||||
|
||||
enabled: UM.Backend.state == UM.Backend.Done
|
||||
currentIndex: UM.Backend.state == UM.Backend.Done ? 0 : 1
|
||||
currentIndex: UM.Backend.state == UM.Backend.Done ? dfFilenameTextfield.text.startsWith("MM")? 1 : 0 : 2
|
||||
|
||||
textRole: "text"
|
||||
valueRole: "value"
|
||||
|
||||
model: [
|
||||
{ text: catalog.i18nc("@option", "Save Cura project and print file"), key: "3mf_ufp", value: ["3mf", "ufp"] },
|
||||
{ text: catalog.i18nc("@option", "Save Cura project and .ufp print file"), key: "3mf_ufp", value: ["3mf", "ufp"] },
|
||||
{ text: catalog.i18nc("@option", "Save Cura project and .makerbot print file"), key: "3mf_makerbot", value: ["3mf", "makerbot"] },
|
||||
{ text: catalog.i18nc("@option", "Save Cura project"), key: "3mf", value: ["3mf"] },
|
||||
]
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ from .ExportFileJob import ExportFileJob
|
|||
class DFFileExportAndUploadManager:
|
||||
"""
|
||||
Class responsible for exporting the scene and uploading the exported data to the Digital Factory Library. Since 3mf
|
||||
and UFP files may need to be uploaded at the same time, this class keeps a single progress and success message for
|
||||
and (UFP or makerbot) files may need to be uploaded at the same time, this class keeps a single progress and success message for
|
||||
both files and updates those messages according to the progress of both the file job uploads.
|
||||
"""
|
||||
def __init__(self, file_handlers: Dict[str, FileHandler],
|
||||
|
@ -118,7 +118,7 @@ class DFFileExportAndUploadManager:
|
|||
library_project_id = self._library_project_id,
|
||||
source_file_id = self._source_file_id
|
||||
)
|
||||
self._api.requestUploadUFP(request, on_finished = self._uploadFileData, on_error = self._onRequestUploadPrintFileFailed)
|
||||
self._api.requestUploadMeshFile(request, on_finished = self._uploadFileData, on_error = self._onRequestUploadPrintFileFailed)
|
||||
|
||||
def _uploadFileData(self, file_upload_response: Union[DFLibraryFileUploadResponse, DFPrintJobUploadResponse]) -> None:
|
||||
"""Uploads the exported file data after the file or print job upload has been registered at the Digital Factory
|
||||
|
@ -279,22 +279,25 @@ class DFFileExportAndUploadManager:
|
|||
This means that something went wrong with the initial request to create a "file" entry in the digital library.
|
||||
"""
|
||||
reply_string = bytes(reply.readAll()).decode()
|
||||
filename_ufp = self._file_name + ".ufp"
|
||||
Logger.log("d", "An error occurred while uploading the print job file '{}' to the Digital Library project '{}': {}".format(filename_ufp, self._library_project_id, reply_string))
|
||||
if "ufp" in self._formats:
|
||||
filename_meshfile = self._file_name + ".ufp"
|
||||
elif "makerbot" in self._formats:
|
||||
filename_meshfile = self._file_name + ".makerbot"
|
||||
Logger.log("d", "An error occurred while uploading the print job file '{}' to the Digital Library project '{}': {}".format(filename_meshfile, self._library_project_id, reply_string))
|
||||
with self._message_lock:
|
||||
# Set the progress to 100% when the upload job fails, to avoid having the progress message stuck
|
||||
self._file_upload_job_metadata[filename_ufp]["upload_status"] = "failed"
|
||||
self._file_upload_job_metadata[filename_ufp]["upload_progress"] = 100
|
||||
self._file_upload_job_metadata[filename_meshfile]["upload_status"] = "failed"
|
||||
self._file_upload_job_metadata[filename_meshfile]["upload_progress"] = 100
|
||||
|
||||
human_readable_error = self.extractErrorTitle(reply_string)
|
||||
self._file_upload_job_metadata[filename_ufp]["file_upload_failed_message"] = getBackwardsCompatibleMessage(
|
||||
self._file_upload_job_metadata[filename_meshfile]["file_upload_failed_message"] = getBackwardsCompatibleMessage(
|
||||
title = "File upload error",
|
||||
text = "Failed to upload the file '{}' to '{}'. {}".format(filename_ufp, self._library_project_name, human_readable_error),
|
||||
text = "Failed to upload the file '{}' to '{}'. {}".format(filename_meshfile, self._library_project_name, human_readable_error),
|
||||
message_type_str = "ERROR",
|
||||
lifetime = 30
|
||||
)
|
||||
self._on_upload_error()
|
||||
self._onFileUploadFinished(filename_ufp)
|
||||
self._onFileUploadFinished(filename_meshfile)
|
||||
|
||||
@staticmethod
|
||||
def extractErrorTitle(reply_body: Optional[str]) -> str:
|
||||
|
@ -407,4 +410,28 @@ class DFFileExportAndUploadManager:
|
|||
job_ufp = ExportFileJob(self._file_handlers["ufp"], self._nodes, self._file_name, "ufp")
|
||||
job_ufp.finished.connect(self._onPrintFileExported)
|
||||
self._upload_jobs.append(job_ufp)
|
||||
|
||||
if "makerbot" in self._formats and "makerbot" in self._file_handlers and self._file_handlers["makerbot"]:
|
||||
filename_makerbot = self._file_name + ".makerbot"
|
||||
metadata[filename_makerbot] = {
|
||||
"export_job_output" : None,
|
||||
"upload_progress" : -1,
|
||||
"upload_status" : "",
|
||||
"file_upload_response": None,
|
||||
"file_upload_success_message": getBackwardsCompatibleMessage(
|
||||
text = "'{}' was uploaded to '{}'.".format(filename_makerbot, self._library_project_name),
|
||||
title = "Upload successful",
|
||||
message_type_str = "POSITIVE",
|
||||
lifetime = 30,
|
||||
),
|
||||
"file_upload_failed_message": getBackwardsCompatibleMessage(
|
||||
text = "Failed to upload the file '{}' to '{}'.".format(filename_makerbot, self._library_project_name),
|
||||
title = "File upload error",
|
||||
message_type_str = "ERROR",
|
||||
lifetime = 30
|
||||
)
|
||||
}
|
||||
job_makerbot = ExportFileJob(self._file_handlers["makerbot"], self._nodes, self._file_name, "makerbot")
|
||||
job_makerbot.finished.connect(self._onPrintFileExported)
|
||||
self._upload_jobs.append(job_makerbot)
|
||||
return metadata
|
||||
|
|
|
@ -313,7 +313,7 @@ class DigitalFactoryApiClient:
|
|||
error_callback = on_error,
|
||||
timeout = self.DEFAULT_REQUEST_TIMEOUT)
|
||||
|
||||
def requestUploadUFP(self, request: DFPrintJobUploadRequest,
|
||||
def requestUploadMeshFile(self, request: DFPrintJobUploadRequest,
|
||||
on_finished: Callable[[DFPrintJobUploadResponse], Any],
|
||||
on_error: Optional[Callable[["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None) -> None:
|
||||
"""Requests the Digital Factory to register the upload of a file in a library project.
|
||||
|
|
|
@ -92,7 +92,8 @@ class DigitalFactoryOutputDevice(ProjectOutputDevice):
|
|||
if not self._controller.file_handlers:
|
||||
self._controller.file_handlers = {
|
||||
"3mf": CuraApplication.getInstance().getWorkspaceFileHandler(),
|
||||
"ufp": CuraApplication.getInstance().getMeshFileHandler()
|
||||
"ufp": CuraApplication.getInstance().getMeshFileHandler(),
|
||||
"makerbot": CuraApplication.getInstance().getMeshFileHandler()
|
||||
}
|
||||
|
||||
self._dialog = CuraApplication.getInstance().createQmlComponent(self._dialog_path, {"manager": self._controller})
|
||||
|
|
306
plugins/MakerbotWriter/MakerbotWriter.py
Normal file
306
plugins/MakerbotWriter/MakerbotWriter.py
Normal file
|
@ -0,0 +1,306 @@
|
|||
# Copyright (c) 2023 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from io import StringIO, BufferedIOBase
|
||||
import json
|
||||
from typing import cast, List, Optional, Dict
|
||||
from zipfile import BadZipFile, ZipFile, ZIP_DEFLATED
|
||||
import pyDulcificum as du
|
||||
|
||||
from PyQt6.QtCore import QBuffer
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Mesh.MeshWriter import MeshWriter
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Snapshot import Snapshot
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
from cura.CuraVersion import ConanInstalls
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class MakerbotWriter(MeshWriter):
|
||||
"""A file writer that writes '.makerbot' files."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(add_to_recent_files=False)
|
||||
Logger.info(f"Using PyDulcificum: {du.__version__}")
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name="application/x-makerbot",
|
||||
comment="Makerbot Toolpath Package",
|
||||
suffixes=["makerbot"]
|
||||
)
|
||||
)
|
||||
|
||||
_PNG_FORMATS = [
|
||||
{"prefix": "isometric_thumbnail", "width": 120, "height": 120},
|
||||
{"prefix": "isometric_thumbnail", "width": 320, "height": 320},
|
||||
{"prefix": "isometric_thumbnail", "width": 640, "height": 640},
|
||||
{"prefix": "thumbnail", "width": 140, "height": 106},
|
||||
{"prefix": "thumbnail", "width": 212, "height": 300},
|
||||
{"prefix": "thumbnail", "width": 960, "height": 1460},
|
||||
{"prefix": "thumbnail", "width": 90, "height": 90},
|
||||
]
|
||||
_META_VERSION = "3.0.0"
|
||||
_PRINT_NAME_MAP = {
|
||||
"UltiMaker Method": "fire_e",
|
||||
"UltiMaker Method X": "lava_f",
|
||||
"UltiMaker Method XL": "magma_10",
|
||||
}
|
||||
_EXTRUDER_NAME_MAP = {
|
||||
"1XA": "mk14_hot",
|
||||
"2XA": "mk14_hot_s",
|
||||
"1C": "mk14_c",
|
||||
"1A": "mk14",
|
||||
"2A": "mk14_s",
|
||||
}
|
||||
_MATERIAL_MAP = {"2780b345-577b-4a24-a2c5-12e6aad3e690": "abs",
|
||||
"88c8919c-6a09-471a-b7b6-e801263d862d": "abs-wss1",
|
||||
"416eead4-0d8e-4f0b-8bfc-a91a519befa5": "asa",
|
||||
"85bbae0e-938d-46fb-989f-c9b3689dc4f0": "nylon-cf",
|
||||
"283d439a-3490-4481-920c-c51d8cdecf9c": "nylon",
|
||||
"62414577-94d1-490d-b1e4-7ef3ec40db02": "pc",
|
||||
"69386c85-5b6c-421a-bec5-aeb1fb33f060": "petg",
|
||||
"0ff92885-617b-4144-a03c-9989872454bc": "pla",
|
||||
"a4255da2-cb2a-4042-be49-4a83957a2f9a": "pva",
|
||||
"a140ef8f-4f26-4e73-abe0-cfc29d6d1024": "wss1",
|
||||
"77873465-83a9-4283-bc44-4e542b8eb3eb": "sr30",
|
||||
"96fca5d9-0371-4516-9e96-8e8182677f3c": "im-pla",
|
||||
"9f52c514-bb53-46a6-8c0c-d507cd6ee742": "abs",
|
||||
"0f9a2a91-f9d6-4b6b-bd9b-a120a29391be": "abs",
|
||||
"d3e972f2-68c0-4d2f-8cfd-91028dfc3381": "abs",
|
||||
"495a0ce5-9daf-4a16-b7b2-06856d82394d": "abs-cf10",
|
||||
"cb76bd6e-91fd-480c-a191-12301712ec77": "abs-wss1",
|
||||
"a017777e-3f37-4d89-a96c-dc71219aac77": "abs-wss1",
|
||||
"4d96000d-66de-4d54-a580-91827dcfd28f": "abs-wss1",
|
||||
"0ecb0e1a-6a66-49fb-b9ea-61a8924e0cf5": "asa",
|
||||
"efebc2ea-2381-4937-926f-e824524524a5": "asa",
|
||||
"b0199512-5714-4951-af85-be19693430f8": "asa",
|
||||
"b9f55a0a-a2b6-4b8d-8d48-07802c575bd1": "pla",
|
||||
"c439d884-9cdc-4296-a12c-1bacae01003f": "pla",
|
||||
"16a723e3-44df-49f4-82ec-2a1173c1e7d9": "pla",
|
||||
"74d0f5c2-fdfd-4c56-baf1-ff5fa92d177e": "pla",
|
||||
"64dcb783-470d-4400-91b1-7001652f20da": "pla",
|
||||
"3a1b479b-899c-46eb-a2ea-67050d1a4937": "pla",
|
||||
"4708ac49-5dde-4cc2-8c0a-87425a92c2b3": "pla",
|
||||
"4b560eda-1719-407f-b085-1c2c1fc8ffc1": "pla",
|
||||
"e10a287d-0067-4a58-9083-b7054f479991": "im-pla",
|
||||
"01a6b5b0-fab1-420c-a5d9-31713cbeb404": "im-pla",
|
||||
"f65df4ad-a027-4a48-a51d-975cc8b87041": "im-pla",
|
||||
"f48739f8-6d96-4a3d-9a2e-8505a47e2e35": "im-pla",
|
||||
"5c7d7672-e885-4452-9a78-8ba90ec79937": "petg",
|
||||
"91e05a6e-2f5b-4964-b973-d83b5afe6db4": "petg",
|
||||
"bdc7dd03-bf38-48ee-aeca-c3e11cee799e": "petg",
|
||||
"54f66c89-998d-4070-aa60-1cb0fd887518": "nylon",
|
||||
"002c84b3-84ac-4b5a-b57d-fe1f555a6351": "pva",
|
||||
"e4da5fcb-f62d-48a2-aaef-0b645aa6973b": "wss1",
|
||||
"77f06146-6569-437d-8380-9edb0d635a32": "sr30"}
|
||||
|
||||
# must be called from the main thread because of OpenGL
|
||||
@staticmethod
|
||||
@call_on_qt_thread
|
||||
def _createThumbnail(width: int, height: int) -> Optional[QBuffer]:
|
||||
if not CuraApplication.getInstance().isVisible:
|
||||
Logger.warning("Can't create snapshot when renderer not initialized.")
|
||||
return
|
||||
try:
|
||||
snapshot = Snapshot.isometricSnapshot(width, height)
|
||||
|
||||
thumbnail_buffer = QBuffer()
|
||||
thumbnail_buffer.open(QBuffer.OpenModeFlag.WriteOnly)
|
||||
|
||||
snapshot.save(thumbnail_buffer, "PNG")
|
||||
|
||||
return thumbnail_buffer
|
||||
|
||||
except:
|
||||
Logger.logException("w", "Failed to create snapshot image")
|
||||
|
||||
return None
|
||||
|
||||
def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode=MeshWriter.OutputMode.BinaryMode) -> bool:
|
||||
if mode != MeshWriter.OutputMode.BinaryMode:
|
||||
Logger.log("e", "MakerbotWriter does not support text mode.")
|
||||
self.setInformation(catalog.i18nc("@error:not supported", "MakerbotWriter does not support text mode."))
|
||||
return False
|
||||
|
||||
# The GCodeWriter plugin is always available since it is in the "required" list of plugins.
|
||||
gcode_writer = PluginRegistry.getInstance().getPluginObject("GCodeWriter")
|
||||
|
||||
if gcode_writer is None:
|
||||
Logger.log("e", "Could not find the GCodeWriter plugin, is it disabled?.")
|
||||
self.setInformation(
|
||||
catalog.i18nc("@error:load", "Could not load GCodeWriter plugin. Try to re-enable the plugin."))
|
||||
return False
|
||||
|
||||
gcode_writer = cast(MeshWriter, gcode_writer)
|
||||
|
||||
gcode_text_io = StringIO()
|
||||
success = gcode_writer.write(gcode_text_io, None)
|
||||
|
||||
# Writing the g-code failed. Then I can also not write the gzipped g-code.
|
||||
if not success:
|
||||
self.setInformation(gcode_writer.getInformation())
|
||||
return False
|
||||
|
||||
json_toolpaths = du.gcode_2_miracle_jtp(gcode_text_io.getvalue())
|
||||
metadata = self._getMeta(nodes)
|
||||
|
||||
png_files = []
|
||||
for png_format in self._PNG_FORMATS:
|
||||
width, height, prefix = png_format["width"], png_format["height"], png_format["prefix"]
|
||||
thumbnail_buffer = self._createThumbnail(width, height)
|
||||
if thumbnail_buffer is None:
|
||||
Logger.warning(f"Could not create thumbnail of size {width}x{height}.")
|
||||
continue
|
||||
png_files.append({
|
||||
"file": f"{prefix}_{width}x{height}.png",
|
||||
"data": thumbnail_buffer.data(),
|
||||
})
|
||||
|
||||
try:
|
||||
with ZipFile(stream, "w", compression=ZIP_DEFLATED) as zip_stream:
|
||||
zip_stream.writestr("meta.json", json.dumps(metadata, indent=4))
|
||||
zip_stream.writestr("print.jsontoolpath", json_toolpaths)
|
||||
for png_file in png_files:
|
||||
file, data = png_file["file"], png_file["data"]
|
||||
zip_stream.writestr(file, data)
|
||||
except (IOError, OSError, BadZipFile) as ex:
|
||||
Logger.log("e", f"Could not write to (.makerbot) file because: '{ex}'.")
|
||||
self.setInformation(catalog.i18nc("@error", "MakerbotWriter could not save to the designated path."))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _getMeta(self, root_nodes: List[SceneNode]) -> Dict[str, any]:
|
||||
application = CuraApplication.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
global_stack = machine_manager.activeMachine
|
||||
extruders = global_stack.extruderList
|
||||
|
||||
nodes = []
|
||||
for root_node in root_nodes:
|
||||
for node in DepthFirstIterator(root_node):
|
||||
if not getattr(node, "_outside_buildarea", False):
|
||||
if node.callDecoration(
|
||||
"isSliceable") and node.getMeshData() and node.isVisible() and not node.callDecoration(
|
||||
"isNonThumbnailVisibleMesh"):
|
||||
nodes.append(node)
|
||||
|
||||
meta = dict()
|
||||
|
||||
meta["bot_type"] = MakerbotWriter._PRINT_NAME_MAP.get((name := global_stack.definition.name), name)
|
||||
|
||||
bounds: Optional[AxisAlignedBox] = None
|
||||
for node in nodes:
|
||||
node_bounds = node.getBoundingBox()
|
||||
if node_bounds is None:
|
||||
continue
|
||||
if bounds is None:
|
||||
bounds = node_bounds
|
||||
else:
|
||||
bounds = bounds + node_bounds
|
||||
|
||||
if bounds is not None:
|
||||
meta["bounding_box"] = {
|
||||
"x_min": bounds.left,
|
||||
"x_max": bounds.right,
|
||||
"y_min": bounds.back,
|
||||
"y_max": bounds.front,
|
||||
"z_min": bounds.bottom,
|
||||
"z_max": bounds.top,
|
||||
}
|
||||
|
||||
material_bed_temperature = global_stack.getProperty("material_bed_temperature", "value")
|
||||
meta["platform_temperature"] = material_bed_temperature
|
||||
|
||||
build_volume_temperature = global_stack.getProperty("build_volume_temperature", "value")
|
||||
meta["build_plane_temperature"] = build_volume_temperature
|
||||
|
||||
print_information = application.getPrintInformation()
|
||||
|
||||
meta["commanded_duration_s"] = int(print_information.currentPrintTime)
|
||||
meta["duration_s"] = int(print_information.currentPrintTime)
|
||||
|
||||
material_lengths = list(map(meterToMillimeter, print_information.materialLengths))
|
||||
meta["extrusion_distance_mm"] = material_lengths[0]
|
||||
meta["extrusion_distances_mm"] = material_lengths
|
||||
|
||||
meta["extrusion_mass_g"] = print_information.materialWeights[0]
|
||||
meta["extrusion_masses_g"] = print_information.materialWeights
|
||||
|
||||
meta["uuid"] = print_information.slice_uuid
|
||||
|
||||
materials = []
|
||||
for extruder in extruders:
|
||||
guid = extruder.material.getMetaData().get("GUID")
|
||||
material_name = extruder.material.getMetaData().get("material")
|
||||
material = self._MATERIAL_MAP.get(guid, material_name)
|
||||
materials.append(material)
|
||||
|
||||
meta["material"] = materials[0]
|
||||
meta["materials"] = materials
|
||||
|
||||
materials_temps = [extruder.getProperty("default_material_print_temperature", "value") for extruder in
|
||||
extruders]
|
||||
meta["extruder_temperature"] = materials_temps[0]
|
||||
meta["extruder_temperatures"] = materials_temps
|
||||
|
||||
meta["model_counts"] = [{"count": 1, "name": node.getName()} for node in nodes]
|
||||
|
||||
tool_types = [MakerbotWriter._EXTRUDER_NAME_MAP.get((name := extruder.variant.getName()), name) for extruder in
|
||||
extruders]
|
||||
meta["tool_type"] = tool_types[0]
|
||||
meta["tool_types"] = tool_types
|
||||
|
||||
meta["version"] = MakerbotWriter._META_VERSION
|
||||
|
||||
meta["preferences"] = dict()
|
||||
for node in nodes:
|
||||
bounds = node.getBoundingBox()
|
||||
meta["preferences"][str(node.getName())] = {
|
||||
"machineBounds": [bounds.right, bounds.back, bounds.left, bounds.front] if bounds is not None else None,
|
||||
"printMode": CuraApplication.getInstance().getIntentManager().currentIntentCategory,
|
||||
}
|
||||
|
||||
meta["miracle_config"] = {"gaggles": {str(node.getName()): {} for node in nodes}}
|
||||
|
||||
cura_engine_info = ConanInstalls.get("curaengine", {"version": "unknown", "revision": "unknown"})
|
||||
meta["curaengine_version"] = cura_engine_info["version"]
|
||||
meta["curaengine_commit_hash"] = cura_engine_info["revision"]
|
||||
|
||||
dulcificum_info = ConanInstalls.get("dulcificum", {"version": "unknown", "revision": "unknown"})
|
||||
meta["dulcificum_version"] = dulcificum_info["version"]
|
||||
meta["dulcificum_commit_hash"] = dulcificum_info["revision"]
|
||||
|
||||
meta["makerbot_writer_version"] = self.getVersion()
|
||||
meta["pyDulcificum_version"] = du.__version__
|
||||
|
||||
# Add engine plugin information to the metadata
|
||||
for name, package_info in ConanInstalls.items():
|
||||
if not name.startswith("curaengine_"):
|
||||
continue
|
||||
meta[f"{name}_version"] = package_info["version"]
|
||||
meta[f"{name}_commit_hash"] = package_info["revision"]
|
||||
|
||||
# TODO add the following instructions
|
||||
# num_tool_changes
|
||||
# num_z_layers
|
||||
# num_z_transitions
|
||||
# platform_temperature
|
||||
# total_commands
|
||||
|
||||
return meta
|
||||
|
||||
|
||||
def meterToMillimeter(value: float) -> float:
|
||||
"""Converts a value in meters to millimeters."""
|
||||
return value * 1000.0
|
28
plugins/MakerbotWriter/__init__.py
Normal file
28
plugins/MakerbotWriter/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Copyright (c) 2023 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from . import MakerbotWriter
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
file_extension = "makerbot"
|
||||
return {
|
||||
"mesh_writer": {
|
||||
"output": [{
|
||||
"extension": file_extension,
|
||||
"description": catalog.i18nc("@item:inlistbox", "Makerbot Printfile"),
|
||||
"mime_type": "application/x-makerbot",
|
||||
"mode": MakerbotWriter.MakerbotWriter.OutputMode.BinaryMode,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def register(app):
|
||||
return {
|
||||
"mesh_writer": MakerbotWriter.MakerbotWriter(),
|
||||
}
|
13
plugins/MakerbotWriter/plugin.json
Normal file
13
plugins/MakerbotWriter/plugin.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "Makerbot Printfile Writer",
|
||||
"author": "UltiMaker",
|
||||
"version": "0.1.0",
|
||||
"description": "Provides support for writing MakerBot Format Packages.",
|
||||
"api": 8,
|
||||
"supported_sdk_versions": [
|
||||
"8.0.0",
|
||||
"8.1.0",
|
||||
"8.2.0"
|
||||
],
|
||||
"i18n-catalog": "cura"
|
||||
}
|
BIN
plugins/UM3NetworkPrinting/resources/png/MakerBot Method X.png
Normal file
BIN
plugins/UM3NetworkPrinting/resources/png/MakerBot Method X.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 202 KiB |
BIN
plugins/UM3NetworkPrinting/resources/png/MakerBot Method XL.png
Normal file
BIN
plugins/UM3NetworkPrinting/resources/png/MakerBot Method XL.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 620 KiB |
BIN
plugins/UM3NetworkPrinting/resources/png/MakerBot Method.png
Normal file
BIN
plugins/UM3NetworkPrinting/resources/png/MakerBot Method.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 140 KiB |
|
@ -115,7 +115,7 @@ UM.Dialog
|
|||
// Utils
|
||||
function formatPrintJobName(name)
|
||||
{
|
||||
var extensions = [ ".gcode.gz", ".gz", ".gcode", ".ufp" ]
|
||||
var extensions = [ ".gcode.gz", ".gz", ".gcode", ".ufp", ".makerbot" ]
|
||||
for (var i = 0; i < extensions.length; i++)
|
||||
{
|
||||
var extension = extensions[i]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Copyright (c) 2023 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
|
@ -6,7 +6,7 @@ import UM 1.3 as UM
|
|||
import Cura 1.0 as Cura
|
||||
|
||||
Item {
|
||||
property var cameraUrl: "";
|
||||
property string cameraUrl: "";
|
||||
|
||||
Rectangle {
|
||||
anchors.fill:parent;
|
||||
|
@ -34,22 +34,29 @@ Item {
|
|||
|
||||
Cura.NetworkMJPGImage {
|
||||
id: cameraImage
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
height: Math.round((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
readonly property real img_scale_factor: {
|
||||
if (imageWidth > maximumWidth || imageHeight > maximumHeight) {
|
||||
return Math.min(maximumWidth / imageWidth, maximumHeight / imageHeight);
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
width: imageWidth === 0 ? 800 * screenScaleFactor : imageWidth * img_scale_factor
|
||||
height: imageHeight === 0 ? 600 * screenScaleFactor : imageHeight * img_scale_factor
|
||||
|
||||
onVisibleChanged: {
|
||||
if (cameraUrl === "") return;
|
||||
|
||||
if (visible) {
|
||||
if (cameraUrl != "") {
|
||||
start();
|
||||
}
|
||||
start();
|
||||
} else {
|
||||
if (cameraUrl != "") {
|
||||
stop();
|
||||
}
|
||||
stop();
|
||||
}
|
||||
}
|
||||
source: cameraUrl
|
||||
width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
|
||||
z: 1
|
||||
}
|
||||
|
||||
|
|
|
@ -82,13 +82,22 @@ class CloudApiClient:
|
|||
# HACK: There is something weird going on with the API, as it reports printer types in formats like
|
||||
# "ultimaker_s3", but wants "Ultimaker S3" when using the machine_variant filter query. So we need to do some
|
||||
# conversion!
|
||||
# API points to "MakerBot Method" for a makerbot printertypes which we already changed to allign with other printer_type
|
||||
|
||||
machine_type = machine_type.replace("_plus", "+")
|
||||
machine_type = machine_type.replace("_", " ")
|
||||
machine_type = machine_type.replace("ultimaker", "ultimaker ")
|
||||
machine_type = machine_type.replace(" ", " ")
|
||||
machine_type = machine_type.title()
|
||||
machine_type = urllib.parse.quote_plus(machine_type)
|
||||
method_x = {
|
||||
"ultimaker_method":"MakerBot Method",
|
||||
"ultimaker_methodx":"MakerBot Method X",
|
||||
"ultimaker_methodxl":"MakerBot Method XL"
|
||||
}
|
||||
if machine_type in method_x:
|
||||
machine_type = method_x[machine_type]
|
||||
else:
|
||||
machine_type = machine_type.replace("_plus", "+")
|
||||
machine_type = machine_type.replace("_", " ")
|
||||
machine_type = machine_type.replace("ultimaker", "ultimaker ")
|
||||
machine_type = machine_type.replace(" ", " ")
|
||||
machine_type = machine_type.title()
|
||||
machine_type = urllib.parse.quote_plus(machine_type)
|
||||
url = f"{self.CLUSTER_API_ROOT}/clusters?machine_variant={machine_type}"
|
||||
self._http.get(url,
|
||||
scope=self._scope,
|
||||
|
|
|
@ -58,6 +58,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
|
||||
# The minimum version of firmware that support print job actions over cloud.
|
||||
PRINT_JOB_ACTIONS_MIN_VERSION = Version("5.2.12")
|
||||
PRINT_JOB_ACTIONS_MIN_VERSION_METHOD = Version("2.700")
|
||||
|
||||
# Notify can only use signals that are defined by the class that they are in, not inherited ones.
|
||||
# Therefore, we create a private signal used to trigger the printersChanged signal.
|
||||
|
@ -325,8 +326,13 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
if not self._printers:
|
||||
return False
|
||||
version_number = self.printers[0].firmwareVersion.split(".")
|
||||
firmware_version = Version([version_number[0], version_number[1], version_number[2]])
|
||||
return firmware_version >= self.PRINT_JOB_ACTIONS_MIN_VERSION
|
||||
if len(version_number)> 2:
|
||||
firmware_version = Version([version_number[0], version_number[1], version_number[2]])
|
||||
return firmware_version >= self.PRINT_JOB_ACTIONS_MIN_VERSION
|
||||
else:
|
||||
firmware_version = Version([version_number[0], version_number[1]])
|
||||
return firmware_version >= self.PRINT_JOB_ACTIONS_MIN_VERSION_METHOD
|
||||
|
||||
|
||||
@pyqtProperty(bool, constant = True)
|
||||
def supportsPrintJobQueue(self) -> bool:
|
||||
|
|
|
@ -9,6 +9,7 @@ from PyQt6.QtWidgets import QMessageBox
|
|||
|
||||
from UM import i18nCatalog
|
||||
from UM.Logger import Logger # To log errors talking to the API.
|
||||
from UM.Message import Message
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from UM.Signal import Signal
|
||||
from UM.Util import parseBool
|
||||
|
@ -25,7 +26,7 @@ from .CloudOutputDevice import CloudOutputDevice
|
|||
from ..Messages.RemovedPrintersMessage import RemovedPrintersMessage
|
||||
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
|
||||
from ..Messages.NewPrinterDetectedMessage import NewPrinterDetectedMessage
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
class CloudOutputDeviceManager:
|
||||
"""The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
|
||||
|
@ -179,6 +180,13 @@ class CloudOutputDeviceManager:
|
|||
return
|
||||
Logger.log("e", f"Failed writing to specific cloud printer: {unique_id} not in remote clusters.")
|
||||
|
||||
# This message is added so that user knows when the print job was not sent to cloud printer
|
||||
message = Message(catalog.i18nc("@info:status",
|
||||
"Failed writing to specific cloud printer: {0} not in remote clusters.").format(unique_id),
|
||||
title=catalog.i18nc("@info:title", "Error"),
|
||||
message_type=Message.MessageType.ERROR)
|
||||
message.show()
|
||||
|
||||
def _createMachineStacksForDiscoveredClusters(self, discovered_clusters: List[CloudClusterResponse]) -> None:
|
||||
"""**Synchronously** create machines for discovered devices
|
||||
|
||||
|
|
|
@ -106,6 +106,10 @@ class MeshFormatHandler:
|
|||
if "application/x-ufp" not in machine_file_formats and Version(firmware_version) >= Version("4.4"):
|
||||
machine_file_formats = ["application/x-ufp"] + machine_file_formats
|
||||
|
||||
# Exception for makerbot firmware version >=2.700: makerbot is supported
|
||||
elif "application/x-makerbot" not in machine_file_formats and Version(firmware_version >= Version("2.700")):
|
||||
machine_file_formats = ["application/x-makerbot"] + machine_file_formats
|
||||
|
||||
# Take the intersection between file_formats and machine_file_formats.
|
||||
format_by_mimetype = {f["mime_type"]: f for f in file_formats}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional, List
|
||||
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
|
||||
from ..BaseModel import BaseModel
|
||||
|
||||
|
||||
|
@ -34,7 +35,7 @@ class CloudClusterResponse(BaseModel):
|
|||
self.host_version = host_version
|
||||
self.host_internal_ip = host_internal_ip
|
||||
self.friendly_name = friendly_name
|
||||
self.printer_type = printer_type
|
||||
self.printer_type = NetworkedPrinterOutputDevice.applyPrinterTypeMapping(printer_type)
|
||||
self.printer_count = printer_count
|
||||
self.capabilities = capabilities if capabilities is not None else []
|
||||
super().__init__(**kwargs)
|
||||
|
@ -51,3 +52,4 @@ class CloudClusterResponse(BaseModel):
|
|||
:return: A human-readable representation of the data in this object.
|
||||
"""
|
||||
return str({k: v for k, v in self.__dict__.items() if k in {"cluster_id", "host_guid", "host_name", "status", "is_online", "host_version", "host_internal_ip", "friendly_name", "printer_type", "printer_count", "capabilities"}})
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import re
|
|||
|
||||
class VersionUpgrade54to55(VersionUpgrade):
|
||||
profile_regex = re.compile(
|
||||
r"um\_(?P<machine>s(3|5|7))_(?P<core_type>aa|cc|bb)(?P<nozzle_size>0\.(6|4|8))_(?P<material>pla|petg|abs|cpe|cpe_plus|nylon|pc|petcf|tough_pla|tpu)_(?P<layer_height>0\.\d{1,2}mm)")
|
||||
r"um\_(?P<machine>s(3|5|7))_(?P<core_type>aa|cc|bb)(?P<nozzle_size>0\.(6|4|8))_(?P<material>pla|petg|abs|tough_pla)_(?P<layer_height>0\.\d{1,2}mm)")
|
||||
|
||||
@staticmethod
|
||||
def _isUpgradedUltimakerDefinitionId(definition_id: str) -> bool:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue