mirror of
https://github.com/QIDITECH/moonraker.git
synced 2026-01-30 16:18:44 +03:00
V4.4.21 Updates
This commit is contained in:
@@ -33,7 +33,7 @@ if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
UFP_MODEL_PATH = "/3D/model.gcode"
|
||||
UFP_THUMB_PATH = "/Metadata/thumbnail.png"
|
||||
UFP_THUMB_PATH = "/Metadata/thumbnail.jpg"
|
||||
|
||||
def log_to_stderr(msg: str) -> None:
|
||||
sys.stderr.write(f"{msg}\n")
|
||||
@@ -217,11 +217,18 @@ class BaseSlicer(object):
|
||||
return None
|
||||
|
||||
def parse_thumbnails(self) -> Optional[List[Dict[str, Any]]]:
|
||||
is_jpg = False
|
||||
for data in [self.header_data, self.footer_data]:
|
||||
thumb_matches: List[str] = re.findall(
|
||||
r"; thumbnail begin[;/\+=\w\s]+?; thumbnail end", data)
|
||||
if thumb_matches:
|
||||
break
|
||||
else:
|
||||
thumb_matches: List[str] = re.findall(
|
||||
r"; thumbnail_JPG begin[;/\+=\w\s]+?; thumbnail_JPG end", data)
|
||||
if thumb_matches:
|
||||
is_jpg = True
|
||||
break
|
||||
else:
|
||||
return None
|
||||
thumb_dir = os.path.join(os.path.dirname(self.path), ".thumbs")
|
||||
@@ -233,7 +240,7 @@ class BaseSlicer(object):
|
||||
return None
|
||||
thumb_base = os.path.splitext(os.path.basename(self.path))[0]
|
||||
parsed_matches: List[Dict[str, Any]] = []
|
||||
has_miniature: bool = False
|
||||
#has_miniature: bool = False
|
||||
for match in thumb_matches:
|
||||
lines = re.split(r"\r?\n", match.replace('; ', ''))
|
||||
info = _regex_find_ints(r".*", lines[0])
|
||||
@@ -248,7 +255,10 @@ class BaseSlicer(object):
|
||||
f"MetadataError: Thumbnail Size Mismatch: "
|
||||
f"detected {info[2]}, actual {len(data)}")
|
||||
continue
|
||||
thumb_name = f"{thumb_base}-{info[0]}x{info[1]}.png"
|
||||
if not is_jpg:
|
||||
thumb_name = f"{thumb_base}-{info[0]}x{info[1]}.png"
|
||||
else:
|
||||
thumb_name = f"{thumb_base}-{info[0]}x{info[1]}.jpg"
|
||||
thumb_path = os.path.join(thumb_dir, thumb_name)
|
||||
rel_thumb_path = os.path.join(".thumbs", thumb_name)
|
||||
with open(thumb_path, "wb") as f:
|
||||
@@ -257,33 +267,71 @@ class BaseSlicer(object):
|
||||
'width': info[0], 'height': info[1],
|
||||
'size': os.path.getsize(thumb_path),
|
||||
'relative_path': rel_thumb_path})
|
||||
if info[0] == 32 and info[1] == 32:
|
||||
has_miniature = True
|
||||
if len(parsed_matches) > 0 and not has_miniature:
|
||||
# find the largest thumb index
|
||||
largest_match = parsed_matches[0]
|
||||
for item in parsed_matches:
|
||||
if item['size'] > largest_match['size']:
|
||||
largest_match = item
|
||||
# Create miniature thumbnail if one does not exist
|
||||
thumb_full_name = largest_match['relative_path'].split("/")[-1]
|
||||
thumb_path = os.path.join(thumb_dir, f"{thumb_full_name}")
|
||||
rel_path_small = os.path.join(".thumbs", f"{thumb_base}-32x32.png")
|
||||
thumb_path_small = os.path.join(
|
||||
thumb_dir, f"{thumb_base}-32x32.png")
|
||||
# read file
|
||||
try:
|
||||
with Image.open(thumb_path) as im:
|
||||
# Create 32x32 thumbnail
|
||||
im.thumbnail((32, 32))
|
||||
im.save(thumb_path_small, format="PNG")
|
||||
parsed_matches.insert(0, {
|
||||
'width': im.width, 'height': im.height,
|
||||
'size': os.path.getsize(thumb_path_small),
|
||||
'relative_path': rel_path_small
|
||||
})
|
||||
except Exception as e:
|
||||
log_to_stderr(str(e))
|
||||
# find the smallest thumb index
|
||||
smallest_match = parsed_matches[0]
|
||||
max_size = min_size = smallest_match['size']
|
||||
for item in parsed_matches:
|
||||
if item['size'] < smallest_match['size']:
|
||||
smallest_match = item
|
||||
if item["size"] < min_size:
|
||||
min_size = item["size"]
|
||||
if item["size"] > max_size:
|
||||
max_size = item["size"]
|
||||
# Create thumbnail for screen
|
||||
thumb_full_name = smallest_match['relative_path'].split("/")[-1]
|
||||
thumb_path = os.path.join(thumb_dir, f"{thumb_full_name}")
|
||||
thumb_QD_full_name = f"{thumb_base}-{smallest_match['width']}x{smallest_match['height']}_QD.jpg"
|
||||
thumb_QD_path = os.path.join(thumb_dir, f"{thumb_QD_full_name}")
|
||||
rel_path_QD = os.path.join(".thumbs", thumb_QD_full_name)
|
||||
try:
|
||||
with Image.open(thumb_path) as img:
|
||||
img = img.convert("RGB")
|
||||
img = img.resize((smallest_match['width'], smallest_match['height']))
|
||||
img = img.rotate(90, expand=True)
|
||||
img.save(thumb_QD_path, "JPEG", quality=90)
|
||||
except Exception as e:
|
||||
log_to_stderr(f"convert failed: {e}")
|
||||
parsed_matches.append({
|
||||
'width': smallest_match['width'], 'height': smallest_match['height'],
|
||||
'size': (max_size + min_size) // 2,
|
||||
'relative_path': rel_path_QD})
|
||||
# if info[0] == 32 and info[1] == 32:
|
||||
# has_miniature = True
|
||||
# if len(parsed_matches) > 0 and not has_miniature:
|
||||
# # find the largest thumb index
|
||||
# largest_match = parsed_matches[0]
|
||||
# for item in parsed_matches:
|
||||
# if item['size'] > largest_match['size']:
|
||||
# largest_match = item
|
||||
# # Create miniature thumbnail if one does not exist
|
||||
# thumb_full_name = largest_match['relative_path'].split("/")[-1]
|
||||
# thumb_path = os.path.join(thumb_dir, f"{thumb_full_name}")
|
||||
# rel_path_small = os.path.join(".thumbs", f"{thumb_base}-32x32.jpg")
|
||||
# thumb_path_small = os.path.join(
|
||||
# thumb_dir, f"{thumb_base}-32x32.jpg")
|
||||
# rel_path_small_png = os.path.join(".thumbs", f"{thumb_base}-32x32.png")
|
||||
# thumb_path_small_png = os.path.join(
|
||||
# thumb_dir, f"{thumb_base}-32x32.png")
|
||||
# # read file
|
||||
# try:
|
||||
# with Image.open(thumb_path) as im:
|
||||
# # Create 32x32 thumbnail
|
||||
# im.thumbnail((32, 32))
|
||||
# im.save(thumb_path_small_png)
|
||||
# im = im.convert('RGB')
|
||||
# im.save(thumb_path_small)
|
||||
# parsed_matches.insert(0, {
|
||||
# 'width': im.width, 'height': im.height,
|
||||
# 'size': os.path.getsize(thumb_path_small),
|
||||
# 'relative_path': rel_path_small
|
||||
# })
|
||||
# parsed_matches.insert(0, {
|
||||
# 'width': im.width, 'height': im.height,
|
||||
# 'size': os.path.getsize(thumb_path_small_png),
|
||||
# 'relative_path': rel_path_small_png
|
||||
# })
|
||||
# except Exception as e:
|
||||
# log_to_stderr(str(e))
|
||||
return parsed_matches
|
||||
|
||||
def parse_layer_count(self) -> Optional[int]:
|
||||
@@ -549,10 +597,10 @@ class Cura(BaseSlicer):
|
||||
# Check for thumbnails extracted from the ufp
|
||||
thumb_dir = os.path.join(os.path.dirname(self.path), ".thumbs")
|
||||
thumb_base = os.path.splitext(os.path.basename(self.path))[0]
|
||||
thumb_path = os.path.join(thumb_dir, f"{thumb_base}.png")
|
||||
rel_path_full = os.path.join(".thumbs", f"{thumb_base}.png")
|
||||
rel_path_small = os.path.join(".thumbs", f"{thumb_base}-32x32.png")
|
||||
thumb_path_small = os.path.join(thumb_dir, f"{thumb_base}-32x32.png")
|
||||
thumb_path = os.path.join(thumb_dir, f"{thumb_base}.jpg")
|
||||
rel_path_full = os.path.join(".thumbs", f"{thumb_base}.jpg")
|
||||
rel_path_small = os.path.join(".thumbs", f"{thumb_base}-32x32.jpg")
|
||||
thumb_path_small = os.path.join(thumb_dir, f"{thumb_base}-32x32.jpg")
|
||||
if not os.path.isfile(thumb_path):
|
||||
return None
|
||||
# read file
|
||||
@@ -566,7 +614,7 @@ class Cura(BaseSlicer):
|
||||
})
|
||||
# Create 32x32 thumbnail
|
||||
im.thumbnail((32, 32), Image.ANTIALIAS)
|
||||
im.save(thumb_path_small, format="PNG")
|
||||
im.save(thumb_path_small, format="JPEG")
|
||||
thumbs.insert(0, {
|
||||
'width': im.width, 'height': im.height,
|
||||
'size': os.path.getsize(thumb_path_small),
|
||||
@@ -1087,7 +1135,7 @@ def extract_ufp(ufp_path: str, dest_path: str) -> None:
|
||||
log_to_stderr(f"UFP file Not Found: {ufp_path}")
|
||||
sys.exit(-1)
|
||||
thumb_name = os.path.splitext(
|
||||
os.path.basename(dest_path))[0] + ".png"
|
||||
os.path.basename(dest_path))[0] + ".jpg"
|
||||
dest_thumb_dir = os.path.join(os.path.dirname(dest_path), ".thumbs")
|
||||
dest_thumb_path = os.path.join(dest_thumb_dir, thumb_name)
|
||||
try:
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
from __future__ import annotations
|
||||
from utils import SentinelClass
|
||||
from websockets import WebRequest, Subscribable
|
||||
import os
|
||||
import shutil
|
||||
import json
|
||||
|
||||
# Annotation imports
|
||||
from typing import (
|
||||
@@ -23,6 +26,7 @@ if TYPE_CHECKING:
|
||||
from confighelper import ConfigHelper
|
||||
from websockets import WebRequest
|
||||
from klippy_connection import KlippyConnection as Klippy
|
||||
from .file_manager.file_manager import FileManager
|
||||
Subscription = Dict[str, Optional[List[Any]]]
|
||||
_T = TypeVar("_T")
|
||||
|
||||
@@ -41,6 +45,7 @@ class KlippyAPI(Subscribable):
|
||||
def __init__(self, config: ConfigHelper) -> None:
|
||||
self.server = config.get_server()
|
||||
self.klippy: Klippy = self.server.lookup_component("klippy_connection")
|
||||
self.fm: FileManager = self.server.lookup_component("file_manager")
|
||||
app_args = self.server.get_app_args()
|
||||
self.version = app_args.get('software_version')
|
||||
# Maintain a subscription for all moonraker requests, as
|
||||
@@ -60,7 +65,12 @@ class KlippyAPI(Subscribable):
|
||||
"/printer/restart", ['POST'], self._gcode_restart)
|
||||
self.server.register_endpoint(
|
||||
"/printer/firmware_restart", ['POST'], self._gcode_firmware_restart)
|
||||
|
||||
self.server.register_endpoint(
|
||||
"/printer/breakheater", ['POST'], self.breakheater)
|
||||
self.server.register_endpoint(
|
||||
"/printer/breakmacro", ['POST'], self.breakmacro)
|
||||
self.server.register_endpoint(
|
||||
"/printer/modifybabystep", ["POST"], self.modifybabystep)
|
||||
async def _gcode_pause(self, web_request: WebRequest) -> str:
|
||||
return await self.pause_print()
|
||||
|
||||
@@ -80,6 +90,11 @@ class KlippyAPI(Subscribable):
|
||||
async def _gcode_firmware_restart(self, web_request: WebRequest) -> str:
|
||||
return await self.do_restart("FIRMWARE_RESTART")
|
||||
|
||||
async def modifybabystep(self, web_request: WebRequest) -> str:
|
||||
adjust: float = web_request.get_float('ADJUST', 0)
|
||||
move: int = web_request.get_int('MOVE', 1)
|
||||
return await self.modify_babystep(adjust, move)
|
||||
|
||||
async def _send_klippy_request(self,
|
||||
method: str,
|
||||
params: Dict[str, Any],
|
||||
@@ -110,10 +125,24 @@ class KlippyAPI(Subscribable):
|
||||
# Doing so will result in "wait_started" blocking for the specifed
|
||||
# timeout (default 20s) and returning False.
|
||||
# XXX - validate that file is on disk
|
||||
homedir = os.path.expanduser("~")
|
||||
if filename[0] == '/':
|
||||
filename = filename[1:]
|
||||
# Escape existing double quotes in the file name
|
||||
filename = filename.replace("\"", "\\\"")
|
||||
if os.path.split(filename)[0].split(os.path.sep)[0] != ".cache":
|
||||
base_path = os.path.join(homedir, "gcode_files")
|
||||
target = os.path.join(".cache", os.path.basename(filename))
|
||||
cache_path = os.path.join(base_path, ".cache")
|
||||
if not os.path.exists(cache_path):
|
||||
os.makedirs(cache_path)
|
||||
shutil.rmtree(cache_path)
|
||||
os.makedirs(cache_path)
|
||||
metadata = self.fm.gcode_metadata.metadata.get(filename, None)
|
||||
self.copy_file_to_cache(os.path.join(base_path, filename), os.path.join(base_path, target))
|
||||
msg = "// metadata=" + json.dumps(metadata)
|
||||
self.server.send_event("server:gcode_response", msg)
|
||||
filename = target
|
||||
script = f'SDCARD_PRINT_FILE FILENAME="{filename}"'
|
||||
await self.klippy.wait_started()
|
||||
return await self.run_gcode(script)
|
||||
@@ -136,9 +165,33 @@ class KlippyAPI(Subscribable):
|
||||
self, default: Union[SentinelClass, _T] = SENTINEL
|
||||
) -> Union[_T, str]:
|
||||
self.server.send_event("klippy_apis:cancel_requested")
|
||||
await self._send_klippy_request(
|
||||
"breakmacro", {}, default)
|
||||
await self._send_klippy_request(
|
||||
"breakheater", {}, default)
|
||||
return await self._send_klippy_request(
|
||||
"pause_resume/cancel", {}, default)
|
||||
|
||||
async def breakheater(
|
||||
self, default: Union[SentinelClass, _T] = SENTINEL
|
||||
) -> Union[_T, str]:
|
||||
return await self._send_klippy_request(
|
||||
"breakheater", {}, default)
|
||||
|
||||
async def breakmacro(
|
||||
self, default: Union[SentinelClass, _T] = SENTINEL
|
||||
) -> Union[_T, str]:
|
||||
return await self._send_klippy_request(
|
||||
"breakmacro", {}, default)
|
||||
|
||||
async def modify_babystep(self,
|
||||
babystep: float = 0,
|
||||
move: int = 1,
|
||||
default: Union[SentinelClass, _T] = SENTINEL
|
||||
) -> Union[_T, str]:
|
||||
return await self._send_klippy_request(
|
||||
"modifybabystep", {"ADJUST": babystep, "MOVE": move} , default)
|
||||
|
||||
async def do_restart(self, gc: str) -> str:
|
||||
# WARNING: Do not call this method from within the following
|
||||
# event handlers:
|
||||
@@ -232,5 +285,16 @@ class KlippyAPI(Subscribable):
|
||||
) -> None:
|
||||
self.server.send_event("server:status_update", status)
|
||||
|
||||
def copy_file_to_cache(self, origin, target):
|
||||
stat = os.statvfs("/")
|
||||
free_space = stat.f_frsize * stat.f_bfree
|
||||
filesize = os.path.getsize(os.path.join(origin))
|
||||
if (filesize < free_space):
|
||||
shutil.copy(origin, target)
|
||||
else:
|
||||
msg = "!! Insufficient disk space, unable to read the file."
|
||||
self.server.send_event("server:gcode_response", msg)
|
||||
raise self.server.error("Insufficient disk space, unable to read the file.", 500)
|
||||
|
||||
def load_component(config: ConfigHelper) -> KlippyAPI:
|
||||
return KlippyAPI(config)
|
||||
|
||||
@@ -105,10 +105,6 @@ class Machine:
|
||||
self.server.register_endpoint(
|
||||
"/machine/system_info", ['POST'],
|
||||
self._handle_sysinfo_request)
|
||||
# self.server.register_endpoint(
|
||||
# "/machine/dev_name", ['GET'],
|
||||
# self._handle_devname_request)
|
||||
|
||||
|
||||
self.server.register_notification("machine:service_state_changed")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user