diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index cf2122b..4ef7b86 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -5,7 +5,7 @@ from copy import copy 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 +from PySide6.QtCore import Qt, QPoint, QPointF, Signal, QLine from python_solvespace import SolverSystem, ResultFlag @@ -51,13 +51,11 @@ class SketchWidget(QWidget): def create_workplane_projected(self): self.wp = self.solv.create_2d_base() - def create_proj_lines(self, lines): + def create_proj_points(self, proj_points): """Lines as orientation projected from the sketch""" - for point in lines: + for point in proj_points: x, y = point - self.proj_snap_lines = lines - # Invert X from projection might be happening in the projection for some reason Careful coord = QPoint(x, y) self.proj_snap_points.append(coord) @@ -69,6 +67,24 @@ class SketchWidget(QWidget): self.slv_points_main.append(relation_point)""" + def create_proj_lines(self, sel_edges): + """Lines as orientation projected from the sketch""" + print("Incoming corrd lines", sel_edges) + for line in sel_edges: + + start = QPoint(line[0][0], line[0][1] ) + end = QPoint(line[1][0], line[1][1]) + coord = QLine(start, end) + self.proj_snap_lines.append(coord) + + """relation_point = {} # Reinitialize the dictionary + #handle_nr = self.get_handle_nr(str(point)) + #relation_point['handle_nr'] = handle_nr + #relation_point['solv_handle'] = point + relation_point['ui_point'] = QPoint(x, y) + + self.slv_points_main.append(relation_point)""" + def find_duplicate_points_2d(self, edges): points = [] seen = set() @@ -675,11 +691,11 @@ class SketchWidget(QWidget): return QPoint(int(widget_x), int(widget_y)) def from_quadrant_coords_no_center(self, point): - """Translate quadrant coordinates to linear coordinates.""" + """Invert Y Coordinate for mesh""" center_x = 0 center_y = 0 - widget_x = center_x + point.x() * self.zoom - widget_y = center_y - point.y() * self.zoom # Note the subtraction here + widget_x = point.x() + widget_y = -point.y() return QPoint(int(widget_x), int(widget_y)) def paintEvent(self, event): @@ -746,6 +762,11 @@ class SketchWidget(QWidget): for cross in self.proj_snap_points: self.draw_cross(painter, cross, 10 / self.zoom) + for selected in self.proj_snap_lines: + pen = QPen(Qt.white, 1, Qt.DashLine) + painter.setPen(pen) + painter.drawLine(selected) + painter.end() def wheelEvent(self, event): diff --git a/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py index 772188c..9836d3f 100644 --- a/drawing_modules/vtk_widget.py +++ b/drawing_modules/vtk_widget.py @@ -13,6 +13,7 @@ class VTKWidget(QtWidgets.QWidget): def __init__(self, parent=None): super().__init__(parent) + self.selected_vtk_line = [] self.access_selected_points = [] self.selected_normal = None self.centroid = None @@ -21,7 +22,8 @@ class VTKWidget(QtWidgets.QWidget): self.local_matrix = None - self.project_tosketch_edge = [] + self.project_tosketch_points = [] + self.project_tosketch_lines = [] self.vtk_widget = QVTKRenderWindowInteractor(self) @@ -112,10 +114,12 @@ class VTKWidget(QtWidgets.QWidget): mapper.SetInputData(grid) actor = vtk.vtkActor() + actor.SetPickable(False) actor.SetMapper(mapper) actor.GetProperty().SetColor(0.5, 0.5, 0.5) # Set grid color to gray self.renderer.AddActor(actor) + def create_grid(self, size=100, spacing=10): # Create a vtkPoints object and store the points in it points = vtk.vtkPoints() @@ -169,7 +173,7 @@ class VTKWidget(QtWidgets.QWidget): normal = normal / np.linalg.norm(normal) return normal - def load_interactor_mesh(self, edges): + def load_interactor_mesh(self, edges, off_vector): # Create vtkPoints to store all points points = vtk.vtkPoints() @@ -195,28 +199,34 @@ class VTKWidget(QtWidgets.QWidget): polydata.SetLines(lines) # Create a transform for mirroring across the y-axis - mirror_transform = vtk.vtkTransform() + matrix_transform = vtk.vtkTransform() - """if self.local_matrix: + if self.local_matrix: print(self.local_matrix) matrix = vtk.vtkMatrix4x4() matrix.DeepCopy(self.local_matrix) matrix.Invert() - mirror_transform.SetMatrix(matrix) + matrix_transform.SetMatrix(matrix) + #matrix_transform.Scale(1, 1, 1) # This mirrors across the y-axis - mirror_transform.Scale(-1, -1, 1) # Inverting the original mirror look down - - mirror_transform.Scale(1, 1, 1) # This mirrors across the y-axis""" - - # Apply the transform to the polydata + # Apply the matrix transform transformFilter = vtk.vtkTransformPolyDataFilter() transformFilter.SetInputData(polydata) - transformFilter.SetTransform(mirror_transform) + transformFilter.SetTransform(matrix_transform) transformFilter.Update() + # Create and apply the offset transform + offset_transform = vtk.vtkTransform() + offset_transform.Translate(off_vector[0], off_vector[1], off_vector[2]) + + offsetFilter = vtk.vtkTransformPolyDataFilter() + offsetFilter.SetInputConnection(transformFilter.GetOutputPort()) + offsetFilter.SetTransform(offset_transform) + offsetFilter.Update() + # Create a mapper and actor mapper = vtk.vtkPolyDataMapper() - mapper.SetInputData(transformFilter.GetOutput()) + mapper.SetInputConnection(offsetFilter.GetOutputPort()) actor = vtk.vtkActor() actor.SetMapper(mapper) @@ -270,6 +280,9 @@ class VTKWidget(QtWidgets.QWidget): actor.GetProperty().SetColor(color) actor.GetProperty().EdgeVisibilityOff() actor.GetProperty().SetLineWidth(line_width) + actor.GetProperty().SetMetallic(1) + actor.GetProperty().SetOpacity(0.8) + actor.SetPickable(False) self.renderer.AddActor(actor) self.body_actors_orig.append(actor) @@ -385,6 +398,62 @@ class VTKWidget(QtWidgets.QWidget): return xy_coordinates + def compute_2d_coordinates_line(self, line_cell, normal): + # Ensure the input is a vtkLine + if not isinstance(line_cell, vtk.vtkLine): + raise ValueError("Input must be a vtkLine cell") + + # Normalize the normal vector + normal = np.array(normal) + normal = normal / np.linalg.norm(normal) + + # Create a vtkTransform + transform = vtk.vtkTransform() + transform.PostMultiply() # This ensures transforms are applied in the order we specify + + # Rotate so that the normal aligns with the Z-axis + rotation_axis = np.cross(normal, [0, 0, 1]) + angle = np.arccos(np.dot(normal, [0, 0, 1])) * 180 / np.pi # Convert to degrees + + if np.linalg.norm(rotation_axis) > 1e-6: # Check if rotation is needed + transform.RotateWXYZ(angle, rotation_axis[0], rotation_axis[1], rotation_axis[2]) + + # Get the transformation matrix + matrix = transform.GetMatrix() + local_matrix = [matrix.GetElement(i, j) for i in range(4) for j in range(4)] + + # Create a vtkPoints object to store the line points + points = vtk.vtkPoints() + points.InsertNextPoint(line_cell.GetPoints().GetPoint(0)) + points.InsertNextPoint(line_cell.GetPoints().GetPoint(1)) + + # Create a vtkPolyData to represent the line + polydata = vtk.vtkPolyData() + polydata.SetPoints(points) + + # Create a vtkCellArray to store the line cell + cells = vtk.vtkCellArray() + cells.InsertNextCell(line_cell) + polydata.SetLines(cells) + + # Apply the transform to the polydata + transform_filter = vtk.vtkTransformPolyDataFilter() + transform_filter.SetInputData(polydata) + transform_filter.SetTransform(transform) + transform_filter.Update() + + # Get the transformed points + transformed_polydata = transform_filter.GetOutput() + transformed_points = transformed_polydata.GetPoints() + + # Extract 2D coordinates + xy_coordinates = [] + for i in range(transformed_points.GetNumberOfPoints()): + point = transformed_points.GetPoint(i) + xy_coordinates.append((point[0], point[1])) + + return xy_coordinates + def project_2d_to_3d(self, xy_coordinates, normal): # Normalize the normal vector normal = np.array(normal) @@ -494,6 +563,8 @@ class VTKWidget(QtWidgets.QWidget): # Ensure it's a line if cell.GetCellType() == vtk.VTK_LINE: + self.selected_vtk_line.append(cell) + # Get the two points of the line point_id1 = cell.GetPointId(0) point_id2 = cell.GetPointId(1) @@ -533,9 +604,11 @@ class VTKWidget(QtWidgets.QWidget): if len(self.selected_edges) == 2: self.compute_projection(False) - elif len(self.selected_edges) > 2: - del self.selected_edges[0] - pass + if len(self.selected_edges) > 2: + self.selected_vtk_line.clear() + self.selected_edges.clear() + self.clear_actors_projection() + self.clear_edge_select() def find_origin_vertex(self, edge1, edge2): if edge1[0] == edge2[0]or edge1[0] == edge2[1]: @@ -559,6 +632,7 @@ class VTKWidget(QtWidgets.QWidget): self.renderer.RemoveActor(normals) def compute_projection(self, direction_invert: bool= False): + # Compute the normal from the two selected edges ) edge1 = self.selected_edges[0][1] - self.selected_edges[0][0] edge2 = self.selected_edges[1][1] - self.selected_edges[1][0] @@ -583,11 +657,14 @@ class VTKWidget(QtWidgets.QWidget): polydata = self.picker.GetActor().GetMapper().GetInput() projected_polydata = self.project_mesh_to_plane(polydata, self.selected_normal, self.centroid) - projected_points = projected_polydata.GetPoints() - #print("proj_points", projected_points) # Extract 2D coordinates - self.project_tosketch_edge = self.compute_2d_coordinates(projected_polydata, self.selected_normal) + self.project_tosketch_points = self.compute_2d_coordinates(projected_polydata, self.selected_normal) + + # Seperately rotate selected edges for drawing + for vtk_line in self.selected_vtk_line: + proj_vtk_line = self.compute_2d_coordinates_line(vtk_line, self.selected_normal) + self.project_tosketch_lines.append(proj_vtk_line) # Create a mapper and actor for the projected data mapper = vtk.vtkPolyDataMapper() @@ -595,13 +672,14 @@ class VTKWidget(QtWidgets.QWidget): actor = vtk.vtkActor() actor.SetMapper(mapper) + #actor.GetProperty().SetRenderLinesAsTubes(True) actor.GetProperty().SetColor(0.0, 1.0, 0.0) # Set color to green - actor.GetProperty().SetLineWidth(4) # Set line width + actor.GetProperty().SetLineWidth(8) # Set line width self.renderer.AddActor(normal_actor) self.displayed_normal_actors.append(normal_actor) - # Add the actor to the scene + # Add the edge lines actor to the scene self.renderer.AddActor(actor) self.picked_edge_actors.append(actor) diff --git a/main.py b/main.py index 27d4514..aa0163d 100644 --- a/main.py +++ b/main.py @@ -133,11 +133,14 @@ class MainWindow(QMainWindow): def add_new_sketch_wp(self): self.sketchWidget.clear_sketch() #edges = [((-158.0, -20.0, -25.0), (286.0, -195.0, -25.0)), ((-158.0, -20.0, 25.0), (-158.0, -20.0, -25.0))] - edges = self.custom_3D_Widget.project_tosketch_edge + points = self.custom_3D_Widget.project_tosketch_points normal = self.custom_3D_Widget.selected_normal + selected_lines = self.custom_3D_Widget.project_tosketch_lines + print("Selected lines", selected_lines) self.sketchWidget.create_workplane_projected() - self.sketchWidget.create_proj_lines(edges) + self.sketchWidget.create_proj_points(points) + self.sketchWidget.create_proj_lines(selected_lines) # CLear all selections after it has been projected #self.custom_3D_Widget.clear_edge_select() @@ -377,7 +380,6 @@ class MainWindow(QMainWindow): selected = self.ui.sketch_list.currentItem() name = selected.text() points = self.model['sketch'][name]['sketch_points'] - lines = self.convert_lines_for_interactor() if points[-1] == points[0]: #detect loop that causes problems in mesh generation @@ -391,12 +393,8 @@ class MainWindow(QMainWindow): length = 0 print("Extrude cancelled") - #Create and draw Interactor geo = Geometry() - # Rotation is done in vtk matrix trans - angle = 0 - normal = self.custom_3D_Widget.selected_normal print("Normie enter", normal) if normal is None: @@ -407,20 +405,9 @@ class MainWindow(QMainWindow): centroid = [0, 0, 0] else: centroid = list(centroid) - print("THis centroid ",centroid) + print("This centroid ", centroid) - f = geo.extrude_shape(points, length, angle, normal, centroid, is_symmetric, invert) - - z_origin = centroid[2] - if is_symmetric: - z_origin = z_origin - length / 2 - - if invert: - edges = interactor_mesh.generate_mesh(lines, z_origin, length, True) - else: - edges = interactor_mesh.generate_mesh(lines, z_origin, length, False) - - self.custom_3D_Widget.load_interactor_mesh(edges) + f = geo.extrude_shape(points, length, normal, centroid, is_symmetric, invert, 0) name_op = f"extrd-{name}" element = { @@ -428,7 +415,18 @@ class MainWindow(QMainWindow): 'type': 'extrude', 'sdf_object': f, } - #print(element) + + ### Interactor + lines = self.convert_lines_for_interactor() + + edges = interactor_mesh.generate_mesh(lines, 0, length) + + offset_vector = geo.vector_to_centroid(None, centroid, normal) + #print("off_ved", offset_vector) + if len(offset_vector) == 0 : + offset_vector = [0, 0, 0] + + self.custom_3D_Widget.load_interactor_mesh(edges, offset_vector) self.model['operation'][name_op] = element self.ui.body_list.addItem(name_op) @@ -500,7 +498,22 @@ class Geometry: print("p2", p2) return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) - def extrude_shape(self, points, length: float, angle, normal, centroid, symet: bool = True, invert: bool = False): + def vector_to_centroid(self, shape_center, centroid, normal): + + if not shape_center: + # Calculate the current center of the shape + shape_center = [0, 0, 0] + + # Calculate the vector from the shape's center to the centroid + center_to_centroid = np.array(centroid) - np.array(shape_center) + + # Project this vector onto the normal to get the required translation along the normal + translation_along_normal = np.dot(center_to_centroid, normal) * normal + + return translation_along_normal + + def extrude_shape(self, points, length: float, normal, centroid, symet: bool = True, invert: bool = False, + offset_length: float = None): """ Extrude a 2D shape into 3D, orient it along the normal, and position it relative to the centroid. """ @@ -520,17 +533,29 @@ class Geometry: # Orient the shape along the normal vector f = f.orient(normal) - # Calculate the current center of the shape - shape_center = [0,0,0] + offset_vector = self.vector_to_centroid(None, centroid, normal) + # Adjust the offset vector by subtracting the inset distance along the normal direction + adjusted_offset = offset_vector - (normal * length) + if invert: + # Translate the shape along the adjusted offset vector + f = f.translate(adjusted_offset) + else: + f = f.translate(offset_vector) - # Calculate the vector from the shape's center to the centroid - center_to_centroid = np.array(centroid) - np.array(shape_center) + # If offset_length is provided, adjust the offset_vector + if offset_length is not None: + # Check if offset_vector is not a zero vector + offset_vector_magnitude = np.linalg.norm(offset_vector) + if offset_vector_magnitude > 1e-10: # Use a small threshold to avoid floating-point issues + # Normalize the offset vector + offset_vector_norm = offset_vector / offset_vector_magnitude + # Scale the normalized vector by the desired length + offset_vector = offset_vector_norm * offset_length + f = f.translate(offset_vector) + else: + print("Warning: Offset vector has zero magnitude. Using original vector.") - # Project this vector onto the normal to get the required translation along the normal - translation_along_normal = np.dot(center_to_centroid, normal) * normal - - # Translate the shape along the normal - f = f.translate(translation_along_normal) + # Translate the shape along the adjusted offset vector return f diff --git a/mesh_modules/interactor_mesh.py b/mesh_modules/interactor_mesh.py index e8d2b3a..26d0e1c 100644 --- a/mesh_modules/interactor_mesh.py +++ b/mesh_modules/interactor_mesh.py @@ -1,8 +1,9 @@ # Draw simple boundary based on the lines and depth -def generate_mesh(lines: list, z_origin: float, depth: float, invert :bool = False): +def generate_mesh(lines: list, z_origin: float, depth: float, invert: bool = False): origin = create_3D(lines, z_origin) + if invert : extruded = create_3D(lines, z_origin - depth) else: