From edbd5ed0d3387493939882ab9eaf69932b0848cb Mon Sep 17 00:00:00 2001 From: bklronin Date: Wed, 19 Jun 2024 17:14:58 +0200 Subject: [PATCH] - Started Solvespace implementation solver --- Gui.py | 152 +++++++++++--------- drawing_modules/draw_widget2d.py | 193 ++++++++++++++++++++------ drawing_modules/solvespace_example.py | 25 ++++ gui.ui | 159 ++++++++++++--------- main.py | 22 ++- 5 files changed, 374 insertions(+), 177 deletions(-) create mode 100644 drawing_modules/solvespace_example.py diff --git a/Gui.py b/Gui.py index a77a575..79fd224 100644 --- a/Gui.py +++ b/Gui.py @@ -127,47 +127,13 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.gl_box, 0, 4, 8, 1) - self.groupBox_2 = QGroupBox(self.centralwidget) - self.groupBox_2.setObjectName(u"groupBox_2") - sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) - self.groupBox_2.setSizePolicy(sizePolicy2) - self.gridLayout_2 = QGridLayout(self.groupBox_2) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.pb_rectool = QPushButton(self.groupBox_2) - self.pb_rectool.setObjectName(u"pb_rectool") - - self.gridLayout_2.addWidget(self.pb_rectool, 0, 1, 1, 1, Qt.AlignTop) - - self.pb_linetool = QPushButton(self.groupBox_2) - self.pb_linetool.setObjectName(u"pb_linetool") - self.pb_linetool.setCheckable(True) - self.pb_linetool.setAutoExclusive(True) - - self.gridLayout_2.addWidget(self.pb_linetool, 0, 0, 1, 1, Qt.AlignTop) - - self.pb_circtool = QPushButton(self.groupBox_2) - self.pb_circtool.setObjectName(u"pb_circtool") - - self.gridLayout_2.addWidget(self.pb_circtool, 1, 0, 1, 1, Qt.AlignTop) - - self.pb_slotool = QPushButton(self.groupBox_2) - self.pb_slotool.setObjectName(u"pb_slotool") - - self.gridLayout_2.addWidget(self.pb_slotool, 1, 1, 1, 1, Qt.AlignTop) - - - self.gridLayout.addWidget(self.groupBox_2, 0, 0, 1, 1) - self.groupBox_5 = QGroupBox(self.centralwidget) self.groupBox_5.setObjectName(u"groupBox_5") - sizePolicy3 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - sizePolicy3.setHorizontalStretch(0) - sizePolicy3.setVerticalStretch(0) - sizePolicy3.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth()) - self.groupBox_5.setSizePolicy(sizePolicy3) + sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth()) + self.groupBox_5.setSizePolicy(sizePolicy2) self.groupBox_5.setMaximumSize(QSize(300, 16777215)) self.verticalLayout_3 = QVBoxLayout(self.groupBox_5) self.verticalLayout_3.setObjectName(u"verticalLayout_3") @@ -179,8 +145,11 @@ class Ui_fluencyCAD(object): self.groupBox_6 = QGroupBox(self.groupBox_5) self.groupBox_6.setObjectName(u"groupBox_6") - sizePolicy2.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth()) - self.groupBox_6.setSizePolicy(sizePolicy2) + sizePolicy3 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy3.setHorizontalStretch(0) + sizePolicy3.setVerticalStretch(0) + sizePolicy3.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth()) + self.groupBox_6.setSizePolicy(sizePolicy3) self.gridLayout_6 = QGridLayout(self.groupBox_6) self.gridLayout_6.setObjectName(u"gridLayout_6") self.gridLayout_6.setContentsMargins(2, 2, 2, 2) @@ -210,8 +179,8 @@ class Ui_fluencyCAD(object): self.groupBox_8 = QGroupBox(self.groupBox_5) self.groupBox_8.setObjectName(u"groupBox_8") - sizePolicy2.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth()) - self.groupBox_8.setSizePolicy(sizePolicy2) + sizePolicy3.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth()) + self.groupBox_8.setSizePolicy(sizePolicy3) self.gridLayout_8 = QGridLayout(self.groupBox_8) self.gridLayout_8.setObjectName(u"gridLayout_8") self.gridLayout_8.setContentsMargins(2, 2, 2, 2) @@ -236,19 +205,81 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.groupBox_5, 0, 3, 8, 1) + self.groupBox_4 = QGroupBox(self.centralwidget) + self.groupBox_4.setObjectName(u"groupBox_4") + self.verticalLayout_2 = QVBoxLayout(self.groupBox_4) + self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.pushButton_2 = QPushButton(self.groupBox_4) + self.pushButton_2.setObjectName(u"pushButton_2") + + self.verticalLayout_2.addWidget(self.pushButton_2) + + + self.gridLayout.addWidget(self.groupBox_4, 7, 5, 1, 1) + + self.groupBox_9 = QGroupBox(self.centralwidget) + self.groupBox_9.setObjectName(u"groupBox_9") + self.gridLayout_7 = QGridLayout(self.groupBox_9) + self.gridLayout_7.setObjectName(u"gridLayout_7") + self.pb_origin_wp = QPushButton(self.groupBox_9) + self.pb_origin_wp.setObjectName(u"pb_origin_wp") + + self.gridLayout_7.addWidget(self.pb_origin_wp, 0, 0, 1, 1) + + self.pb_origin_face = QPushButton(self.groupBox_9) + self.pb_origin_face.setObjectName(u"pb_origin_face") + + self.gridLayout_7.addWidget(self.pb_origin_face, 0, 1, 1, 1) + + + self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1) + + self.groupBox_2 = QGroupBox(self.centralwidget) + self.groupBox_2.setObjectName(u"groupBox_2") + sizePolicy3.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) + self.groupBox_2.setSizePolicy(sizePolicy3) + self.gridLayout_2 = QGridLayout(self.groupBox_2) + self.gridLayout_2.setObjectName(u"gridLayout_2") + self.pb_rectool = QPushButton(self.groupBox_2) + self.pb_rectool.setObjectName(u"pb_rectool") + + self.gridLayout_2.addWidget(self.pb_rectool, 1, 1, 1, 1, Qt.AlignTop) + + self.pb_circtool = QPushButton(self.groupBox_2) + self.pb_circtool.setObjectName(u"pb_circtool") + + self.gridLayout_2.addWidget(self.pb_circtool, 2, 0, 1, 1, Qt.AlignTop) + + self.pb_slotool = QPushButton(self.groupBox_2) + self.pb_slotool.setObjectName(u"pb_slotool") + + self.gridLayout_2.addWidget(self.pb_slotool, 2, 1, 1, 1, Qt.AlignTop) + + self.pb_linetool = QPushButton(self.groupBox_2) + self.pb_linetool.setObjectName(u"pb_linetool") + self.pb_linetool.setCheckable(True) + self.pb_linetool.setAutoExclusive(True) + + self.gridLayout_2.addWidget(self.pb_linetool, 1, 0, 1, 1) + + + self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1) + self.groupBox_3 = QGroupBox(self.centralwidget) self.groupBox_3.setObjectName(u"groupBox_3") - sizePolicy2.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) - self.groupBox_3.setSizePolicy(sizePolicy2) + sizePolicy3.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) + self.groupBox_3.setSizePolicy(sizePolicy3) self.gridLayout_4 = QGridLayout(self.groupBox_3) self.gridLayout_4.setObjectName(u"gridLayout_4") self.pb_con_line = QPushButton(self.groupBox_3) self.pb_con_line.setObjectName(u"pb_con_line") + self.pb_con_line.setCheckable(True) self.gridLayout_4.addWidget(self.pb_con_line, 0, 1, 1, 1) self.pb_con_ptpt = QPushButton(self.groupBox_3) self.pb_con_ptpt.setObjectName(u"pb_con_ptpt") + self.pb_con_ptpt.setCheckable(True) self.gridLayout_4.addWidget(self.pb_con_ptpt, 0, 0, 1, 1) @@ -263,19 +294,7 @@ class Ui_fluencyCAD(object): self.gridLayout_4.addWidget(self.pb_con_vert, 1, 1, 1, 1) - self.gridLayout.addWidget(self.groupBox_3, 1, 0, 1, 1) - - self.groupBox_4 = QGroupBox(self.centralwidget) - self.groupBox_4.setObjectName(u"groupBox_4") - self.verticalLayout_2 = QVBoxLayout(self.groupBox_4) - self.verticalLayout_2.setObjectName(u"verticalLayout_2") - self.pushButton_2 = QPushButton(self.groupBox_4) - self.pushButton_2.setObjectName(u"pushButton_2") - - self.verticalLayout_2.addWidget(self.pushButton_2) - - - self.gridLayout.addWidget(self.groupBox_4, 7, 5, 1, 1) + self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1) fluencyCAD.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(fluencyCAD) @@ -309,11 +328,6 @@ class Ui_fluencyCAD(object): self.pb_arrayop.setText(QCoreApplication.translate("fluencyCAD", u"Arry", None)) self.pb_revop.setText(QCoreApplication.translate("fluencyCAD", u"Rev", None)) self.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", None)) - self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None)) - self.pb_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None)) - self.pb_linetool.setText(QCoreApplication.translate("fluencyCAD", u"Line", None)) - self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None)) - self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None)) self.groupBox_5.setTitle(QCoreApplication.translate("fluencyCAD", u"Sketch", None)) self.groupBox_6.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None)) self.pb_del_sketch.setText(QCoreApplication.translate("fluencyCAD", u"Del sketch", None)) @@ -323,12 +337,20 @@ class Ui_fluencyCAD(object): self.pb_del_body.setText(QCoreApplication.translate("fluencyCAD", u"Del Bdy", None)) self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Bdy Upd", None)) self.pb_edt_sktch_3.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None)) + self.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) + self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None)) + self.groupBox_9.setTitle(QCoreApplication.translate("fluencyCAD", u"Workplanes", None)) + self.pb_origin_wp.setText(QCoreApplication.translate("fluencyCAD", u"WP Origin", None)) + self.pb_origin_face.setText(QCoreApplication.translate("fluencyCAD", u" WP Face", None)) + self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None)) + self.pb_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None)) + self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None)) + self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None)) + self.pb_linetool.setText(QCoreApplication.translate("fluencyCAD", u"Line", None)) self.groupBox_3.setTitle(QCoreApplication.translate("fluencyCAD", u"Constrain", None)) self.pb_con_line.setText(QCoreApplication.translate("fluencyCAD", u"Pt_Line", None)) self.pb_con_ptpt.setText(QCoreApplication.translate("fluencyCAD", u"Pt_Pt", None)) self.pb_con_horiz.setText(QCoreApplication.translate("fluencyCAD", u"Horiz", None)) self.pb_con_vert.setText(QCoreApplication.translate("fluencyCAD", u"Vert", None)) - self.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) - self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None)) # retranslateUi diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index 5014333..51efbe4 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -1,3 +1,6 @@ +import re +from copy import copy + from PySide6.QtWidgets import QApplication, QWidget, QMessageBox from PySide6.QtGui import QPainter, QPen, QColor from PySide6.QtCore import Qt, QPoint @@ -6,61 +9,154 @@ from python_solvespace import SolverSystem, ResultFlag class SketchWidget(QWidget): def __init__(self): super().__init__() + self.hovered_point = None + self.line_buffer = None + self.pt_pt_buffer = None + + self.points = [] self.selected_line = None self.snapping_range = 20 # Range in pixels for snapping - self.line_mode = False + self.setMouseTracking(True) + self.mouse_mode = False + self.wp = None self.solv = SolverSystem() - def solve_constraint(self): - solv = SolverSystem() - wp = solv.create_2d_base() # Workplane (Entity) - p0 = solv.add_point_2d(0, 0, wp) # Entity - solv.dragged(p0, wp) # Make a constraint with the entity - ... - line0 = solv.add_line_2d(p0, p1, wp) # Create entity with others - ... - line1 = solv.add_line_2d(p0, p3, wp) - solv.angle(line0, line1, 45, wp) # Constrain two entities - line1 = solv.entity(-1) # Entity handle can be re-generated and negatively indexed - ... - if solv.solve() == ResultFlag.OKAY: - # Get the result (unpack from the entity or parameters) - # x and y are actually float type - dof = solv.dof() - x, y = solv.params(p2.params) - ... + self.solventies = {} + + + + + def create_worplane(self): + self.wp = self.solv.create_2d_base() + + def get_handle_nr(self, input_str: str) -> int: + # Define the regex pattern to extract the handle number + pattern = r"handle=(\d+)" + + # Use re.search to find the handle number in the string + match = re.search(pattern, input_str) + + if match: + handle_number = int(match.group(1)) + print(f"Handle number: {handle_number}") + + return int(handle_number) + else: - # Error! - # Get the list of all constraints - failures = solv.failures() - ... + print("Handle number not found.") + return 0 - def set_points(self, points: list): - self.points = points - #self.update() + def get_keys(self, d: dict, target: QPoint) -> list: + result = [] + path = [] + print(d) + print(target) + for k, v in d.items(): + path.append(k) + if isinstance(v, dict): + self.get_keys(v, target) + if v == target: + result.append(copy(path)) + path.pop() + return result def mousePressEvent(self, event): - if event.button() == Qt.LeftButton and self.line_mode: - self.points.append(event.pos()) - self.update() + relation = { + 'handle_nr': None, + 'solv_handle': None + } - elif event.button() == Qt.RightButton: - for i in range(len(self.points) - 1): - if self.is_point_on_line(event.pos(), self.points[i], self.points[i + 1]): - self.selected_line = i - break - else: - self.selected_line = None + if event.button() == Qt.LeftButton and self.mouse_mode == "line": + clicked_pos = event.pos() + + # Paintline + """self.points.append(clicked_pos) + self.update()""" + + u = clicked_pos.x() + v = clicked_pos.y() + point = self.solv.add_point_2d(u, v, self.wp) + + #print(point) + + self.solv.dragged(point, self.wp) + + # Solverline + if self.line_buffer: + line = self.solv.add_line_2d(self.line_buffer, point, self.wp) + #print(line) + + self.line_buffer = point + + # Track Relationship + handle_nr = self.get_handle_nr(str(point)) + relation['handle_nr'] = handle_nr + relation['solv_handle'] = point + + self.solventies[clicked_pos] = relation + + self.points = list(self.solventies) + #print(self.points) + + if event.button() == Qt.LeftButton and self.mouse_mode == "pt_pt": + + point_solve_now = self.solventies[self.hovered_point]['solv_handle'] + + if self.pt_pt_buffer: + point_solve_old = self.solventies[self.pt_pt_buffer]['solv_handle'] + print(point_solve_old) + print(point_solve_now) + self.solv.coincident(point_solve_now, point_solve_old, self.wp) + + if self.solv.solve() == ResultFlag.OKAY: + # Get the result (unpack from the entity or parameters) + # x and y are actually float type + dof = self.solv.dof() + print(dof) + + if self.pt_pt_buffer: + # Get the entry form the old point and copy it into new point with the new key and postion (key = postiion + move_point = self.solventies[self.pt_pt_buffer] + print(move_point) + del self.solventies[self.pt_pt_buffer] + self.solventies[self.hovered_point] = move_point + #print(f"Coordinates: {x1}, {y1}; {x2}, {y2}") + + elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge" ) + + elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns" ) + + elif self.solv.solve() == ResultFlag.INCONSISTENT: + print("Solve_failed - Incons" ) + + self.points = [] + self.points = list(self.solventies) + print(self.points) + + self.pt_pt_buffer = self.hovered_point + + + + def mouseMoveEvent(self, event): - if event.buttons() & Qt.RightButton: - if self.selected_line is not None: - self.points[self.selected_line] = event.pos() - else: - self.points[-1] = event.pos() + closest_point = None + min_distance = float('inf') + threshold = 10 # Distance threshold for highlighting + + for point in self.points: + distance = (event.pos() - point).manhattanLength() + if distance < threshold and distance < min_distance: + closest_point = point + min_distance = distance + + if closest_point != self.hovered_point: + self.hovered_point = closest_point self.update() def mouseDoubleClickEvent(self, event): @@ -69,6 +165,10 @@ class SketchWidget(QWidget): def distance(self, p1, p2): return ((p1.x() - p2.x()) ** 2 + (p1.y() - p2.y()) ** 2) ** 0.5 + def set_points(self, points: list): + self.points = points + #self.update() + def is_point_on_line(self, p, p1, p2): distance1 = self.distance(p, p1) distance2 = self.distance(p, p2) @@ -93,11 +193,18 @@ class SketchWidget(QWidget): for point in self.points: painter.drawEllipse(point, 3, 3) - if self.selected_line is not None: + #Highlight point hovered + if self.hovered_point: + highlight_pen = QPen(QColor(255, 0, 0)) + highlight_pen.setWidth(2) + painter.setPen(highlight_pen) + painter.drawEllipse(self.hovered_point, 5, 5) + + """if self.selected_line is not None: p1 = self.points[self.selected_line] p2 = self.points[self.selected_line + 1] painter.setPen(QPen(Qt.red, 2)) - painter.drawLine(p1, p2) + painter.drawLine(p1, p2)""" painter.end() def clear_sketch(self): diff --git a/drawing_modules/solvespace_example.py b/drawing_modules/solvespace_example.py new file mode 100644 index 0000000..e1aeb28 --- /dev/null +++ b/drawing_modules/solvespace_example.py @@ -0,0 +1,25 @@ +from python_solvespace import SolverSystem, ResultFlag + +def solve_constraint(self): + solv = SolverSystem() + wp = solv.create_2d_base() # Workplane (Entity) + p0 = solv.add_point_2d(0, 0, wp) # Entity + solv.dragged(p0, wp) # Make a constraint with the entity + ... + line0 = solv.add_line_2d(p0, p1, wp) # Create entity with others + ... + line1 = solv.add_line_2d(p0, p3, wp) + solv.angle(line0, line1, 45, wp) # Constrain two entities + line1 = solv.entity(-1) # Entity handle can be re-generated and negatively indexed + ... + if solv.solve() == ResultFlag.OKAY: + # Get the result (unpack from the entity or parameters) + # x and y are actually float type + dof = solv.dof() + x, y = solv.params(p2.params) + ... + else: + # Error! + # Get the list of all constraints + failures = solv.failures() + ... \ No newline at end of file diff --git a/gui.ui b/gui.ui index 2c6fc70..f55828b 100644 --- a/gui.ui +++ b/gui.ui @@ -144,55 +144,6 @@ - - - - - 0 - 0 - - - - Drawing - - - - - - Rctgl - - - - - - - Line - - - true - - - true - - - - - - - Circle - - - - - - - Slot - - - - - - @@ -324,7 +275,95 @@ + + + + Export + + + + + + STL + + + + + + + + + + Workplanes + + + + + + WP Origin + + + + + + + WP Face + + + + + + + + + + 0 + 0 + + + + Drawing + + + + + + Rctgl + + + + + + + Circle + + + + + + + Slot + + + + + + + Line + + + true + + + true + + + + + + + @@ -341,6 +380,9 @@ Pt_Line + + true + @@ -348,6 +390,9 @@ Pt_Pt + + true + @@ -367,22 +412,6 @@ - - - - Export - - - - - - STL - - - - - - diff --git a/main.py b/main.py index 89cebef..056f9e0 100644 --- a/main.py +++ b/main.py @@ -44,25 +44,39 @@ class MainWindow(QMainWindow): self.ui.sketch_list.itemChanged.connect(self.view_update) ### Sketches + self.ui.pb_origin_wp.pressed.connect(self.add_wp_origin) + self.ui.pb_nw_sktch.pressed.connect(self.add_sketch) self.ui.pb_del_sketch.pressed.connect(self.del_sketch) self.ui.pb_edt_sktch.pressed.connect(self.edit_sketch) + ###Modes self.ui.pb_linetool.pressed.connect(self.act_line_mode) + self.ui.pb_con_ptpt.pressed.connect(self.act_constrain_pt_pt_mode) ### Operations self.ui.pb_extrdop.pressed.connect(self.send_extrude) self.ui.pb_cutop.pressed.connect(self.send_cut) self.ui.pb_del_body.pressed.connect(self.del_body) + def add_wp_origin(self): + #Select orientation + #orientation, ok = Q .getDouble(self, 'Extrude Length', 'Enter a mm value:', decimals=2) + self.sketchWidget.create_worplane() + def act_line_mode(self): if not self.ui.pb_linetool.isChecked(): - self.sketchWidget.line_mode = True - self.sketchWidget.points = [] + self.sketchWidget.mouse_mode = 'line' + #self.sketchWidget.points = [] else: - self.sketchWidget.line_mode = False - + self.sketchWidget.mouse_mode = None + def act_constrain_pt_pt_mode(self): + if not self.ui.pb_linetool.isChecked(): + self.sketchWidget.mouse_mode = 'pt_pt' + #self.sketchWidget.points = [] + else: + self.sketchWidget.mouse_mode = None def view_update(self):