- Working project and draw on exiting

This commit is contained in:
bklronin 2024-07-11 20:16:20 +02:00
parent cb471b4108
commit d2b8d9540a
4 changed files with 256 additions and 55 deletions

View File

@ -637,7 +637,7 @@ class SketchWidget(QWidget):
def draw_cross(self, painter, x, y, size=10): def draw_cross(self, painter, x, y, size=10):
# Set up the pen # Set up the pen
pen = QPen(QColor('red')) # You can change the color as needed 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) painter.setPen(pen)
# Calculate the endpoints of the cross # Calculate the endpoints of the cross
@ -677,10 +677,6 @@ class SketchWidget(QWidget):
for point in self.slv_points_main: for point in self.slv_points_main:
painter.drawEllipse(point['ui_point'], 3 / self.zoom, 3 / self.zoom) 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: for dic in self.slv_lines_main:
p1 = dic['ui_points'][0] p1 = dic['ui_points'][0]
p2 = dic['ui_points'][1] p2 = dic['ui_points'][1]
@ -716,6 +712,10 @@ class SketchWidget(QWidget):
painter.setPen(QPen(Qt.red, 2)) painter.setPen(QPen(Qt.red, 2))
painter.drawLine(p1, p2) 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) # self.drawBackgroundGrid(painter)
painter.end() painter.end()

View File

@ -18,6 +18,8 @@ class VTKWidget(QtWidgets.QWidget):
self.selected_edges = [] self.selected_edges = []
self.cell_normals = None self.cell_normals = None
self.local_matrix = None
self.project_tosketch_edge = [] self.project_tosketch_edge = []
self.vtk_widget = QVTKRenderWindowInteractor(self) self.vtk_widget = QVTKRenderWindowInteractor(self)
@ -113,10 +115,30 @@ class VTKWidget(QtWidgets.QWidget):
polydata.SetPoints(points) polydata.SetPoints(points)
polydata.SetLines(lines) 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 # Create a mapper and actor
mapper = vtk.vtkPolyDataMapper() mapper = vtk.vtkPolyDataMapper()
mapper.SetInputData(polydata) mapper.SetInputData(transformFilter.GetOutput())
actor = vtk.vtkActor() actor = vtk.vtkActor()
actor.SetMapper(mapper) actor.SetMapper(mapper)
@ -130,18 +152,21 @@ class VTKWidget(QtWidgets.QWidget):
mapper.Update() mapper.Update()
self.vtk_widget.GetRenderWindow().Render() 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() 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 # Create a vtkCellArray to store the triangles
triangles = vtk.vtkCellArray() triangles = vtk.vtkCellArray()
for i in range(faces.shape[0]): for face in faces:
triangle = vtk.vtkTriangle() triangle = vtk.vtkTriangle()
triangle.GetPointIds().SetId(0, faces[i, 0]) triangle.GetPointIds().SetId(0, face[0])
triangle.GetPointIds().SetId(1, faces[i, 1]) triangle.GetPointIds().SetId(1, face[1])
triangle.GetPointIds().SetId(2, faces[i, 2]) triangle.GetPointIds().SetId(2, face[2])
triangles.InsertNextCell(triangle) triangles.InsertNextCell(triangle)
# Create a polydata object # Create a polydata object
@ -156,11 +181,19 @@ class VTKWidget(QtWidgets.QWidget):
normalGenerator.ComputeCellNormalsOn() normalGenerator.ComputeCellNormalsOn()
normalGenerator.Update() normalGenerator.Update()
### There might be aproblem earlier but this fixes the drawing for now. # Create a transform for mirroring across the y-axis
#TODO: Investigate upstream conversion errors.
# Create a transform for mirroring across the x-axis
mirror_transform = vtk.vtkTransform() 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 # Apply the transform to the polydata
transformFilter = vtk.vtkTransformPolyDataFilter() transformFilter = vtk.vtkTransformPolyDataFilter()
@ -176,19 +209,45 @@ class VTKWidget(QtWidgets.QWidget):
actor = vtk.vtkActor() actor = vtk.vtkActor()
actor.SetMapper(mapper) actor.SetMapper(mapper)
actor.GetProperty().SetColor(1, 1, 1) # Set color (white in this case) actor.GetProperty().SetColor(color)
actor.GetProperty().EdgeVisibilityOn() # Show edges actor.GetProperty().EdgeVisibilityOn()
actor.GetProperty().SetLineWidth(2) # Set line width actor.GetProperty().SetLineWidth(line_width)
self.renderer.AddActor(actor)
# (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.vtk_widget.GetRenderWindow().Render() 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): def load_custom_mesh(self, vertices, faces):
### Load meshes by own module ### Load meshes by own module
# Create a vtkPoints object and store the points in it # 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, 1, y_axis[i])
matrix.SetElement(i, 2, z_axis[i]) matrix.SetElement(i, 2, z_axis[i])
matrix.SetElement(i, 3, centroid[i]) matrix.SetElement(i, 3, centroid[i])
self.local_matrix = matrix
matrix.Invert() matrix.Invert()
# Transform points to 2D coordinates # Transform points to 2D coordinates
transform = vtk.vtkTransform() transform = vtk.vtkTransform()
transform.SetMatrix(matrix) transform.SetMatrix(matrix)
transform.Scale([1,1,1])
transformer = vtk.vtkTransformPolyDataFilter() transformer = vtk.vtkTransformPolyDataFilter()
transformer.SetInputData(projected_mesh) transformer.SetInputData(projected_mesh)
@ -299,10 +360,84 @@ class VTKWidget(QtWidgets.QWidget):
xy_coordinates = [] xy_coordinates = []
for i in range(points.GetNumberOfPoints()): for i in range(points.GetNumberOfPoints()):
point = points.GetPoint(i) point = points.GetPoint(i)
xy_coordinates.append((point[0], point[1])) xy_coordinates.append((-point[0], point[1]))
return xy_coordinates 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): def on_click(self, obj, event):
click_pos = self.interactor.GetEventPosition() 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) 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_polydata = self.project_mesh_to_plane(polydata, self.selected_normal, centroid)
projected_points = projected_polydata.GetPoints() projected_points = projected_polydata.GetPoints()
print("proj_points", projected_points) 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().SetColor(0.0, 1.0, 0.0) # Set color to green
actor.GetProperty().SetLineWidth(4) # Set line width actor.GetProperty().SetLineWidth(4) # Set line width
self.renderer.AddActor(normal_actor)
# Add the actor to the scene # Add the actor to the scene
self.renderer.AddActor(actor) self.renderer.AddActor(actor)
if len(self.selected_edges) > 2: # Clear selection after
self.selected_edges = [] self.selected_edges = []
self.selected_normal = [] 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 # Render the scene

