|
9 | 9 | import ctypes |
10 | 10 | import enum |
11 | 11 | import logging |
| 12 | +import math |
12 | 13 | import os |
13 | 14 | import pathlib |
14 | 15 | import struct |
15 | | -import typing |
16 | 16 |
|
17 | 17 | logger = logging.getLogger(__name__) |
18 | 18 |
|
|
21 | 21 | libxext = ctypes.CDLL("libXext.so.6") |
22 | 22 |
|
23 | 23 | class DPMSState(enum.Enum): |
24 | | - """Available DPMS states""" |
| 24 | + """Available DPMS states.""" |
25 | 25 |
|
26 | 26 | FAIL = -1 |
27 | 27 | ON = 0 |
@@ -86,7 +86,7 @@ def set_dpms_mode(mode: DPMSState) -> None: |
86 | 86 | finally: |
87 | 87 | libxext.XCloseDisplay(display) |
88 | 88 |
|
89 | | - def get_dpms_timeouts() -> typing.Dict: |
| 89 | + def get_dpms_timeouts() -> dict: |
90 | 90 | """Get current DPMS timeouts""" |
91 | 91 | _display_name = ctypes.c_char_p(b":0") |
92 | 92 | libxext.XOpenDisplay.restype = ctypes.c_void_p |
@@ -118,9 +118,7 @@ def get_dpms_timeouts() -> typing.Dict: |
118 | 118 | "off_seconds": _off_timeout, |
119 | 119 | } |
120 | 120 |
|
121 | | - def set_dpms_timeouts( |
122 | | - suspend: int = 0, standby: int = 0, off: int = 0 |
123 | | - ) -> typing.Dict: |
| 121 | + def set_dpms_timeouts(suspend: int = 0, standby: int = 0, off: int = 0) -> dict: |
124 | 122 | """Set DPMS timeout""" |
125 | 123 | _display_name = ctypes.c_char_p(b":0") |
126 | 124 | libxext.XOpenDisplay.restype = ctypes.c_void_p |
@@ -155,11 +153,11 @@ def set_dpms_timeouts( |
155 | 153 | "off_seconds": _off_timeout, |
156 | 154 | } |
157 | 155 |
|
158 | | - def get_dpms_info() -> typing.Dict: |
| 156 | + def get_dpms_info() -> dict: |
159 | 157 | """Get DPMS information |
160 | 158 |
|
161 | 159 | Returns: |
162 | | - typing.Dict: Dpms state |
| 160 | + dict: Dpms state |
163 | 161 | """ |
164 | 162 | _dpms_state = DPMSState.FAIL |
165 | 163 | onoff = 0 |
@@ -227,133 +225,122 @@ def disable_dpms() -> None: |
227 | 225 | logger.exception(f"Unexpected exception occurred {e}") |
228 | 226 |
|
229 | 227 |
|
230 | | -def convert_bytes_to_mb(self, bytes: int | float) -> float: |
231 | | - """Converts byte size to megabyte size |
| 228 | +def convert_bytes_to_mb(size_bytes: int | float) -> float: |
| 229 | + """Converts byte size to megabyte size. |
232 | 230 |
|
233 | 231 | Args: |
234 | | - bytes (int | float): bytes |
| 232 | + size_bytes: Value in bytes. |
235 | 233 |
|
236 | 234 | Returns: |
237 | | - mb: float that represents the number of mb |
| 235 | + Equivalent value in megabytes. |
238 | 236 | """ |
239 | 237 | _relation = 2 ** (-20) |
240 | | - return bytes * _relation |
| 238 | + return size_bytes * _relation |
241 | 239 |
|
242 | 240 |
|
243 | 241 | def calculate_current_layer( |
244 | 242 | z_position: float, |
245 | | - object_height: float, |
246 | 243 | layer_height: float, |
247 | 244 | first_layer_height: float, |
| 245 | + max_layers: int = 0, |
248 | 246 | ) -> int: |
249 | | - """Calculated the current printing layer given the GCODE z position received by the |
250 | | - gcode_move object update. |
251 | | - Also updates the label where the current layer should be displayed |
| 247 | + """Calculate current layer from Z position (fallback when Klipper |
| 248 | + does not provide ``print_stats.info.current_layer``). |
| 249 | +
|
| 250 | + Formula ported from Mainsail ``getPrintCurrentLayer`` getter: |
| 251 | + ``src/store/printer/getters.ts`` in ``mainsail-crew/mainsail``. |
| 252 | +
|
| 253 | + Uses ``ceil((z - first_layer_height) / layer_height + 1)`` |
| 254 | + and clamps the result to ``[0, max_layers]``. |
252 | 255 |
|
253 | 256 | Returns: |
254 | | - int: Current layer |
| 257 | + int: Current layer number (0 when not yet printing). |
255 | 258 | """ |
256 | | - if z_position == 0: |
257 | | - return -1 |
258 | | - if z_position <= first_layer_height: |
259 | | - return 1 |
| 259 | + if layer_height <= 0 or first_layer_height < 0: |
| 260 | + return 0 |
260 | 261 |
|
261 | | - _current_layer = (z_position) / layer_height |
| 262 | + layer = math.ceil((z_position - first_layer_height) / layer_height + 1) |
| 263 | + if max_layers > 0 and layer > max_layers: |
| 264 | + return max_layers |
| 265 | + return layer if layer > 0 else 0 |
262 | 266 |
|
263 | | - return int(_current_layer) |
264 | 267 |
|
| 268 | +def calculate_max_layers( |
| 269 | + object_height: float, |
| 270 | + layer_height: float, |
| 271 | + first_layer_height: float, |
| 272 | +) -> int: |
| 273 | + """Calculate total layers from metadata dimensions (fallback when |
| 274 | + Klipper does not provide ``print_stats.info.total_layer``). |
265 | 275 |
|
266 | | -def estimate_print_time(seconds: int) -> list: |
267 | | - """Convert time in seconds format to days, hours, minutes, seconds. |
| 276 | + Formula ported from Mainsail ``getPrintMaxLayers`` getter: |
| 277 | + ``src/store/printer/getters.ts`` in ``mainsail-crew/mainsail``. |
268 | 278 |
|
269 | | - Args: |
270 | | - seconds (int): Seconds |
| 279 | + Uses ``ceil((object_height - first_layer_height) / layer_height + 1)``. |
271 | 280 |
|
272 | 281 | Returns: |
273 | | - list: list that contains the converted information [days, hours, minutes, seconds] |
| 282 | + int: Total layer count, or 0 if metadata is insufficient. |
274 | 283 | """ |
275 | | - num_min, seconds = divmod(seconds, 60) |
276 | | - num_hours, minutes = divmod(num_min, 60) |
277 | | - days, hours = divmod(num_hours, 24) |
278 | | - return [days, hours, minutes, seconds] |
279 | | - |
| 284 | + if layer_height <= 0 or object_height <= 0: |
| 285 | + return 0 |
| 286 | + return max(1, math.ceil((object_height - first_layer_height) / layer_height + 1)) |
280 | 287 |
|
281 | | -def normalize(value, r_min=0.0, r_max=1.0, t_min=0.0, t_max=100): |
282 | | - """Normalize values between a rage""" |
283 | | - # https://stats.stackexchange.com/questions/281162/scale-a-number-between-a-range |
284 | | - c1 = (value - r_min) / (r_max - r_min) |
285 | | - c2 = (t_max - t_min) + t_min |
286 | | - return c1 * c2 |
287 | 288 |
|
| 289 | +def estimate_print_time(seconds: int) -> list[int]: |
| 290 | + """Convert *seconds* to ``[days, hours, minutes, seconds]``.""" |
| 291 | + num_min, secs = divmod(seconds, 60) |
| 292 | + num_hours, mins = divmod(num_min, 60) |
| 293 | + days, hours = divmod(num_hours, 24) |
| 294 | + return [days, hours, mins, secs] |
288 | 295 |
|
289 | | -def check_filepath_permission(filepath, access_type: int = os.R_OK) -> bool: |
290 | | - # if not isinstance(filepath, pathlib.Path): |
291 | | - """Checks for file path access |
292 | 296 |
|
293 | | - Args: |
294 | | - filepath (str | pathlib.Path): path to file |
295 | | - access_type (int, optional): _description_. Defaults to os.R_OK. |
| 297 | +def normalize( |
| 298 | + value: float, |
| 299 | + r_min: float = 0.0, |
| 300 | + r_max: float = 1.0, |
| 301 | + t_min: float = 0.0, |
| 302 | + t_max: float = 100.0, |
| 303 | +) -> float: |
| 304 | + """Scale *value* from range [r_min, r_max] into [t_min, t_max].""" |
| 305 | + return (value - r_min) / (r_max - r_min) * (t_max - t_min) + t_min |
296 | 306 |
|
297 | | - *** |
298 | 307 |
|
299 | | - #### **Access type can be:** |
| 308 | +def check_filepath_permission( |
| 309 | + filepath: str | pathlib.Path, access_type: int = os.R_OK |
| 310 | +) -> bool: |
| 311 | + """Check whether *filepath* exists and has the requested access. |
300 | 312 |
|
301 | | - - F_OK -> Checks file existence on path |
302 | | - - R_OK -> Checks if file is readable |
303 | | - - W_OK -> Checks if file is Writable |
304 | | - - X_OK -> Checks if file can be executed |
| 313 | + Args: |
| 314 | + filepath: Path to file. |
| 315 | + access_type: ``os.F_OK`` (existence), ``os.R_OK`` (read), |
| 316 | + ``os.W_OK`` (write), or ``os.X_OK`` (execute). |
305 | 317 |
|
306 | | - *** |
307 | 318 | Returns: |
308 | | - bool: _description_ |
309 | | - """ # return False |
310 | | - if not os.path.isfile(filepath): |
311 | | - return False |
312 | | - return os.access(filepath, access_type) |
| 319 | + ``True`` if the file exists and satisfies *access_type*. |
| 320 | + """ |
| 321 | + path = pathlib.Path(filepath) |
| 322 | + return path.is_file() and os.access(path, access_type) |
313 | 323 |
|
314 | 324 |
|
315 | | -def check_dir_existence( |
316 | | - directory: typing.Union[str, pathlib.Path], |
317 | | -) -> bool: |
318 | | - """Check if a directory exists. Returns a true if it exists""" |
319 | | - if isinstance(directory, pathlib.Path): |
320 | | - return bool(directory.is_dir()) |
321 | | - return bool(os.path.isdir(directory)) |
| 325 | +def check_dir_existence(directory: str | pathlib.Path) -> bool: |
| 326 | + """Return ``True`` if *directory* exists and is a directory.""" |
| 327 | + return pathlib.Path(directory).is_dir() |
322 | 328 |
|
323 | 329 |
|
324 | 330 | def check_file_on_path( |
325 | | - path: typing.Union[typing.LiteralString, pathlib.Path], |
326 | | - filename: typing.Union[typing.LiteralString, pathlib.Path], |
| 331 | + path: str | pathlib.Path, |
| 332 | + filename: str | pathlib.Path, |
327 | 333 | ) -> bool: |
328 | | - """Check if file exists on path. Returns true if file exists on that specified directory""" |
329 | | - _filepath = os.path.join(path, filename) |
330 | | - return os.path.exists(_filepath) |
331 | | - |
| 334 | + """Return ``True`` if *filename* exists under *path*.""" |
| 335 | + return (pathlib.Path(path) / filename).exists() |
332 | 336 |
|
333 | | -def get_file_loc(filename) -> pathlib.Path: ... |
334 | 337 |
|
| 338 | +def get_file_name(filename: str | None) -> str: |
| 339 | + """Extract the basename from a file path (handles ``/`` and ``\\``). |
335 | 340 |
|
336 | | -def get_file_name(filename: typing.Optional[str]) -> str: |
337 | | - # If filename is None or empty, return empty string instead of None |
| 341 | + Returns: |
| 342 | + The last path component, or ``""`` if *filename* is falsy. |
| 343 | + """ |
338 | 344 | if not filename: |
339 | 345 | return "" |
340 | | - # Remove trailing slashes or backslashes |
341 | | - filename = filename.rstrip("/\\") |
342 | | - |
343 | | - # Normalize Windows backslashes to forward slashes |
344 | | - filename = filename.replace("\\", "/") |
345 | | - |
346 | | - parts = filename.split("/") |
347 | | - |
348 | | - # Split and return the last path component |
349 | | - return parts[-1] if filename else "" |
350 | | - |
351 | | - |
352 | | -# def get_hash(data) -> hashlib._Hash: |
353 | | -# hash = hashlib.sha256() |
354 | | -# hash.update(data.encode()) |
355 | | -# hash.digest() |
356 | | -# return hash |
357 | | - |
358 | | - |
359 | | -def digest_hash() -> None: ... |
| 346 | + return pathlib.PurePosixPath(filename.replace("\\", "/")).name |
0 commit comments