V4.4.21 Updates

This commit is contained in:
CChen616
2024-07-19 19:14:47 +08:00
parent 1006bcb85e
commit 756cb2e8bb
3 changed files with 150 additions and 42 deletions

View File

@@ -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
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]
# 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'] > largest_match['size']:
largest_match = item
# Create miniature thumbnail if one does not exist
thumb_full_name = largest_match['relative_path'].split("/")[-1]
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}")
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
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 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
})
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(str(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:

View File

@@ -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)

View File

@@ -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")