94
main.py
View File

@ -1,7 +1,7 @@
import uuid import uuid
import names import names
from PySide6.QtCore import Qt, QPoint 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 Gui import Ui_fluencyCAD # Import the generated GUI module
from drawing_modules.vtk_widget import VTKWidget from drawing_modules.vtk_widget import VTKWidget
from drawing_modules.vysta_widget import PyVistaWidget 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 # 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): class MainWindow(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -184,7 +222,6 @@ class MainWindow(QMainWindow):
return points_for_interact return points_for_interact
def add_sketch(self): def add_sketch(self):
name = f"sketch-{str(names.get_first_name())}" name = f"sketch-{str(names.get_first_name())}"
points_for_sdf = self.convert_points_for_sdf() points_for_sdf = self.convert_points_for_sdf()
@ -281,6 +318,8 @@ class MainWindow(QMainWindow):
return point.x(), point.y() return point.x(), point.y()
def send_extrude(self): def send_extrude(self):
is_symmetric = None
length = None
selected = self.ui.sketch_list.currentItem() selected = self.ui.sketch_list.currentItem()
name = selected.text() name = selected.text()
points = self.model['sketch'][name]['sketch_points'] points = self.model['sketch'][name]['sketch_points']
@ -290,25 +329,31 @@ class MainWindow(QMainWindow):
#detect loop that causes problems in mesh generation #detect loop that causes problems in mesh generation
del points[-1] del points[-1]
length, ok = QInputDialog.getDouble(self, 'Extrude Length', 'Enter a mm value:', decimals=2) """length, ok = QInputDialog.getDouble(self, 'Extrude Length', 'Enter a mm value:', decimals=2)
#TODO : Implement cancel #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 #Create and draw Interactor
geo = Geometry() geo = Geometry()
# Get the nromal form the 3d widget for extrusion # Rotation is done in vtk matrix trans
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 angle = 0
f = geo.extrude_shape(points, length, angle, normal) normal = [0, 0, 1]
f = geo.extrude_shape(points, length, angle, normal, is_symmetric)
if not is_symmetric:
origin_z_lvl = length / 2
else:
origin_z_lvl = 0
self.calc_sketch_projection_3d(lines, origin_z_lvl, length)
name_op = f"extrd-{name}" name_op = f"extrd-{name}"
element = { element = {
@ -322,7 +367,7 @@ class MainWindow(QMainWindow):
self.ui.body_list.addItem(name_op) self.ui.body_list.addItem(name_op)
items = self.ui.body_list.findItems(name_op, Qt.MatchExactly)[0] items = self.ui.body_list.findItems(name_op, Qt.MatchExactly)[0]
self.ui.body_list.setCurrentItem(items) self.ui.body_list.setCurrentItem(items)
self.calc_sketch_projection_3d(lines, 0, length)
self.draw_mesh() self.draw_mesh()
def send_cut(self): def send_cut(self):
@ -375,16 +420,26 @@ class Geometry:
return angle_rad return angle_rad
def offset_syn(self, f, length):
f = f.translate((0,0, length / 2))
return f
def distance(self, p1, p2): def distance(self, p1, p2):
"""Calculate the distance between two points.""" """Calculate the distance between two points."""
print("p1", p1) print("p1", p1)
print("p2", p2) print("p2", p2)
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) 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""" """2D to 3D sdf always first"""
f = polygon(points).rotate(angle) 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 return f
@ -393,7 +448,6 @@ class Geometry:
return f return f
def cut_shapes(self, sdf_object1, sdf_object2): def cut_shapes(self, sdf_object1, sdf_object2):
f = difference(sdf_object1, sdf_object2) # equivalent f = difference(sdf_object1, sdf_object2) # equivalent
return f return f

View File

@ -1,8 +1,7 @@
from sdf import * 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.save("out.stl")
c = cylinder(0.3)
f -= c.orient(X) | c.orient(Y) | c.orient(Z)
stl_object = None
f.save("out.stl", step=0.05)