From a5202e1630dd900ad74dab7269e6c51e4241cbe9 Mon Sep 17 00:00:00 2001 From: bklronin Date: Sat, 26 Oct 2024 18:02:06 +0200 Subject: [PATCH] - Basic oop sketch widget implement --- .gitignore | 3 +- drawing_modules/draw_widget2d.py | 32 +- drawing_modules/draw_widget_solve.py | 932 +++++++++++++++++++++++++++ main.py | 5 +- 4 files changed, 941 insertions(+), 31 deletions(-) create mode 100644 drawing_modules/draw_widget_solve.py diff --git a/.gitignore b/.gitignore index cb46844..3c40110 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.xml -*.iml \ No newline at end of file +*.iml +.idea \ No newline at end of file diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index b42072f..ea7f469 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -383,8 +383,6 @@ class SketchWidget(QWidget): # Track Relationship # Points - # CONSTRAINTS - if event.button() == Qt.LeftButton and self.mouse_mode == "pt_pt": if self.hovered_point and not self.main_buffer[0]: self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) @@ -773,7 +771,7 @@ class SketchWidget(QWidget): return self.width() / self.height() * (1.0 / abs(self.zoom)) -class Point2D_ALT: +class Point2D: """Improved oop aaproach?""" def __init__(self): self.ui_point = None @@ -901,35 +899,11 @@ class Point2D_ALT: return QPoint(self.to_quadrant_coords(qt_pos)) - -class Point2D: - ui_x: int = None - ui_y: int = None - handle = None - class Line2D: - crd1: Point2D = None - crd2: Point2D = None - handle = None + pass class Sketch2d(SolverSystem): - def __init__(self): - self.wp = self.create_2d_base() - self.points = [] - self.lines = [] - - def add_point(self, point: Point2D): - point.handle = self.add_point_2d(point.ui_x, point.ui_y, self.wo) - self.points.append(point) - - def add_line(self, line: Line2D): - line.handle = self.add_line_2d(line.crd1, line.crd2, self.wp) - self.lines.append(line) - - - - - + if __name__ == "__main__": import sys diff --git a/drawing_modules/draw_widget_solve.py b/drawing_modules/draw_widget_solve.py new file mode 100644 index 0000000..8e060d8 --- /dev/null +++ b/drawing_modules/draw_widget_solve.py @@ -0,0 +1,932 @@ +import math +import re +from copy import copy +from typing import Optional + +import numpy as np +from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QInputDialog +from PySide6.QtGui import QPainter, QPen, QColor, QTransform +from PySide6.QtCore import Qt, QPoint, QPointF, Signal, QLine +from python_solvespace import SolverSystem, ResultFlag + + +class SketchWidget(QWidget): + constrain_done = Signal() + + def __init__(self): + super().__init__() + + self.line_draw_buffer = [None, None] + self.drag_buffer = [None, None] + self.main_buffer = [None, None] + + self.hovered_point = None + self.selected_line = None + + self.snapping_range = 20 # Range in pixels for snapping + self.zoom = 1 + + self.setMouseTracking(True) + self.mouse_mode = False + self.solv = SolverSystem() + + self.sketch = Sketch2d() + + def get_sketch(self): + return self.sketch + + def reset_buffers(self): + self.line_draw_buffer = [None, None] + self.drag_buffer = [None, None] + self.main_buffer = [None, None] + + def set_points(self, points: list): + self.points = points + #self.update() + + def create_workplane(self): + self.sketch.working_plane = self.solv.create_2d_base() + + def create_workplane_projected(self): + self.sketch.working_plane = self.solv.create_2d_base() + + def convert_proj_points(self): + out_points = [] + for point in self.sketch.proj_points: + x, y = point + coord = QPoint(x, y) + out_points.append(coord) + + self.sketch.proj_points = out_points + + def convert_proj_lines(self): + out_lines = [] + for line in self.sketch.proj_lines: + start = QPoint(line[0][0], line[0][1]) + end = QPoint(line[1][0], line[1][1]) + coord = QLine(start, end) + out_lines.append(coord) + self.sketch.proj_lines = out_lines + + def find_duplicate_points_2d(self, edges): + points = [] + seen = set() + duplicates = [] + + for edge in edges: + for point in edge: + # Extract only x and y coordinates + point_2d = (point[0], point[1]) + if point_2d in seen: + if point_2d not in duplicates: + duplicates.append(point_2d) + else: + seen.add(point_2d) + points.append(point_2d) + + return duplicates + + def normal_to_quaternion(self, normal): + normal = np.array(normal) + #normal = normal / np.linalg.norm(normal) + + axis = np.cross([0, 0, 1], normal) + if np.allclose(axis, 0): + axis = np.array([1, 0, 0]) + else: + axis = axis / np.linalg.norm(axis) # Normalize the axis + + angle = np.arccos(np.dot([0, 0, 1], normal)) + + qw = np.cos(angle / 2) + sin_half_angle = np.sin(angle / 2) + qx, qy, qz = axis * sin_half_angle # This will now work correctly + + return qw, qx, qy, qz + + def create_workplane_space(self, points, normal): + print("edges", points) + origin = self.find_duplicate_points_2d(points) + print(origin) + x, y = origin[0] + origin = QPoint(x, y) + + origin_handle = self.get_handle_from_ui_point(origin) + qw, qx, qy, qz = self.normal_to_quaternion(normal) + + slv_normal = self.solv.add_normal_3d(qw, qx, qy, qz) + self.sketch.working_plane = self.solv.add_work_plane(origin_handle, slv_normal) + print(self.sketch.working_plane) + + 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: + print("Handle number not found.") + return 0 + + 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 get_handle_from_ui_point(self, ui_point: QPoint): + """Input QPoint and you shall reveive a slvs entity handle!""" + for point in self.sketch.points: + if ui_point == point.ui_point: + slv_handle = point.handle + + return slv_handle + + def get_line_handle_from_ui_point(self, ui_point: QPoint): + """Input Qpoint that is on a line and you shall receive the handle of the line!""" + for target_line_con in self.sketch.lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + slv_handle = target_line_con['solv_handle'] + + return slv_handle + + def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: + """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" + for target_line_con in self.sketch.slv_lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + lines_to_cons = target_line_con['solv_entity_points'] + + return lines_to_cons + + def distance(self, p1, p2): + return math.sqrt((p1.x() - p2.x())**2 + (p1.y() - p2.y())**2) + + def calculate_midpoint(self, point1, point2): + mx = (point1.x() + point2.x()) // 2 + my = (point1.y() + point2.y()) // 2 + return QPoint(mx, my) + + def is_point_on_line(self, p, p1, p2, tolerance=5): + # Calculate the lengths of the sides of the triangle + a = self.distance(p, p1) + b = self.distance(p, p2) + c = self.distance(p1, p2) + + # Calculate the semi-perimeter + s = (a + b + c) / 2 + + # Calculate the area using Heron's formula + area = math.sqrt(s * (s - a) * (s - b) * (s - c)) + + # Calculate the height (perpendicular distance from the point to the line) + if c > 0: + height = (2 * area) / c + # Check if the height is within the tolerance distance to the line + if height > tolerance: + return False + + # Check if the projection of the point onto the line is within the line segment + dot_product = ((p.x() - p1.x()) * (p2.x() - p1.x()) + (p.y() - p1.y()) * (p2.y() - p1.y())) / (c ** 2) + + return 0 <= dot_product <= 1 + else: + return None + + def viewport_to_local_coord(self, qt_pos : QPoint) -> QPoint: + return QPoint(self.to_quadrant_coords(qt_pos)) + + def check_all_points(self,) -> list: + old_points_ui = [] + new_points_ui = [] + + for old_point_ui in self.sketch.points: + old_points_ui.append(old_point_ui.ui_point) + + for i in range(self.solv.entity_len()): + # Iterate though full length because mixed list from SS + entity = self.solv.entity(i) + if entity.is_point_2d() and self.solv.params(entity.params): + x_tbu, y_tbu = self.solv.params(entity.params) + point_solved = QPoint(x_tbu, y_tbu) + new_points_ui.append(point_solved) + + # Now we have old_points_ui and new_points_ui, let's compare them + differences = [] + + if len(old_points_ui) != len(new_points_ui): + print(f"Length mismatch {len(old_points_ui)} - {len(new_points_ui)}") + + for index, (old_point, new_point) in enumerate(zip(old_points_ui, new_points_ui)): + if old_point != new_point: + differences.append((index, old_point, new_point)) + + return differences + + def update_ui_points(self, point_list: list): + # Print initial state of slv_points_main + # print("Initial slv_points_main:", self.slv_points_main) + print("Change list:", point_list) + + if len(point_list) > 0: + for tbu_points_idx in point_list: + # Each tbu_points_idx is a tuple: (index, old_point, new_point) + index, old_point, new_point = tbu_points_idx + + # Update the point in slv_points_main + self.sketch.points[index].point = new_point + # Print updated state + # print("Updated slv_points_main:", self.slv_points_main) + + def check_all_lines_and_update(self,changed_points: list): + for tbu_points_idx in changed_points: + index, old_point, new_point = tbu_points_idx + for line_needs_update in self.sketch.lines: + if old_point == line_needs_update.points[0]: + line_needs_update['ui_points'][0] = new_point + elif old_point == line_needs_update.points[1]: + line_needs_update['ui_points'][1] = new_point + + def mouseReleaseEvent(self, event): + local_event_pos = self.viewport_to_local_coord(event.pos()) + + if event.button() == Qt.LeftButton and not self.mouse_mode: + self.drag_buffer[1] = local_event_pos + + print("Le main buffer", self.drag_buffer) + + if not None in self.main_buffer and len(self.main_buffer) == 2: + entry = self.drag_buffer[0] + new_params = self.drag_buffer[1].x(), self.drag_buffer[1].y() + self.sketch.set_params(entry.params, new_params) + + self.sketch.solve() + + points_need_update = self.check_all_points() + self.update_ui_points(points_need_update) + self.check_all_lines_and_update(points_need_update) + + self.update() + self.drag_buffer = [None, None] + + def mousePressEvent(self, event): + local_event_pos = self.viewport_to_local_coord(event.pos()) + + + if event.button() == Qt.LeftButton and not self.mouse_mode: + self.drag_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) + + if event.button() == Qt.RightButton and self.mouse_mode: + self.reset_buffers() + + if event.button() == Qt.LeftButton and self.mouse_mode == "line": + if self.hovered_point: + clicked_pos = self.hovered_point + else: + clicked_pos = local_event_pos + + if not self.line_draw_buffer[0]: + + u = clicked_pos.x() + v = clicked_pos.y() + + point = Point2D(u,v) + self.sketch.add_point(point) + + self.line_draw_buffer[0] = point + + elif self.line_draw_buffer[0]: + + u = clicked_pos.x() + v = clicked_pos.y() + + point = Point2D(u, v) + self.sketch.add_point(point) + + self.line_draw_buffer[1] = point + + print("Buffer state", self.line_draw_buffer) + + if self.line_draw_buffer[0] and self.line_draw_buffer[1]: + + line = Line2D(self.line_draw_buffer[0], self.line_draw_buffer[1]) + self.sketch.add_line(line) + + # Reset the buffer for the next line segment + self.line_draw_buffer[0] = self.line_draw_buffer[1] + self.line_draw_buffer[1] = None + + # Track Relationship + # Points + + # CONSTRAINTS + + if event.button() == Qt.LeftButton and self.mouse_mode == "pt_pt": + if self.hovered_point and not self.main_buffer[0]: + self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) + + elif self.main_buffer[0]: + self.main_buffer[1] = self.get_handle_from_ui_point(self.hovered_point) + + if self.main_buffer[0] and self.main_buffer[1]: + print("buf", self.main_buffer) + + self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + 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.constrain_done.emit() + self.main_buffer = [None, None] + + if event.button() == Qt.LeftButton and self.mouse_mode == "pt_line": + print("ptline") + line_selected = None + + if self.hovered_point and not self.main_buffer[1]: + self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) + + elif self.main_buffer[0]: + self.main_buffer[1] = self.get_line_handle_from_ui_point(local_event_pos) + + # Contrain point to line + if self.main_buffer[1]: + self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + self.constrain_done.emit() + + 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.constrain_done.emit() + # Clear saved_points after solve attempt + self.main_buffer = [None, None] + + if event.button() == Qt.LeftButton and self.mouse_mode == "pb_con_mid": + print("ptline") + line_selected = None + + if self.hovered_point and not self.main_buffer[1]: + self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) + + elif self.main_buffer[0]: + self.main_buffer[1] = self.get_line_handle_from_ui_point(local_event_pos) + + # Contrain point to line + if self.main_buffer[1]: + self.solv.midpoint(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + 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.constrain_done.emit() + + self.main_buffer = [None, None] + + if event.button() == Qt.LeftButton and self.mouse_mode == "horiz": + + line_selected = self.get_line_handle_from_ui_point(local_event_pos) + + if line_selected: + self.solv.horizontal(line_selected, self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + 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") + + if event.button() == Qt.LeftButton and self.mouse_mode == "vert": + line_selected = self.get_line_handle_from_ui_point(local_event_pos) + + if line_selected: + self.solv.vertical(line_selected, self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + 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") + + if event.button() == Qt.LeftButton and self.mouse_mode == "distance": + # Depending on selected elemnts either point line or line distance + #print("distance") + e1 = None + e2 = None + + if self.hovered_point: + print("buf point") + # Get the point as UI point as buffer + self.main_buffer[0] = self.hovered_point + + elif self.selected_line: + # Get the point as UI point as buffer + self.main_buffer[1] = local_event_pos + + if self.main_buffer[0] and self.main_buffer[1]: + # Define point line combination + e1 = self.get_handle_from_ui_point(self.main_buffer[0]) + e2 = self.get_line_handle_from_ui_point(self.main_buffer[1]) + + elif not self.main_buffer[0]: + # Define only line selection + e1, e2 = self.get_point_line_handles_from_ui_point(local_event_pos) + + if e1 and e2: + # Ask fo the dimension and solve if both elements are present + length, ok = QInputDialog.getDouble(self, 'Distance', 'Enter a mm value:', value=100, decimals=2) + self.solv.distance(e1, e2, length, self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + 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.constrain_done.emit() + self.main_buffer = [None, None] + + # Update the main point list with the new elements and draw them + points_need_update = self.check_all_points() + self.update_ui_points(points_need_update) + self.check_all_lines_and_update(points_need_update) + + self.update() + + def mouseMoveEvent(self, event): + local_event_pos = self.viewport_to_local_coord(event.pos()) + + closest_point = None + min_distance = float('inf') + threshold = 10 # Distance threshold for highlighting + + if len(self.sketch.points) > 0: + + for point in self.sketch.points: + distance = (local_event_pos - point.ui_point).manhattanLength() + if distance < threshold and distance < min_distance: + closest_point = point.ui_point + min_distance = distance + + """for point in self.sketch.proj_points: + distance = (local_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 + print(self.hovered_point) + + for line in self.sketch.lines: + p1 = line.crd1.ui_point + p2 = line.crd2.ui_point + + if self.is_point_on_line(local_event_pos, p1, p2): + self.selected_line = p1, p2 + break + else: + self.selected_line = None + + self.update() + + def mouseDoubleClickEvent(self, event): + pass + + def drawBackgroundGrid(self, painter): + """Draw a background grid.""" + grid_spacing = 50 + pen = QPen(QColor(200, 200, 200), 1, Qt.SolidLine) + painter.setPen(pen) + + # Draw vertical grid lines + for x in range(-self.width() // 2, self.width() // 2, grid_spacing): + painter.drawLine(x, -self.height() // 2, x, self.height() // 2) + + # Draw horizontal grid lines + for y in range(-self.height() // 2, self.height() // 2, grid_spacing): + painter.drawLine(-self.width() // 2, y, self.width() // 2, y) + + def drawAxes(self, painter): + painter.setRenderHint(QPainter.Antialiasing) + + # Set up pen for dashed lines + pen = QPen(Qt.gray, 1, Qt.DashLine) + painter.setPen(pen) + + middle_x = self.width() // 2 + middle_y = self.height() // 2 + + # Draw X axis as dashed line + painter.drawLine(0, middle_y, self.width(), middle_y) + + # Draw Y axis as dashed line + painter.drawLine(middle_x, 0, middle_x, self.height()) + + # Draw tick marks + tick_length = int(10 * self.zoom) + tick_spacing = int(50 * self.zoom) + + pen = QPen(Qt.gray, 1, Qt.SolidLine) + painter.setPen(pen) + + # Draw tick marks on the X axis to the right and left from the middle point + for x in range(0, self.width() // 2, tick_spacing): + painter.drawLine(middle_x + x, middle_y - tick_length // 2, middle_x + x, middle_y + tick_length // 2) + painter.drawLine(middle_x - x, middle_y - tick_length // 2, middle_x - x, middle_y + tick_length // 2) + + # Draw tick marks on the Y axis upwards and downwards from the middle point + for y in range(0, self.height() // 2, tick_spacing): + painter.drawLine(middle_x - tick_length // 2, middle_y + y, middle_x + tick_length // 2, middle_y + y) + painter.drawLine(middle_x - tick_length // 2, middle_y - y, middle_x + tick_length // 2, middle_y - y) + + # Draw the origin point in red + painter.setPen(QPen(Qt.red, 4)) + painter.drawPoint(middle_x, middle_y) + + def draw_cross(self, painter, pos: QPoint, size=10): + # Set up the pen + pen = QPen(QColor('green')) # You can change the color as needed + pen.setWidth(int(2 / self.zoom)) # Set the line widt)h + painter.setPen(pen) + x = pos.x() + y = pos.y() + + # Calculate the endpoints of the cross + half_size = size // 2 + + # Draw the horizontal line + painter.drawLine(x - half_size, y, x + half_size, y) + + # Draw the vertical line + painter.drawLine(x, y - half_size, x, y + half_size) + + def to_quadrant_coords(self, point): + """Translate linear coordinates to quadrant coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + quadrant_x = point.x() - center_x + quadrant_y = center_y - point.y() # Note the change here + return QPoint(quadrant_x, quadrant_y) / self.zoom + + def from_quadrant_coords(self, point: QPoint): + """Translate quadrant coordinates to linear coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + widget_x = center_x + point.x() * self.zoom + widget_y = center_y - point.y() * self.zoom # Note the subtraction here + + return QPoint(int(widget_x), int(widget_y)) + + def from_quadrant_coords_no_center(self, point): + """Invert Y Coordinate for mesh""" + center_x = 0 + center_y = 0 + widget_x = point.x() + widget_y = -point.y() + return QPoint(int(widget_x), int(widget_y)) + + def paintEvent(self, event): + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + + self.drawAxes(painter) + + # Create a QTransform object + transform = QTransform() + + # Translate the origin to the center of the widget + center = QPointF(self.width() / 2, self.height() / 2) + transform.translate(center.x(), center.y()) + + # Apply the zoom factor + transform.scale(self.zoom, -self.zoom) # Negative y-scale to invert y-axis + + # Set the transform to the painter + painter.setTransform(transform) + + pen = QPen(Qt.gray) + pen.setWidthF(2 / self.zoom) + painter.setPen(pen) + + # Draw points + if self.sketch: + for point in self.sketch.points: + painter.drawEllipse(point.ui_point, 3 / self.zoom, 3 / self.zoom) + + for line in self.sketch.lines: + p1 = line.crd1.ui_point + p2 = line.crd2.ui_point + painter.drawLine(p1, p2) + + dis = self.distance(p1, p2) + mid = self.calculate_midpoint(p1, p2) + painter.drawText(mid, str(round(dis, 2))) + + pen = QPen(Qt.green) + pen.setWidthF(2 / self.zoom) + painter.setPen(pen) + + if self.solv.entity_len(): + for i in range(self.solv.entity_len()): + entity = self.solv.entity(i) + if entity.is_point_2d() and self.solv.params(entity.params): + x, y = self.solv.params(entity.params) + point = QPointF(x, y) + painter.drawEllipse(point, 6 / self.zoom, 6 / self.zoom) + + # Highlight point hovered + if self.hovered_point: + highlight_pen = QPen(QColor(255, 0, 0)) + highlight_pen.setWidthF(2 / self.zoom) + painter.setPen(highlight_pen) + painter.drawEllipse(self.hovered_point, 5 / self.zoom, 5 / self.zoom) + + # Highlight line hovered + if self.selected_line and not self.hovered_point: + p1, p2 = self.selected_line + painter.setPen(QPen(Qt.red, 2 / self.zoom)) + painter.drawLine(p1, p2) + + """for cross in self.sketch.proj_points: + self.draw_cross(painter, cross, 10 / self.zoom) + + for selected in self.sketch.proj_lines: + pen = QPen(Qt.white, 1, Qt.DashLine) + painter.setPen(pen) + painter.drawLine(selected)""" + + painter.end() + + def wheelEvent(self, event): + delta = event.angleDelta().y() + self.zoom += (delta / 200) * 0.1 + self.update() + + def aspect_ratio(self): + return self.width() / self.height() * (1.0 / abs(self.zoom)) + + +class Point2D_ALT: + """Improved oop aaproach?""" + def __init__(self): + self.ui_point = None + self.solve_handle_nr = None + self.solve_handle = None + self.part_of_entity = None + + def to_quadrant_coords(self, point): + """Translate linear coordinates to quadrant coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + quadrant_x = point.x() - center_x + quadrant_y = center_y - point.y() # Note the change here + + return QPoint(quadrant_x, quadrant_y) / self.zoom + + def from_quadrant_coords(self, point: QPoint): + """Translate quadrant coordinates to linear coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + widget_x = center_x + point.x() * self.zoom + widget_y = center_y - point.y() * self.zoom # Note the subtraction here + + return QPoint(int(widget_x), int(widget_y)) + + def from_quadrant_coords_no_center(self, point): + """Invert Y Coordinate for mesh""" + center_x = 0 + center_y = 0 + widget_x = point.x() + widget_y = -point.y() + + return QPoint(int(widget_x), int(widget_y)) + + 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: + print("Handle number not found.") + return 0 + + 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 get_handle_from_ui_point(self, ui_point: QPoint): + """Input QPoint and you shall reveive a slvs entity handle!""" + for point in self.sketch.slv_points: + if ui_point == point['ui_point']: + slv_handle = point['solv_handle'] + + return slv_handle + + def get_line_handle_from_ui_point(self, ui_point: QPoint): + """Input Qpoint that is on a line and you shall receive the handle of the line!""" + for target_line_con in self.sketch.slv_lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + slv_handle = target_line_con['solv_handle'] + + return slv_handle + + def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: + """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" + for target_line_con in self.sketch.slv_lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + lines_to_cons = target_line_con['solv_entity_points'] + + return lines_to_cons + + def distance(self, p1, p2): + return math.sqrt((p1.x() - p2.x())**2 + (p1.y() - p2.y())**2) + + def calculate_midpoint(self, point1, point2): + mx = (point1.x() + point2.x()) // 2 + my = (point1.y() + point2.y()) // 2 + return QPoint(mx, my) + + def is_point_on_line(self, p, p1, p2, tolerance=5): + # Calculate the lengths of the sides of the triangle + a = self.distance(p, p1) + b = self.distance(p, p2) + c = self.distance(p1, p2) + + # Calculate the semi-perimeter + s = (a + b + c) / 2 + + # Calculate the area using Heron's formula + area = math.sqrt(s * (s - a) * (s - b) * (s - c)) + + # Calculate the height (perpendicular distance from the point to the line) + if c > 0: + height = (2 * area) / c + # Check if the height is within the tolerance distance to the line + if height > tolerance: + return False + + # Check if the projection of the point onto the line is within the line segment + dot_product = ((p.x() - p1.x()) * (p2.x() - p1.x()) + (p.y() - p1.y()) * (p2.y() - p1.y())) / (c ** 2) + + return 0 <= dot_product <= 1 + else: + return None + + def viewport_to_local_coord(self, qt_pos : QPoint) -> QPoint: + return QPoint(self.to_quadrant_coords(qt_pos)) + + + +class Point2D: + def __init__(self, x, y): + self.ui_x: int = x + self.ui_y: int = y + self.ui_point = QPoint(self.ui_x, self.ui_y) + self.handle = None + self.handle_nr: int = None + +class Line2D: + def __init__(self, point_s: Point2D, point_e: Point2D): + self.crd1: Point2D = point_s + self.crd2: Point2D = point_e + self.handle = None + self.handle_nr = None + +class Sketch2d(SolverSystem): + """ + Primary class for internal drawing based on the SolveSpace libray + """ + + def __init__(self): + self.wp = self.create_2d_base() + self.points = [] + self.lines = [] + + def add_point(self, point: Point2D): + """ + Adds a point into the solversystem and reurns the handle. + Appends the added point to the points list. + :param point: 2D point in Point2D class format + :return: + """ + + point.handle = self.add_point_2d(point.ui_x, point.ui_y, self.wp) + point.handle_nr = self.get_handle_nr(str(point.handle)) + + self.points.append(point) + + def add_line(self, line: Line2D): + """ + Adds a line into the solversystem and returns the handle. + Appends the added line to the line list. + :param line: + :param point: 2D point in Point2D class format + :return: + """ + + line.handle = self.add_line_2d(line.crd1.handle, line.crd2.handle, self.wp) + line.handle_nr = self.get_handle_nr(str(line.handle)) + + self.lines.append(line) + + ### HELPER AND TOOLS + 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: + print("Handle number not found.") + return 0 + +if __name__ == "__main__": + import sys + + app = QApplication(sys.argv) + window = SketchWidget() + window.setWindowTitle("Snap Line Widget") + window.resize(800, 600) + window.show() + sys.exit(app.exec()) diff --git a/main.py b/main.py index 405349a..aa12c1f 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDial from Gui import Ui_fluencyCAD # Import the generated GUI module from drawing_modules.vtk_widget import VTKWidget from drawing_modules.vysta_widget import PyVistaWidget -from drawing_modules.draw_widget2d import SketchWidget +from drawing_modules.draw_widget_solve import SketchWidget from sdf import * from python_solvespace import SolverSystem, ResultFlag from mesh_modules import simple_mesh, vesta_mesh, interactor_mesh @@ -294,7 +294,10 @@ class MainWindow(QMainWindow): self.custom_3D_Widget.clear_actors_normals() def add_sketch(self): + """ + :return: + """ sketch = self.sketchWidget.get_sketch() sketch.convert_points_for_sdf()