diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index 3def902..3c73e6b 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -637,7 +637,7 @@ class SketchWidget(QWidget): 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 + pen.setWidth(int(2 / self.zoom)) # Set the line widt)h painter.setPen(pen) # Calculate the endpoints of the cross @@ -677,10 +677,6 @@ 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] @@ -716,6 +712,10 @@ class SketchWidget(QWidget): painter.setPen(QPen(Qt.red, 2)) painter.drawLine(p1, p2) + for cross in self.proj_snap_points: + # Calculate the endpoints of the cross + self.draw_cross(painter, cross[0], cross[1], 10) + # self.drawBackgroundGrid(painter) painter.end() diff --git a/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py index 16f7349..f65333b 100644 --- a/drawing_modules/vtk_widget.py +++ b/drawing_modules/vtk_widget.py @@ -18,6 +18,8 @@ class VTKWidget(QtWidgets.QWidget): self.selected_edges = [] self.cell_normals = None + self.local_matrix = None + self.project_tosketch_edge = [] self.vtk_widget = QVTKRenderWindowInteractor(self) @@ -113,10 +115,30 @@ class VTKWidget(QtWidgets.QWidget): polydata.SetPoints(points) polydata.SetLines(lines) + # Create a transform for mirroring across the y-axis + mirror_transform = vtk.vtkTransform() + + if self.local_matrix: + print(self.local_matrix) + matrix = vtk.vtkMatrix4x4() + matrix.DeepCopy(self.local_matrix) + matrix.Invert() + mirror_transform.SetMatrix(matrix) + + mirror_transform.Scale(-1, -1, 1) # Inverting the original mirror look down + else: + pass + #mirror_transform.Scale(1, -1, 1) # This mirrors across the y-axis + + # Apply the transform to the polydata + transformFilter = vtk.vtkTransformPolyDataFilter() + transformFilter.SetInputData(polydata) + transformFilter.SetTransform(mirror_transform) + transformFilter.Update() + # Create a mapper and actor mapper = vtk.vtkPolyDataMapper() - mapper.SetInputData(polydata) - + mapper.SetInputData(transformFilter.GetOutput()) actor = vtk.vtkActor() actor.SetMapper(mapper) @@ -130,18 +152,21 @@ class VTKWidget(QtWidgets.QWidget): mapper.Update() self.vtk_widget.GetRenderWindow().Render() - def render_from_points_direct_with_faces(self, vertices, faces): + + def render_from_points_direct_with_faces(self, vertices, faces, color=(1, 1, 1), line_width=2, point_size=5): points = vtk.vtkPoints() - for i in range(vertices.shape[0]): - points.InsertNextPoint(vertices[i]) + + # Use SetData with numpy array + vtk_array = numpy_to_vtk(vertices, deep=True) + points.SetData(vtk_array) # Create a vtkCellArray to store the triangles triangles = vtk.vtkCellArray() - for i in range(faces.shape[0]): + for face in faces: triangle = vtk.vtkTriangle() - triangle.GetPointIds().SetId(0, faces[i, 0]) - triangle.GetPointIds().SetId(1, faces[i, 1]) - triangle.GetPointIds().SetId(2, faces[i, 2]) + triangle.GetPointIds().SetId(0, face[0]) + triangle.GetPointIds().SetId(1, face[1]) + triangle.GetPointIds().SetId(2, face[2]) triangles.InsertNextCell(triangle) # Create a polydata object @@ -156,11 +181,19 @@ 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 + # Create a transform for mirroring across the y-axis mirror_transform = vtk.vtkTransform() - mirror_transform.Scale(-1, -1, 1) # This mirrors across the x-axis + + if self.local_matrix: + print(self.local_matrix) + matrix = vtk.vtkMatrix4x4() + matrix.DeepCopy(self.local_matrix) + matrix.Invert() + mirror_transform.SetMatrix(matrix) + + mirror_transform.Scale(-1, 1, 1) #Inverting the original mirror look down + else: + mirror_transform.Scale(1, -1, 1) # This mirrors across the y-axis # Apply the transform to the polydata transformFilter = vtk.vtkTransformPolyDataFilter() @@ -176,19 +209,45 @@ class VTKWidget(QtWidgets.QWidget): actor = vtk.vtkActor() actor.SetMapper(mapper) - actor.GetProperty().SetColor(1, 1, 1) # Set color (white in this case) - actor.GetProperty().EdgeVisibilityOn() # Show edges - actor.GetProperty().SetLineWidth(2) # Set line width + actor.GetProperty().SetColor(color) + actor.GetProperty().EdgeVisibilityOn() + actor.GetProperty().SetLineWidth(line_width) - - # (assuming you have the original mesh mapper and actor set up) - self.renderer.AddActor(actor) # Add the original mesh actor - # Add the edge actor to the renderer - - # Force an update of the pipeline - #mapper.Update() + self.renderer.AddActor(actor) self.vtk_widget.GetRenderWindow().Render() + def visualize_matrix(self, matrix): + points = vtk.vtkPoints() + for i in range(4): + for j in range(4): + points.InsertNextPoint(matrix.GetElement(0, j), + matrix.GetElement(1, j), + matrix.GetElement(2, j)) + + polydata = vtk.vtkPolyData() + polydata.SetPoints(points) + + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputData(polydata) + + actor = vtk.vtkActor() + actor.SetMapper(mapper) + actor.GetProperty().SetPointSize(5) + + self.renderer.AddActor(actor) + + def numpy_to_vtk(self, array, deep=True): + """Convert a numpy array to a vtk array.""" + vtk_array = vtk.vtkDoubleArray() + vtk_array.SetNumberOfComponents(array.shape[1]) + vtk_array.SetNumberOfTuples(array.shape[0]) + + for i in range(array.shape[0]): + for j in range(array.shape[1]): + vtk_array.SetComponent(i, j, array[i, j]) + + return vtk_array + def load_custom_mesh(self, vertices, faces): ### Load meshes by own module # Create a vtkPoints object and store the points in it @@ -282,11 +341,13 @@ class VTKWidget(QtWidgets.QWidget): matrix.SetElement(i, 1, y_axis[i]) matrix.SetElement(i, 2, z_axis[i]) matrix.SetElement(i, 3, centroid[i]) + self.local_matrix = matrix matrix.Invert() # Transform points to 2D coordinates transform = vtk.vtkTransform() transform.SetMatrix(matrix) + transform.Scale([1,1,1]) transformer = vtk.vtkTransformPolyDataFilter() transformer.SetInputData(projected_mesh) @@ -299,10 +360,84 @@ class VTKWidget(QtWidgets.QWidget): xy_coordinates = [] for i in range(points.GetNumberOfPoints()): point = points.GetPoint(i) - xy_coordinates.append((point[0], point[1])) + xy_coordinates.append((-point[0], point[1])) return xy_coordinates + def create_normal_gizmo(self, normal, scale=1.0): + # Normalize the normal vector + normal = np.array(normal) + normal = normal / np.linalg.norm(normal) + + # Create an arrow source + arrow_source = vtk.vtkArrowSource() + arrow_source.SetTipResolution(20) + arrow_source.SetShaftResolution(20) + + # Create a transform to orient and position the arrow + transform = vtk.vtkTransform() + + # Translate to the origin point + transform.SetMatrix(self.local_matrix) + + # Apply the transform to the arrow + transform_filter = vtk.vtkTransformPolyDataFilter() + transform_filter.SetInputConnection(arrow_source.GetOutputPort()) + transform_filter.SetTransform(transform) + transform_filter.Update() + + # Create mapper and actor + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(transform_filter.GetOutputPort()) + + actor = vtk.vtkActor() + actor.SetMapper(mapper) + actor.GetProperty().SetColor(1, 0, 0) # Red color for the arrow + + return actor + + def add_normal_line(self, origin, normal, length=10.0, color=(1, 0, 0)): + # Normalize the normal vector + normal = np.array(normal) + normal = normal / np.linalg.norm(normal) + + # Calculate the end point + end_point = origin + normal * length + + # Create vtkPoints + points = vtk.vtkPoints() + points.InsertNextPoint(origin) + points.InsertNextPoint(end_point) + + # Create a line + line = vtk.vtkLine() + line.GetPointIds().SetId(0, 0) + line.GetPointIds().SetId(1, 1) + + # Create a cell array to store the line + lines = vtk.vtkCellArray() + lines.InsertNextCell(line) + + # Create a polydata to store everything in + polyData = vtk.vtkPolyData() + polyData.SetPoints(points) + polyData.SetLines(lines) + + # Create mapper and actor + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputData(polyData) + + actor = vtk.vtkActor() + actor.SetMapper(mapper) + actor.GetProperty().SetColor(color) + actor.GetProperty().SetLineWidth(2) # Adjust line width as needed + + # Add to renderer + self.renderer.AddActor(actor) + self.vtk_widget.GetRenderWindow().Render() + + return actor # Return the actor in case you need to remove or modify it later + def on_click(self, obj, event): click_pos = self.interactor.GetEventPosition() @@ -367,6 +502,11 @@ class VTKWidget(QtWidgets.QWidget): centroid = np.mean([point for edge in self.selected_edges for point in edge], axis=0) + # Draw the normal line + normal_length = 50 # Adjust this value to change the length of the normal line + normal_actor = self.add_normal_line(centroid, self.selected_normal, length=normal_length, + color=(1, 0, 0)) + projected_polydata = self.project_mesh_to_plane(polydata, self.selected_normal, centroid) projected_points = projected_polydata.GetPoints() print("proj_points", projected_points) @@ -384,12 +524,20 @@ class VTKWidget(QtWidgets.QWidget): actor.GetProperty().SetColor(0.0, 1.0, 0.0) # Set color to green actor.GetProperty().SetLineWidth(4) # Set line width + self.renderer.AddActor(normal_actor) + # Add the actor to the scene self.renderer.AddActor(actor) - if len(self.selected_edges) > 2: + # Clear selection after self.selected_edges = [] self.selected_normal = [] + for edge_line in self.picked_edge_actors: + self.renderer.RemoveActor(edge_line) + + elif len(self.selected_edges) > 2: + pass + # Render the scene diff --git a/main.py b/main.py index d2f0be4..ed027e8 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ import uuid import names from PySide6.QtCore import Qt, QPoint -from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog +from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QDoubleSpinBox, QCheckBox, QPushButton from Gui import Ui_fluencyCAD # Import the generated GUI module from drawing_modules.vtk_widget import VTKWidget from drawing_modules.vysta_widget import PyVistaWidget @@ -13,6 +13,44 @@ from mesh_modules import simple_mesh, vesta_mesh, interactor_mesh # main, draw_widget, gl_widget +class ExtrudeDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle('Extrude Options') + + layout = QVBoxLayout() + + # Length input + length_layout = QHBoxLayout() + length_label = QLabel('Extrude Length (mm):') + self.length_input = QDoubleSpinBox() + self.length_input.setDecimals(2) + self.length_input.setRange(0, 1000) # Adjust range as needed + length_layout.addWidget(length_label) + length_layout.addWidget(self.length_input) + + # Symmetric checkbox + self.symmetric_checkbox = QCheckBox('Symmetric Extrude') + + # OK and Cancel buttons + button_layout = QHBoxLayout() + ok_button = QPushButton('OK') + cancel_button = QPushButton('Cancel') + ok_button.clicked.connect(self.accept) + cancel_button.clicked.connect(self.reject) + button_layout.addWidget(ok_button) + button_layout.addWidget(cancel_button) + + # Add all widgets to main layout + layout.addLayout(length_layout) + layout.addWidget(self.symmetric_checkbox) + layout.addLayout(button_layout) + + self.setLayout(layout) + + def get_values(self): + return self.length_input.value(), self.symmetric_checkbox.isChecked() + class MainWindow(QMainWindow): def __init__(self): super().__init__() @@ -184,7 +222,6 @@ class MainWindow(QMainWindow): return points_for_interact def add_sketch(self): - name = f"sketch-{str(names.get_first_name())}" points_for_sdf = self.convert_points_for_sdf() @@ -281,6 +318,8 @@ class MainWindow(QMainWindow): return point.x(), point.y() def send_extrude(self): + is_symmetric = None + length = None selected = self.ui.sketch_list.currentItem() name = selected.text() points = self.model['sketch'][name]['sketch_points'] @@ -290,25 +329,31 @@ class MainWindow(QMainWindow): #detect loop that causes problems in mesh generation del points[-1] - length, ok = QInputDialog.getDouble(self, 'Extrude Length', 'Enter a mm value:', decimals=2) - #TODO : Implement cancel + """length, ok = QInputDialog.getDouble(self, 'Extrude Length', 'Enter a mm value:', decimals=2) + #TODO : Implement cancel""" + + dialog = ExtrudeDialog(self) + if dialog.exec(): + length, is_symmetric = dialog.get_values() + print(f"Extrude length: {length}, Symmetric: {is_symmetric}") + else: + length = 0 + print("Extrude cancelled") #Create and draw Interactor geo = Geometry() - # 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) + # Rotation is done in vtk matrix trans + angle = 0 + normal = [0, 0, 1] + f = geo.extrude_shape(points, length, angle, normal, is_symmetric) + if not is_symmetric: + origin_z_lvl = length / 2 else: - normal = [0,0,-1] - angle = 0 - f = geo.extrude_shape(points, length, angle, normal) + origin_z_lvl = 0 + + self.calc_sketch_projection_3d(lines, origin_z_lvl, length) name_op = f"extrd-{name}" element = { @@ -322,7 +367,7 @@ class MainWindow(QMainWindow): self.ui.body_list.addItem(name_op) items = self.ui.body_list.findItems(name_op, Qt.MatchExactly)[0] self.ui.body_list.setCurrentItem(items) - self.calc_sketch_projection_3d(lines, 0, length) + self.draw_mesh() def send_cut(self): @@ -375,16 +420,26 @@ class Geometry: return angle_rad + def offset_syn(self, f, length): + f = f.translate((0,0, length / 2)) + return f + 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, angle, normal): + def extrude_shape(self, points, length: float, angle, normal, symet: bool = True): """2D to 3D sdf always first""" f = polygon(points).rotate(angle) - f = f.extrude(length).orient(normal) # orient(normal) + + if not symet: + print("Offsetting", symet) + f = f.extrude(length).orient(normal).translate((0, 0, length/2)) # orient(normal) + + else: + f = f.extrude(length).orient(normal) return f @@ -393,7 +448,6 @@ class Geometry: return f - def cut_shapes(self, sdf_object1, sdf_object2): f = difference(sdf_object1, sdf_object2) # equivalent return f diff --git a/meshtest.py b/meshtest.py index d85e81d..1b976cf 100644 --- a/meshtest.py +++ b/meshtest.py @@ -1,8 +1,7 @@ from sdf import * +c = box(1).translate((0,0,0.2)) +f = capped_cylinder(-Z, Z, 0.5) +c.orient([0.5, 0.5, 1]) +c = f - c -f = sphere(1) & box(0.9) - -c = cylinder(0.3) -f -= c.orient(X) | c.orient(Y) | c.orient(Z) -stl_object = None -f.save("out.stl", step=0.05) \ No newline at end of file +c.save("out.stl") \ No newline at end of file