1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
# Copyright (c) 2022 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
import enum
from typing import Optional
from PyQt6.QtCore import QObject, pyqtProperty, pyqtEnum, pyqtSignal
from UM.FlameProfiler import pyqtSlot
from datetime import timedelta
import math
from UM.Logger import Logger
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")
class DurationFormat(QObject):
class Format(enum.IntEnum):
Seconds = 0
Short = 1
Long = 2
ISO8601 = 3
pyqtEnum(Format)
class Duration(QObject):
"""A class representing a time duration.
This is primarily used as a value type to QML so we can report things
like "How long will this print take" without needing a bunch of logic
in the QML.
"""
def __init__(self, duration: Optional[int] = None, parent = None) -> None:
"""Create a duration object.
:param duration: The duration in seconds. If this is None (the default), an invalid Duration object will be created.
:param parent: The QObject parent.
"""
super().__init__(parent)
self._days = -1
self._hours = -1
self._minutes = -1
self._seconds = -1
if duration is not None:
self.setDuration(duration)
durationChanged = pyqtSignal()
@pyqtProperty(int, notify = durationChanged)
def days(self):
return self._days
@pyqtProperty(int, notify = durationChanged)
def hours(self):
return self._hours
@pyqtProperty(int, notify = durationChanged)
def minutes(self):
return self._minutes
@pyqtProperty(int, notify = durationChanged)
def seconds(self):
return self._seconds
@pyqtProperty(bool, notify = durationChanged)
def valid(self):
return self._days != -1 and self._hours != -1 and self._minutes != -1 and self._seconds != -1
@pyqtProperty(bool, notify = durationChanged)
def isTotalDurationZero(self):
return self._days == 0 and self._hours == 0 and self._minutes == 0 and self._seconds == 0
def setDuration(self, duration: int) -> None:
"""Set the duration in seconds.
This will convert the given amount of seconds into an amount of days, hours, minutes and seconds.
Note that this is mostly a workaround for issues with PyQt, as a value type this class should not
really have a setter.
"""
if duration < 0:
self._days = -1
self._hours = -1
self._minutes = -1
self._seconds = -1
else:
try:
duration = round(duration)
except OverflowError:
Logger.log("w", "Duration was too large to convert, so resetting it.")
duration = 0
# If a Python int goes above the upper bound of C++ int, which is 2^16 - 1, you will get a error when Qt
# tries to convert the Python int to C++ int:
# TypeError: unable to convert a Python 'int' object to a C++ 'int' instance
# So we make sure here that the number won't exceed the limit due to CuraEngine bug or whatever, and
# Cura won't crash.
if int(duration) >= (2**31):
Logger.log("w", "Duration was too large to convert, so resetting it.")
duration = 0
self._days = math.floor(duration / (3600 * 24))
duration -= self._days * 3600 * 24
self._hours = math.floor(duration / 3600)
duration -= self._hours * 3600
self._minutes = math.floor(duration / 60)
duration -= self._minutes * 60
self._seconds = duration
self.durationChanged.emit()
@pyqtSlot(int, result = str)
def getDisplayString(self, display_format = DurationFormat.Format.Short):
"""Get a string representation of this object that can be used to display
in interfaces.
This is not called toString() primarily because that conflicts with
JavaScript's toString().
:return: A human-readable string representation of this duration.
"""
if display_format == DurationFormat.Format.Seconds:
return str(((self._days * 24 + self._hours)* 60 + self._minutes) * 60 + self._seconds )
elif display_format == DurationFormat.Format.Short:
if self._days > 0:
return i18n_catalog.i18nc("@label Short days-hours-minutes format. {0} is days, {1} is hours, {2} is minutes", "{0:0>2}d {1:0>2}h {2:0>2}min", self._days, self._hours, self._minutes)
else:
return i18n_catalog.i18nc("@label Short hours-minutes format. {0} is hours, {1} is minutes", "{0:0>2}h {1:0>2}min", self._hours, self._minutes)
elif display_format == DurationFormat.Format.Long:
if self._days > 0:
return i18n_catalog.i18ncp("@label Long duration format. {0} is days", "{0} day", "{0} days", self._days) + " " + i18n_catalog.i18ncp("@label Long duration format. {0} is hours", "{0} hour", "{0} hours", self._hours) + " " + i18n_catalog.i18ncp("@label Long duration format. {0} is minutes", "{0} minute", "{0} minutes", self._minutes)
elif self._hours > 0:
return i18n_catalog.i18ncp("@label Long duration format. {0} is hours", "{0} hour", "{0} hours", self._hours) + " " + i18n_catalog.i18ncp("@label Long duration format. {0} is minutes", "{0} minute", "{0} minutes", self._minutes)
else:
return i18n_catalog.i18ncp("@label Long duration format. {0} is minutes", "{0} minute", "{0} minutes", self._minutes)
elif display_format == DurationFormat.Format.ISO8601:
return "%02d:%02d:%02d" % (self._days * 24 + self._hours, self._minutes, self._seconds)
return ""
def __int__(self):
"""Get an integer representation of this duration.
The integer contains the number of seconds in the duration. Convert it
back to a Duration instance by providing the number of seconds to the
constructor.
"""
return self._days * 3600 * 24 + self._hours * 3600 + self._minutes * 60 + self._seconds
|