From a26ca3430f1ff859dbe254355fc28a748bf22cc3 Mon Sep 17 00:00:00 2001 From: Lev Kozlov Date: Mon, 18 Mar 2024 20:52:03 +0300 Subject: [PATCH] refactor: apply formatter (#141) * refactor: apply formatter * feat: add action * fix: change python version * fix: fix ruff version --- .github/workflows/ruff.yaml | 20 ++ .pre-commit-config.yaml | 8 + main.py | 39 ++- setup.py | 15 - src/InteractorAroundActivePlane.py | 93 ++++-- src/bug_report.py | 45 ++- src/client.py | 13 +- src/cone_slicing.py | 50 +++- src/controller.py | 441 ++++++++++++++++++++-------- src/debug.py | 67 ----- src/entry_window.py | 99 +++++-- src/exampl.py | 86 ------ src/figure_editor.py | 70 +++-- src/gcode.py | 59 ++-- src/gui_utils.py | 90 ++++-- src/interactor_style.py | 24 +- src/interface_style_sheet.py | 3 +- src/locales.py | 247 ++++++++-------- src/model.py | 1 - src/process.py | 18 +- src/qt_utils.py | 7 +- src/settings.py | 120 +++++--- src/window.py | 449 ++++++++++++++++++++++------- test/gcode_test.py | 51 +++- 24 files changed, 1342 insertions(+), 773 deletions(-) create mode 100644 .github/workflows/ruff.yaml create mode 100644 .pre-commit-config.yaml delete mode 100644 setup.py delete mode 100644 src/debug.py delete mode 100644 src/exampl.py diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml new file mode 100644 index 0000000..7ce3b67 --- /dev/null +++ b/.github/workflows/ruff.yaml @@ -0,0 +1,20 @@ +name: CI + +on: push + +jobs: + ruff_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff==0.3.3 + # Update output format to enable automatic inline annotations. + - name: Run Ruff + run: ruff format --diff . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..9319c02 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.3.3 + hooks: + # Run the formatter. + - id: ruff-format + types_or: [python, pyi, jupyter] diff --git a/main.py b/main.py index bb1af34..bef673c 100644 --- a/main.py +++ b/main.py @@ -2,17 +2,17 @@ import os, sys # check whether we are in pyinstaller bundle and on linux -if getattr(sys, 'frozen', False) and sys.platform.startswith('linux'): +if getattr(sys, "frozen", False) and sys.platform.startswith("linux"): app_path = os.path.dirname(sys.executable) - prev_ld_path = os.environ.get('LD_LIBRARY_PATH', '') - + prev_ld_path = os.environ.get("LD_LIBRARY_PATH", "") + # shared libraries are located at lib/ - shared_libs = os.path.join(app_path, 'lib') + shared_libs = os.path.join(app_path, "lib") # add shared libraries to LD_LIBRARY_PATH - os.environ['LD_LIBRARY_PATH'] = shared_libs + ':' + prev_ld_path - print("LD_LIBRARY_PATH:", os.environ['LD_LIBRARY_PATH']) + os.environ["LD_LIBRARY_PATH"] = shared_libs + ":" + prev_ld_path + print("LD_LIBRARY_PATH:", os.environ["LD_LIBRARY_PATH"]) import logging @@ -22,7 +22,12 @@ from PyQt5 import QtWidgets from PyQt5.QtWidgets import QApplication import pathlib -from src.settings import copy_project_files, load_settings, sett, create_temporary_project_files +from src.settings import ( + copy_project_files, + load_settings, + sett, + create_temporary_project_files, +) from src.window import MainWindow from src.model import MainModel from src.controller import MainController @@ -30,7 +35,12 @@ from src.entry_window import EntryWindow from src.gui_utils import read_plane -logging.basicConfig(filename='interface.log', filemode='a+', level=logging.INFO, format='%(asctime)s %(message)s') +logging.basicConfig( + filename="interface.log", + filemode="a+", + level=logging.INFO, + format="%(asctime)s %(message)s", +) def excepthook(exc_type, exc_value, exc_tb): @@ -40,6 +50,7 @@ def excepthook(exc_type, exc_value, exc_tb): logging.error(tb) QtWidgets.QApplication.quit() + if __name__ == "__main__": load_settings() @@ -57,7 +68,7 @@ def open_project(project_path: str): window = MainWindow() window.close_signal.connect(entry_window.show) - + model = MainModel() cntrl = MainController(window, model) @@ -66,7 +77,7 @@ def open_project(project_path: str): if os.path.isfile(stlpath): cntrl.load_stl(stlpath) - if hasattr(sett().slicing, 'splanes_file'): + if hasattr(sett().slicing, "splanes_file"): # we have kinda old settings which point to separate file with planes # load planes as it is, but remove this parameter and save settings # TODO: we can remove this condition after one release @@ -75,13 +86,15 @@ def open_project(project_path: str): figpath = pathlib.Path(project_path, sett().slicing.splanes_file) if os.path.isfile(figpath): cntrl.load_planes_from_file(figpath) - + del sett().slicing.splanes_file cntrl.save_settings("vip") else: # load splanes from settings - cntrl.load_planes([read_plane(figure.description) for figure in sett().figures]) + cntrl.load_planes( + [read_plane(figure.description) for figure in sett().figures] + ) window.showMaximized() window.show() @@ -96,7 +109,7 @@ def create_project(project_path: str): window = MainWindow() window.close_signal.connect(entry_window.show) - + model = MainModel() cntrl = MainController(window, model) window.showMaximized() diff --git a/setup.py b/setup.py deleted file mode 100644 index 9a44545..0000000 --- a/setup.py +++ /dev/null @@ -1,15 +0,0 @@ -import sys -from cx_Freeze import setup, Executable - -buildOptions = dict(includes=['src/'], packages=['vtkmodules'], excludes=['tkinter']) - -base = None -if sys.platform == "win32": - base = "Win32GUI" - -setup( - name="spycer", - version="3.7", - description="Spycer vis.", - options=dict(build_exe=buildOptions), - executables=[Executable("C:\l1va\spycer\main.py", base=base)]) diff --git a/src/InteractorAroundActivePlane.py b/src/InteractorAroundActivePlane.py index a3bc73c..a95268f 100644 --- a/src/InteractorAroundActivePlane.py +++ b/src/InteractorAroundActivePlane.py @@ -2,6 +2,7 @@ Module contains custom interactor for vtk inspired by: https://fossies.org/linux/VTK/Examples/GUI/Python/CustomInteraction.py """ + from typing import Tuple import numpy as np @@ -26,11 +27,13 @@ def RZ(rotRadian: float): :param rotRadian: angle of rotations in radians :return: numpy matrix of 3 by 3 size """ - return np.array([ - [np.cos(rotRadian), -np.sin(rotRadian), 0], - [np.sin(rotRadian), np.cos(rotRadian), 0], - [0, 0, 1] - ]) + return np.array( + [ + [np.cos(rotRadian), -np.sin(rotRadian), 0], + [np.sin(rotRadian), np.cos(rotRadian), 0], + [0, 0, 1], + ] + ) def Raxis(axis: Tuple[float, float, float], a: float, origin=np.array([0, 0, 0])): @@ -45,15 +48,29 @@ def Raxis(axis: Tuple[float, float, float], a: float, origin=np.array([0, 0, 0]) oneminuscos = 1 - np.cos(a) x, y, z = axis - return np.array([ - [np.cos(a) + (x ** 2) * oneminuscos, x * y * oneminuscos - z * np.sin(a), x * z * oneminuscos + y * np.sin(a), - origin[0]], - [y * x * oneminuscos + z * np.sin(a), np.cos(a) + (y ** 2) * oneminuscos, y * z * oneminuscos - x * np.sin(a), - origin[1]], - [z * x * oneminuscos - y * np.sin(a), z * y * oneminuscos + x * np.sin(a), np.cos(a) + (z ** 2) * oneminuscos, - origin[2]], - [0, 0, 0, 1] - ]) + return np.array( + [ + [ + np.cos(a) + (x**2) * oneminuscos, + x * y * oneminuscos - z * np.sin(a), + x * z * oneminuscos + y * np.sin(a), + origin[0], + ], + [ + y * x * oneminuscos + z * np.sin(a), + np.cos(a) + (y**2) * oneminuscos, + y * z * oneminuscos - x * np.sin(a), + origin[1], + ], + [ + z * x * oneminuscos - y * np.sin(a), + z * y * oneminuscos + x * np.sin(a), + np.cos(a) + (z**2) * oneminuscos, + origin[2], + ], + [0, 0, 0, 1], + ] + ) class InteractionAroundActivePlane: @@ -77,11 +94,11 @@ def __init__(self, currentInteractor, render): self.axes = [] - # real ability - # actor.SetOrigin(1, 0, 1) - # actor.SetOrientation(0, 0, 90) + # real ability + # actor.SetOrigin(1, 0, 1) + # actor.SetOrientation(0, 0, 90) - def leftBtnPress(self, obj, event, view = None): + def leftBtnPress(self, obj, event, view=None): """ These events are bind: "LeftButtonPressEvent" "LeftButtonReleaseEvent" """ @@ -91,7 +108,9 @@ def leftBtnPress(self, obj, event, view = None): picker = self.getPicker() actor = picker.GetActor() - if (picker.GetCellId() >= 0) and (isinstance(actor, src.gui_utils.StlActor)): + if (picker.GetCellId() >= 0) and ( + isinstance(actor, src.gui_utils.StlActor) + ): triangle_id = picker.GetCellId() normal = actor.GetTriangleNormal(triangle_id) actor.RotateByVector(-normal) @@ -127,7 +146,9 @@ def mouseMove(self, obj, event, view): # that means if we are close to critical points, we may allow to move in opposite direction # apply rotation around Z-axis - self.pos = Raxis((0, 0, 1), np.deg2rad(-deltaOnYaw * angleSpeed), self.focalPoint).dot(np.array(self.pos)) + self.pos = Raxis( + (0, 0, 1), np.deg2rad(-deltaOnYaw * angleSpeed), self.focalPoint + ).dot(np.array(self.pos)) # float in range (-1, 1) where if abs == 1 means camera view is on the z-axis # 1 - we are facing in opposite direction than z-axis @@ -135,22 +156,38 @@ def mouseMove(self, obj, event, view): viewGoesAlongZ = np.dot(np.array([0, 0, 1]), unit(self.pos[:3])) # normalize value maxv = 2 # normalizing range for deltaOnPitchRoll - deltaOnPitchRoll = maxv if deltaOnPitchRoll > maxv else -maxv if deltaOnPitchRoll < -maxv else deltaOnPitchRoll + deltaOnPitchRoll = ( + maxv + if deltaOnPitchRoll > maxv + else -maxv + if deltaOnPitchRoll < -maxv + else deltaOnPitchRoll + ) # do not allow camera go too along with z-axis threshold = 0.95 - if abs(viewGoesAlongZ) < threshold or (viewGoesAlongZ < threshold and deltaOnPitchRoll < 0) or ( - viewGoesAlongZ > threshold and deltaOnPitchRoll > 0): - self.pos = Raxis((self.pos[1], -self.pos[0], 0), np.deg2rad(-deltaOnPitchRoll / self.distanceToFocal / 2), - self.focalPoint).dot(np.array(self.pos)) + if ( + abs(viewGoesAlongZ) < threshold + or (viewGoesAlongZ < threshold and deltaOnPitchRoll < 0) + or (viewGoesAlongZ > threshold and deltaOnPitchRoll > 0) + ): + self.pos = Raxis( + (self.pos[1], -self.pos[0], 0), + np.deg2rad(-deltaOnPitchRoll / self.distanceToFocal / 2), + self.focalPoint, + ).dot(np.array(self.pos)) if self.isMoving: # in this type we will have to move only on 2 axis # if we move mouse up-down we will move along projection of line connecting the camera with the origin # if we move mouse left-right we will move along perpendicular line to the previous one, also in xy plane - projVector = unit(np.array([self.pos[0] - self.focalPoint[0], self.pos[1] - self.focalPoint[1]])) + projVector = unit( + np.array( + [self.pos[0] - self.focalPoint[0], self.pos[1] - self.focalPoint[1]] + ) + ) perpVector = unit(np.array([projVector[1], -projVector[0]])) deltaOnProjection: float = xCur - xLast @@ -180,7 +217,9 @@ def mouseMove(self, obj, event, view): picker = self.getPicker() actor = picker.GetActor() - if (picker.GetCellId() >= 0) and (isinstance(actor, src.gui_utils.StlActor)): + if (picker.GetCellId() >= 0) and ( + isinstance(actor, src.gui_utils.StlActor) + ): actor.ResetColorize() poly_data = actor.GetMapper().GetInput() triangle_id = picker.GetCellId() diff --git a/src/bug_report.py b/src/bug_report.py index aef2ce2..2d4472c 100644 --- a/src/bug_report.py +++ b/src/bug_report.py @@ -6,10 +6,20 @@ from datetime import datetime from functools import partial from PyQt5 import QtCore, QtGui -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QLabel, QHBoxLayout, QFileDialog, QMessageBox +from PyQt5.QtWidgets import ( + QWidget, + QVBoxLayout, + QTextEdit, + QPushButton, + QLabel, + QHBoxLayout, + QFileDialog, + QMessageBox, +) from src.settings import sett, save_splanes_to_file, PathBuilder from src.client import send_bug_report + class bugReportDialog(QWidget): def __init__(self, controller): super().__init__() @@ -62,7 +72,9 @@ def addImage(self, controller): if not os.path.exists(self.temp_images_folder): os.mkdir(self.temp_images_folder) - image_path, _ = QFileDialog.getOpenFileName(self, controller.view.locale.AddingImage, "", "Images (*.png *.jpg)") + image_path, _ = QFileDialog.getOpenFileName( + self, controller.view.locale.AddingImage, "", "Images (*.png *.jpg)" + ) if image_path: image_name = os.path.basename(image_path) @@ -74,7 +86,9 @@ def addImage(self, controller): self.update_image_names_label() def update_image_names_label(self): - image_names = ', '.join([os.path.basename(image_name) for image_name in self.images]) + image_names = ", ".join( + [os.path.basename(image_name) for image_name in self.images] + ) self.image_list.setText(image_names) def send(self, controller): @@ -97,15 +111,18 @@ def send(self, controller): current_datetime = datetime.now().strftime("%Y-%m-%d %H-%M-%S") self.archive_path = os.path.join("temp", f"{current_datetime}.zip") - self.addFolderToArchive(self.archive_path, PathBuilder.project_path(), "project") - self.addFolderToArchive(self.archive_path, self.temp_images_folder, "images") - + self.addFolderToArchive( + self.archive_path, PathBuilder.project_path(), "project" + ) + self.addFolderToArchive( + self.archive_path, self.temp_images_folder, "images" + ) if os.path.exists("interface.log"): - with zipfile.ZipFile(self.archive_path, 'a') as archive: + with zipfile.ZipFile(self.archive_path, "a") as archive: archive.write("interface.log") - with zipfile.ZipFile(self.archive_path, 'a') as archive: + with zipfile.ZipFile(self.archive_path, "a") as archive: archive.writestr("error_description.txt", error_description) successfully_sent = send_bug_report(self.archive_path, error_description) @@ -124,14 +141,16 @@ def send(self, controller): self.failedSendWindow(controller, str(e)) - def addFolderToArchive(self, archive_path, folder_path, subfolder = ""): - with zipfile.ZipFile(archive_path, 'a') as archive: + def addFolderToArchive(self, archive_path, folder_path, subfolder=""): + with zipfile.ZipFile(archive_path, "a") as archive: for path, _, files in os.walk(folder_path): for file in files: file_path = os.path.join(path, file) archive_relative_path = os.path.relpath(file_path, folder_path) if subfolder: - archive.write(file_path, os.path.join(subfolder, archive_relative_path)) + archive.write( + file_path, os.path.join(subfolder, archive_relative_path) + ) else: archive.write(file_path, archive_relative_path) @@ -145,7 +164,9 @@ def successfulSendWindow(self, controller): def failedSendWindow(self, controller, error_msg: str = ""): message_box = QMessageBox(parent=self) message_box.setWindowTitle(controller.view.locale.SubmittingBugReport) - message_box.setText(controller.view.locale.ErrorReport + f"\nError message: {error_msg}") + message_box.setText( + controller.view.locale.ErrorReport + f"\nError message: {error_msg}" + ) message_box.setIcon(QMessageBox.Critical) message_box.exec_() diff --git a/src/client.py b/src/client.py index 3cf15ab..7347389 100644 --- a/src/client.py +++ b/src/client.py @@ -6,24 +6,26 @@ CHUNK_SIZE = 1024 * 1024 # 1MB + def get_file_chunks(filename): - with open(filename, 'rb') as f: + with open(filename, "rb") as f: while True: - piece = f.read(CHUNK_SIZE); + piece = f.read(CHUNK_SIZE) if len(piece) == 0: return yield piece + def prepare_bug(filename, error_description): - with open("auth.yaml", 'r') as file: + with open("auth.yaml", "r") as file: auth_data = yaml.safe_load(file) req = srv_bug_pb2.AddBugRequest( info=srv_bug_pb2.BugInfo( message=error_description, creds=srv_bug_pb2.Credentials( - login=str(auth_data.get('login')), - passw=str(auth_data.get('password'))), + login=str(auth_data.get("login")), passw=str(auth_data.get("password")) + ), ) ) yield req @@ -31,6 +33,7 @@ def prepare_bug(filename, error_description): for piece in get_file_chunks(filename): yield srv_bug_pb2.AddBugRequest(content=piece) + def send_bug_report(filename, error_description): with grpc.insecure_channel("app.epit3d.com:3456") as channel: stub = srv_bug_pb2_grpc.BugServiceStub(channel) diff --git a/src/cone_slicing.py b/src/cone_slicing.py index 6af3b0d..deab3a3 100644 --- a/src/cone_slicing.py +++ b/src/cone_slicing.py @@ -1,6 +1,7 @@ """ Module contains logic behind the cone slicing """ + from typing import Tuple import numpy as np @@ -52,12 +53,16 @@ def cross_stl(mesh_input: mesh.Mesh, cone: Tuple[float, Tuple[float, float, floa points = [] for x, y in ((t[0], t[1]), (t[0], t[2]), (t[1], t[2])): - cross_p = cone_cross(x, y, cone[0], np.array(vertex)) # find cross point(s) of line and cone + cross_p = cone_cross( + x, y, cone[0], np.array(vertex) + ) # find cross point(s) of line and cone if cross_p: if len(cross_p) == 3: # one intersection point of line and cone if cross_p not in points: points.append(cross_p) - elif len(cross_p) == 2: # two intersection points of line and cone + elif ( + len(cross_p) == 2 + ): # two intersection points of line and cone points = cross_p if len(points) in [2, 4, 6]: cross_p_list.append(points) # add intersection lines @@ -76,16 +81,29 @@ def cone_cross(p_1, p_2, alpha_cone=10.0, p_cone=np.array([0.0, 0.0, 0.0])): alpha_cone - cone angle in degrees p_cone - vertex of the cone: [x, y, z] """ - a = [p_2[0] - p_1[0], p_2[1] - p_1[1], p_2[2] - p_1[2]] # direction vector of the line + a = [ + p_2[0] - p_1[0], + p_2[1] - p_1[1], + p_2[2] - p_1[2], + ] # direction vector of the line ctg_alpha_cone = 1 / np.tan(alpha_cone * np.pi / 180) - a_1 = a[2] ** 2 - (a[0] ** 2 + a[1] ** 2) * ctg_alpha_cone ** 2 - b_1 = 2 * (a[2] * p_1[2] - a[2] * p_cone[2] - (a[0] * p_1[0] + a[1] * p_1[1]) * ctg_alpha_cone ** 2) - c_1 = p_1[2] ** 2 + p_cone[2] ** 2 - 2 * p_cone[2] * p_1[2] - (p_1[0] ** 2 + p_1[1] ** 2) * ctg_alpha_cone ** 2 - D = b_1 ** 2 - 4 * a_1 * c_1 # discriminant + a_1 = a[2] ** 2 - (a[0] ** 2 + a[1] ** 2) * ctg_alpha_cone**2 + b_1 = 2 * ( + a[2] * p_1[2] + - a[2] * p_cone[2] + - (a[0] * p_1[0] + a[1] * p_1[1]) * ctg_alpha_cone**2 + ) + c_1 = ( + p_1[2] ** 2 + + p_cone[2] ** 2 + - 2 * p_cone[2] * p_1[2] + - (p_1[0] ** 2 + p_1[1] ** 2) * ctg_alpha_cone**2 + ) + D = b_1**2 - 4 * a_1 * c_1 # discriminant if D > 0: - lam_1 = (-b_1 - D ** 0.5) / (2 * a_1) - lam_2 = (-b_1 + D ** 0.5) / (2 * a_1) + lam_1 = (-b_1 - D**0.5) / (2 * a_1) + lam_2 = (-b_1 + D**0.5) / (2 * a_1) x_1 = lam_1 * a[0] + p_1[0] y_1 = lam_1 * a[1] + p_1[1] @@ -96,12 +114,14 @@ def cone_cross(p_1, p_2, alpha_cone=10.0, p_cone=np.array([0.0, 0.0, 0.0])): z_2 = lam_2 * a[2] + p_1[2] # check that points are on the line - check_points = [min(p_1[0], p_2[0]) <= x_1 <= max(p_1[0], p_2[0]), - min(p_1[1], p_2[1]) <= y_1 <= max(p_1[1], p_2[1]), - min(p_1[2], p_2[2]) <= z_1 <= max(p_1[2], p_2[2]), - min(p_1[0], p_2[0]) <= x_2 <= max(p_1[0], p_2[0]), - min(p_1[1], p_2[1]) <= y_2 <= max(p_1[1], p_2[1]), - min(p_1[2], p_2[2]) <= z_2 <= max(p_1[2], p_2[2])] + check_points = [ + min(p_1[0], p_2[0]) <= x_1 <= max(p_1[0], p_2[0]), + min(p_1[1], p_2[1]) <= y_1 <= max(p_1[1], p_2[1]), + min(p_1[2], p_2[2]) <= z_1 <= max(p_1[2], p_2[2]), + min(p_1[0], p_2[0]) <= x_2 <= max(p_1[0], p_2[0]), + min(p_1[1], p_2[1]) <= y_2 <= max(p_1[1], p_2[1]), + min(p_1[2], p_2[2]) <= z_2 <= max(p_1[2], p_2[2]), + ] if all(check_points) and z_1 <= p_cone[2] and z_2 <= p_cone[2]: return [[x_1, y_1, z_1], [x_2, y_2, z_2]] # two intersection points elif all(check_points[:3]) and z_1 <= p_cone[2]: diff --git a/src/controller.py b/src/controller.py index 47c9dc4..08dd862 100644 --- a/src/controller.py +++ b/src/controller.py @@ -19,16 +19,33 @@ from src import gui_utils, locales, qt_utils from src.figure_editor import PlaneEditor, ConeEditor -from src.gui_utils import showErrorDialog, plane_tf, read_planes, Plane, Cone, showInfoDialog +from src.gui_utils import ( + showErrorDialog, + plane_tf, + read_planes, + Plane, + Cone, + showInfoDialog, +) from src.process import Process -from src.settings import (sett, save_settings, save_splanes_to_file, load_settings, get_color, PathBuilder, - create_temporary_project_files, update_last_open_project, get_recent_projects, delete_temporary_project_files) +from src.settings import ( + sett, + save_settings, + save_splanes_to_file, + load_settings, + get_color, + PathBuilder, + create_temporary_project_files, + update_last_open_project, + get_recent_projects, + delete_temporary_project_files, +) import src.settings as settings try: from src.bug_report import bugReportDialog except: - print('bug reporting is unavailable') + print("bug reporting is unavailable") # try import of private hardware module try: @@ -36,7 +53,8 @@ import src.hardware.calibration as calibration import src.hardware.printer as printer except Exception as e: - print(f'hardware module is unavailable: {e}') + print(f"hardware module is unavailable: {e}") + class MainController: def __init__(self, view, model): @@ -50,8 +68,7 @@ def __init__(self, view, model): self.servicePanel = service.ServicePanel(view) self.servicePanel.setModal(True) self.serviceController = service.ServiceController( - self.servicePanel, - service.ServiceModel(self.printer) + self.servicePanel, service.ServiceModel(self.printer) ) # embed calibration tool @@ -59,7 +76,9 @@ def __init__(self, view, model): self.calibrationPanel.setModal(True) self.calibrationController = calibration.CalibrationController( self.calibrationPanel, - calibration.CalibrationModel(self.printer, PathBuilder.calibration_file()) + calibration.CalibrationModel( + self.printer, PathBuilder.calibration_file() + ), ) except: print("printer is not initialized") @@ -82,18 +101,14 @@ def _connect_signals(self): self.view.check_updates_action.triggered.connect(self.open_updater) try: - self.view.calibration_action.triggered.connect( - self.calibration_action_show - ) + self.view.calibration_action.triggered.connect(self.calibration_action_show) except: self.view.calibration_action.triggered.connect( lambda: showInfoDialog(locales.getLocale().ErrorHardwareModule) ) try: - self.view.bug_report.triggered.connect( - self.bugReportDialog.show - ) + self.view.bug_report.triggered.connect(self.bugReportDialog.show) except: self.view.bug_report.triggered.connect( lambda: showInfoDialog(locales.getLocale().ErrorBugModule) @@ -102,13 +117,23 @@ def _connect_signals(self): # right panel self.view.printer_add_btn.clicked.connect(self.create_printer) self.view.printer_path_edit.clicked.connect(self.choose_printer_path) - self.view.number_wall_lines_value.textChanged.connect(self.update_wall_thickness) + self.view.number_wall_lines_value.textChanged.connect( + self.update_wall_thickness + ) self.view.line_width_value.textChanged.connect(self.update_wall_thickness) self.view.layer_height_value.textChanged.connect(self.change_layer_height) - self.view.number_of_bottom_layers_value.textChanged.connect(self.update_bottom_thickness) - self.view.number_of_lid_layers_value.textChanged.connect(self.update_lid_thickness) - self.view.supports_number_of_bottom_layers_value.textChanged.connect(self.update_supports_bottom_thickness) - self.view.supports_number_of_lid_layers_value.textChanged.connect(self.update_supports_lid_thickness) + self.view.number_of_bottom_layers_value.textChanged.connect( + self.update_bottom_thickness + ) + self.view.number_of_lid_layers_value.textChanged.connect( + self.update_lid_thickness + ) + self.view.supports_number_of_bottom_layers_value.textChanged.connect( + self.update_supports_bottom_thickness + ) + self.view.supports_number_of_lid_layers_value.textChanged.connect( + self.update_supports_lid_thickness + ) self.view.model_switch_box.stateChanged.connect(self.view.switch_stl_gcode) self.view.model_centering_box.stateChanged.connect(self.view.model_centering) self.view.picture_slider.valueChanged.connect(self.change_layer_view) @@ -156,27 +181,33 @@ def save_planes_on_close(self): def create_printer(self): # query user for printer name and create directory in data/printers/ relative to FASP root - text, ok = QInputDialog.getText(self.view, locales.getLocale().AddNewPrinter, locales.getLocale().ChoosePrinterDirectory) + text, ok = QInputDialog.getText( + self.view, + locales.getLocale().AddNewPrinter, + locales.getLocale().ChoosePrinterDirectory, + ) if not ok: return - + printer_name = text.strip() if not printer_name: return # create directory in data/printers/ relative to FASP root printer_path = path.join(settings.APP_PATH, "data", "printers", printer_name) - + # check if directory already exists if path.exists(printer_path): showErrorDialog("Printer with this name already exists") return - + # create directory os.makedirs(printer_path) # copy calibration data from default directory to new printer directory - default_calibration_file = path.join(settings.APP_PATH, "data", "printers", "default", "calibration_data.csv") + default_calibration_file = path.join( + settings.APP_PATH, "data", "printers", "default", "calibration_data.csv" + ) target_calibration_file = path.join(printer_path, "calibration_data.csv") shutil.copyfile(default_calibration_file, target_calibration_file) @@ -191,32 +222,37 @@ def create_printer(self): # update path in calibration model try: - self.calibrationController.updateCalibrationFilepath(PathBuilder.calibration_file()) + self.calibrationController.updateCalibrationFilepath( + PathBuilder.calibration_file() + ) except AttributeError: print("hardware module is unavailable, skip") # show info dialog - showInfoDialog("Printer created successfully, please calibrate before first use") - + showInfoDialog( + "Printer created successfully, please calibrate before first use" + ) def choose_printer_path(self): printer_path = QFileDialog.getExistingDirectory( self.view, locales.getLocale().ChoosePrinterDirectory, - sett().hardware.printer_dir + sett().hardware.printer_dir, ) if printer_path: # check if directory contains calibration file calibration_file = path.join(printer_path, "calibration_data.csv") if not path.exists(calibration_file): - showErrorDialog("Directory doesn't contain calibration file. Please choose another directory.") + showErrorDialog( + "Directory doesn't contain calibration file. Please choose another directory." + ) return sett().hardware.printer_dir = printer_path # calibration file will be at default location sett().hardware.calibration_file = "calibration_data.csv" - + # save settings save_settings() @@ -225,7 +261,9 @@ def choose_printer_path(self): # update path in calibration model try: - self.calibrationController.updateCalibrationFilepath(PathBuilder.calibration_file()) + self.calibrationController.updateCalibrationFilepath( + PathBuilder.calibration_file() + ) except AttributeError: print("hardware module is unavailable, skip") @@ -249,24 +287,34 @@ def moving_figure(self, sourceParent, previousRow): if previousItemNumber > 0: currentRow = previousItemNumber - 1 - self.view.splanes_tree.setCurrentItem(self.view.splanes_tree.topLevelItem(previousRow)) + self.view.splanes_tree.setCurrentItem( + self.view.splanes_tree.topLevelItem(previousRow) + ) if previousRow != currentRow: - if previousRow > currentRow: #Down - self.model.splanes.insert(previousRow + 1, self.model.splanes[currentRow]) + if previousRow > currentRow: # Down + self.model.splanes.insert( + previousRow + 1, self.model.splanes[currentRow] + ) del self.model.splanes[currentRow] - if previousRow < currentRow: #Up - self.model.splanes.insert(previousRow, self.model.splanes[currentRow]) + if previousRow < currentRow: # Up + self.model.splanes.insert( + previousRow, self.model.splanes[currentRow] + ) del self.model.splanes[currentRow + 1] for i in range(len(self.model.splanes)): self.view.splanes_tree.topLevelItem(i).setText(1, str(i + 1)) - self.view.splanes_tree.topLevelItem(i).setText(2, self.model.splanes[i].toFile()) + self.view.splanes_tree.topLevelItem(i).setText( + 2, self.model.splanes[i].toFile() + ) self.view._recreate_splanes(self.model.splanes) self.view.splanes_tree.itemIsMoving = False - self.view.change_combo_select(self.model.splanes[previousRow], previousRow) + self.view.change_combo_select( + self.model.splanes[previousRow], previousRow + ) def change_figure_check_state(self, item, column): ind = self.view.splanes_tree.indexFromItem(item).row() @@ -299,14 +347,28 @@ def change_figure_parameters(self): self.view.tabs.setTabEnabled(1, True) if isinstance(self.model.splanes[ind], Plane): - self.view.parameters_tooling = PlaneEditor(self.view.tabs, self.update_plane_common, self.model.splanes[ind].params()) + self.view.parameters_tooling = PlaneEditor( + self.view.tabs, + self.update_plane_common, + self.model.splanes[ind].params(), + ) elif isinstance(self.model.splanes[ind], Cone): - self.view.parameters_tooling = ConeEditor(self.view.tabs, self.update_cone_common, self.model.splanes[ind].params()) + self.view.parameters_tooling = ConeEditor( + self.view.tabs, + self.update_cone_common, + self.model.splanes[ind].params(), + ) def save_planes(self): try: - directory = "Planes_" + os.path.basename(sett().slicing.stl_file).split('.')[0] - filename = str(self.view.save_dialog(self.view.locale.SavePlanes, "TXT (*.txt *.TXT)", directory)) + directory = ( + "Planes_" + os.path.basename(sett().slicing.stl_file).split(".")[0] + ) + filename = str( + self.view.save_dialog( + self.view.locale.SavePlanes, "TXT (*.txt *.TXT)", directory + ) + ) if filename != "": if not (filename.endswith(".txt") or filename.endswith(".TXT")): filename += ".txt" @@ -316,7 +378,11 @@ def save_planes(self): def download_planes(self): try: - filename = str(self.view.open_dialog(self.view.locale.DownloadPlanes,"TXT (*.txt *.TXT)")) + filename = str( + self.view.open_dialog( + self.view.locale.DownloadPlanes, "TXT (*.txt *.TXT)" + ) + ) if filename != "": file_ext = os.path.splitext(filename)[1].upper() filename = str(Path(filename)) @@ -326,8 +392,7 @@ def download_planes(self): except: showErrorDialog("Error during reading planes file") else: - showErrorDialog( - "This file format isn't supported:" + file_ext) + showErrorDialog("This file format isn't supported:" + file_ext) except IOError as e: showErrorDialog("Error during file opening:" + str(e)) @@ -348,17 +413,29 @@ def change_layer_view(self): if self.view.picture_slider.value() == self.model.current_slider_value: return - step = 1 if self.view.picture_slider.value() > self.model.current_slider_value else -1 + step = ( + 1 + if self.view.picture_slider.value() > self.model.current_slider_value + else -1 + ) - for i in range(self.model.current_slider_value, self.view.picture_slider.value(), step): - self.model.current_slider_value = self.view.change_layer_view(i + step, self.model.current_slider_value, self.model.gcode) + for i in range( + self.model.current_slider_value, self.view.picture_slider.value(), step + ): + self.model.current_slider_value = self.view.change_layer_view( + i + step, self.model.current_slider_value, self.model.gcode + ) self.view.reload_scene() self.view.hide_checkbox.setChecked(True) self.view.model_switch_box.setChecked(False) def update_wall_thickness(self): - self.update_dependent_fields(self.view.number_wall_lines_value, self.view.line_width_value, self.view.wall_thickness_value) + self.update_dependent_fields( + self.view.number_wall_lines_value, + self.view.line_width_value, + self.view.wall_thickness_value, + ) def change_layer_height(self): self.update_bottom_thickness() @@ -367,25 +444,45 @@ def change_layer_height(self): self.update_supports_lid_thickness() def update_bottom_thickness(self): - self.update_dependent_fields(self.view.number_of_bottom_layers_value, self.view.layer_height_value, self.view.bottom_thickness_value) + self.update_dependent_fields( + self.view.number_of_bottom_layers_value, + self.view.layer_height_value, + self.view.bottom_thickness_value, + ) def update_lid_thickness(self): - self.update_dependent_fields(self.view.number_of_lid_layers_value, self.view.layer_height_value, self.view.lid_thickness_value) + self.update_dependent_fields( + self.view.number_of_lid_layers_value, + self.view.layer_height_value, + self.view.lid_thickness_value, + ) def update_supports_bottom_thickness(self): - self.update_dependent_fields(self.view.supports_number_of_bottom_layers_value, self.view.layer_height_value, self.view.supports_bottom_thickness_value) - + self.update_dependent_fields( + self.view.supports_number_of_bottom_layers_value, + self.view.layer_height_value, + self.view.supports_bottom_thickness_value, + ) + def update_supports_lid_thickness(self): - self.update_dependent_fields(self.view.supports_number_of_lid_layers_value, self.view.layer_height_value, self.view.supports_lid_thickness_value) + self.update_dependent_fields( + self.view.supports_number_of_lid_layers_value, + self.view.layer_height_value, + self.view.supports_lid_thickness_value, + ) def update_dependent_fields(self, entry_field_1, entry_field_2, output_field): - entry_field_1_text = entry_field_1.text().replace(',', '.') - entry_field_2_text = entry_field_2.text().replace(',', '.') + entry_field_1_text = entry_field_1.text().replace(",", ".") + entry_field_2_text = entry_field_2.text().replace(",", ".") - if ((not entry_field_1_text) or entry_field_1_text == "." ) or ((not entry_field_2_text) or entry_field_2_text == "."): + if ((not entry_field_1_text) or entry_field_1_text == ".") or ( + (not entry_field_2_text) or entry_field_2_text == "." + ): output_field.setText("0.0") else: - output_field.setText(str(round(float(entry_field_1_text) * float(entry_field_2_text), 2))) + output_field.setText( + str(round(float(entry_field_1_text) * float(entry_field_2_text), 2)) + ) def move_model(self): self.view.move_stl2() @@ -464,15 +561,15 @@ def work(): print("start parsing gcode") start_time = time.time() gc = self.model.load_gcode(filename) - print('finish parsing gcode') + print("finish parsing gcode") end_time = time.time() - print('spent time for gcode loading: ', end_time - start_time, 's') + print("spent time for gcode loading: ", end_time - start_time, "s") return gc gc = qt_utils.progress_dialog( - locales.getLocale().GCodeLoadingTitle, - locales.getLocale().GCodeLoadingProgress, + locales.getLocale().GCodeLoadingTitle, + locales.getLocale().GCodeLoadingProgress, work, ) blocks = gui_utils.makeBlocks(gc.layers, gc.rotations, gc.lays2rots) @@ -487,8 +584,12 @@ def work(): if len(self.model.splanes) > 0: self.view._recreate_splanes(self.model.splanes) - self.view.splanes_actors[currentItem].GetProperty().SetColor(get_color(sett().colors.last_layer)) - self.view.splanes_actors[currentItem].GetProperty().SetOpacity(sett().common.opacity_last_layer) + self.view.splanes_actors[currentItem].GetProperty().SetColor( + get_color(sett().colors.last_layer) + ) + self.view.splanes_actors[currentItem].GetProperty().SetOpacity( + sett().common.opacity_last_layer + ) def slice_stl(self, slicing_type): if slicing_type == "vip" and len(self.model.splanes) == 0: @@ -507,8 +608,8 @@ def work(): p.wait() print("finished command") end_time = time.time() - print('spent time for slicing: ', end_time - start_time, 's') - + print("spent time for slicing: ", end_time - start_time, "s") + if p.returncode == 2: # panic return p.stderr @@ -516,11 +617,11 @@ def work(): # fatal, the error is in the latest line return p.stdout.splitlines()[-1] - # no errors + # no errors return "" error = qt_utils.progress_dialog( - locales.getLocale().SlicingTitle, + locales.getLocale().SlicingTitle, locales.getLocale().SlicingProgress, work, ) @@ -557,23 +658,33 @@ def check_calibration_data_catalog(self): def get_slicer_version(self): proc = Process(sett().slicing.cmd_version).wait() - + if proc.returncode: showErrorDialog("Error during getting slicer version:" + str(proc.stdout)) else: showInfoDialog(locales.getLocale().SlicerVersion + proc.stdout) - def save_settings(self, slicing_type, filename = ""): + def save_settings(self, slicing_type, filename=""): s = sett() - print(f"saving settings of stl file {self.model.opened_stl} {s.slicing.stl_file}") + print( + f"saving settings of stl file {self.model.opened_stl} {s.slicing.stl_file}" + ) # s.slicing.stl_file = self.model.opened_stl tf = vtk.vtkTransform() if self.view.stlActor is not None: tf = self.view.stlActor.GetUserTransform() - s.uninterrupted_print.enabled = bool(self.view.uninterrupted_print_box.isChecked()) - s.uninterrupted_print.cut_distance = float(self.view.m10_cut_distance_value.text()) + s.uninterrupted_print.enabled = bool( + self.view.uninterrupted_print_box.isChecked() + ) + s.uninterrupted_print.cut_distance = float( + self.view.m10_cut_distance_value.text() + ) s.slicing.originx, s.slicing.originy, s.slicing.originz = tf.GetPosition() - s.slicing.rotationx, s.slicing.rotationy, s.slicing.rotationz = tf.GetOrientation() + ( + s.slicing.rotationx, + s.slicing.rotationy, + s.slicing.rotationz, + ) = tf.GetOrientation() s.slicing.scalex, s.slicing.scaley, s.slicing.scalez = tf.GetScale() s.slicing.layer_height = float(self.view.layer_height_value.text()) s.slicing.print_speed = float(self.view.print_speed_value.text()) @@ -585,30 +696,46 @@ def save_settings(self, slicing_type, filename = ""): s.slicing.wall_thickness = float(self.view.wall_thickness_value.text()) s.slicing.line_width = float(self.view.line_width_value.text()) s.slicing.filling_type = locales.getLocaleByLang("en").FillingTypeValues[ - self.view.filling_type_values.currentIndex()] + self.view.filling_type_values.currentIndex() + ] s.slicing.retraction_on = self.view.retraction_on_box.isChecked() - s.slicing.retraction_distance = float(self.view.retraction_distance_value.text()) + s.slicing.retraction_distance = float( + self.view.retraction_distance_value.text() + ) s.slicing.retraction_speed = float(self.view.retraction_speed_value.text()) - s.slicing.retract_compensation_amount = float(self.view.retract_compensation_amount_value.text()) + s.slicing.retract_compensation_amount = float( + self.view.retract_compensation_amount_value.text() + ) s.slicing.skirt_line_count = int(self.view.skirt_line_count_value.text()) s.slicing.fan_off_layer1 = self.view.fan_off_layer1_box.isChecked() s.slicing.fan_speed = float(self.view.fan_speed_value.text()) s.slicing.angle = float(self.view.colorize_angle_value.text()) - + s.slicing.lids_depth = int(self.view.number_of_lid_layers_value.text()) s.slicing.bottoms_depth = int(self.view.number_of_bottom_layers_value.text()) - + s.supports.enabled = self.view.supports_on_box.isChecked() s.supports.xy_offset = float(self.view.support_xy_offset_value.text()) - s.supports.z_offset_layers = int(float(self.view.support_z_offset_layers_value.text())) + s.supports.z_offset_layers = int( + float(self.view.support_z_offset_layers_value.text()) + ) s.supports.fill_density = float(self.view.support_density_value.text()) s.supports.fill_type = locales.getLocaleByLang("en").FillingTypeValues[ - self.view.support_fill_type_values.currentIndex()] - s.supports.priority_z_offset = bool(self.view.support_priority_z_offset_box.isChecked()) - s.supports.lids_depth = int(self.view.supports_number_of_lid_layers_value.text()) - s.supports.bottoms_depth = int(self.view.supports_number_of_bottom_layers_value.text()) + self.view.support_fill_type_values.currentIndex() + ] + s.supports.priority_z_offset = bool( + self.view.support_priority_z_offset_box.isChecked() + ) + s.supports.lids_depth = int( + self.view.supports_number_of_lid_layers_value.text() + ) + s.supports.bottoms_depth = int( + self.view.supports_number_of_bottom_layers_value.text() + ) - s.slicing.overlapping_infill_percentage = float(self.view.overlapping_infill_value.text()) + s.slicing.overlapping_infill_percentage = float( + self.view.overlapping_infill_value.text() + ) s.slicing.material_shrinkage = float(self.view.material_shrinkage_value.text()) s.slicing.slicing_type = slicing_type @@ -622,10 +749,12 @@ def save_settings(self, slicing_type, filename = ""): # save planes to settings s.figures = [] for idx, plane in enumerate(self.model.splanes): - s.figures.append(dict( - index=idx, - description=plane.toFile(), - )) + s.figures.append( + dict( + index=idx, + description=plane.toFile(), + ) + ) if filename != "": save_settings(filename) @@ -642,8 +771,14 @@ def save_gcode_file(self): def save_settings_file(self): try: - directory = "Settings_" + os.path.basename(sett().slicing.stl_file).split('.')[0] - filename = str(self.view.save_dialog(self.view.locale.SaveSettings, "YAML (*.yaml *.YAML)", directory)) + directory = ( + "Settings_" + os.path.basename(sett().slicing.stl_file).split(".")[0] + ) + filename = str( + self.view.save_dialog( + self.view.locale.SaveSettings, "YAML (*.yaml *.YAML)", directory + ) + ) if filename != "": if not (filename.endswith(".yaml") or filename.endswith(".YAML")): filename += ".yaml" @@ -651,13 +786,15 @@ def save_settings_file(self): except IOError as e: showErrorDialog("Error during file saving:" + str(e)) - def save_project_files(self, save_path = ""): + def save_project_files(self, save_path=""): if save_path == "": self.save_settings("vip", PathBuilder.settings_file()) shutil.copy2(PathBuilder.stl_model_temp(), PathBuilder.stl_model()) else: self.save_settings("vip", path.join(save_path, "settings.yaml")) - shutil.copy2(PathBuilder.stl_model_temp(), path.join(save_path, "model.stl + shutil.copy2( + PathBuilder.stl_model_temp(), path.join(save_path, "model.stl") + ) def save_project(self): try: @@ -671,7 +808,11 @@ def save_project_as(self): project_path = PathBuilder.project_path() try: - save_directory = str(QFileDialog.getExistingDirectory(self.view, locales.getLocale().SavingProject)) + save_directory = str( + QFileDialog.getExistingDirectory( + self.view, locales.getLocale().SavingProject + ) + ) if not save_directory: return @@ -701,7 +842,11 @@ def successful_saving_project(self): def load_settings_file(self): try: - filename = str(self.view.open_dialog(self.view.locale.LoadSettings,"YAML (*.yaml *.YAML)")) + filename = str( + self.view.open_dialog( + self.view.locale.LoadSettings, "YAML (*.yaml *.YAML)" + ) + ) if filename != "": file_ext = os.path.splitext(filename)[1].upper() filename = str(Path(filename)) @@ -712,8 +857,7 @@ def load_settings_file(self): except: showErrorDialog("Error during reading settings file") else: - showErrorDialog( - "This file format isn't supported:" + file_ext) + showErrorDialog("This file format isn't supported:" + file_ext) except IOError as e: showErrorDialog("Error during file opening:" + str(e)) @@ -735,23 +879,31 @@ def display_settings(self): self.view.print_speed_value.setText(str(s.slicing.print_speed)) self.view.print_speed_layer1_value.setText(str(s.slicing.print_speed_layer1)) self.view.print_speed_wall_value.setText(str(s.slicing.print_speed_wall)) - ind = locales.getLocaleByLang("en").FillingTypeValues.index(s.slicing.filling_type) + ind = locales.getLocaleByLang("en").FillingTypeValues.index( + s.slicing.filling_type + ) self.view.filling_type_values.setCurrentIndex(ind) self.view.fill_density_value.setText(str(s.slicing.fill_density)) - self.view.overlapping_infill_value.setText(str(s.slicing.overlapping_infill_percentage)) + self.view.overlapping_infill_value.setText( + str(s.slicing.overlapping_infill_percentage) + ) if s.slicing.retraction_on: self.view.retraction_on_box.setCheckState(QtCore.Qt.Checked) else: self.view.retraction_on_box.setCheckState(QtCore.Qt.Unchecked) self.view.retraction_distance_value.setText(str(s.slicing.retraction_distance)) self.view.retraction_speed_value.setText(str(s.slicing.retraction_speed)) - self.view.retract_compensation_amount_value.setText(str(s.slicing.retract_compensation_amount)) + self.view.retract_compensation_amount_value.setText( + str(s.slicing.retract_compensation_amount) + ) if s.supports.enabled: self.view.supports_on_box.setCheckState(QtCore.Qt.Checked) else: self.view.supports_on_box.setCheckState(QtCore.Qt.Unchecked) self.view.support_density_value.setText(str(s.supports.fill_density)) - ind = locales.getLocaleByLang("en").FillingTypeValues.index(s.supports.fill_type) + ind = locales.getLocaleByLang("en").FillingTypeValues.index( + s.supports.fill_type + ) self.view.support_fill_type_values.setCurrentIndex(ind) self.view.support_xy_offset_value.setText(str(s.supports.xy_offset)) self.view.support_z_offset_layers_value.setText(str(s.supports.z_offset_layers)) @@ -759,14 +911,22 @@ def display_settings(self): self.view.support_priority_z_offset_box.setCheckState(QtCore.Qt.Checked) else: self.view.support_priority_z_offset_box.setCheckState(QtCore.Qt.Unchecked) - self.view.supports_number_of_bottom_layers_value.setText(str(s.supports.bottoms_depth)) - self.view.supports_bottom_thickness_value.setText(str(round(s.slicing.layer_height*s.supports.bottoms_depth,2))) - self.view.supports_number_of_lid_layers_value.setText(str(int(s.supports.lids_depth))) - self.view.supports_lid_thickness_value.setText(str(round(s.slicing.layer_height*s.supports.lids_depth,2))) + self.view.supports_number_of_bottom_layers_value.setText( + str(s.supports.bottoms_depth) + ) + self.view.supports_bottom_thickness_value.setText( + str(round(s.slicing.layer_height * s.supports.bottoms_depth, 2)) + ) + self.view.supports_number_of_lid_layers_value.setText( + str(int(s.supports.lids_depth)) + ) + self.view.supports_lid_thickness_value.setText( + str(round(s.slicing.layer_height * s.supports.lids_depth, 2)) + ) self.view.colorize_angle_value.setText(str(s.slicing.angle)) def colorize_model(self): - shutil.copyfile(PathBuilder.stl_model(),PathBuilder.colorizer_stl()) + shutil.copyfile(PathBuilder.stl_model(), PathBuilder.colorizer_stl()) self.save_settings("vip") p = Process(PathBuilder.colorizer_cmd()).wait() @@ -774,7 +934,7 @@ def colorize_model(self): logging.error(f"error: <{p.stdout}>") gui_utils.showErrorDialog(p.stdout) return - + lastMove = self.view.stlActor.lastMove self.load_stl(PathBuilder.colorizer_stl(), colorize=True) self.view.stlActor.lastMove = lastMove @@ -806,7 +966,10 @@ def remove_splane(self): self.view.splanes_tree.takeTopLevelItem(ind) self.view.reload_splanes(self.model.splanes) if len(self.model.splanes) == 0: - if self.view.parameters_tooling and not self.view.parameters_tooling.isHidden(): + if ( + self.view.parameters_tooling + and not self.view.parameters_tooling.isHidden() + ): self.view.parameters_tooling.close() self.change_figure_parameters() @@ -817,11 +980,14 @@ def change_combo_select(self): return if not self.view.splanes_tree.itemIsMoving: - if self.view.parameters_tooling and not self.view.parameters_tooling.isHidden(): + if ( + self.view.parameters_tooling + and not self.view.parameters_tooling.isHidden() + ): self.change_figure_parameters() if len(self.model.splanes) > ind: - self.view.change_combo_select(self.model.splanes[ind], ind) + self.view.change_combo_select(self.model.splanes[ind], ind) def update_plane_common(self, values: Dict[str, Union[float, bool]]): center = [values.get("X", 0), values.get("Y", 0), values.get("Z", 0)] @@ -838,21 +1004,27 @@ def update_plane_common(self, values: Dict[str, Union[float, bool]]): for i in range(len(self.model.splanes)): self.view.splanes_tree.topLevelItem(i).setText(1, str(i + 1)) - self.view.splanes_tree.topLevelItem(i).setText(2, self.model.splanes[i].toFile()) + self.view.splanes_tree.topLevelItem(i).setText( + 2, self.model.splanes[i].toFile() + ) def update_cone_common(self, values: Dict[str, float]): center: List[float] = [0, 0, values.get("Z", 0)] ind = self.view.splanes_tree.currentIndex().row() if ind == -1: return - self.model.splanes[ind] = gui_utils.Cone(values.get("A", 0), tuple(center), values.get("H1", 0), values.get("H2", 15)) + self.model.splanes[ind] = gui_utils.Cone( + values.get("A", 0), tuple(center), values.get("H1", 0), values.get("H2", 15) + ) self.view.update_cone(self.model.splanes[ind], ind) for i in range(len(self.model.splanes)): self.view.splanes_tree.topLevelItem(i).setText(1, str(i + 1)) - self.view.splanes_tree.topLevelItem(i).setText(2, self.model.splanes[i].toFile()) + self.view.splanes_tree.topLevelItem(i).setText( + 2, self.model.splanes[i].toFile() + ) - def update_interface(self, filename = ""): + def update_interface(self, filename=""): s = sett() if not filename: @@ -863,21 +1035,29 @@ def update_interface(self, filename = ""): file_ext = os.path.splitext(filename)[1].upper() self.view.setWindowTitle(name_stl_file + " - FASP") - self.view.name_stl_file.setText(self.view.locale.FileName + name_stl_file + file_ext) + self.view.name_stl_file.setText( + self.view.locale.FileName + name_stl_file + file_ext + ) string_print_time = "" if s.slicing.print_time > 3600: hours = s.slicing.print_time / 3600 - string_print_time += str(math.floor(hours)) + " " + self.view.locale.Hour + ", " + string_print_time += ( + str(math.floor(hours)) + " " + self.view.locale.Hour + ", " + ) if s.slicing.print_time > 60: minutes = (s.slicing.print_time % 3600) / 60 - string_print_time += str(math.floor(minutes)) + " " + self.view.locale.Minute + ", " + string_print_time += ( + str(math.floor(minutes)) + " " + self.view.locale.Minute + ", " + ) if s.slicing.print_time > 0: seconds = (s.slicing.print_time % 3600) % 60 - string_print_time += str(math.floor(seconds)) + " " + self.view.locale.Second + string_print_time += ( + str(math.floor(seconds)) + " " + self.view.locale.Second + ) self.view.print_time_value.setText(string_print_time) @@ -887,14 +1067,31 @@ def update_interface(self, filename = ""): string_consumption_material = "" if s.slicing.consumption_material > 0: - material_weight = (s.slicing.consumption_material * math.pow(s.hardware.bar_diameter/2, 2) * math.pi) * s.hardware.density / 1000 - string_consumption_material += str(math.ceil(material_weight)) + " " + self.view.locale.Gram + ", " - string_consumption_material += str(float("{:.2f}".format(s.slicing.consumption_material/1000))) + " " + self.view.locale.Meter + material_weight = ( + ( + s.slicing.consumption_material + * math.pow(s.hardware.bar_diameter / 2, 2) + * math.pi + ) + * s.hardware.density + / 1000 + ) + string_consumption_material += ( + str(math.ceil(material_weight)) + " " + self.view.locale.Gram + ", " + ) + string_consumption_material += ( + str(float("{:.2f}".format(s.slicing.consumption_material / 1000))) + + " " + + self.view.locale.Meter + ) self.view.consumption_material_value.setText(string_consumption_material) if s.slicing.planes_contact_with_nozzle: - self.view.warning_nozzle_and_table_collision.setText(self.view.locale.WarningNozzleAndTableCollision + s.slicing.planes_contact_with_nozzle) + self.view.warning_nozzle_and_table_collision.setText( + self.view.locale.WarningNozzleAndTableCollision + + s.slicing.planes_contact_with_nozzle + ) else: self.view.warning_nozzle_and_table_collision.setText("") diff --git a/src/debug.py b/src/debug.py deleted file mode 100644 index cc1859c..0000000 --- a/src/debug.py +++ /dev/null @@ -1,67 +0,0 @@ -import vtk - - -def drawLine(p1, p2): - pts = vtk.vtkPoints() - pts.InsertNextPoint(p1) - pts.InsertNextPoint(p2) - - line = vtk.vtkLine() - line.GetPointIds().SetId(0, 0) - line.GetPointIds().SetId(1, 1) - - lines = vtk.vtkCellArray() - lines.InsertNextCell(line) - - linePolyData = vtk.vtkPolyData() - linePolyData.SetPoints(pts) - linePolyData.SetLines(lines) - return linePolyData - - -def drawTriangle(p1, p2, p3): - points = vtk.vtkPoints() - points.InsertNextPoint(p1) - points.InsertNextPoint(p2) - points.InsertNextPoint(p3) - - triangle = vtk.vtkTriangle() - triangle.GetPointIds().SetId(0, 0) - triangle.GetPointIds().SetId(1, 1) - triangle.GetPointIds().SetId(2, 2) - - triangles = vtk.vtkCellArray() - triangles.InsertNextCell(triangle) - - trianglePolyData = vtk.vtkPolyData() - trianglePolyData.SetPoints(points) - trianglePolyData.SetPolys(triangles) - return trianglePolyData - - -def makeActor(block, vtkColor, size): - mapper = vtk.vtkPolyDataMapper() - mapper.SetInputData(block) - actor = vtk.vtkActor() - actor.SetMapper(mapper) - actor.GetProperty().SetColor(vtkColor) - actor.GetProperty().SetLineWidth(size) - return actor - - -def readFile(render, file, size): - with open(file) as f: - content = f.readlines() - - for line in content: - vals = line.strip().split(" ") - if vals[0] == "line": - block = drawLine(toPoint(vals[2:5]), toPoint(vals[5:8])) - else: # vals[0] == "triangle" - block = drawTriangle(toPoint(vals[2:5]), toPoint(vals[5:8]), toPoint(vals[8:11])) - vtkColor = vtk.vtkNamedColors().GetColor3d(vals[1]) - render.AddActor(makeActor(block, vtkColor, size)) - - -def toPoint(vs): - return [float(x) for x in vs] diff --git a/src/entry_window.py b/src/entry_window.py index ee9ca16..3a202f6 100644 --- a/src/entry_window.py +++ b/src/entry_window.py @@ -1,6 +1,17 @@ import os, math -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, \ - QPushButton, QLabel, QListWidget, QListWidgetItem, QLineEdit, QFileDialog, QMessageBox, QShortcut +from PyQt5.QtWidgets import ( + QWidget, + QVBoxLayout, + QHBoxLayout, + QPushButton, + QLabel, + QListWidget, + QListWidgetItem, + QLineEdit, + QFileDialog, + QMessageBox, + QShortcut, +) from PyQt5.QtCore import QSettings from PyQt5.QtGui import QKeySequence @@ -10,11 +21,19 @@ from typing import List from src.gui_utils import showErrorDialog -from src.settings import (sett, get_version, set_version, paths_transfer_in_settings, PathBuilder, - update_last_open_project, save_recent_projects) +from src.settings import ( + sett, + get_version, + set_version, + paths_transfer_in_settings, + PathBuilder, + update_last_open_project, + save_recent_projects, +) import src.locales as locales import shutil + class EntryWindow(QWidget): # entry window is a window that is shown before main window # it is used to choose between creation of new project and opening existing one @@ -53,7 +72,7 @@ def showEvent(self, e): # update list of recent projects self.reload_recent_projects_list() - + super().showEvent(e) def init_ui(self): @@ -69,22 +88,27 @@ def init_ui(self): new_proj_layout.addWidget(self.new_project_button) # create project directory label - self.project_directory_label = QLabel(locales.getLocale().ProjectDirectory, self) + self.project_directory_label = QLabel( + locales.getLocale().ProjectDirectory, self + ) new_proj_layout.addWidget(self.project_directory_label) # create project location label with current folder self.project_directory_edit = QLineEdit(self.load_recent_projects_root(), self) self.project_directory_edit.setPlaceholderText( - locales.getLocale().ChooseProjectDirectory) + locales.getLocale().ChooseProjectDirectory + ) self.project_directory_edit.setReadOnly(True) new_proj_layout.addWidget(self.project_directory_edit) # create project location folder dialog self.project_location_folder_dialog = QPushButton( - locales.getLocale().ChooseProjectDirectory, self) + locales.getLocale().ChooseProjectDirectory, self + ) new_proj_layout.addWidget(self.project_location_folder_dialog) self.project_location_folder_dialog.clicked.connect( - self.choose_project_location) + self.choose_project_location + ) # create "project name" label self.project_name_label = QLabel(locales.getLocale().ProjectName, self) @@ -100,7 +124,9 @@ def init_ui(self): # Create "Open Project" button self.open_project_button = QPushButton(locales.getLocale().OpenProject, self) - self.open_project_button.clicked.connect(self.open_existing_project_via_directory) + self.open_project_button.clicked.connect( + self.open_existing_project_via_directory + ) existing_proj_layout.addWidget(self.open_project_button) @@ -110,8 +136,11 @@ def init_ui(self): # Create list widget for recent projects self.recent_projects_list_widget = QListWidget(self) self.recent_projects_list_widget.itemActivated.connect( - self.open_existing_project_in_list) - delete_shortcut = QShortcut(QKeySequence(QtCore.Qt.Key_Delete), self.recent_projects_list_widget) + self.open_existing_project_in_list + ) + delete_shortcut = QShortcut( + QKeySequence(QtCore.Qt.Key_Delete), self.recent_projects_list_widget + ) delete_shortcut.activated.connect(self.delete_selected_item) # Add recent projects to list widget @@ -161,38 +190,41 @@ def add_recent_projects_in_list(self): self.adjust_item_height(item) def choose_project_location(self): - file = str(QFileDialog.getExistingDirectory(self, locales.getLocale().ChooseFolder)) + file = str( + QFileDialog.getExistingDirectory(self, locales.getLocale().ChooseFolder) + ) if not file: return - + # update latest project root - settings = QSettings('Epit3D', 'Spycer') - settings.setValue('latest-project-root', file) + settings = QSettings("Epit3D", "Spycer") + settings.setValue("latest-project-root", file) self.project_directory_edit.setText(file) def load_recent_projects(self) -> List[str]: - settings = QSettings('Epit3D', 'Spycer') + settings = QSettings("Epit3D", "Spycer") - if settings.contains('recent_projects'): - projects = settings.value('recent_projects', type=list) + if settings.contains("recent_projects"): + projects = settings.value("recent_projects", type=list) # filter projects which do not exist import pathlib + projects = [p for p in projects if pathlib.Path(p).exists()] number_of_recent_projects = 10 return projects[:number_of_recent_projects] return [] - + def load_recent_projects_root(self) -> str: # returns latest directory for projects - settings = QSettings('Epit3D', 'Spycer') + settings = QSettings("Epit3D", "Spycer") + + if settings.contains("latest-project-root"): + return settings.value("latest-project-root", type=str) - if settings.contains('latest-project-root'): - return settings.value('latest-project-root', type=str) - return "" def create_new_project(self): @@ -210,7 +242,8 @@ def create_new_project(self): import pathlib full_path = pathlib.Path( - self.project_directory_edit.text(), self.project_name_text_edit.text()) + self.project_directory_edit.text(), self.project_name_text_edit.text() + ) print(full_path) # check if project already exists @@ -228,11 +261,15 @@ def create_new_project(self): self.create_project_signal.emit(str(full_path)) def open_existing_project_in_list(self): - selected_project = self.recent_projects[self.recent_projects_list_widget.currentRow()] + selected_project = self.recent_projects[ + self.recent_projects_list_widget.currentRow() + ] self.open_existing_project(selected_project) def open_existing_project_via_directory(self): - if directory := str(QFileDialog.getExistingDirectory(self, locales.getLocale().ChooseFolder)): + if directory := str( + QFileDialog.getExistingDirectory(self, locales.getLocale().ChooseFolder) + ): selected_project = directory else: # didn't choose any project, release @@ -273,9 +310,13 @@ def сheck_project_version(self, project_path): if reply == QMessageBox.Yes: project_settings_old_filename = PathBuilder.settings_file_old() - shutil.copyfile(project_settings_filename, project_settings_old_filename) + shutil.copyfile( + project_settings_filename, project_settings_old_filename + ) shutil.copyfile("settings.yaml", project_settings_filename) - paths_transfer_in_settings(project_settings_old_filename, project_settings_filename) + paths_transfer_in_settings( + project_settings_old_filename, project_settings_filename + ) set_version(project_settings_filename, build_version) return True diff --git a/src/exampl.py b/src/exampl.py deleted file mode 100644 index 77a5fd1..0000000 --- a/src/exampl.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python - -# Demonstrate how to use the vtkBoxWidget to translate, scale, and -# rotate actors. The basic idea is that the box widget controls an -# actor's transform. A callback which modifies the transform is -# invoked as the box widget is manipulated. - -import vtk - -# Start by creating some simple geometry; in this case a mace. -import gui_utils - -sphere = vtk.vtkSphereSource() -cone = vtk.vtkConeSource() -glyph = vtk.vtkGlyph3D() -glyph.SetInputConnection(sphere.GetOutputPort()) -glyph.SetSourceConnection(cone.GetOutputPort()) -glyph.SetVectorModeToUseNormal() -glyph.SetScaleModeToScaleByVector() -glyph.SetScaleFactor(0.25) -appendData = vtk.vtkAppendPolyData() -appendData.AddInputConnection(glyph.GetOutputPort()) -appendData.AddInputConnection(sphere.GetOutputPort()) -maceMapper = vtk.vtkPolyDataMapper() -maceMapper.SetInputConnection(appendData.GetOutputPort()) -maceActor = vtk.vtkLODActor() -maceActor.SetMapper(maceMapper) -maceActor.VisibilityOn() - -# Create the RenderWindow, Renderer and both Actors -ren = vtk.vtkRenderer() -renWin = vtk.vtkRenderWindow() -renWin.AddRenderer(ren) -iren = vtk.vtkRenderWindowInteractor() -iren.SetRenderWindow(renWin) - -# The box widget observes the events invoked by the render window -# interactor. These events come from user interaction in the render -# window. -boxWidget = vtk.vtkBoxWidget() -boxWidget.SetInteractor(iren) -boxWidget.SetPlaceFactor(1.25) -boxWidget.HandlesOn() - -# Add the actors to the renderer, set the background and window size. -ren.AddActor(maceActor) -ren.SetBackground(0.1, 0.2, 0.4) -renWin.SetSize(300, 300) - -# As the box widget is interacted with, it produces a transformation -# matrix that is set on the actor. -t = vtk.vtkTransform() -def TransformActor(obj, event): - global t, maceActor - obj.GetTransform(t) - #print(t.GetScale()) - print(t.GetPosition()) - - maceActor.SetUserTransform(t) - -# Place the interactor initially. The actor is used to place and scale -# the interactor. An observer is added to the box widget to watch for -# interaction events. This event is captured and used to set the -# transformation matrix of the actor. -boxWidget.SetProp3D(maceActor) -boxWidget.PlaceWidget() -boxWidget.AddObserver("InteractionEvent", TransformActor) - -cylinder = vtk.vtkCylinderSource() -cylinder.SetResolution(50) -cylinder.SetRadius(2) -cylinder.SetHeight(0.1) -cylinder.SetCenter(0,0,0) # WHAT? vtk :( -mapper = vtk.vtkPolyDataMapper() -mapper.SetInputConnection(cylinder.GetOutputPort()) -actor = vtk.vtkActor() -actor.SetMapper(mapper) -actor.RotateX(90) - -ren.AddActor(actor) - -axesWidget = gui_utils.createAxes(iren) - -iren.Initialize() -renWin.Render() -iren.Start() \ No newline at end of file diff --git a/src/figure_editor.py b/src/figure_editor.py index e41ae85..f1eec44 100644 --- a/src/figure_editor.py +++ b/src/figure_editor.py @@ -1,13 +1,24 @@ """ Provides a class creating a new window to edit parameters of custom figures """ + import sys from typing import List, Callable, Dict, Tuple, Optional, Union from functools import partial from PyQt5 import QtCore, sip -from PyQt5.QtWidgets import (QSlider, QLineEdit, QApplication, QGridLayout, QWidget, QLabel, QSizePolicy, - QPushButton, QHBoxLayout, QCheckBox) +from PyQt5.QtWidgets import ( + QSlider, + QLineEdit, + QApplication, + QGridLayout, + QWidget, + QLabel, + QSizePolicy, + QPushButton, + QHBoxLayout, + QCheckBox, +) class FigureEditor(QWidget): @@ -18,9 +29,14 @@ class FigureEditor(QWidget): _checkboxes = [] - def __init__(self, tabs, params: List[str], constrains: List[Tuple[int, int]], - on_change: Callable[[Dict[str, float]], None] = None, - initial_params: Optional[Dict[str, float]] = None): + def __init__( + self, + tabs, + params: List[str], + constrains: List[Tuple[int, int]], + on_change: Callable[[Dict[str, float]], None] = None, + initial_params: Optional[Dict[str, float]] = None, + ): super().__init__() self.on_change = on_change self.setWindowTitle("Parameters tooling") @@ -33,7 +49,8 @@ def __init__(self, tabs, params: List[str], constrains: List[Tuple[int, int]], # TODO add implementation of True/False parameters self.params_dict: Dict[str, float] = dict( (el, initial_params[el] if initial_params and initial_params[el] else 0) - for el in params + self._checkboxes) + for el in params + self._checkboxes + ) for param_idx, param in enumerate(params): # add label for parameter name @@ -41,7 +58,9 @@ def __init__(self, tabs, params: List[str], constrains: List[Tuple[int, int]], self.params_widgets.append(label) self.layout.addWidget(label, param_idx, 0) - def pass_updated_value_edit(param_name: str, qslider: QSlider, qlineedit: QLineEdit): + def pass_updated_value_edit( + param_name: str, qslider: QSlider, qlineedit: QLineEdit + ): # return a function to be called from QLineEdit callback def emmit_value(state: str): try: @@ -58,11 +77,11 @@ def emmit_value(state: str): if value < minimumValue: value = minimumValue - qlineedit.setText(str(int(value))) # TODO: use validator + qlineedit.setText(str(int(value))) # TODO: use validator elif value > maximumValue: value = maximumValue - qlineedit.setText(str(int(value))) # TODO: use validator + qlineedit.setText(str(int(value))) # TODO: use validator self.params_dict[param_name] = float(value) @@ -149,14 +168,21 @@ def deleteLayout(self, cur_lay): sip.delete(cur_lay) + class PlaneEditor(FigureEditor): __params = ["X", "Y", "Z", "Rotation", "Tilt"] __constrains = [(-100, 100), (-100, 100), (0, 200), (-180, 180), (-90, 0)] _checkboxes = ["Smooth"] - def __init__(self, tabs, on_change: Callable[[Dict[str, float]], None], - initial_params: Optional[Dict[str, Union[float, bool]]] = None): - super().__init__(tabs, self.__params, self.__constrains, on_change, initial_params) + def __init__( + self, + tabs, + on_change: Callable[[Dict[str, float]], None], + initial_params: Optional[Dict[str, Union[float, bool]]] = None, + ): + super().__init__( + tabs, self.__params, self.__constrains, on_change, initial_params + ) def params(self): return self.__params @@ -166,16 +192,21 @@ class ConeEditor(FigureEditor): __params = ["Z", "A", "H1", "H2"] __constrains = [(-100, 200), (-80, 80), (0, 150), (1, 150)] - def __init__(self, tabs, on_change: Callable[[Dict[str, float]], None], - initial_params: Optional[Dict[str, float]] = None): - super().__init__(tabs, self.__params, self.__constrains, on_change, initial_params) + def __init__( + self, + tabs, + on_change: Callable[[Dict[str, float]], None], + initial_params: Optional[Dict[str, float]] = None, + ): + super().__init__( + tabs, self.__params, self.__constrains, on_change, initial_params + ) def params(self): return self.__params class StlMovePanel(QWidget): - def __init__(self, methods, captions): super().__init__() @@ -192,10 +223,10 @@ def __init__(self, methods, captions): label.setAlignment(QtCore.Qt.AlignCenter) gridLayout.addWidget(label, 0, 0, 1, 3) for row, param in enumerate(["X", "Y", "Z"], start=1): - btn_pos = QPushButton(str(param) + '+') + btn_pos = QPushButton(str(param) + "+") gridLayout.addWidget(btn_pos, row, 0) - btn_neg = QPushButton(str(param) + '-') + btn_neg = QPushButton(str(param) + "-") gridLayout.addWidget(btn_neg, row, 1) edit = QLineEdit() @@ -238,14 +269,13 @@ def update(self, pos, orient, scale): if __name__ == "__main__": + def handle1(d): print(1, d) - def handle2(d): print(2, d) - app = QApplication(sys.argv) f1 = PlaneEditor(handle1, None) f2 = PlaneEditor(handle2, None) diff --git a/src/gcode.py b/src/gcode.py index d9d4fe2..90303c6 100644 --- a/src/gcode.py +++ b/src/gcode.py @@ -18,13 +18,16 @@ def rotation_matrix(axis, theta): b, c, d = -axis * np.sin(theta / 2.0) aa, bb, cc, dd = a * a, b * b, c * c, d * d bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d - return np.array([[aa + bb - cc - dd, 2 * (bc + ad), - 2 * (bd - ac)], [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], - [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc]]) + return np.array( + [ + [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], + [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], + [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc], + ] + ) class GCode: - def __init__(self, layers, rotations, lays2rots): self.layers: List[List[Point]] = layers self.rotations: List[Rotation] = rotations @@ -32,7 +35,6 @@ def __init__(self, layers, rotations, lays2rots): class Rotation: - def __init__(self, x, z): self.x_rot = x self.z_rot = z @@ -42,7 +44,6 @@ def __str__(self): class Point: - def __init__(self, x, y, z, a, b): self.x = x self.y = y @@ -83,7 +84,13 @@ def __init__(self, s): self.currPos = Position(0, 0, 0, 0, 0, 0) self.prevPos = Position(0, 0, 0, 0, 0, 0) - self.rotationPoint = np.array([s.hardware.rotation_center_x, s.hardware.rotation_center_y, s.hardware.rotation_center_z]) + self.rotationPoint = np.array( + [ + s.hardware.rotation_center_x, + s.hardware.rotation_center_y, + s.hardware.rotation_center_z, + ] + ) self.cone_axis = rotation_matrix([1, 0, 0], 0).dot([0, 0, 1]) self.path = [] @@ -157,20 +164,15 @@ def pathSplit(self): for pos in self.path[1:]: numPoints = int(abs(lastPos.U - pos.U) // maxDeltaU) if numPoints > 0: - rangeU = list(np.linspace( - lastPos.U, pos.U, numPoints + 2)[1:]) - rangeX = list(np.linspace( - lastPos.X, pos.X, numPoints + 2)[1:]) - rangeY = list(np.linspace( - lastPos.Y, pos.Y, numPoints + 2)[1:]) - rangeZ = list(np.linspace( - lastPos.Z, pos.Z, numPoints + 2)[1:]) + rangeU = list(np.linspace(lastPos.U, pos.U, numPoints + 2)[1:]) + rangeX = list(np.linspace(lastPos.X, pos.X, numPoints + 2)[1:]) + rangeY = list(np.linspace(lastPos.Y, pos.Y, numPoints + 2)[1:]) + rangeZ = list(np.linspace(lastPos.Z, pos.Z, numPoints + 2)[1:]) for dU, dX, dY, dZ in zip(rangeU, rangeX, rangeY, rangeZ): res.append((dX, dY, dZ, dU)) else: - res.append( - (pos.X, pos.Y, pos.Z, pos.U)) + res.append((pos.X, pos.Y, pos.Z, pos.U)) lastPos = pos return res @@ -198,7 +200,12 @@ def finishPath(self): r = yr z = zr - xr, yr, zr = rotation_matrix(cone_axis, -u).dot(np.array([xr, r, z]) - rotationPoint) + rotationPoint + xr, yr, zr = ( + rotation_matrix(cone_axis, -u).dot( + np.array([xr, r, z]) - rotationPoint + ) + + rotationPoint + ) points.append(Point(xr, yr, zr, 0, 0)) else: @@ -230,7 +237,7 @@ def parseRotation(args: List[str]): def readGCode(filename): - with open(filename, 'r', encoding="utf-8") as f: + with open(filename, "r", encoding="utf-8") as f: lines = [line.strip() for line in f] return parseGCode(lines) @@ -250,7 +257,7 @@ def parseGCode(lines): line = line.strip() if len(line) == 0: continue - if line[0] == ';': # comment + if line[0] == ";": # comment if line.startswith(";LAYER:"): current_layer = int(line[7:]) printer.finishLayer() @@ -278,14 +285,20 @@ def parseGCode(lines): if comment.lower() == "rotation": # we have either rotation or incline printer.finishLayer() # if any(a.lower().startswith('u') for a in args): # rotation - printer.rotations.append(Rotation(printer.rotations[-1].x_rot, parseRotation(args[1:]))) + printer.rotations.append( + Rotation(printer.rotations[-1].x_rot, parseRotation(args[1:])) + ) printer.currPos.U = printer.rotations[-1].z_rot elif comment.lower() == "incline": printer.finishLayer() # if any(a.lower().startswith('v') for a in args): # incline - printer.rotations.append(Rotation(parseRotation(args[1:]), printer.rotations[-1].z_rot)) + printer.rotations.append( + Rotation(parseRotation(args[1:]), printer.rotations[-1].z_rot) + ) - printer.cone_axis = rotation_matrix([1, 0, 0], np.radians(printer.rotations[-1].x_rot)).dot([0, 0, 1]) + printer.cone_axis = rotation_matrix( + [1, 0, 0], np.radians(printer.rotations[-1].x_rot) + ).dot([0, 0, 1]) printer.currPos.V = printer.rotations[-1].x_rot elif args[0] == "G0": # move to (or rotate) pos = args[1:] diff --git a/src/gui_utils.py b/src/gui_utils.py index 3f9724a..8ed518f 100644 --- a/src/gui_utils.py +++ b/src/gui_utils.py @@ -85,7 +85,9 @@ def create_splane_actor(center, x_rot, z_rot): return actor -def create_cone_actor(vertex: Tuple[float, float, float], bending_angle: float, h1: float, h2: float): +def create_cone_actor( + vertex: Tuple[float, float, float], bending_angle: float, h1: float, h2: float +): # TODO maybe it is not good to pass cone object destructed (hard to add new parameters) """ :param bending_angle: angle of triangles relative Z axis we want to compensate (in degrees) @@ -108,13 +110,13 @@ def create_cone_actor(vertex: Tuple[float, float, float], bending_angle: float, coneSource.SetResolution(120) # coneSource.SetHeight(vertex[2]) import math + coneSource.SetRadius(h2 * math.tan(math.radians(math.fabs(cone_angle)))) coneSource.SetCenter(vertex[0], vertex[1], vertex[2] - sign(cone_angle) * h2 / 2) coneSource.SetDirection(0, 0, 1 * sign(cone_angle)) coneSource.Update() # update parameters - # plane to cut from h1 clipPlane = vtk.vtkPlane() clipPlane.SetOrigin(vertex[0], vertex[1], vertex[2] - h1 * sign(cone_angle)) @@ -161,8 +163,11 @@ def createBoxActors(): cylinder.SetResolution(50) cylinder.SetRadius(3) cylinder.SetHeight(200) - cylinder.SetCenter(s.hardware.plane_center_x - 100, s.hardware.plane_center_z, - s.hardware.plane_center_y + 100) # WHAT? vtk :( + cylinder.SetCenter( + s.hardware.plane_center_x - 100, + s.hardware.plane_center_z, + s.hardware.plane_center_y + 100, + ) # WHAT? vtk :( mapper = vtk.vtkPolyDataMapper() mapper.SetInputConnection(cylinder.GetOutputPort()) actor = vtk.vtkActor() @@ -176,8 +181,11 @@ def createBoxActors(): cylinder.SetRadius(3) cylinder.SetHeight(200) - cylinder.SetCenter(s.hardware.plane_center_x + 100, s.hardware.plane_center_z, - s.hardware.plane_center_y - 100) # WHAT? vtk :( + cylinder.SetCenter( + s.hardware.plane_center_x + 100, + s.hardware.plane_center_z, + s.hardware.plane_center_y - 100, + ) # WHAT? vtk :( mapper = vtk.vtkPolyDataMapper() mapper.SetInputConnection(cylinder.GetOutputPort()) actor = vtk.vtkActor() @@ -191,8 +199,11 @@ def createBoxActors(): cylinder.SetResolution(50) cylinder.SetRadius(3) cylinder.SetHeight(200) - cylinder.SetCenter(s.hardware.plane_center_x - 100, s.hardware.plane_center_z, - s.hardware.plane_center_y - 100) # WHAT? vtk :( + cylinder.SetCenter( + s.hardware.plane_center_x - 100, + s.hardware.plane_center_z, + s.hardware.plane_center_y - 100, + ) # WHAT? vtk :( mapper = vtk.vtkPolyDataMapper() mapper.SetInputConnection(cylinder.GetOutputPort()) actor = vtk.vtkActor() @@ -237,6 +248,7 @@ def createStlActorInOrigin(filename, colorize=False): actor = setTransformFromSettings(actor) return actor + def setTransformFromSettings(actor): s = sett() transform = vtk.vtkTransform() @@ -251,6 +263,7 @@ def setTransformFromSettings(actor): return actor + def makeBlocks(layers, rotations, lays2rots): blocks = [] for i in range(len(layers)): @@ -265,7 +278,9 @@ def makeBlocks(layers, rotations, lays2rots): line.GetPointIds().SetId(0, points_count + k) line.GetPointIds().SetId(1, points_count + k + 1) lines.InsertNextCell(line) - points.InsertNextPoint(path[-1].xyz(rotations[lays2rots[i]])) # not forget to add last point + points.InsertNextPoint( + path[-1].xyz(rotations[lays2rots[i]]) + ) # not forget to add last point points_count += len(path) block.SetPoints(points) block.SetLines(lines) @@ -333,7 +348,6 @@ def plane_tf(rotation): class ActorFromPolyData(vtkActor): - def __init__(self, output): super().__init__() mapper = vtkPolyDataMapper() @@ -342,7 +356,6 @@ def __init__(self, output): class ActorWithColor(vtkAssembly): - def __init__(self, output): polys = output.GetPolys() allpoints = output.GetPoints() @@ -491,7 +504,9 @@ def GetTriangleNormal(self, triangle_id): def RotateByVector(self, vector): v = [0, 0, 1] - theta = np.arccos(np.dot(vector, v) / (np.linalg.norm(vector) * np.linalg.norm(v))) + theta = np.arccos( + np.dot(vector, v) / (np.linalg.norm(vector) * np.linalg.norm(v)) + ) rotation_angle = np.degrees(theta) if np.array_equal(vector, [0, 0, -1]): @@ -509,14 +524,13 @@ def RotateByVector(self, vector): self.SetUserTransform(rotation_matrix) -class StlActor(StlActorMixin, ActorFromPolyData): +class StlActor(StlActorMixin, ActorFromPolyData): def __init__(self, output): super().__init__(output) class ColorizedStlActor(StlActorMixin, ActorWithColor): - def __init__(self, output): super().__init__(output) @@ -548,7 +562,13 @@ def params(self) -> Dict[str, float]: class Cone: - def __init__(self, cone_angle: float, point: Tuple[float, float, float], h1: float = 0, h2: float = 100): + def __init__( + self, + cone_angle: float, + point: Tuple[float, float, float], + h1: float = 0, + h2: float = 100, + ): self.cone_angle = cone_angle self.x, self.y, self.z = point self.h1 = h1 @@ -558,7 +578,14 @@ def toFile(self) -> str: return f"cone X{self.x:.2f} Y{self.y:.2f} Z{self.z:.2f} A{self.cone_angle:.2f} H{self.h1:.2f} H{self.h2:.2f}" def params(self) -> Dict[str, float]: - return {"X": self.x, "Y": self.y, "Z": self.z, "A": self.cone_angle, "H1": self.h1, "H2": self.h2} + return { + "X": self.x, + "Y": self.y, + "Z": self.z, + "A": self.cone_angle, + "H1": self.h1, + "H2": self.h2, + } def read_planes(filename): @@ -569,11 +596,12 @@ def read_planes(filename): return planes + def read_plane(line: str): - v = line.strip().split(' ') + v = line.strip().split(" ") - if v[0] == 'plane': - #plane X10 Y10 Z10 T-60 R0 - Plane string format + if v[0] == "plane": + # plane X10 Y10 Z10 T-60 R0 - Plane string format return Plane( float(v[4][1:]), float(v[5][1:]), @@ -581,8 +609,14 @@ def read_plane(line: str): len(v) > 6 and v[6] == "S", ) else: - #cone X0 Y0 Z10 A60 H10 H50 - Cone string format - return Cone(float(v[4][1:]), (float(v[1][1:]), float(v[2][1:]), float(v[3][1:])), float(v[5][1:]), float(v[6][1:])) + # cone X0 Y0 Z10 A60 H10 H50 - Cone string format + return Cone( + float(v[4][1:]), + (float(v[1][1:]), float(v[2][1:]), float(v[3][1:])), + float(v[5][1:]), + float(v[6][1:]), + ) + def isfloat(value): try: @@ -606,6 +640,7 @@ def showErrorDialog(text_msg): retval = msg.exec_() # print "value of pressed message box button:", retval + def showInfoDialog(text_msg): msg = QMessageBox() msg.setIcon(QMessageBox.Information) @@ -617,8 +652,9 @@ def showInfoDialog(text_msg): retval = msg.exec_() -def createCustomXYaxis(origin: Tuple[float, float, float], endPoints: List[Tuple[float, float, float]]) -> List[ - vtkActor]: +def createCustomXYaxis( + origin: Tuple[float, float, float], endPoints: List[Tuple[float, float, float]] +) -> List[vtkActor]: """ Function creates 4 ended axes which describe position of focal point :param origin: @@ -657,7 +693,6 @@ def createLine(point1: tuple, point2: tuple, color: str = "Black") -> vtkActor: class StlMover: - def __init__(self, view): self.view = view self.tf = vtkTransform() @@ -693,7 +728,6 @@ def actMethod(self, val, axis): class StlTranslator(StlMover): - def __init__(self, view): super().__init__(view) @@ -716,7 +750,6 @@ def actMethod(self, val, axis): class StlRotator(StlMover): - def __init__(self, view): super().__init__(view) @@ -749,8 +782,8 @@ def actMethod(self, val, axis): self.setMethod(val, axis) -class StlScale(StlMover): +class StlScale(StlMover): def __init__(self, view): super().__init__(view) @@ -770,7 +803,6 @@ def setMethod(self, val, axis): self.tf = tf - def actMethod(self, val, axis): x, y, z = axis rx, ry, rz = self.tf.GetScale() @@ -778,4 +810,4 @@ def actMethod(self, val, axis): val = val + ry * 100 if y else val val = val + rz * 100 if z else val - self.setMethod(val, axis) \ No newline at end of file + self.setMethod(val, axis) diff --git a/src/interactor_style.py b/src/interactor_style.py index ae58825..6e996f9 100644 --- a/src/interactor_style.py +++ b/src/interactor_style.py @@ -2,16 +2,15 @@ class ActorInteractorStyle(vtk.vtkInteractorStyleTrackballActor): - def __init__(self, onChange, parent=None): self.WorkingActor = None self.onChange = onChange self.AddObserver("LeftButtonPressEvent", self.onPressEvent) - self.AddObserver('MiddleButtonPressEvent', self.onPressEvent) - self.AddObserver('RightButtonPressEvent', self.onPressEvent) - self.AddObserver('LeftButtonReleaseEvent', self.onReleaseEvent) - self.AddObserver('MiddleButtonReleaseEvent', self.onReleaseEvent) - self.AddObserver('RightButtonReleaseEvent', self.onReleaseEvent) + self.AddObserver("MiddleButtonPressEvent", self.onPressEvent) + self.AddObserver("RightButtonPressEvent", self.onPressEvent) + self.AddObserver("LeftButtonReleaseEvent", self.onReleaseEvent) + self.AddObserver("MiddleButtonReleaseEvent", self.onReleaseEvent) + self.AddObserver("RightButtonReleaseEvent", self.onReleaseEvent) # self.AddObserver('MouseWheelForwardEvent', self.bimodal_mouse_handler) # self.AddObserver('MouseWheelBackwardEvent', self.bimodal_mouse_handler) @@ -32,20 +31,20 @@ def onPressEvent(self, obj, event): print("BEFORE ON LEFT") - if event == 'LeftButtonPressEvent': + if event == "LeftButtonPressEvent": self.OnLeftButtonDown() - elif event == 'MiddleButtonPressEvent': + elif event == "MiddleButtonPressEvent": self.OnMiddleButtonDown() - elif event == 'RightButtonPressEvent': + elif event == "RightButtonPressEvent": self.OnRightButtonDown() return def onReleaseEvent(self, obj, event): - if event == 'LeftButtonReleaseEvent': + if event == "LeftButtonReleaseEvent": self.OnLeftButtonUp() - elif event == 'MiddleButtonReleaseEvent': + elif event == "MiddleButtonReleaseEvent": self.OnMiddleButtonUp() - elif event == 'RightButtonReleaseEvent': + elif event == "RightButtonReleaseEvent": self.OnRightButtonUp() self.onChange() return @@ -55,7 +54,6 @@ def setStlActor(self, stlActor): class CameraInteractorStyle(vtk.vtkInteractorStyleTrackballCamera): - def __init__(self, parent=None): # self.__init__() pass diff --git a/src/interface_style_sheet.py b/src/interface_style_sheet.py index 03e2515..691f19c 100644 --- a/src/interface_style_sheet.py +++ b/src/interface_style_sheet.py @@ -1,5 +1,6 @@ import qdarkstyle + def getStyleSheet(): # Loading the style so that can get pictures from the link darkstyle = qdarkstyle.load_stylesheet() @@ -2027,4 +2028,4 @@ def getStyleSheet(): }} """ - return style_sheet \ No newline at end of file + return style_sheet diff --git a/src/locales.py b/src/locales.py index 33d9b68..232adac 100644 --- a/src/locales.py +++ b/src/locales.py @@ -10,13 +10,13 @@ class Locale: BedTemp = "Bed temperature, °C:" FillDensity = "Fill density, %:" WallThickness = "Wall thickness:" - NumberWallLines='Number of wall lines:' - BottomThickness='Bottom thickness:' - NumberOfBottomLayers='Number of bottom layers:' - LidsThickness='Lids thickness:' - NumberOfLidLayers='Number of lid layers:' - LineWidth = 'Extruder diameter, mm:' - FillingType = 'Filling type:' + NumberWallLines = "Number of wall lines:" + BottomThickness = "Bottom thickness:" + NumberOfBottomLayers = "Number of bottom layers:" + LidsThickness = "Lids thickness:" + NumberOfLidLayers = "Number of lid layers:" + LineWidth = "Extruder diameter, mm:" + FillingType = "Filling type:" FillingTypeValues = ["Lines", "Squares", "Triangles", "Cross", "ZigZag"] ShowStl = "Show stl" LayersCount = "Layers count:" @@ -134,7 +134,9 @@ class Locale: SettingsUpdate = "We want to update the project settings. Please check the values of the new fields. They will be set to default values." Update = "Update" EmptyDescription = "The error description cannot be empty" - CalibrationDataWarning= "You have not selected printer calibration data. Continue with default data?" + CalibrationDataWarning = ( + "You have not selected printer calibration data. Continue with default data?" + ) SelectingCalibrationData = "Selecting calibration data" UninterruptedPrint = "Uninterrupted print" @@ -147,40 +149,46 @@ def __init__(self, **entries): dicts = { "en": Locale(), "ru": Locale( - LayerHeight='Высота слоя, мм:', - PrintSpeed='Скорость печати, мм/с:', - PrintSpeedLayer1='Скорость печати первого слоя, мм/с:', - PrintSpeedWall='Скорость печати стенок, мм/с:', - ExtruderTemp='Температура сопла, °C:', - BedTemp='Температура стола, °C:', - FillDensity='Плотность заполнения, %:', - WallThickness='Толщина стенки:', - NumberWallLines='Количество проходов стенки:', - BottomThickness='Толщина дна:', - NumberOfBottomLayers='Количество слоев дна:', - LidsThickness='Толщина крышки:', - NumberOfLidLayers='Количество слоев крышки:', - LineWidth='Диаметр сопла, мм:', - ShowStl='Отображение STL модели', - LayersCount='Отображаемые слои:', - FillingType='Тип заполнения:', - FillingTypeValues=["Линии", "Квадраты", "Треугольники", "Перекрёстное", "Зигзаг"], - OpenModel='Открыть модель', - ColorModel='Выделить критические свесы', - MoveModel='Передвинуть модель', - Slice='Нарезать на слои', + LayerHeight="Высота слоя, мм:", + PrintSpeed="Скорость печати, мм/с:", + PrintSpeedLayer1="Скорость печати первого слоя, мм/с:", + PrintSpeedWall="Скорость печати стенок, мм/с:", + ExtruderTemp="Температура сопла, °C:", + BedTemp="Температура стола, °C:", + FillDensity="Плотность заполнения, %:", + WallThickness="Толщина стенки:", + NumberWallLines="Количество проходов стенки:", + BottomThickness="Толщина дна:", + NumberOfBottomLayers="Количество слоев дна:", + LidsThickness="Толщина крышки:", + NumberOfLidLayers="Количество слоев крышки:", + LineWidth="Диаметр сопла, мм:", + ShowStl="Отображение STL модели", + LayersCount="Отображаемые слои:", + FillingType="Тип заполнения:", + FillingTypeValues=[ + "Линии", + "Квадраты", + "Треугольники", + "Перекрёстное", + "Зигзаг", + ], + OpenModel="Открыть модель", + ColorModel="Выделить критические свесы", + MoveModel="Передвинуть модель", + Slice="Нарезать на слои", Slice3Axes="3D слайсинг", SliceVip="5D слайсинг", - Settings = "Настройки", + Settings="Настройки", # SliceCone="Конический слайсинг", - SaveGCode='Сохранить GCode', + SaveGCode="Сохранить GCode", FanOffLayer1="Выключить обдув на первом слое", Retraction="Ретракция", RetractionDistance="Величина ретракта, мм:", RetractionSpeed="Скорость ретракта, мм/с:", - Tilted='Наклонена', - Hide='Скрыть', - SupportsOn='Добавить поддержки', + Tilted="Наклонена", + Hide="Скрыть", + SupportsOn="Добавить поддержки", # EditPlanes='Редактировать', # Analyze='Анализировать', AddPlane="Добавить плоскость", @@ -188,9 +196,9 @@ def __init__(self, **entries): DeletePlane="Удалить", EditFigure="Редактировать", Rotated="Повёрнута", - SupportXYOffset = "Поддержки XY отступ, мм:", - SupportZOffsetLayers = "Поддержки Z отступ, слои:", - SupportPriorityZOffset = "Приоритет отступа по Z", + SupportXYOffset="Поддержки XY отступ, мм:", + SupportZOffsetLayers="Поддержки Z отступ, слои:", + SupportPriorityZOffset="Приоритет отступа по Z", FloatParsingError="Ошибка в написании дробного числа", SkirtLineCount="Количество линий юбки:", FanSpeed="Величина обдува детали, %:", @@ -202,89 +210,86 @@ def __init__(self, **entries): StlMoveTranslate="Перемещение", StlMoveRotate="Вращение", StlMoveScale="Масштабирование", - ModelCentering = "Поместить модель в центр", - AlignModelHeight = "Привязка к столу", - SavePlanes = "Сохранить фигуры", - DownloadPlanes = "Загрузить фигуры", - NamePlanes = "Название", - PrintTime = "Время печати:", - ConsumptionMaterial = "Расход материала:", - WarningNozzleAndTableCollision = "Внимание! Угроза столкновения сопла печатающей головки с поверхностью стола. Измените настройки печати. Критические плоскости: ", - Hour = "час.", - Minute = "мин.", - Second = "сек.", - Gram = "г.", - Meter = "м.", - Millimeter = "мм.", - OverlappingInfillPercentage = "Наложение заполнение-стенка, %:", - CriticalWallOverhangAngle = "Критический угол свеса стенки:", - ModelCoordinates = "Координаты модели", - FigureSettings = "Настройки фигуры", - RetractCompensationAmount = "Величина компенсации ретракта, мм:", - SupportDensity = "Плотность поддержек, %:", - FileName = "Имя файла: ", - File = "Файл", - Open = "Открыть", - SaveSettings = "Сохранить настройки", - LoadSettings = "Загрузить настройки", - SaveProject = "Сохранить проект", - SaveProjectAs = "Сохранить проект как...", - SlicerInfo = "Информация о слайсере", - SlicerVersion = "Версия слайсера: ", - SlicingTitle = "Слайсинг", - SlicingProgress = "Слайсинг в прогрессе...", - GCodeLoadingTitle = "Загрузка GCode", - GCodeLoadingProgress = "Загрузка GCode в прогрессе...", - SupportsSettings = "Настройки поддержек", - MaterialShrinkage = "Величина усадки материала, %:", - ProjectManager = "FASP менеджер проектов", - NewProject = "Новый проект", - ProjectDirectory = "Директория проекта:", - ChooseProjectDirectory = "Выберите директорию проекта", - ChooseFolder = "Выберите папку", - ProjectName = "Имя проекта:", - OpenProject = "Открыть проект", - RecentProjects = "Последние проекты", - ProjectNameCannotBeEmpty = "Имя проекта не может быть пустым", - ProjectDirectoryCannotBeEmpty = "Директория проекта не может быть пустой", - ProjectAlreadyExists = "Проект с таким именем уже существует", - NoProjectSelected = "Проект не выбран", - Tools = "Инструменты", - Calibration = "Калибровка", - SubmitBugReport = "Отправить сообщение об ошибке", - SubmittingBugReport = "Отправка сообщения об ошибке", - ErrorDescription = "Описание ошибки:", - AddImage = "Добавить изображение", - Send = "Отправить", - Cancel = "Отмена", - Save = "Сохранить", - DontSave = "Не сохранять", - Continue = "Продолжить", - AddingImage = "Выбрать изображение", - ReportSubmitSuccessfully = "Отчет успешно отправлен. \nВы будете уведомлены, как только проблема будет устранена. При необходимости, наши специалисты свяжутся с вами для получения дополнительной информации. Благодарим Вас, что помогаете нам сделать продукт лучше!", - ErrorReport = "Произошла ошибка при отправке отчета", - PlaceModelOnEdge = "Положить модель на грань", - SavingProject = "Сохранение проекта", - ProjectSaved = "Проект успешно сохранен", - ProjectChange = "Проект был изменен. Сохранить изменения?", - - ErrorHardwareModule = "Модуль калибровки недоступен публично", - ErrorBugModule = "Модуль отправки багов недоступен публично", - - PrinterName = "Конфигурация принтера:", - ChoosePrinterDirectory = "Выберите название принтера", - AddNewPrinter = "Добавить новый принтер", - DefaultPrinterWarn = "Будьте внимательны, Вы используете принтер по умолчанию. Данные этого принтера будут перезаписываться при обновлениях. Мы рекомендуем создать и использовать свою конфигурацию принтера.", - CheckUpdates = "Проверить наличие обновлений", - ProjectUpdate = "Обновление проекта", - SettingsUpdate = "Мы хотим обновить настройки проекта. Пожалуйста, проверьте значения новых полей. Они будут выставлены в значения по умолчанию.", - Update = "Обновить", - EmptyDescription = "Описание ошибки не может быть пустым", - CalibrationDataWarning= "Вы не выбрали калибровочные данные принтера. Продолжить с данными по умолчанию?", - SelectingCalibrationData = "Выбор калибровочных данных", - - UninterruptedPrint = "Печать непрерывным волокном", - M10CutDistance = "Дистанция отреза филамента, мм:", + ModelCentering="Поместить модель в центр", + AlignModelHeight="Привязка к столу", + SavePlanes="Сохранить фигуры", + DownloadPlanes="Загрузить фигуры", + NamePlanes="Название", + PrintTime="Время печати:", + ConsumptionMaterial="Расход материала:", + WarningNozzleAndTableCollision="Внимание! Угроза столкновения сопла печатающей головки с поверхностью стола. Измените настройки печати. Критические плоскости: ", + Hour="час.", + Minute="мин.", + Second="сек.", + Gram="г.", + Meter="м.", + Millimeter="мм.", + OverlappingInfillPercentage="Наложение заполнение-стенка, %:", + CriticalWallOverhangAngle="Критический угол свеса стенки:", + ModelCoordinates="Координаты модели", + FigureSettings="Настройки фигуры", + RetractCompensationAmount="Величина компенсации ретракта, мм:", + SupportDensity="Плотность поддержек, %:", + FileName="Имя файла: ", + File="Файл", + Open="Открыть", + SaveSettings="Сохранить настройки", + LoadSettings="Загрузить настройки", + SaveProject="Сохранить проект", + SaveProjectAs="Сохранить проект как...", + SlicerInfo="Информация о слайсере", + SlicerVersion="Версия слайсера: ", + SlicingTitle="Слайсинг", + SlicingProgress="Слайсинг в прогрессе...", + GCodeLoadingTitle="Загрузка GCode", + GCodeLoadingProgress="Загрузка GCode в прогрессе...", + SupportsSettings="Настройки поддержек", + MaterialShrinkage="Величина усадки материала, %:", + ProjectManager="FASP менеджер проектов", + NewProject="Новый проект", + ProjectDirectory="Директория проекта:", + ChooseProjectDirectory="Выберите директорию проекта", + ChooseFolder="Выберите папку", + ProjectName="Имя проекта:", + OpenProject="Открыть проект", + RecentProjects="Последние проекты", + ProjectNameCannotBeEmpty="Имя проекта не может быть пустым", + ProjectDirectoryCannotBeEmpty="Директория проекта не может быть пустой", + ProjectAlreadyExists="Проект с таким именем уже существует", + NoProjectSelected="Проект не выбран", + Tools="Инструменты", + Calibration="Калибровка", + SubmitBugReport="Отправить сообщение об ошибке", + SubmittingBugReport="Отправка сообщения об ошибке", + ErrorDescription="Описание ошибки:", + AddImage="Добавить изображение", + Send="Отправить", + Cancel="Отмена", + Save="Сохранить", + DontSave="Не сохранять", + Continue="Продолжить", + AddingImage="Выбрать изображение", + ReportSubmitSuccessfully="Отчет успешно отправлен. \nВы будете уведомлены, как только проблема будет устранена. При необходимости, наши специалисты свяжутся с вами для получения дополнительной информации. Благодарим Вас, что помогаете нам сделать продукт лучше!", + ErrorReport="Произошла ошибка при отправке отчета", + PlaceModelOnEdge="Положить модель на грань", + SavingProject="Сохранение проекта", + ProjectSaved="Проект успешно сохранен", + ProjectChange="Проект был изменен. Сохранить изменения?", + ErrorHardwareModule="Модуль калибровки недоступен публично", + ErrorBugModule="Модуль отправки багов недоступен публично", + PrinterName="Конфигурация принтера:", + ChoosePrinterDirectory="Выберите название принтера", + AddNewPrinter="Добавить новый принтер", + DefaultPrinterWarn="Будьте внимательны, Вы используете принтер по умолчанию. Данные этого принтера будут перезаписываться при обновлениях. Мы рекомендуем создать и использовать свою конфигурацию принтера.", + CheckUpdates="Проверить наличие обновлений", + ProjectUpdate="Обновление проекта", + SettingsUpdate="Мы хотим обновить настройки проекта. Пожалуйста, проверьте значения новых полей. Они будут выставлены в значения по умолчанию.", + Update="Обновить", + EmptyDescription="Описание ошибки не может быть пустым", + CalibrationDataWarning="Вы не выбрали калибровочные данные принтера. Продолжить с данными по умолчанию?", + SelectingCalibrationData="Выбор калибровочных данных", + UninterruptedPrint="Печать непрерывным волокном", + M10CutDistance="Дистанция отреза филамента, мм:", ), } diff --git a/src/model.py b/src/model.py index 8707172..448e9b7 100644 --- a/src/model.py +++ b/src/model.py @@ -2,7 +2,6 @@ class MainModel: - def __init__(self): self.current_slider_value = None self.opened_stl = "" diff --git a/src/process.py b/src/process.py index 00ea578..0ec499f 100644 --- a/src/process.py +++ b/src/process.py @@ -4,13 +4,14 @@ import subprocess import tempfile -#Process(f'xdotool search --all --name {name}').wait().stdout +# Process(f'xdotool search --all --name {name}').wait().stdout # with Process(CMD, capture=False): - + # p = Process('roslaunch test test.launch') # p.wait() - + + class Process: """ Convenience wrapper for subprocess.Popen. Allows to: @@ -33,9 +34,10 @@ def __init__(self, cmd, shell=False, env=None, capture=True): self._files = dict( stdout=tempfile.NamedTemporaryFile(mode="w", delete=False), stderr=tempfile.NamedTemporaryFile(mode="w", delete=False), - stdin=tempfile.NamedTemporaryFile(mode="r", delete=False)) + stdin=tempfile.NamedTemporaryFile(mode="r", delete=False), + ) kw.update(**self._files) - if os.name == 'posix': + if os.name == "posix": kw.update(preexec_fn=os.setsid) self._process = subprocess.Popen(cmd, **kw) @@ -69,17 +71,17 @@ def done(self): @property def stdout(self): - with open(self._files['stdout'].name) as f: + with open(self._files["stdout"].name) as f: return f.read() @property def stderr(self): - with open(self._files['stderr'].name) as f: + with open(self._files["stderr"].name) as f: return f.read() def kill(self): if not self.done: - if os.name == 'posix': + if os.name == "posix": os.killpg(os.getpgid(self.pid), signal.SIGINT) else: os.kill(self.pid, signal.CTRL_BREAK_EVENT) diff --git a/src/qt_utils.py b/src/qt_utils.py index d8f779a..f11c184 100644 --- a/src/qt_utils.py +++ b/src/qt_utils.py @@ -4,15 +4,17 @@ from PyQt5.QtWidgets import QProgressDialog, QLineEdit from PyQt5.QtCore import Qt + class ClickableLineEdit(QLineEdit): clicked = QtCore.pyqtSignal() def mousePressEvent(self, event): - if event.button() == Qt.LeftButton: + if event.button() == Qt.LeftButton: self.clicked.emit() - else: + else: super().mousePressEvent(event) + class TaskManager(QtCore.QObject): # source: https://stackoverflow.com/questions/64500883/pyqt5-widget-qthread-issue-when-using-concurrent-futures-threadpoolexecutor finished = QtCore.pyqtSignal(object) @@ -53,6 +55,7 @@ def task_finished(v): return result[0] + def _exec_dialog(dg, closer=None): """ Executes a dialog running the main event-loop instead of spawning a local one. diff --git a/src/settings.py b/src/settings.py index 1f78966..4aeddfa 100644 --- a/src/settings.py +++ b/src/settings.py @@ -13,7 +13,7 @@ _sett = None # do not forget to load_settings() at start # setup app path -if getattr(sys, 'frozen', False): +if getattr(sys, "frozen", False): APP_PATH = path.dirname(sys.executable) # uncomment if you want some protection that nothing would be broken # if not path.exists(path.join(app_path, settings_filename)): @@ -39,6 +39,7 @@ def get_color(key): _colors[key] = val return val + def get_color_rgb(color_name): color_rgba = get_color(color_name) @@ -50,6 +51,7 @@ def get_color_rgb(color_name): return rgb + def copy_project_files(project_path: str): load_settings() global _sett @@ -57,9 +59,12 @@ def copy_project_files(project_path: str): _sett.slicing.stl_file = "" save_settings() + def project_change_check(): save_settings("vip") - saved_settings = Settings(read_settings(str(pathlib.Path(sett().project_path, "settings.yaml")))) + saved_settings = Settings( + read_settings(str(pathlib.Path(sett().project_path, "settings.yaml"))) + ) if sett() != saved_settings: return False if not compare_project_file("model.stl"): @@ -69,6 +74,7 @@ def project_change_check(): return True + def compare_figures(settings): current_figures = getattr(sett(), "figures") if hasattr(settings, "figures"): @@ -80,23 +86,25 @@ def compare_figures(settings): return False for i in range(len(current_figures)): - if current_figures[i]['description'] != figures_from_settings[i].description: + if current_figures[i]["description"] != figures_from_settings[i].description: return False return True + def compare_project_file(filename): filename = pathlib.Path(sett().project_path, filename) filename_temp = get_temp_path(filename) return compare_files(filename, filename_temp) + def compare_files(file1_path, file2_path): try: - with open(file1_path, 'rb') as file1: + with open(file1_path, "rb") as file1: data1 = file1.read() - with open(file2_path, 'rb') as file2: + with open(file2_path, "rb") as file2: data2 = file2.read() - + if data1 == data2: return True else: @@ -106,10 +114,12 @@ def compare_files(file1_path, file2_path): print("Error during file comparison!") return True + def create_temporary_project_files(): create_temporary_project_file("settings.yaml") sett().slicing.stl_file = create_temporary_project_file("model.stl") + def create_temporary_project_file(filename): filename_temp = get_temp_path(filename) filename_path = str(pathlib.Path(sett().project_path, filename)) @@ -121,16 +131,19 @@ def create_temporary_project_file(filename): else: return "" + def get_temp_path(filename): basename, extension = os.path.splitext(filename) filename_temp = basename + "_temp" + extension return filename_temp -def delete_temporary_project_files(project_path = ""): + +def delete_temporary_project_files(project_path=""): delete_project_file("settings_temp.yaml", project_path) delete_project_file("model_temp.stl", project_path) -def delete_project_file(filename, project_path = ""): + +def delete_project_file(filename, project_path=""): if project_path == "": project_path = sett().project_path @@ -138,23 +151,27 @@ def delete_project_file(filename, project_path = ""): if os.path.exists(filename_path): os.remove(filename_path) + def get_recent_projects(): - settings = QSettings('Epit3D', 'Spycer') + settings = QSettings("Epit3D", "Spycer") recent_projects = list() - if settings.contains('recent_projects'): - recent_projects = settings.value('recent_projects', type=list) + if settings.contains("recent_projects"): + recent_projects = settings.value("recent_projects", type=list) # filter projects which do not exist import pathlib + recent_projects = [p for p in recent_projects if pathlib.Path(p).exists()] return recent_projects + def save_recent_projects(recent_projects): - settings = QSettings('Epit3D', 'Spycer') - settings.setValue('recent_projects', recent_projects) + settings = QSettings("Epit3D", "Spycer") + settings.setValue("recent_projects", recent_projects) + def update_last_open_project(recent_projects, project_path): project_path = str(project_path) @@ -166,28 +183,32 @@ def update_last_open_project(recent_projects, project_path): # add new project to recent projects add_recent_project(recent_projects, project_path) + def move_project_to_top(recent_projects, project_path): last_opened_project_index = recent_projects.index(project_path) last_opened_project = recent_projects.pop(last_opened_project_index) recent_projects.insert(0, last_opened_project) save_recent_projects(recent_projects) + def add_recent_project(recent_projects, project_path): recent_projects.insert(0, str(project_path)) save_recent_projects(recent_projects) + def load_settings(filename=""): data = read_settings(filename) if data != None: global _sett _sett = Settings(data) - print(f'after loading stl_file is {_sett.slicing.stl_file}') + print(f"after loading stl_file is {_sett.slicing.stl_file}") + def read_settings(filename=""): if not filename: - print('retrieving settings') - if getattr(sys, 'frozen', False): + print("retrieving settings") + if getattr(sys, "frozen", False): app_path = path.dirname(sys.executable) # uncomment if you want some protection that nothing would be broken # if not path.exists(path.join(app_path, settings_filename)): @@ -206,11 +227,12 @@ def read_settings(filename=""): return None + def save_settings(filename=""): if not filename: if _sett.project_path: app_path = _sett.project_path - elif getattr(sys, 'frozen', False): + elif getattr(sys, "frozen", False): app_path = path.dirname(sys.executable) else: # have to add .. because settings.py is under src folder @@ -221,10 +243,11 @@ def save_settings(filename=""): temp = prepare_temp_settings(_sett) - print(f'saving settings to {filename}') - with open(filename, 'w') as f: + print(f"saving settings to {filename}") + with open(filename, "w") as f: f.write(temp) + def prepare_temp_settings(_sett): temp = yaml.dump(_sett) temp = temp.replace("!!python/object:src.settings.Settings", "").strip() @@ -232,10 +255,12 @@ def prepare_temp_settings(_sett): return temp + def save_splanes_to_file(splanes, filename): - with open(filename, 'w') as out: + with open(filename, "w") as out: for p in splanes: - out.write(p.toFile() + '\n') + out.write(p.toFile() + "\n") + def get_version(settings_filename): try: @@ -248,6 +273,7 @@ def get_version(settings_filename): print("Error reading version") return "" + def set_version(settings_filename, version): try: with open(settings_filename, "r") as settings_file: @@ -261,6 +287,7 @@ def set_version(settings_filename, version): except Exception as e: print("Error writing version") + def paths_transfer_in_settings(initial_settings_filename, final_settings_filename): with open(initial_settings_filename, "r") as settings_file: initial_settings = yaml.safe_load(settings_file) @@ -273,6 +300,7 @@ def paths_transfer_in_settings(initial_settings_filename, final_settings_filenam with open(final_settings_filename, "w") as settings_file: yaml.dump(final_settings, settings_file, default_flow_style=False) + def compare_settings(initial_settings, final_settings): for key in set(final_settings): if key in initial_settings: @@ -282,19 +310,25 @@ def compare_settings(initial_settings, final_settings): if not initial_settings[key] is None: final_settings[key] = initial_settings[key] + class Settings(object): def __init__(self, d): for a, b in d.items(): if isinstance(b, (list, tuple)): - setattr(self, a, - [Settings(x) if isinstance(x, dict) else x for x in b]) + setattr(self, a, [Settings(x) if isinstance(x, dict) else x for x in b]) else: setattr(self, a, Settings(b) if isinstance(b, dict) else b) def __eq__(self, other): if not isinstance(other, Settings): return False - ignore_attributes = ["splanes_file", "print_time", "consumption_material", "planes_contact_with_nozzle", "figures"] + ignore_attributes = [ + "splanes_file", + "print_time", + "consumption_material", + "planes_contact_with_nozzle", + "figures", + ] for attr in self.__dict__: if attr in ignore_attributes: @@ -305,17 +339,18 @@ def __eq__(self, other): return False return True + class PathBuilder: # class to build paths to files and folders @staticmethod def project_path(): return sett().project_path - + @staticmethod def stl_model(): return path.join(PathBuilder.project_path(), "model.stl") - + @staticmethod def stl_model_temp(): return path.join(PathBuilder.project_path(), "model_temp.stl") @@ -323,11 +358,11 @@ def stl_model_temp(): @staticmethod def settings_file(): return path.join(PathBuilder.project_path(), "settings.yaml") - + @staticmethod def settings_file_temp(): return path.join(PathBuilder.project_path(), "settings_temp.yaml") - + @staticmethod def settings_file_default(): return "settings.yaml" @@ -339,33 +374,42 @@ def settings_file_old(): @staticmethod def colorizer_cmd(): return sett().colorizer.cmd + f'"{PathBuilder.settings_file()}"' - + @staticmethod def colorizer_stl(): return path.join(PathBuilder.project_path(), sett().colorizer.copy_stl_file) - + @staticmethod def colorizer_result(): return path.join(PathBuilder.project_path(), sett().colorizer.result) - + @staticmethod def slicing_cmd(): temp_settings = prepare_temp_settings(sett()) - encoded_temp_settings = base64.b64encode(temp_settings.encode('utf-8')).decode('utf-8') - return sett().slicing.cmd + f'"{PathBuilder.settings_file_temp()}"' + " --data=" + f'{encoded_temp_settings}' - + encoded_temp_settings = base64.b64encode(temp_settings.encode("utf-8")).decode( + "utf-8" + ) + return ( + sett().slicing.cmd + + f'"{PathBuilder.settings_file_temp()}"' + + " --data=" + + f"{encoded_temp_settings}" + ) + @staticmethod def gcodevis_file(): - return path.join(PathBuilder.project_path(), sett().slicing.gcode_file_without_calibration) - + return path.join( + PathBuilder.project_path(), sett().slicing.gcode_file_without_calibration + ) + @staticmethod def gcode_file(): return path.join(PathBuilder.project_path(), sett().slicing.gcode_file) - + @staticmethod def printer_dir(): return sett().hardware.printer_dir @staticmethod def calibration_file(): - return path.join(PathBuilder.printer_dir(), sett().hardware.calibration_file) \ No newline at end of file + return path.join(PathBuilder.printer_dir(), sett().hardware.calibration_file) diff --git a/src/window.py b/src/window.py index f2193bb..5c43b4c 100644 --- a/src/window.py +++ b/src/window.py @@ -4,16 +4,40 @@ from PyQt5 import QtCore from PyQt5 import QtGui from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import (QMainWindow, QWidget, QLabel, QLineEdit, QComboBox, QGridLayout, QSlider, - QCheckBox, QVBoxLayout, - QPushButton, QFileDialog, QScrollArea, QGroupBox, QAction, QDialog, - QTreeWidget, QTreeWidgetItem, QAbstractItemView, QTabWidget, QMessageBox) +from PyQt5.QtWidgets import ( + QMainWindow, + QWidget, + QLabel, + QLineEdit, + QComboBox, + QGridLayout, + QSlider, + QCheckBox, + QVBoxLayout, + QPushButton, + QFileDialog, + QScrollArea, + QGroupBox, + QAction, + QDialog, + QTreeWidget, + QTreeWidgetItem, + QAbstractItemView, + QTabWidget, + QMessageBox, +) from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor from src import locales, gui_utils, interactor_style from src.InteractorAroundActivePlane import InteractionAroundActivePlane from src.gui_utils import plane_tf, Plane, Cone -from src.settings import sett, get_color, save_settings, delete_temporary_project_files, project_change_check +from src.settings import ( + sett, + get_color, + save_settings, + delete_temporary_project_files, + project_change_check, +) import src.settings as settings from src.figure_editor import StlMovePanel from src.qt_utils import ClickableLineEdit @@ -26,13 +50,16 @@ BothState = "both" MovingState = "moving" + class TreeWidget(QTreeWidget): itemIsMoving = False def __init__(self, parent=None): super().__init__(parent) - self.setHeaderLabels([locales.getLocale().Hide, "№", locales.getLocale().NamePlanes]) + self.setHeaderLabels( + [locales.getLocale().Hide, "№", locales.getLocale().NamePlanes] + ) self.resizeColumnToContents(0) self.resizeColumnToContents(1) self.setMinimumWidth(400) @@ -47,6 +74,7 @@ def dragMoveEvent(self, event): self.itemIsMoving = True super().dragMoveEvent(event) + class LineEdit(QLineEdit): colorize_invalid_value = False @@ -56,7 +84,7 @@ def __init__(self, parent=None): self.textChanged.connect(self.input_validation) self.textChanged.connect(self.colorize_field) - def setValidator(self, validator, colorize_invalid_value = False): + def setValidator(self, validator, colorize_invalid_value=False): self.colorize_invalid_value = colorize_invalid_value super().setValidator(validator) @@ -78,7 +106,7 @@ def value_formatting(self): def input_validation(self): cursor_position = self.cursorPosition() - self.setText(self.text().replace(',', '.')) + self.setText(self.text().replace(",", ".")) if (not self.colorize_invalid_value) and self.validator(): value = float(self.text()) if self.text() else 0 @@ -98,12 +126,16 @@ def colorize_field(self): if self.colorize_invalid_value: if self.hasAcceptableInput() or (not self.text()): - self.setStyleSheet(f'background-color: {default_background_color}') + self.setStyleSheet(f"background-color: {default_background_color}") else: - self.setStyleSheet(f'background-color: {invalid_value_background_color}') + self.setStyleSheet( + f"background-color: {invalid_value_background_color}" + ) + class MainWindow(QMainWindow): from src.figure_editor import FigureEditor + # by default it is None, because there is nothing to edit, will be updated by derived from FigureEditor parameters_tooling: Optional[FigureEditor] = None @@ -113,7 +145,7 @@ class MainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) - self.setWindowTitle('FASP') + self.setWindowTitle("FASP") self.setWindowIcon(QtGui.QIcon("icon.png")) self.setMinimumWidth(1600) # self.statusBar().showMessage('Ready') @@ -259,7 +291,7 @@ def init3d_widget(self): self.interactor.Initialize() self.interactor.Start() - #self.render.ResetCamera() + # self.render.ResetCamera() # self.render.GetActiveCamera().AddObserver('ModifiedEvent', CameraModifiedCallback) # set position of camera to (5, 5, 5) and look at (0, 0, 0) and z-axis is looking up @@ -267,14 +299,32 @@ def init3d_widget(self): self.render.GetActiveCamera().SetFocalPoint(0, 0, 0) self.render.GetActiveCamera().SetViewUp(0, 0, 1) - self.customInteractor = InteractionAroundActivePlane(self.interactor, self.render) - self.interactor.AddObserver("MouseWheelBackwardEvent", self.customInteractor.middleBtnPress) - self.interactor.AddObserver("MouseWheelForwardEvent", self.customInteractor.middleBtnPress) - self.interactor.AddObserver("RightButtonPressEvent", self.customInteractor.rightBtnPress) - self.interactor.AddObserver("RightButtonReleaseEvent", self.customInteractor.rightBtnPress) - self.interactor.AddObserver("LeftButtonPressEvent", lambda obj, event: self.customInteractor.leftBtnPress(obj, event, self)) - self.interactor.AddObserver("LeftButtonReleaseEvent", self.customInteractor.leftBtnPress) - self.interactor.AddObserver("MouseMoveEvent", lambda obj, event: self.customInteractor.mouseMove(obj, event, self)) + self.customInteractor = InteractionAroundActivePlane( + self.interactor, self.render + ) + self.interactor.AddObserver( + "MouseWheelBackwardEvent", self.customInteractor.middleBtnPress + ) + self.interactor.AddObserver( + "MouseWheelForwardEvent", self.customInteractor.middleBtnPress + ) + self.interactor.AddObserver( + "RightButtonPressEvent", self.customInteractor.rightBtnPress + ) + self.interactor.AddObserver( + "RightButtonReleaseEvent", self.customInteractor.rightBtnPress + ) + self.interactor.AddObserver( + "LeftButtonPressEvent", + lambda obj, event: self.customInteractor.leftBtnPress(obj, event, self), + ) + self.interactor.AddObserver( + "LeftButtonReleaseEvent", self.customInteractor.leftBtnPress + ) + self.interactor.AddObserver( + "MouseMoveEvent", + lambda obj, event: self.customInteractor.mouseMove(obj, event, self), + ) # self.actor_interactor_style = interactor_style.ActorInteractorStyle(self.updateTransform) # self.actor_interactor_style.SetDefaultRenderer(self.render) @@ -312,7 +362,9 @@ def add_legend(self): self.legend.GetPosition2Coordinate().SetCoordinateSystemToDisplay() self.legend.GetPosition2Coordinate().SetValue(290, 3 * 30) self.legend.SetEntry(0, hackData, "rotate - left mouse button", [1, 1, 1]) - self.legend.SetEntry(1, hackData, "move - middle mouse button (or shift+left)", [1, 1, 1]) + self.legend.SetEntry( + 1, hackData, "move - middle mouse button (or shift+left)", [1, 1, 1] + ) self.legend.SetEntry(2, hackData, "scale - right mouse button", [1, 1, 1]) self.render.AddActor(self.legend) @@ -343,21 +395,27 @@ def get_next_row(): def get_cur_row(): return self.cur_row - + # printer choice printer_label = QLabel(locales.getLocale().PrinterName) printer_basename = "" try: printer_basename = path.basename(sett().hardware.printer_dir) - if sett().hardware.printer_dir == "" or not path.isdir(sett().hardware.printer_dir): + if sett().hardware.printer_dir == "" or not path.isdir( + sett().hardware.printer_dir + ): # empty directory raise Exception("Choose default printer") logging.info(f"hardware printer path is {sett().hardware.printer_dir}") except: # set default path to printer config - sett().hardware.printer_dir = path.join(settings.APP_PATH, "data", "printers", "default") - logging.info(f"hardware printer path is default: {sett().hardware.printer_dir}") + sett().hardware.printer_dir = path.join( + settings.APP_PATH, "data", "printers", "default" + ) + logging.info( + f"hardware printer path is default: {sett().hardware.printer_dir}" + ) printer_basename = path.basename(sett().hardware.printer_dir) save_settings() @@ -369,26 +427,33 @@ def get_cur_row(): right_panel.addWidget(printer_label, get_next_row(), 1) right_panel.addWidget(self.printer_add_btn, get_cur_row(), 2) - right_panel.addWidget(self.printer_path_edit, get_cur_row(), 3, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.printer_path_edit, get_cur_row(), 3, 1, сolumn2_number_of_cells + ) uninterrupted_print = QLabel(self.locale.UninterruptedPrint) self.uninterrupted_print_box = QCheckBox() if sett().uninterrupted_print.enabled: self.uninterrupted_print_box.setCheckState(QtCore.Qt.Checked) right_panel.addWidget(uninterrupted_print, get_next_row(), 1) - right_panel.addWidget(self.uninterrupted_print_box, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.uninterrupted_print_box, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) + # on check on this box, we should restrict fill type to zigzag only def on_uninterrupted_print_change(): isUninterrupted = self.uninterrupted_print_box.isChecked() - + self.filling_type_values.setEnabled(not isUninterrupted) self.retraction_on_box.setEnabled(not isUninterrupted) self.retraction_distance_value.setEnabled(not isUninterrupted) self.retraction_speed_value.setEnabled(not isUninterrupted) - self.retract_compensation_amount_value.setEnabled(not isUninterrupted) + self.retract_compensation_amount_value.setEnabled(not isUninterrupted) if isUninterrupted: - zigzag_idx = locales.getLocaleByLang("en").FillingTypeValues.index("ZigZag") + zigzag_idx = locales.getLocaleByLang("en").FillingTypeValues.index( + "ZigZag" + ) self.filling_type_values.setCurrentIndex(zigzag_idx) self.retraction_on_box.setChecked(False) @@ -396,26 +461,36 @@ def on_uninterrupted_print_change(): # M10 cut distance setting m10_cut_distance_label = QLabel(self.locale.M10CutDistance) - self.m10_cut_distance_value = LineEdit(str(sett().uninterrupted_print.cut_distance)) + self.m10_cut_distance_value = LineEdit( + str(sett().uninterrupted_print.cut_distance) + ) self.m10_cut_distance_value.setValidator(doubleValidator) right_panel.addWidget(m10_cut_distance_label, get_next_row(), 1) - right_panel.addWidget(self.m10_cut_distance_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.m10_cut_distance_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) line_width_label = QLabel(self.locale.LineWidth) self.line_width_value = LineEdit(str(sett().slicing.line_width)) self.line_width_value.setValidator(doubleValidator, True) right_panel.addWidget(line_width_label, get_next_row(), 1) - right_panel.addWidget(self.line_width_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.line_width_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) layer_height_label = QLabel(self.locale.LayerHeight) self.layer_height_value = LineEdit(str(sett().slicing.layer_height)) self.layer_height_value.setValidator(doubleValidator) right_panel.addWidget(layer_height_label, get_next_row(), 1) - right_panel.addWidget(self.layer_height_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.layer_height_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) number_wall_lines_label = QLabel(self.locale.NumberWallLines) if sett().slicing.line_width > 0: - number_wall_lines = int(sett().slicing.wall_thickness/sett().slicing.line_width) + number_wall_lines = int( + sett().slicing.wall_thickness / sett().slicing.line_width + ) else: number_wall_lines = 0 self.number_wall_lines_value = LineEdit(str(number_wall_lines)) @@ -436,7 +511,9 @@ def on_uninterrupted_print_change(): right_panel.addWidget(number_of_bottom_layers_label, get_next_row(), 1) right_panel.addWidget(self.number_of_bottom_layers_value, get_cur_row(), 2) bottom_thickness_label = QLabel(self.locale.BottomThickness) - self.bottom_thickness_value = LineEdit(str(round(sett().slicing.layer_height*sett().slicing.bottoms_depth,2))) + self.bottom_thickness_value = LineEdit( + str(round(sett().slicing.layer_height * sett().slicing.bottoms_depth, 2)) + ) self.bottom_thickness_value.setReadOnly(True) millimeter_label = QLabel(self.locale.Millimeter) right_panel.addWidget(bottom_thickness_label, get_cur_row(), 3) @@ -450,7 +527,9 @@ def on_uninterrupted_print_change(): right_panel.addWidget(number_of_lid_layers_label, get_next_row(), 1) right_panel.addWidget(self.number_of_lid_layers_value, get_cur_row(), 2) lid_thickness_label = QLabel(self.locale.LidsThickness) - self.lid_thickness_value = LineEdit(str(round(sett().slicing.layer_height*sett().slicing.lids_depth,2))) + self.lid_thickness_value = LineEdit( + str(round(sett().slicing.layer_height * sett().slicing.lids_depth, 2)) + ) self.lid_thickness_value.setReadOnly(True) millimeter_label = QLabel(self.locale.Millimeter) right_panel.addWidget(lid_thickness_label, get_cur_row(), 3) @@ -461,51 +540,67 @@ def on_uninterrupted_print_change(): self.extruder_temp_value = LineEdit(str(sett().slicing.extruder_temperature)) self.extruder_temp_value.setValidator(doubleValidator) right_panel.addWidget(extruder_temp_label, get_next_row(), 1) - right_panel.addWidget(self.extruder_temp_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.extruder_temp_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) bed_temp_label = QLabel(self.locale.BedTemp) self.bed_temp_value = LineEdit(str(sett().slicing.bed_temperature)) self.bed_temp_value.setValidator(doubleValidator) right_panel.addWidget(bed_temp_label, get_next_row(), 1) - right_panel.addWidget(self.bed_temp_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.bed_temp_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) skirt_line_count_label = QLabel(self.locale.SkirtLineCount) self.skirt_line_count_value = LineEdit(str(sett().slicing.skirt_line_count)) self.skirt_line_count_value.setValidator(intValidator) right_panel.addWidget(skirt_line_count_label, get_next_row(), 1) - right_panel.addWidget(self.skirt_line_count_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.skirt_line_count_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) fan_speed_label = QLabel(self.locale.FanSpeed) self.fan_speed_value = LineEdit(str(sett().slicing.fan_speed)) self.fan_speed_value.setValidator(doublePercentValidator) right_panel.addWidget(fan_speed_label, get_next_row(), 1) - right_panel.addWidget(self.fan_speed_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.fan_speed_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) fan_off_layer1_label = QLabel(self.locale.FanOffLayer1) self.fan_off_layer1_box = QCheckBox() if sett().slicing.fan_off_layer1: self.fan_off_layer1_box.setCheckState(QtCore.Qt.Checked) right_panel.addWidget(fan_off_layer1_label, get_next_row(), 1) - right_panel.addWidget(self.fan_off_layer1_box, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.fan_off_layer1_box, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) print_speed_label = QLabel(self.locale.PrintSpeed) self.print_speed_value = LineEdit(str(sett().slicing.print_speed)) self.print_speed_value.setValidator(doubleValidator) right_panel.addWidget(print_speed_label, get_next_row(), 1) - right_panel.addWidget(self.print_speed_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.print_speed_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) print_speed_layer1_label = QLabel(self.locale.PrintSpeedLayer1) self.print_speed_layer1_value = LineEdit(str(sett().slicing.print_speed_layer1)) self.print_speed_layer1_value.setValidator(doubleValidator) right_panel.addWidget(print_speed_layer1_label, get_next_row(), 1) - right_panel.addWidget(self.print_speed_layer1_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.print_speed_layer1_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) print_speed_wall_label = QLabel(self.locale.PrintSpeedWall) self.print_speed_wall_value = LineEdit(str(sett().slicing.print_speed_wall)) self.print_speed_wall_value.setValidator(doubleValidator) right_panel.addWidget(print_speed_wall_label, get_next_row(), 1) - right_panel.addWidget(self.print_speed_wall_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.print_speed_wall_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) filling_type_label = QLabel(self.locale.FillingType) right_panel.addWidget(filling_type_label, get_next_row(), 1) @@ -513,68 +608,104 @@ def on_uninterrupted_print_change(): filling_type_values_widget.setFixedHeight(26) self.filling_type_values = QComboBox(filling_type_values_widget) self.filling_type_values.addItems(self.locale.FillingTypeValues) - ind = locales.getLocaleByLang("en").FillingTypeValues.index(sett().slicing.filling_type) + ind = locales.getLocaleByLang("en").FillingTypeValues.index( + sett().slicing.filling_type + ) self.filling_type_values.setCurrentIndex(ind) - right_panel.addWidget(filling_type_values_widget, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + filling_type_values_widget, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) fill_density_label = QLabel(self.locale.FillDensity) self.fill_density_value = LineEdit(str(sett().slicing.fill_density)) self.fill_density_value.setValidator(doublePercentValidator) right_panel.addWidget(fill_density_label, get_next_row(), 1) - right_panel.addWidget(self.fill_density_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.fill_density_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) overlapping_infill = QLabel(self.locale.OverlappingInfillPercentage) - self.overlapping_infill_value = LineEdit(str(sett().slicing.overlapping_infill_percentage)) + self.overlapping_infill_value = LineEdit( + str(sett().slicing.overlapping_infill_percentage) + ) self.overlapping_infill_value.setValidator(doublePercentValidator) right_panel.addWidget(overlapping_infill, get_next_row(), 1) - right_panel.addWidget(self.overlapping_infill_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.overlapping_infill_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) retraction_on_label = QLabel(self.locale.Retraction) self.retraction_on_box = QCheckBox() if sett().slicing.retraction_on: self.retraction_on_box.setCheckState(QtCore.Qt.Checked) right_panel.addWidget(retraction_on_label, get_next_row(), 1) - right_panel.addWidget(self.retraction_on_box, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.retraction_on_box, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) retraction_distance_label = QLabel(self.locale.RetractionDistance) - self.retraction_distance_value = LineEdit(str(sett().slicing.retraction_distance)) + self.retraction_distance_value = LineEdit( + str(sett().slicing.retraction_distance) + ) self.retraction_distance_value.setValidator(doubleValidator) right_panel.addWidget(retraction_distance_label, get_next_row(), 1) - right_panel.addWidget(self.retraction_distance_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.retraction_distance_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) retraction_speed_label = QLabel(self.locale.RetractionSpeed) self.retraction_speed_value = LineEdit(str(sett().slicing.retraction_speed)) self.retraction_speed_value.setValidator(doubleValidator) right_panel.addWidget(retraction_speed_label, get_next_row(), 1) - right_panel.addWidget(self.retraction_speed_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.retraction_speed_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) - retract_compensation_amount_label = QLabel(self.locale.RetractCompensationAmount) - self.retract_compensation_amount_value = LineEdit(str(sett().slicing.retract_compensation_amount)) + retract_compensation_amount_label = QLabel( + self.locale.RetractCompensationAmount + ) + self.retract_compensation_amount_value = LineEdit( + str(sett().slicing.retract_compensation_amount) + ) self.retract_compensation_amount_value.setValidator(doubleValidator) right_panel.addWidget(retract_compensation_amount_label, get_next_row(), 1) - right_panel.addWidget(self.retract_compensation_amount_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.retract_compensation_amount_value, + get_cur_row(), + 2, + 1, + сolumn2_number_of_cells, + ) material_shrinkage_label = QLabel(self.locale.MaterialShrinkage) self.material_shrinkage_value = LineEdit(str(sett().slicing.material_shrinkage)) self.material_shrinkage_value.setValidator(doubleValidator) right_panel.addWidget(material_shrinkage_label, get_next_row(), 1) - right_panel.addWidget(self.material_shrinkage_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.material_shrinkage_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) # supports related stuff section - right_panel.addWidget(QLabel(self.locale.SupportsSettings), get_next_row(), 1, Qt.AlignCenter) + right_panel.addWidget( + QLabel(self.locale.SupportsSettings), get_next_row(), 1, Qt.AlignCenter + ) supports_on_label = QLabel(self.locale.SupportsOn) self.supports_on_box = QCheckBox() if sett().supports.enabled: self.supports_on_box.setCheckState(QtCore.Qt.Checked) right_panel.addWidget(supports_on_label, get_next_row(), 1) - right_panel.addWidget(self.supports_on_box, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.supports_on_box, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) support_density_label = QLabel(self.locale.SupportDensity) self.support_density_value = LineEdit(str(sett().supports.fill_density)) self.support_density_value.setValidator(doublePercentValidator) right_panel.addWidget(support_density_label, get_next_row(), 1) - right_panel.addWidget(self.support_density_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.support_density_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) support_fill_type_label = QLabel(self.locale.FillingType) right_panel.addWidget(support_fill_type_label, get_next_row(), 1) @@ -582,36 +713,68 @@ def on_uninterrupted_print_change(): support_fill_type_values_widget.setFixedHeight(26) self.support_fill_type_values = QComboBox(support_fill_type_values_widget) self.support_fill_type_values.addItems(self.locale.FillingTypeValues) - ind = locales.getLocaleByLang("en").FillingTypeValues.index(sett().supports.fill_type) + ind = locales.getLocaleByLang("en").FillingTypeValues.index( + sett().supports.fill_type + ) self.support_fill_type_values.setCurrentIndex(ind) - right_panel.addWidget(support_fill_type_values_widget, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + support_fill_type_values_widget, + get_cur_row(), + 2, + 1, + сolumn2_number_of_cells, + ) support_xy_offset_label = QLabel(self.locale.SupportXYOffset) self.support_xy_offset_value = LineEdit(str(sett().supports.xy_offset)) self.support_xy_offset_value.setValidator(doubleValidator) right_panel.addWidget(support_xy_offset_label, get_next_row(), 1) - right_panel.addWidget(self.support_xy_offset_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) - + right_panel.addWidget( + self.support_xy_offset_value, get_cur_row(), 2, 1, сolumn2_number_of_cells + ) + support_z_offset_layers_label = QLabel(self.locale.SupportZOffsetLayers) - self.support_z_offset_layers_value = LineEdit(str(sett().supports.z_offset_layers)) + self.support_z_offset_layers_value = LineEdit( + str(sett().supports.z_offset_layers) + ) self.support_z_offset_layers_value.setValidator(intValidator) right_panel.addWidget(support_z_offset_layers_label, get_next_row(), 1) - right_panel.addWidget(self.support_z_offset_layers_value, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.support_z_offset_layers_value, + get_cur_row(), + 2, + 1, + сolumn2_number_of_cells, + ) support_priorityZoffset_label = QLabel(self.locale.SupportPriorityZOffset) self.support_priority_z_offset_box = QCheckBox() if sett().supports.priority_z_offset: self.support_priority_z_offset_box.setCheckState(QtCore.Qt.Checked) right_panel.addWidget(support_priorityZoffset_label, get_next_row(), 1) - right_panel.addWidget(self.support_priority_z_offset_box, get_cur_row(), 2, 1, сolumn2_number_of_cells) + right_panel.addWidget( + self.support_priority_z_offset_box, + get_cur_row(), + 2, + 1, + сolumn2_number_of_cells, + ) - supports_number_of_bottom_layers_label = QLabel(self.locale.NumberOfBottomLayers) - self.supports_number_of_bottom_layers_value = LineEdit(str(sett().supports.bottoms_depth)) + supports_number_of_bottom_layers_label = QLabel( + self.locale.NumberOfBottomLayers + ) + self.supports_number_of_bottom_layers_value = LineEdit( + str(sett().supports.bottoms_depth) + ) self.supports_number_of_bottom_layers_value.setValidator(intValidator) right_panel.addWidget(supports_number_of_bottom_layers_label, get_next_row(), 1) - right_panel.addWidget(self.supports_number_of_bottom_layers_value, get_cur_row(), 2) + right_panel.addWidget( + self.supports_number_of_bottom_layers_value, get_cur_row(), 2 + ) supports_bottom_thickness_label = QLabel(self.locale.BottomThickness) - self.supports_bottom_thickness_value = LineEdit(str(round(sett().slicing.layer_height*sett().supports.bottoms_depth,2))) + self.supports_bottom_thickness_value = LineEdit( + str(round(sett().slicing.layer_height * sett().supports.bottoms_depth, 2)) + ) self.supports_bottom_thickness_value.setReadOnly(True) millimeter_label = QLabel(self.locale.Millimeter) right_panel.addWidget(supports_bottom_thickness_label, get_cur_row(), 3) @@ -619,13 +782,19 @@ def on_uninterrupted_print_change(): right_panel.addWidget(millimeter_label, get_cur_row(), 5) supports_number_of_lid_layers_label = QLabel(self.locale.NumberOfLidLayers) - self.supports_number_of_lid_layers_value = LineEdit(str(int(sett().supports.lids_depth))) + self.supports_number_of_lid_layers_value = LineEdit( + str(int(sett().supports.lids_depth)) + ) # self.number_of_lid_layers_value.setValidator(QtGui.QIntValidator(0, 100)) self.supports_number_of_lid_layers_value.setValidator(intValidator) right_panel.addWidget(supports_number_of_lid_layers_label, get_next_row(), 1) - right_panel.addWidget(self.supports_number_of_lid_layers_value, get_cur_row(), 2) + right_panel.addWidget( + self.supports_number_of_lid_layers_value, get_cur_row(), 2 + ) supports_lid_thickness_label = QLabel(self.locale.LidsThickness) - self.supports_lid_thickness_value = LineEdit(str(round(sett().slicing.layer_height*sett().supports.lids_depth,2))) + self.supports_lid_thickness_value = LineEdit( + str(round(sett().slicing.layer_height * sett().supports.lids_depth, 2)) + ) self.supports_lid_thickness_value.setReadOnly(True) millimeter_label = QLabel(self.locale.Millimeter) right_panel.addWidget(supports_lid_thickness_label, get_cur_row(), 3) @@ -648,14 +817,18 @@ def on_uninterrupted_print_change(): buttons_layout.addWidget(self.model_switch_box, get_next_row(), 1) self.print_time_label = QLabel(self.locale.PrintTime) self.print_time_value = QLabel("") - buttons_layout.addWidget(self.print_time_label, get_cur_row(), 2, Qt.AlignmentFlag(3)) + buttons_layout.addWidget( + self.print_time_label, get_cur_row(), 2, Qt.AlignmentFlag(3) + ) buttons_layout.addWidget(self.print_time_value, get_cur_row(), 3) self.model_centering_box = QCheckBox(self.locale.ModelCentering) buttons_layout.addWidget(self.model_centering_box, get_next_row(), 1) self.consumption_material_label = QLabel(self.locale.ConsumptionMaterial) self.consumption_material_value = QLabel("") - buttons_layout.addWidget(self.consumption_material_label, get_cur_row(), 2, Qt.AlignmentFlag(3)) + buttons_layout.addWidget( + self.consumption_material_label, get_cur_row(), 2, Qt.AlignmentFlag(3) + ) buttons_layout.addWidget(self.consumption_material_value, get_cur_row(), 3) self.model_align_height = QCheckBox(self.locale.AlignModelHeight) @@ -693,8 +866,12 @@ def on_uninterrupted_print_change(): self.save_gcode_button = QPushButton(self.locale.SaveGCode) buttons_layout.addWidget(self.save_gcode_button, get_cur_row(), 3) - self.critical_wall_overhang_angle_label = QLabel(self.locale.CriticalWallOverhangAngle) - buttons_layout.addWidget(self.critical_wall_overhang_angle_label, get_next_row(), 1, 1, 2) + self.critical_wall_overhang_angle_label = QLabel( + self.locale.CriticalWallOverhangAngle + ) + buttons_layout.addWidget( + self.critical_wall_overhang_angle_label, get_next_row(), 1, 1, 2 + ) buttons_layout.setColumnMinimumWidth(1, 230) self.colorize_angle_value = LineEdit(str(sett().slicing.angle)) self.colorize_angle_value.setValidator(doubleValidator) @@ -778,7 +955,6 @@ def init_stl_move_panel(self): stlRotator = gui_utils.StlRotator(self) def translate(x, y, z): - def translatePos(): stlTranslator.act(5, [x, y, z]) @@ -791,7 +967,6 @@ def translateSet(self): return translatePos, translateNeg, translateSet def rotate(x, y, z): - def rotatePos(): stlRotator.act(5, [x, y, z]) @@ -808,7 +983,6 @@ def rotateSet(text): stlScale = gui_utils.StlScale(self) def scale(x, y, z): - def scalePos(): stlScale.act(5, [x, y, z]) @@ -836,7 +1010,7 @@ def scaleSet(self): self.locale.StlMoveTranslate, self.locale.StlMoveRotate, self.locale.StlMoveScale, - ] + ], ) self.stl_move_panel.setFixedHeight = 100 return self.stl_move_panel @@ -898,8 +1072,9 @@ def reload_scene(self): self.render.Modified() self.interactor.Render() - def change_layer_view(self, new_slider_value, prev_value, gcd): # shows +1 layer to preview finish - + def change_layer_view( + self, new_slider_value, prev_value, gcd + ): # shows +1 layer to preview finish if prev_value is None: return new_slider_value @@ -907,21 +1082,33 @@ def change_layer_view(self, new_slider_value, prev_value, gcd): # shows +1 laye prev_last = False if len(self.actors) > prev_value else True if not last: - self.actors[new_slider_value].GetProperty().SetColor(get_color(sett().colors.last_layer)) + self.actors[new_slider_value].GetProperty().SetColor( + get_color(sett().colors.last_layer) + ) self.actors[new_slider_value].GetProperty().SetLineWidth(4) - self.actors[new_slider_value].GetProperty().SetOpacity(sett().common.opacity_last_layer) + self.actors[new_slider_value].GetProperty().SetOpacity( + sett().common.opacity_last_layer + ) if not prev_last: - self.actors[prev_value].GetProperty().SetColor(get_color(sett().colors.layer)) + self.actors[prev_value].GetProperty().SetColor( + get_color(sett().colors.layer) + ) self.actors[prev_value].GetProperty().SetLineWidth(1) - self.actors[prev_value].GetProperty().SetOpacity(sett().common.opacity_layer) + self.actors[prev_value].GetProperty().SetOpacity( + sett().common.opacity_layer + ) self.layers_number_label.setText(str(new_slider_value)) if new_slider_value < prev_value: - for layer in range(new_slider_value + 1, prev_value if prev_last else prev_value + 1): + for layer in range( + new_slider_value + 1, prev_value if prev_last else prev_value + 1 + ): self.actors[layer].VisibilityOff() else: - for layer in range(prev_value, new_slider_value if last else new_slider_value + 1): + for layer in range( + prev_value, new_slider_value if last else new_slider_value + 1 + ): self.actors[layer].VisibilityOn() new_rot = gcd.lays2rots[0] if last else gcd.lays2rots[new_slider_value] @@ -931,7 +1118,9 @@ def change_layer_view(self, new_slider_value, prev_value, gcd): # shows +1 laye curr_rotation = gcd.rotations[new_rot] for block in range(new_slider_value if last else new_slider_value + 1): # revert prev rotation firstly and then apply current - tf = gui_utils.prepareTransform(gcd.rotations[gcd.lays2rots[block]], curr_rotation) + tf = gui_utils.prepareTransform( + gcd.rotations[gcd.lays2rots[block]], curr_rotation + ) self.actors[block].SetUserTransform(tf) self.rotate_plane(plane_tf(curr_rotation)) @@ -1010,10 +1199,20 @@ def updateTransform(self): i, j, k = tf.GetOrientation() self.xyz_orient_value.setText(f"Orientation: {i:.2f} {j:.2f} {k:.2f}") - def save_dialog(self, caption, format = "STL (*.stl *.STL);;Gcode (*.gcode)", directory="/home/l1va/Downloads/5axes_3d_printer/test"): # TODO: fix path + def save_dialog( + self, + caption, + format="STL (*.stl *.STL);;Gcode (*.gcode)", + directory="/home/l1va/Downloads/5axes_3d_printer/test", + ): # TODO: fix path return QFileDialog.getSaveFileName(None, caption, directory, format)[0] - def open_dialog(self, caption, format = "STL (*.stl *.STL);;Gcode (*.gcode)", directory="/home/l1va/Downloads/5axes_3d_printer/test"): # TODO: fix path + def open_dialog( + self, + caption, + format="STL (*.stl *.STL);;Gcode (*.gcode)", + directory="/home/l1va/Downloads/5axes_3d_printer/test", + ): # TODO: fix path return QFileDialog.getOpenFileName(None, caption, directory, format)[0] def load_stl(self, stl_actor): @@ -1026,7 +1225,7 @@ def load_stl(self, stl_actor): self.render.AddActor(self.stlActor) self.state_stl() - #self.render.ResetCamera() + # self.render.ResetCamera() self.render.GetActiveCamera().SetClippingRange(100, 10000) self.reload_scene() @@ -1036,7 +1235,10 @@ def hide_splanes(self): s.VisibilityOff() else: for i, s in enumerate(self.splanes_actors): - if not self.splanes_tree.topLevelItem(i).checkState(0) == Qt.CheckState.Checked: + if ( + not self.splanes_tree.topLevelItem(i).checkState(0) + == Qt.CheckState.Checked + ): s.VisibilityOn() self.reload_scene() @@ -1057,7 +1259,9 @@ def reload_splanes(self, splanes): # self.splanes_list.addItem(self.locale.Plane + " " + str(i + 1)) if len(splanes) > 0: - self.splanes_tree.setCurrentItem(self.splanes_tree.topLevelItem(len(splanes) - 1)) + self.splanes_tree.setCurrentItem( + self.splanes_tree.topLevelItem(len(splanes) - 1) + ) self.reload_scene() def _recreate_splanes(self, splanes): @@ -1068,11 +1272,15 @@ def _recreate_splanes(self, splanes): if isinstance(p, Plane): act = gui_utils.create_splane_actor([p.x, p.y, p.z], p.incline, p.rot) else: # isinstance(p, Cone): - act = gui_utils.create_cone_actor((p.x, p.y, p.z), p.cone_angle, p.h1, p.h2) + act = gui_utils.create_cone_actor( + (p.x, p.y, p.z), p.cone_angle, p.h1, p.h2 + ) row = self.splanes_tree.topLevelItem(i) if row != None: - if (row.checkState(0) == QtCore.Qt.CheckState.Checked) or self.hide_checkbox.isChecked(): + if ( + row.checkState(0) == QtCore.Qt.CheckState.Checked + ) or self.hide_checkbox.isChecked(): act.VisibilityOff() # act = gui_utils.create_cone_actor((p.x, p.y, p.z), p.cone_angle) self.splanes_actors.append(act) @@ -1080,7 +1288,10 @@ def _recreate_splanes(self, splanes): def update_splane(self, sp, ind): self.reset_colorize() - settableVisibility = self.splanes_actors[ind].GetVisibility() and not self.hide_checkbox.isChecked() + settableVisibility = ( + self.splanes_actors[ind].GetVisibility() + and not self.hide_checkbox.isChecked() + ) self.render.RemoveActor(self.splanes_actors[ind]) # TODO update to pass values as self.splanes_actors[ind], and only then destruct object act = gui_utils.create_splane_actor([sp.x, sp.y, sp.z], sp.incline, sp.rot) @@ -1094,28 +1305,40 @@ def update_splane(self, sp, ind): self.render.AddActor(act) sel = self.splanes_tree.currentIndex().row() if sel == ind: - self.splanes_actors[sel].GetProperty().SetColor(get_color(sett().colors.last_layer)) + self.splanes_actors[sel].GetProperty().SetColor( + get_color(sett().colors.last_layer) + ) self.splanes_actors[sel].GetProperty().SetOpacity(0.8) self.reload_scene() def update_cone(self, cone: Cone, ind): self.render.RemoveActor(self.splanes_actors[ind]) # TODO update to pass values as self.splanes_actors[ind], and only then destruct object - act = gui_utils.create_cone_actor((cone.x, cone.y, cone.z), cone.cone_angle, cone.h1, cone.h2) + act = gui_utils.create_cone_actor( + (cone.x, cone.y, cone.z), cone.cone_angle, cone.h1, cone.h2 + ) self.splanes_actors[ind] = act self.render.AddActor(act) sel = self.splanes_tree.currentIndex().row() if sel == ind: - self.splanes_actors[sel].GetProperty().SetColor(get_color(sett().colors.last_layer)) - self.splanes_actors[sel].GetProperty().SetOpacity(sett().common.opacity_current_plane) + self.splanes_actors[sel].GetProperty().SetColor( + get_color(sett().colors.last_layer) + ) + self.splanes_actors[sel].GetProperty().SetOpacity( + sett().common.opacity_current_plane + ) self.reload_scene() def change_combo_select(self, plane, ind): for p in self.splanes_actors: p.GetProperty().SetColor(get_color(sett().colors.splane)) p.GetProperty().SetOpacity(sett().common.opacity_plane) - self.splanes_actors[ind].GetProperty().SetColor(get_color(sett().colors.last_layer)) - self.splanes_actors[ind].GetProperty().SetOpacity(sett().common.opacity_current_plane) + self.splanes_actors[ind].GetProperty().SetColor( + get_color(sett().colors.last_layer) + ) + self.splanes_actors[ind].GetProperty().SetOpacity( + sett().common.opacity_current_plane + ) self.reload_scene() def load_gcode(self, actors, is_from_stl, plane_tf): @@ -1137,7 +1360,7 @@ def load_gcode(self, actors, is_from_stl, plane_tf): else: self.state_gcode(len(self.actors)) - #self.render.ResetCamera() + # self.render.ResetCamera() self.reload_scene() def rotate_plane(self, tf): @@ -1149,7 +1372,9 @@ def rotate_plane(self, tf): # self.xyz_orient_value.setText("Orientation: " + strF(i) + " " + strF(j) + " " + strF(k)) def save_gcode_dialog(self): - return QFileDialog.getSaveFileName(None, self.locale.SaveGCode, "", "Gcode (*.gcode)")[0] + return QFileDialog.getSaveFileName( + None, self.locale.SaveGCode, "", "Gcode (*.gcode)" + )[0] def about_dialog(self): d = QDialog() @@ -1159,7 +1384,7 @@ def about_dialog(self): v_layout = QVBoxLayout() - site_label = QLabel("Site Url: epit3d.ru") + site_label = QLabel('Site Url: epit3d.ru') site_label.setOpenExternalLinks(True) # site_label.setTextInteractionFlags(Qt.TextSelectableByMouse) v_layout.addWidget(site_label) @@ -1169,7 +1394,8 @@ def about_dialog(self): v_layout.addWidget(phone_label) email_label = QLabel( - "E-mail: Info@epit3d.ru") + "E-mail: Info@epit3d.ru" + ) # email_label.setTextInteractionFlags(Qt.TextSelectableByMouse) site_label.setOpenExternalLinks(True) v_layout.addWidget(email_label) @@ -1298,9 +1524,10 @@ def reset_colorize(self): if self.stlActor: self.stlActor.ResetColorize() + def strF(v): # cut 3 numbers after the point in float s = str(v) i = s.find(".") if i != -1: - s = s[:min(len(s), i + 3)] + s = s[: min(len(s), i + 3)] return s diff --git a/test/gcode_test.py b/test/gcode_test.py index 223a6ea..deb22db 100644 --- a/test/gcode_test.py +++ b/test/gcode_test.py @@ -8,13 +8,27 @@ def testParseArgs(self): self.assertEqual((0, 0, 0, None), parseArgs([], 0, 0, 0)) self.assertEqual((1, 3, 4, None), parseArgs(["X1"], 0, 3, 4)) self.assertEqual((1.11, 2.22, 4, None), parseArgs(["Y2.22"], 1.11, 3, 4)) - self.assertEqual((1.11, 2.22, 3.33, None), parseArgs(["Y2.22", "Z3.33"], 1.11, 8, 9)) - self.assertEqual((4.44, 2.22, 3.33, None), parseArgs(["Y2.22", "Z3.33", "X4.44"], 1.11, 8, 9)) - self.assertEqual((1.11, 2.22, 53.3299999999999983, None), parseArgs(["Y2.22", "Z53.33"], 1.11, 8, 9)) - self.assertEqual((1.11, 2.22, 9, None), parseArgs(["Y2.22", ";comment", "about", "smtg"], 1.11, 8, 9)) + self.assertEqual( + (1.11, 2.22, 3.33, None), parseArgs(["Y2.22", "Z3.33"], 1.11, 8, 9) + ) + self.assertEqual( + (4.44, 2.22, 3.33, None), parseArgs(["Y2.22", "Z3.33", "X4.44"], 1.11, 8, 9) + ) + self.assertEqual( + (1.11, 2.22, 53.3299999999999983, None), + parseArgs(["Y2.22", "Z53.33"], 1.11, 8, 9), + ) + self.assertEqual( + (1.11, 2.22, 9, None), + parseArgs(["Y2.22", ";comment", "about", "smtg"], 1.11, 8, 9), + ) - self.assertEqual((1.11, 10.22, 9, None), parseArgs(["Y2.22"], 1.11, 8, 9, False)) - self.assertEqual((4.11, 8, 11.22, None), parseArgs(["Z+2.22", "X-1"], 5.11, 8, 9, False)) + self.assertEqual( + (1.11, 10.22, 9, None), parseArgs(["Y2.22"], 1.11, 8, 9, False) + ) + self.assertEqual( + (4.11, 8, 11.22, None), parseArgs(["Z+2.22", "X-1"], 5.11, 8, 9, False) + ) def testParseRotation(self): compare = { @@ -45,18 +59,25 @@ def testParseGCode(self): "G1 X23.3 Z4.45", "G0 F1800 X85.188 Y66.146", ";End gcode ", - "G1 X23.3 Z4.45" + "G1 X23.3 Z4.45", ] gode = parseGCode(gcode) layers = gode.layers self.assertEqual(4, len(layers)) # one dummy layer - self.assertSequenceEqual(layers[0], - [[[81.848, 55.873, 0.2], [83.547, 53.478, 1.5], [83.756, 53.208, 1.5]], - [[56.78, 12.34, 0.5], [5, 7, 6]]]) - self.assertSequenceEqual(layers[1], - [[[84.696, 66.058, 2.3], [85.223, 65.95, 2.3]]]) - self.assertSequenceEqual(layers[2], - [[[85.223, 65.95, 2.3], [89.223, 67.95, 2.3], [23.3, 67.95, 4.45]]]) + self.assertSequenceEqual( + layers[0], + [ + [[81.848, 55.873, 0.2], [83.547, 53.478, 1.5], [83.756, 53.208, 1.5]], + [[56.78, 12.34, 0.5], [5, 7, 6]], + ], + ) + self.assertSequenceEqual( + layers[1], [[[84.696, 66.058, 2.3], [85.223, 65.95, 2.3]]] + ) + self.assertSequenceEqual( + layers[2], + [[[85.223, 65.95, 2.3], [89.223, 67.95, 2.3], [23.3, 67.95, 4.45]]], + ) rotations = gode.rotations self.assertEqual(2, len(rotations)) @@ -68,5 +89,5 @@ def testParseGCode(self): self.assertSequenceEqual([0, 0, 1, 1], gode.lays2rots) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main()