mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-05-10 13:39:02 +08:00
Merge branch 'master' into libArachne_rebased
This commit is contained in:
commit
14ce560904
@ -10,9 +10,9 @@ For crashes and similar issues, please attach the following information:
|
||||
|
||||
* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output)
|
||||
* The Cura GUI log file, located at
|
||||
* `%APPDATA%\cura\<Cura version>\cura.log` (Windows), or usually `C:\Users\\<your username>\AppData\Roaming\cura\<Cura version>\cura.log`
|
||||
* `$USER/Library/Application Support/cura/<Cura version>/cura.log` (OSX)
|
||||
* `$USER/.local/share/cura/<Cura version>/cura.log` (Ubuntu/Linux)
|
||||
* `%APPDATA%\cura\<Cura version>\cura.log` (Windows), or usually `C:\Users\<your username>\AppData\Roaming\cura\<Cura version>\cura.log`
|
||||
* `$HOME/Library/Application Support/cura/<Cura version>/cura.log` (OSX)
|
||||
* `$HOME/.local/share/cura/<Cura version>/cura.log` (Ubuntu/Linux)
|
||||
|
||||
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
|
||||
|
||||
|
@ -610,15 +610,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||
|
||||
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]:
|
||||
Logger.log("w", "A socket error caused the connection to be reset")
|
||||
elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError:
|
||||
Logger.error("CuraEngine crashed abnormally! The socket connection was reset unexpectedly.")
|
||||
self._slicing_error_message.show()
|
||||
self.setState(BackendState.Error)
|
||||
self.stopSlicing()
|
||||
|
||||
# _terminate()' function sets the job status to 'cancel', after reconnecting to another Port the job status
|
||||
# needs to be updated. Otherwise backendState is "Unable To Slice"
|
||||
elif error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
|
||||
if error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
|
||||
self._start_slice_job.setIsCancelled(False)
|
||||
|
||||
# Check if there's any slicable object in the scene.
|
||||
|
@ -20,7 +20,7 @@ Item
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
property alias createNewProjectButtonVisible: createNewProjectButton.visible
|
||||
property bool createNewProjectButtonVisible: true
|
||||
|
||||
anchors
|
||||
{
|
||||
@ -61,6 +61,7 @@ Item
|
||||
id: createNewProjectButton
|
||||
|
||||
text: "New Library project"
|
||||
visible: createNewProjectButtonVisible && manager.userAccountCanCreateNewLibraryProject && (manager.retrievingProjectsStatus == DF.RetrievalStatus.Success || manager.retrievingProjectsStatus == DF.RetrievalStatus.Failed)
|
||||
|
||||
onClicked:
|
||||
{
|
||||
@ -68,6 +69,20 @@ Item
|
||||
}
|
||||
busy: manager.creatingNewProjectStatus == DF.RetrievalStatus.InProgress
|
||||
}
|
||||
|
||||
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
id: upgradePlanButton
|
||||
|
||||
text: "Upgrade plan"
|
||||
iconSource: UM.Theme.getIcon("LinkExternal")
|
||||
visible: createNewProjectButtonVisible && !manager.userAccountCanCreateNewLibraryProject && (manager.retrievingProjectsStatus == DF.RetrievalStatus.Success || manager.retrievingProjectsStatus == DF.RetrievalStatus.Failed)
|
||||
tooltip: "You have reached the maximum number of projects allowed by your subscription. Please upgrade to the Professional subscription to create more projects."
|
||||
tooltipWidth: parent.width * 0.5
|
||||
|
||||
onClicked: Qt.openUrlExternally("https://ultimaker.com/software/ultimaker-essentials/sign-up-cura?utm_source=cura&utm_medium=software&utm_campaign=lib-max")
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
|
@ -55,6 +55,7 @@ class DigitalFactoryApiClient:
|
||||
self._http = HttpRequestManager.getInstance()
|
||||
self._on_error = on_error
|
||||
self._file_uploader = None # type: Optional[DFFileUploader]
|
||||
self._library_max_private_projects: Optional[int] = None
|
||||
|
||||
self._projects_pagination_mgr = PaginationManager(limit = projects_limit_per_page) if projects_limit_per_page else None # type: Optional[PaginationManager]
|
||||
|
||||
@ -69,6 +70,7 @@ class DigitalFactoryApiClient:
|
||||
callback(
|
||||
response.library_max_private_projects == -1 or # Note: -1 is unlimited
|
||||
response.library_max_private_projects > 0)
|
||||
self._library_max_private_projects = response.library_max_private_projects
|
||||
else:
|
||||
Logger.warning(f"Digital Factory: Response is not a feature budget, likely an error: {str(response)}")
|
||||
callback(False)
|
||||
@ -79,6 +81,41 @@ class DigitalFactoryApiClient:
|
||||
error_callback = callbackWrap,
|
||||
timeout = self.DEFAULT_REQUEST_TIMEOUT)
|
||||
|
||||
def checkUserCanCreateNewLibraryProject(self, callback: Callable) -> None:
|
||||
"""
|
||||
Checks if the user is allowed to create new library projects.
|
||||
A user is allowed to create new library projects if the haven't reached their maximum allowed private projects.
|
||||
"""
|
||||
|
||||
def callbackWrap(response: Optional[Any] = None, *args, **kwargs) -> None:
|
||||
if response is not None:
|
||||
if isinstance(response, DigitalFactoryProjectResponse): # The user has only one private project
|
||||
callback(True)
|
||||
elif isinstance(response, list) and all(isinstance(r, DigitalFactoryProjectResponse) for r in response):
|
||||
callback(len(response) < cast(int, self._library_max_private_projects))
|
||||
else:
|
||||
Logger.warning(f"Digital Factory: Incorrect response type received when requesting private projects: {str(response)}")
|
||||
callback(False)
|
||||
else:
|
||||
Logger.warning(f"Digital Factory: Response is empty, likely an error: {str(response)}")
|
||||
callback(False)
|
||||
|
||||
if self._library_max_private_projects is not None and self._library_max_private_projects > 0:
|
||||
# The user has a limit in the number of private projects they can create. Check whether they have already
|
||||
# reached that limit.
|
||||
# Note: Set the pagination manager to None when doing this get request, or else the next/previous links
|
||||
# of the pagination will become corrupted
|
||||
url = f"{self.CURA_API_ROOT}/projects?shared=false&limit={self._library_max_private_projects}"
|
||||
self._http.get(url,
|
||||
scope = self._scope,
|
||||
callback = self._parseCallback(callbackWrap, DigitalFactoryProjectResponse, callbackWrap, pagination_manager = None),
|
||||
error_callback = callbackWrap,
|
||||
timeout = self.DEFAULT_REQUEST_TIMEOUT)
|
||||
else:
|
||||
# If the limit is -1, then the user is allowed unlimited projects. If its 0 then they are not allowed to
|
||||
# create any projects
|
||||
callback(self._library_max_private_projects == -1)
|
||||
|
||||
def getProject(self, library_project_id: str, on_finished: Callable[[DigitalFactoryProjectResponse], Any], failed: Callable) -> None:
|
||||
"""
|
||||
Retrieves a digital factory project by its library project id.
|
||||
@ -329,7 +366,7 @@ class DigitalFactoryApiClient:
|
||||
:param on_error: The function to be called if anything goes wrong.
|
||||
"""
|
||||
|
||||
display_name = re.sub(r"[^a-zA-Z0-9- ./™®ö+']", " ", project_name)
|
||||
display_name = re.sub(r"^[\\w\\-\\. ()]+\\.[a-zA-Z0-9]+$", " ", project_name)
|
||||
Logger.log("i", "Attempt to create new DF project '{}'.".format(display_name))
|
||||
|
||||
url = "{}/projects".format(self.CURA_API_ROOT)
|
||||
|
@ -94,6 +94,9 @@ class DigitalFactoryController(QObject):
|
||||
"""Signal to inform about the state of user access."""
|
||||
userAccessStateChanged = pyqtSignal(bool)
|
||||
|
||||
"""Signal to inform whether the user is allowed to create more Library projects."""
|
||||
userCanCreateNewLibraryProjectChanged = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, application: CuraApplication) -> None:
|
||||
super().__init__(parent = None)
|
||||
|
||||
@ -143,6 +146,7 @@ class DigitalFactoryController(QObject):
|
||||
self._application.initializationFinished.connect(self._applicationInitializationFinished)
|
||||
|
||||
self._user_has_access = False
|
||||
self._user_account_can_create_new_project = False
|
||||
|
||||
def clear(self) -> None:
|
||||
self._project_model.clearProjects()
|
||||
@ -169,10 +173,8 @@ class DigitalFactoryController(QObject):
|
||||
|
||||
:return: True if the user account has Digital Library access, else False
|
||||
"""
|
||||
if self._account.userProfile:
|
||||
subscriptions = self._account.userProfile.get("subscriptions", [])
|
||||
if len(subscriptions) > 0:
|
||||
return True
|
||||
if self._user_has_access:
|
||||
self._api.checkUserCanCreateNewLibraryProject(callback = self.setCanCreateNewLibraryProject)
|
||||
return self._user_has_access
|
||||
|
||||
def initialize(self, preselected_project_id: Optional[str] = None) -> None:
|
||||
@ -556,6 +558,7 @@ class DigitalFactoryController(QObject):
|
||||
self._project_model.clearProjects()
|
||||
self.setSelectedProjectIndex(-1)
|
||||
self._api.getProjectsFirstPage(search_filter = self._project_filter, on_finished = self._onGetProjectsFirstPageFinished, failed = self._onGetProjectsFailed)
|
||||
self._api.checkUserCanCreateNewLibraryProject(callback = self.setCanCreateNewLibraryProject)
|
||||
self.setRetrievingProjectsStatus(RetrievalStatus.InProgress)
|
||||
self._has_preselected_project = new_has_preselected_project
|
||||
self.preselectedProjectChanged.emit()
|
||||
@ -564,6 +567,14 @@ class DigitalFactoryController(QObject):
|
||||
def hasPreselectedProject(self) -> bool:
|
||||
return self._has_preselected_project
|
||||
|
||||
def setCanCreateNewLibraryProject(self, can_create_new_library_project: bool) -> None:
|
||||
self._user_account_can_create_new_project = can_create_new_library_project
|
||||
self.userCanCreateNewLibraryProjectChanged.emit(self._user_account_can_create_new_project)
|
||||
|
||||
@pyqtProperty(bool, fset = setCanCreateNewLibraryProject, notify = userCanCreateNewLibraryProjectChanged)
|
||||
def userAccountCanCreateNewLibraryProject(self) -> bool:
|
||||
return self._user_account_can_create_new_project
|
||||
|
||||
@pyqtSlot(str, "QStringList")
|
||||
def saveFileToSelectedProject(self, filename: str, formats: List[str]) -> None:
|
||||
"""
|
||||
|
@ -16,9 +16,9 @@ class DigitalFactoryFeatureBudgetResponse(BaseModel):
|
||||
library_can_use_status: Optional[bool] = False,
|
||||
library_can_use_tags: Optional[bool] = False,
|
||||
library_can_use_technical_requirements: Optional[bool] = False,
|
||||
library_max_organization_shared_projects: Optional[int] = False, # -1 means unlimited
|
||||
library_max_private_projects: Optional[int] = False, # -1 means unlimited
|
||||
library_max_team_shared_projects: Optional[int] = False, # -1 means unlimited
|
||||
library_max_organization_shared_projects: Optional[int] = None, # -1 means unlimited
|
||||
library_max_private_projects: Optional[int] = None, # -1 means unlimited
|
||||
library_max_team_shared_projects: Optional[int] = None, # -1 means unlimited
|
||||
**kwargs) -> None:
|
||||
|
||||
self.library_can_use_business_value = library_can_use_business_value
|
||||
|
@ -5,6 +5,7 @@
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"platform": "kossel_pro_build_platform.3mf",
|
||||
"platform_offset": [0, -0.25, 0],
|
||||
"machine_extruder_trains": {
|
||||
"0": "anycubic_kossel_extruder_0"
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ Button
|
||||
property alias textFont: buttonText.font
|
||||
property alias cornerRadius: backgroundRect.radius
|
||||
property alias tooltip: tooltip.tooltipText
|
||||
property alias tooltipWidth: tooltip.width
|
||||
|
||||
property color color: UM.Theme.getColor("primary")
|
||||
property color hoverColor: UM.Theme.getColor("primary_hover")
|
||||
|
@ -417,6 +417,7 @@ UM.MainWindow
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
text: model.name
|
||||
iconSource: UM.Theme.getIcon(model.icon)
|
||||
height: UM.Theme.getSize("message_action_button").height
|
||||
}
|
||||
}
|
||||
@ -426,6 +427,7 @@ UM.MainWindow
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
text: model.name
|
||||
iconSource: UM.Theme.getIcon(model.icon)
|
||||
height: UM.Theme.getSize("message_action_button").height
|
||||
}
|
||||
}
|
||||
@ -434,6 +436,14 @@ UM.MainWindow
|
||||
Cura.TertiaryButton
|
||||
{
|
||||
text: model.name
|
||||
iconSource:
|
||||
{
|
||||
if (model.icon == null || model.icon == "")
|
||||
{
|
||||
return UM.Theme.getIcon("LinkExternal")
|
||||
}
|
||||
return UM.Theme.getIcon(model.icon)
|
||||
}
|
||||
height: UM.Theme.getSize("message_action_button").height
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user