From 255f6d59a739bd2525c6ed877e8067dee7153c21 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 12 Oct 2021 14:27:16 +0800 Subject: [PATCH] Update Qt API and add a clipboard option for copied curve. --- pyslvs_ui/graphics/canvas.py | 8 +- pyslvs_ui/graphics/nx_layout.py | 4 +- pyslvs_ui/io/format_editor.py | 2 +- .../synthesis/collections/configure_widget.py | 2 +- pyslvs_ui/widgets/canvas_base.py | 66 +++++------ pyslvs_ui/widgets/inputs/__init__.py | 23 ++-- pyslvs_ui/widgets/inputs/inputs.ui | 111 ++++++++++-------- pyslvs_ui/widgets/inputs/inputs_ui.py | 55 +++++---- 8 files changed, 150 insertions(+), 121 deletions(-) diff --git a/pyslvs_ui/graphics/canvas.py b/pyslvs_ui/graphics/canvas.py index f63ddece2..924803246 100644 --- a/pyslvs_ui/graphics/canvas.py +++ b/pyslvs_ui/graphics/canvas.py @@ -16,7 +16,7 @@ from enum import auto, unique, IntEnum from math import radians, sin, cos, atan2, hypot, isnan from functools import reduce -from qtpy.QtCore import Slot, Qt, QPointF, QRectF, QSizeF, Signal +from qtpy.QtCore import Slot, Qt, QPointF, QRectF, QSizeF, Signal, QLineF from qtpy.QtWidgets import QWidget, QSizePolicy from qtpy.QtGui import ( QPolygonF, QPainter, QBrush, QPen, QColor, QFont, @@ -217,10 +217,10 @@ def paintEvent(self, event: QPaintEvent) -> None: self.painter.setPen(pen) x_l = -self.ox x_r = self.width() - self.ox - self.painter.drawLine(x_l, 0, x_r, 0) + self.painter.drawLine(QLineF(x_l, 0, x_r, 0)) y_t = self.height() - self.oy y_b = -self.oy - self.painter.drawLine(0, y_b, 0, y_t) + self.painter.drawLine(QLineF(0, y_b, 0, y_t)) def indexing(v: float) -> int: """Draw tick.""" @@ -378,7 +378,7 @@ def draw_arrow( y2 = (y1 + y2) / 2 - 7.5 * sin(a) first_point = QPointF(x2, -y2) if line: - self.painter.drawLine(x1, -y1, x2, -y2) + self.painter.drawLine(QLineF(x1, -y1, x2, -y2)) self.painter.drawLine(first_point, QPointF( x2 + 15 * cos(a + radians(20)), -y2 - 15 * sin(a + radians(20)) diff --git a/pyslvs_ui/graphics/nx_layout.py b/pyslvs_ui/graphics/nx_layout.py index 139e28090..6c9c4c955 100644 --- a/pyslvs_ui/graphics/nx_layout.py +++ b/pyslvs_ui/graphics/nx_layout.py @@ -8,7 +8,7 @@ __email__ = "pyslvs@gmail.com" from typing import Mapping, Tuple, Optional -from qtpy.QtCore import Qt, QSize, QPointF +from qtpy.QtCore import Qt, QSize, QPointF, QLineF from qtpy.QtGui import QImage, QPainter, QBrush, QPen, QIcon, QPixmap, QFont from pyslvs import edges_view from pyslvs.graph import Graph, external_loop_layout @@ -107,7 +107,7 @@ def graph2icon( else: pen.setColor(Qt.black) painter.setPen(pen) - painter.drawLine(pos[l1][0], -pos[l1][1], pos[l2][0], -pos[l2][1]) + painter.drawLine(QLineF(pos[l1][0], -pos[l1][1], pos[l2][0], -pos[l2][1])) else: color = color_qt('dark-gray') if monochrome else LINK_COLOR color.setAlpha(150) diff --git a/pyslvs_ui/io/format_editor.py b/pyslvs_ui/io/format_editor.py index 096d87cea..ff6958254 100644 --- a/pyslvs_ui/io/format_editor.py +++ b/pyslvs_ui/io/format_editor.py @@ -43,7 +43,7 @@ def format_name(self): elif self == ProjectFormat.PICKLE: return "Pickle" else: - raise KeyError("invalid format") + raise ValueError("invalid format") class FormatEditor(QObject, metaclass=QABCMeta): diff --git a/pyslvs_ui/synthesis/collections/configure_widget.py b/pyslvs_ui/synthesis/collections/configure_widget.py index c82b14425..d21ea25e1 100644 --- a/pyslvs_ui/synthesis/collections/configure_widget.py +++ b/pyslvs_ui/synthesis/collections/configure_widget.py @@ -387,7 +387,7 @@ def __set_target(self) -> None: @Slot(QListWidgetItem) def __set_parm_bind(self, _=None) -> None: """Set parameters binding.""" - link_expr_list: List[str] = [] + link_expr_list = [] for row, gs in enumerate(list_texts(self.grounded_list)): try: link_expr = [] diff --git a/pyslvs_ui/widgets/canvas_base.py b/pyslvs_ui/widgets/canvas_base.py index d0f520270..c51f91a5d 100644 --- a/pyslvs_ui/widgets/canvas_base.py +++ b/pyslvs_ui/widgets/canvas_base.py @@ -190,25 +190,25 @@ def __draw_frame(self) -> None: pos_y = -self.oy neg_x = -self.ox neg_y = self.height() - self.oy - self.painter.drawLine(neg_x, pos_y, pos_x, pos_y) - self.painter.drawLine(neg_x, neg_y, pos_x, neg_y) - self.painter.drawLine(neg_x, pos_y, neg_x, neg_y) - self.painter.drawLine(pos_x, pos_y, pos_x, neg_y) + self.painter.drawLine(QLineF(neg_x, pos_y, pos_x, pos_y)) + self.painter.drawLine(QLineF(neg_x, neg_y, pos_x, neg_y)) + self.painter.drawLine(QLineF(neg_x, pos_y, neg_x, neg_y)) + self.painter.drawLine(QLineF(pos_x, pos_y, pos_x, neg_y)) - def __draw_point(self, i: int, vpoint: VPoint) -> None: + def __draw_point(self, i: int, vpt: VPoint) -> None: """Draw a point.""" - connected = len(vpoint.links) - 1 - if vpoint.type in {VJoint.P, VJoint.RP}: - pen = QPen(color_qt(vpoint.color)) + connected = len(vpt.links) - 1 + if vpt.type in {VJoint.P, VJoint.RP}: + pen = QPen(color_qt(vpt.color)) pen.setWidth(2) # Draw slot point and pin point - for j, (cx, cy) in enumerate(vpoint.c): + for j, (cx, cy) in enumerate(vpt.c): # Slot point - if j == 0 or vpoint.type == VJoint.P: + if j == 0 or vpt.type == VJoint.P: if self.monochrome: color = Qt.black else: - color = color_qt(vpoint.color) + color = color_qt(vpt.color) pen.setColor(color) self.painter.setPen(pen) cp = QPointF(cx, -cy) * self.zoom @@ -223,43 +223,41 @@ def __draw_point(self, i: int, vpoint: VPoint) -> None: text += f":({cx:.02f}, {cy:.02f})" self.painter.drawText(cp + rp, text) else: - grounded = (len(vpoint.c) == len(vpoint.links) - and vpoint.links[j] == VLink.FRAME) - self.draw_point(i, cx, cy, grounded, vpoint.color, - connected) + grounded = (len(vpt.c) == len(vpt.links) + and vpt.links[j] == VLink.FRAME) + self.draw_point(i, cx, cy, grounded, vpt.color, connected) # Slider line - pen.setColor(color_qt(vpoint.color).darker()) + pen.setColor(color_qt(vpt.color).darker()) self.painter.setPen(pen) - qline_m = QLineF( - QPointF(vpoint.c[1, 0], -vpoint.c[1, 1]) * self.zoom, - QPointF(vpoint.c[0, 0], -vpoint.c[0, 1]) * self.zoom + line_m = QLineF( + QPointF(vpt.c[1, 0], -vpt.c[1, 1]) * self.zoom, + QPointF(vpt.c[0, 0], -vpt.c[0, 1]) * self.zoom ) - nv = qline_m.normalVector() + nv = line_m.normalVector() nv.setLength(self.joint_size) nv.setPoints(nv.p2(), nv.p1()) - qline_1 = nv.normalVector() - qline_1.setLength(qline_m.length()) - self.painter.drawLine(qline_1) + line1 = nv.normalVector() + line1.setLength(line_m.length()) + self.painter.drawLine(line1) nv.setLength(nv.length() * 2) nv.setPoints(nv.p2(), nv.p1()) - qline_2 = nv.normalVector() - qline_2.setLength(qline_m.length()) - qline_2.setAngle(qline_2.angle() + 180) - self.painter.drawLine(qline_2) + line2 = nv.normalVector() + line2.setLength(line_m.length()) + line2.setAngle(line2.angle() + 180) + self.painter.drawLine(line2) else: - self.draw_point(i, vpoint.cx, vpoint.cy, vpoint.grounded(), - vpoint.color, connected) - + self.draw_point(i, vpt.cx, vpt.cy, vpt.grounded(), vpt.color, + connected) # For selects function if self.select_mode == SelectMode.JOINT and (i in self.selections): pen = QPen(QColor(161, 16, 239)) pen.setWidth(3) self.painter.setPen(pen) - self.painter.drawRect( - vpoint.cx * self.zoom - 12, - vpoint.cy * -self.zoom - 12, + self.painter.drawRect(QRectF( + vpt.cx * self.zoom - 12, + vpt.cy * -self.zoom - 12, 24, 24 - ) + )) def __draw_link(self, vlink: VLink) -> None: """Draw a link.""" diff --git a/pyslvs_ui/widgets/inputs/__init__.py b/pyslvs_ui/widgets/inputs/__init__.py index 6be9181a3..2464d1e50 100644 --- a/pyslvs_ui/widgets/inputs/__init__.py +++ b/pyslvs_ui/widgets/inputs/__init__.py @@ -458,15 +458,17 @@ def __remove_path(self) -> None: def __path_dlg(self, item: QListWidgetItem) -> None: """View path data.""" name = item.text().split(":", maxsplit=1)[0] - try: - paths = self.__paths[name] - except KeyError: + if name not in self.__paths: return - points_text = ", ".join(f"Point{i}" for i in range(len(paths))) + paths = self.__paths[name] + if paths: + points_text = ", ".join(f"Point{i}" for i in range(len(paths))) + else: + points_text = "nothing" if QMessageBox.question( self, "Path data", - f"This path data including {points_text}.", + f"This path data includes {points_text}.", (QMessageBox.Save | QMessageBox.Close), QMessageBox.Close ) != QMessageBox.Save: @@ -506,9 +508,14 @@ def __copy_path_data(self) -> None: data = self.__paths[self.__current_path_name()] if not data: return - QApplication.clipboard().setText('\n'.join( - f"[{x}, {y}]," for x, y in data[self.plot_joint.currentIndex()] - )) + index = self.plot_joint.currentIndex() + if self.copy_as_csv.isChecked(): + text = '\n'.join(f"{x},{y}" for x, y in data[index]) + elif self.copy_as_array.isChecked(): + text = '\n'.join(f"[{x}, {y}]," for x, y in data[index]) + else: + raise ValueError("invalid option") + QApplication.clipboard().setText(text) @Slot(name='on_show_btn_clicked') def __show_path(self) -> None: diff --git a/pyslvs_ui/widgets/inputs/inputs.ui b/pyslvs_ui/widgets/inputs/inputs.ui index 768016135..9bb4f82e9 100644 --- a/pyslvs_ui/widgets/inputs/inputs.ui +++ b/pyslvs_ui/widgets/inputs/inputs.ui @@ -565,53 +565,44 @@ - - - - - Copy the data of this joint to clipboard. - - - Copy data - - - - icons:copy.pngicons:copy.png - - - - - - - - 0 - 0 - - - - Plot the data of this joint. - - - Plot - - - - icons:formula.pngicons:formula.png - - - - - - - Vector Animate - - - - icons:motor.pngicons:motor.png - - - - + + + Copy + + + + + + Comma-Separated Values + + + true + + + + + + + Array-like + + + + + + + Copy the data of this joint to clipboard. + + + Copy Curve Data + + + + icons:copy.pngicons:copy.png + + + + + @@ -755,6 +746,31 @@ + + + + Plot the data of this joint. + + + Plot + + + + icons:formula.pngicons:formula.png + + + + + + + Vector Animation + + + + icons:motor.pngicons:motor.png + + + @@ -766,6 +782,7 @@ + wrt_label diff --git a/pyslvs_ui/widgets/inputs/inputs_ui.py b/pyslvs_ui/widgets/inputs/inputs_ui.py index c6231f2dd..5011f357d 100644 --- a/pyslvs_ui/widgets/inputs/inputs_ui.py +++ b/pyslvs_ui/widgets/inputs/inputs_ui.py @@ -262,28 +262,22 @@ def setupUi(self, Form): self.show_all_btn.setObjectName("show_all_btn") self.horizontalLayout_5.addWidget(self.show_all_btn) self.verticalLayout_6.addLayout(self.horizontalLayout_5) - self.horizontalLayout_9 = QtWidgets.QHBoxLayout() - self.horizontalLayout_9.setObjectName("horizontalLayout_9") - self.cp_data_btn = QtWidgets.QPushButton(self.analysis_tab) + self.copy_format_groupbox = QtWidgets.QGroupBox(self.analysis_tab) + self.copy_format_groupbox.setObjectName("copy_format_groupbox") + self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.copy_format_groupbox) + self.verticalLayout_8.setObjectName("verticalLayout_8") + self.copy_as_csv = QtWidgets.QRadioButton(self.copy_format_groupbox) + self.copy_as_csv.setChecked(True) + self.copy_as_csv.setObjectName("copy_as_csv") + self.verticalLayout_8.addWidget(self.copy_as_csv) + self.copy_as_array = QtWidgets.QRadioButton(self.copy_format_groupbox) + self.copy_as_array.setObjectName("copy_as_array") + self.verticalLayout_8.addWidget(self.copy_as_array) + self.cp_data_btn = QtWidgets.QPushButton(self.copy_format_groupbox) self.cp_data_btn.setIcon(icon9) self.cp_data_btn.setObjectName("cp_data_btn") - self.horizontalLayout_9.addWidget(self.cp_data_btn) - self.plot_btn = QtWidgets.QPushButton(self.analysis_tab) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.plot_btn.sizePolicy().hasHeightForWidth()) - self.plot_btn.setSizePolicy(sizePolicy) - icon10 = QtGui.QIcon() - icon10.addPixmap(QtGui.QPixmap("icons:formula.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.plot_btn.setIcon(icon10) - self.plot_btn.setObjectName("plot_btn") - self.horizontalLayout_9.addWidget(self.plot_btn) - self.animate_btn = QtWidgets.QPushButton(self.analysis_tab) - self.animate_btn.setIcon(icon) - self.animate_btn.setObjectName("animate_btn") - self.horizontalLayout_9.addWidget(self.animate_btn) - self.verticalLayout_6.addLayout(self.horizontalLayout_9) + self.verticalLayout_8.addWidget(self.cp_data_btn) + self.verticalLayout_6.addWidget(self.copy_format_groupbox) self.plot_groupbox = QtWidgets.QGroupBox(self.analysis_tab) self.plot_groupbox.setObjectName("plot_groupbox") self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.plot_groupbox) @@ -357,6 +351,16 @@ def setupUi(self, Form): self.plot_curvature = QtWidgets.QCheckBox(self.plot_groupbox) self.plot_curvature.setObjectName("plot_curvature") self.gridLayout.addWidget(self.plot_curvature, 1, 1, 1, 1) + self.plot_btn = QtWidgets.QPushButton(self.plot_groupbox) + icon10 = QtGui.QIcon() + icon10.addPixmap(QtGui.QPixmap("icons:formula.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.plot_btn.setIcon(icon10) + self.plot_btn.setObjectName("plot_btn") + self.gridLayout.addWidget(self.plot_btn, 3, 1, 1, 1) + self.animate_btn = QtWidgets.QPushButton(self.plot_groupbox) + self.animate_btn.setIcon(icon) + self.animate_btn.setObjectName("animate_btn") + self.gridLayout.addWidget(self.animate_btn, 3, 2, 1, 1) self.verticalLayout_4.addLayout(self.gridLayout) self.verticalLayout_6.addWidget(self.plot_groupbox) self.tab_widget.addTab(self.analysis_tab, icon10, "") @@ -408,11 +412,11 @@ def retranslateUi(self, Form): self.show_btn.setStatusTip(_translate("Form", "Show this joint only.")) self.show_btn.setText(_translate("Form", "Show only")) self.show_all_btn.setText(_translate("Form", "Show all")) + self.copy_format_groupbox.setTitle(_translate("Form", "Copy")) + self.copy_as_csv.setText(_translate("Form", "Comma-Separated Values")) + self.copy_as_array.setText(_translate("Form", "Array-like")) self.cp_data_btn.setStatusTip(_translate("Form", "Copy the data of this joint to clipboard.")) - self.cp_data_btn.setText(_translate("Form", "Copy data")) - self.plot_btn.setStatusTip(_translate("Form", "Plot the data of this joint.")) - self.plot_btn.setText(_translate("Form", "Plot")) - self.animate_btn.setText(_translate("Form", "Vector Animate")) + self.cp_data_btn.setText(_translate("Form", "Copy Curve Data")) self.plot_groupbox.setTitle(_translate("Form", "Plot")) self.c_coord_sys.setText(_translate("Form", "&Cartesian coordinates")) self.p_coord_sys.setText(_translate("Form", "&Polar coordinates")) @@ -427,4 +431,7 @@ def retranslateUi(self, Form): self.plot_acc.setText(_translate("Form", "Acceleration")) self.plot_signature.setText(_translate("Form", "Path Signature")) self.plot_curvature.setText(_translate("Form", "Curvature")) + self.plot_btn.setStatusTip(_translate("Form", "Plot the data of this joint.")) + self.plot_btn.setText(_translate("Form", "Plot")) + self.animate_btn.setText(_translate("Form", "Vector Animation")) self.tab_widget.setTabText(self.tab_widget.indexOf(self.analysis_tab), _translate("Form", "Analysis"))