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