diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index c02d9bb..3def902 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -22,6 +22,8 @@ class SketchWidget(QWidget): self.drag_buffer = [None, None] self.main_buffer = [None, None] + self.proj_snap_points = [] + self.hovered_point = None self.selected_line = None @@ -54,23 +56,20 @@ class SketchWidget(QWidget): def create_proj_lines(self, lines): """Lines as orientation projected from the sketch""" - for line in lines: - for point in line: - x, y, z = point + for point in lines: + print(point) + x, y = point + self.proj_snap_points = lines - point = self.solv.add_point_2d(x, y, self.wp) + #point = self.solv.add_point_2d(x, y, self.wp) - 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) + """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) - - print("points", self.slv_points_main) - print("lines", self.slv_lines_main) - print("lines", lines) + self.slv_points_main.append(relation_point)""" def find_duplicate_points_2d(self, edges): points = [] @@ -635,6 +634,21 @@ class SketchWidget(QWidget): painter.setPen(QPen(Qt.red, 4)) painter.drawPoint(middle_x, middle_y) + def draw_cross(self, painter, x, y, size=10): + # Set up the pen + pen = QPen(QColor('red')) # You can change the color as needed + pen.setWidth(2) # Set the line width + painter.setPen(pen) + + # 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 @@ -663,6 +677,10 @@ class SketchWidget(QWidget): for point in self.slv_points_main: painter.drawEllipse(point['ui_point'], 3 / self.zoom, 3 / self.zoom) + for cross in self.proj_snap_points: + # Calculate the endpoints of the cross + self.draw_cross(painter, cross[0], cross[1], 10) + for dic in self.slv_lines_main: p1 = dic['ui_points'][0] p2 = dic['ui_points'][1] diff --git a/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py index 24463a8..16f7349 100644 --- a/drawing_modules/vtk_widget.py +++ b/drawing_modules/vtk_widget.py @@ -61,6 +61,24 @@ class VTKWidget(QtWidgets.QWidget): # Add observer for mouse clicks self.interactor.AddObserver("RightButtonPressEvent", self.on_click) + # Add axis gizmo (smaller size) + self.axes = vtk.vtkAxesActor() + self.axes.SetTotalLength(0.5, 0.5, 0.5) # Reduced size + self.axes.SetShaftType(0) + self.axes.SetAxisLabels(1) + + # Create an orientation marker + self.axes_widget = vtk.vtkOrientationMarkerWidget() + self.axes_widget.SetOrientationMarker(self.axes) + self.axes_widget.SetInteractor(self.interactor) + self.axes_widget.SetViewport(0.0, 0.0, 0.2, 0.2) # Set position and size + self.axes_widget.EnabledOn() + self.axes_widget.InteractiveOff() + + # Start the interactor + self.interactor.Initialize() + self.interactor.Start() + @staticmethod def compute_normal_from_lines(line1, line2): vec1 = line1[1] - line1[0] @@ -138,11 +156,23 @@ class VTKWidget(QtWidgets.QWidget): normalGenerator.ComputeCellNormalsOn() normalGenerator.Update() + ### There might be aproblem earlier but this fixes the drawing for now. + #TODO: Investigate upstream conversion errors. + # Create a transform for mirroring across the x-axis + mirror_transform = vtk.vtkTransform() + mirror_transform.Scale(-1, -1, 1) # This mirrors across the x-axis + + # Apply the transform to the polydata + transformFilter = vtk.vtkTransformPolyDataFilter() + transformFilter.SetInputData(polydata) + transformFilter.SetTransform(mirror_transform) + transformFilter.Update() + self.cell_normals = vtk_to_numpy(normalGenerator.GetOutput().GetCellData().GetNormals()) # Create a mapper and actor mapper = vtk.vtkPolyDataMapper() - mapper.SetInputData(polydata) + mapper.SetInputData(transformFilter.GetOutput()) actor = vtk.vtkActor() actor.SetMapper(mapper) @@ -212,6 +242,67 @@ class VTKWidget(QtWidgets.QWidget): return points, edges + def project_mesh_to_plane(self, input_mesh, normal, origin): + # Create the projector + projector = vtk.vtkProjectPointsToPlane() + projector.SetInputData(input_mesh) + projector.SetProjectionTypeToSpecifiedPlane() + + # Set the normal and origin of the plane + projector.SetNormal(normal) + projector.SetOrigin(origin) + + # Execute the projection + projector.Update() + + # Get the projected mesh + projected_mesh = projector.GetOutput() + return projected_mesh + + def compute_2d_coordinates(self, projected_mesh, normal): + # Compute centroid of projected points + center_of_mass = vtk.vtkCenterOfMass() + center_of_mass.SetInputData(projected_mesh) + center_of_mass.SetUseScalarsAsWeights(False) + center_of_mass.Update() + centroid = center_of_mass.GetCenter() + + # Create a coordinate system on the plane + z_axis = np.array(normal) + x_axis = np.cross(z_axis, [0, 0, 1]) + if np.allclose(x_axis, 0): + x_axis = np.cross(z_axis, [0, 1, 0]) + x_axis = x_axis / np.linalg.norm(x_axis) + y_axis = np.cross(z_axis, x_axis) + + # Create transformation matrix + matrix = vtk.vtkMatrix4x4() + for i in range(3): + matrix.SetElement(i, 0, x_axis[i]) + matrix.SetElement(i, 1, y_axis[i]) + matrix.SetElement(i, 2, z_axis[i]) + matrix.SetElement(i, 3, centroid[i]) + matrix.Invert() + + # Transform points to 2D coordinates + transform = vtk.vtkTransform() + transform.SetMatrix(matrix) + + transformer = vtk.vtkTransformPolyDataFilter() + transformer.SetInputData(projected_mesh) + transformer.SetTransform(transform) + transformer.Update() + + transformed_mesh = transformer.GetOutput() + points = transformed_mesh.GetPoints() + + xy_coordinates = [] + for i in range(points.GetNumberOfPoints()): + point = points.GetPoint(i) + xy_coordinates.append((point[0], point[1])) + + return xy_coordinates + def on_click(self, obj, event): click_pos = self.interactor.GetEventPosition() @@ -274,43 +365,15 @@ class VTKWidget(QtWidgets.QWidget): self.selected_normal = self.selected_normal / np.linalg.norm(self.selected_normal) print("Computed normal:", self.selected_normal) - # Compute the centroid of the selected edges - centroid = ( - 0, 0, 0) # point1 #np.mean([point for edge in self.selected_edges for point in edge], axis=0) + centroid = np.mean([point for edge in self.selected_edges for point in edge], axis=0) - # Create a transform for projection - transform = vtk.vtkTransform() - transform.Identity() + projected_polydata = self.project_mesh_to_plane(polydata, self.selected_normal, centroid) + projected_points = projected_polydata.GetPoints() + print("proj_points", projected_points) - # Translate to center the transform on the centroid - transform.Translate(-centroid[0], -centroid[1], -centroid[2]) - - # Compute rotation to align normal with Z-axis - z_axis = np.array([0, 0, 1]) - rotation_axis = np.cross(self.selected_normal, z_axis) - rotation_angle = np.arccos(np.dot(self.selected_normal, z_axis)) * 180 / np.pi - - if np.linalg.norm(rotation_axis) > 1e-6: # Avoid division by zero - transform.RotateWXYZ(rotation_angle, rotation_axis[0], rotation_axis[1], rotation_axis[2]) - - # Apply scaling to flatten - transform.Scale(1, 1, 0) - - # Apply the inverse rotation to bring it back to original orientation - transform.RotateWXYZ(-rotation_angle, rotation_axis[0], rotation_axis[1], rotation_axis[2]) - - # Translate back - transform.Translate(centroid[0], centroid[1], centroid[2]) - - # Apply the transform to the polydata - transformFilter = vtk.vtkTransformPolyDataFilter() - transformFilter.SetInputData(polydata) - transformFilter.SetTransform(transform) - transformFilter.Update() - - # Get the projected polydata - projected_polydata = transformFilter.GetOutput() - print("Polydata", projected_polydata) + # Extract 2D coordinates + self.project_tosketch_edge = self.compute_2d_coordinates(projected_polydata, self.selected_normal) + print("3d_points_proj", self.project_tosketch_edge) # Create a mapper and actor for the projected data mapper = vtk.vtkPolyDataMapper() @@ -324,41 +387,14 @@ class VTKWidget(QtWidgets.QWidget): # Add the actor to the scene self.renderer.AddActor(actor) - # Add a plane to visualize the projection plane - plane_source = vtk.vtkPlaneSource() - plane_source.SetCenter(centroid) - plane_source.SetNormal(self.selected_normal) - plane_mapper = vtk.vtkPolyDataMapper() - plane_mapper.SetInputConnection(plane_source.GetOutputPort()) - plane_actor = vtk.vtkActor() - plane_actor.SetMapper(plane_mapper) - plane_actor.GetProperty().SetColor(0.8, 0.8, 0.8) # Light gray - plane_actor.GetProperty().SetOpacity(0.5) - self.renderer.AddActor(plane_actor) - - # Reset camera to show all actors - self.renderer.ResetCamera() - - # Render and interact - self.vtk_widget.GetRenderWindow().Render() - - self.project_tosketch_edge = self.get_points_and_edges_from_polydata(projected_polydata) - print("Edges", self.project_tosketch_edge[0]) - - - elif len(self.access_selected_points) > 2: - self.access_selected_points = [] - # Clear the picked edge actors - for actor in self.picked_edge_actors: - self.renderer.RemoveActor(actor) - self.picked_edge_actors.clear() - # Reset selected edges + if len(self.selected_edges) > 2: self.selected_edges = [] - else: - print("Selected cell is not a line") + self.selected_normal = [] + # Render the scene self.vtk_widget.GetRenderWindow().Render() + def start(self): self.interactor.Initialize() self.interactor.Start() diff --git a/main.py b/main.py index 74143c8..d2f0be4 100644 --- a/main.py +++ b/main.py @@ -295,7 +295,20 @@ class MainWindow(QMainWindow): #Create and draw Interactor geo = Geometry() - f = geo.extrude_shape(points, length) + + # Get the nromal form the 3d widget for extrusion + if self.custom_3D_Widget.selected_normal is not None: + normal = self.custom_3D_Widget.selected_normal.tolist() + print("normal", normal) + angle = geo.angle_between_normals([0, 1, 0], normal) + print("Angle", angle) + f = geo.extrude_shape(points, length, angle, normal) + f = geo.mirror_body(f) + + else: + normal = [0,0,-1] + angle = 0 + f = geo.extrude_shape(points, length, angle, normal) name_op = f"extrd-{name}" element = { @@ -316,7 +329,6 @@ class MainWindow(QMainWindow): name = self.ui.body_list.currentItem().text() points = self.model['operation'][name]['sdf_object'] self.list_selected.append(points) - #print(self.list_selected) if len(self.list_selected) > 1: geo = Geometry() @@ -332,8 +344,8 @@ class MainWindow(QMainWindow): self.model['operation'][name_op] = element self.ui.body_list.addItem(name_op) items = self.ui.sketch_list.findItems(name_op, Qt.MatchExactly) - self.ui.body_list.setCurrentItem(items[-1]) - #self.draw_mesh() + #self.ui.body_list.setCurrentItem(items[-1]) + self.draw_mesh() else: print("mindestens 2!") @@ -342,17 +354,46 @@ class MainWindow(QMainWindow): self.custom_3D_Widget.update() class Geometry: + + def angle_between_normals(self, normal1, normal2): + # Ensure the vectors are normalized + n1 = normal1 / np.linalg.norm(normal1) + n2 = normal2 / np.linalg.norm(normal2) + + # Compute the dot product + dot_product = np.dot(n1, n2) + + # Clip the dot product to the valid range [-1, 1] + dot_product = np.clip(dot_product, -1.0, 1.0) + + # Compute the angle in radians + angle_rad = np.arccos(dot_product) + + # Convert to degrees if needed + angle_deg = np.degrees(angle_rad) + print("Angle deg", angle_deg) + + return angle_rad + def distance(self, p1, p2): """Calculate the distance between two points.""" print("p1", p1) print("p2", p2) return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) - def extrude_shape(self, points, length: float): + def extrude_shape(self, points, length: float, angle, normal): """2D to 3D sdf always first""" - f = polygon(points).extrude(length) + f = polygon(points).rotate(angle) + f = f.extrude(length).orient(normal) # orient(normal) + return f + def mirror_body(self, sdf_object3d): + f = sdf_object3d.rotate(pi) + + return f + + def cut_shapes(self, sdf_object1, sdf_object2): f = difference(sdf_object1, sdf_object2) # equivalent return f diff --git a/mesh_modules/interactor_mesh.py b/mesh_modules/interactor_mesh.py index b5397b2..916ddc3 100644 --- a/mesh_modules/interactor_mesh.py +++ b/mesh_modules/interactor_mesh.py @@ -31,11 +31,11 @@ def create_3D(lines, z_origin, depth): for coordinate2d in lines: start, end = coordinate2d - xs,ys = start - coordinate3d_start_orig = xs, ys, z_origin + depth + xs, ys = start + coordinate3d_start_orig = xs, -ys, z_origin + depth xe, ye = end - coordinate3d_end_orig = xe, ye, z_origin + depth + coordinate3d_end_orig = xe, -ye, z_origin + depth line3d_orig = coordinate3d_start_orig, coordinate3d_end_orig