- Basic 2D projection
This commit is contained in:
parent
9daf263aad
commit
5ff48c0f5e
81
2d_draw.py
81
2d_draw.py
@ -1,81 +0,0 @@
|
||||
import sys
|
||||
|
||||
from OpenGL.raw.GL.VERSION.GL_1_0 import glClearColor
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtOpenGLWidgets import QOpenGLWidget
|
||||
from PySide6.QtWidgets import QDoubleSpinBox, QPushButton, QVBoxLayout, QApplication, QWidget
|
||||
from OpenGL.GL import *
|
||||
from OpenGL.GLUT import *
|
||||
|
||||
class GLWidget(QOpenGLWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(GLWidget, self).__init__(parent)
|
||||
self.shapes = []
|
||||
self.extruded_shapes = []
|
||||
|
||||
def initializeGL(self):
|
||||
glClearColor(1, 1, 1, 1)
|
||||
|
||||
def paintGL(self):
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||
glLoadIdentity()
|
||||
glColor3f(1, 0, 0)
|
||||
|
||||
for shape in self.shapes:
|
||||
glBegin(GL_LINE_LOOP)
|
||||
glVertex2f(shape[0], shape[1]) # Access x and y coordinates of the point
|
||||
glEnd()
|
||||
|
||||
def resizeGL(self, w, h):
|
||||
glViewport(0, 0, w, h)
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
glOrtho(0, w, h, 0, -1, 1)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.LeftButton:
|
||||
pos = event.pos()
|
||||
x = int(pos.x())
|
||||
y = int(pos.y())
|
||||
self.shapes.append((x, y)) # Append coordinate tuple (x, y)
|
||||
self.update()
|
||||
def extrude_shapes(self, height):
|
||||
self.extruded_shapes = []
|
||||
for shape in self.shapes:
|
||||
extruded_shape = []
|
||||
for point in shape:
|
||||
extruded_shape.append([point[0], point[1]])
|
||||
for point in shape:
|
||||
extruded_shape.append([point[0], point[1] + height])
|
||||
self.extruded_shapes.append(extruded_shape)
|
||||
|
||||
class MainWindow(QWidget):
|
||||
def __init__(self):
|
||||
super(MainWindow, self).__init__()
|
||||
self.gl_widget = GLWidget()
|
||||
self.height_spin_box = QDoubleSpinBox()
|
||||
self.height_spin_box.setRange(0, 100)
|
||||
self.height_spin_box.setValue(10)
|
||||
self.extrude_button = QPushButton("Extrude")
|
||||
self.extrude_button.clicked.connect(self.extrude_shapes)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.gl_widget)
|
||||
layout.addWidget(self.height_spin_box)
|
||||
layout.addWidget(self.extrude_button)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def extrude_shapes(self):
|
||||
height = self.height_spin_box.value()
|
||||
self.gl_widget.extrude_shapes(height)
|
||||
self.gl_widget.update()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
window.setGeometry(100, 100, 800, 600)
|
||||
window.setWindowTitle("Extrude Shapes")
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
62
2dtest.py
62
2dtest.py
@ -1,62 +0,0 @@
|
||||
import sys
|
||||
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton
|
||||
from PySide6.QtGui import QPainter, QPen
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
class CADWindow(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("CAD")
|
||||
self.resize(800, 600)
|
||||
|
||||
# Create a label for the dimensions input
|
||||
self.dimension_label = QLabel("Dimensions:", self)
|
||||
self.dimension_label.move(10, 10)
|
||||
|
||||
# Create a line edit for the user to enter the dimensions
|
||||
self.dimension_input = QLineEdit()
|
||||
self.dimension_input.setPlaceholderText("Enter dimensions...")
|
||||
self.dimension_input.setFixedWidth(300)
|
||||
self.dimension_input.move(10, 40)
|
||||
|
||||
# Create a button for the user to confirm the dimensions
|
||||
self.confirm_button = QPushButton("Confirm", self)
|
||||
self.confirm_button.clicked.connect(self.confirm_dimensions)
|
||||
self.confirm_button.move(10, 70)
|
||||
|
||||
# Create a label for the canvas
|
||||
self.canvas_label = QLabel("Canvas:", self)
|
||||
self.canvas_label.move(400, 10)
|
||||
|
||||
# Create a canvas widget for the user to draw on
|
||||
self.canvas = QWidget()
|
||||
self.canvas.setFixedSize(300, 300)
|
||||
self.canvas.move(400, 40)
|
||||
|
||||
# Set the default dimensions of the canvas
|
||||
self.dimensions = (300, 300)
|
||||
|
||||
def confirm_dimensions(self):
|
||||
# Get the dimensions from the line edit and convert to a tuple
|
||||
dimensions = self.dimension_input.text().split(",")
|
||||
dimensions = [int(d) for d in dimensions]
|
||||
self.dimensions = dimensions
|
||||
|
||||
# Resize the canvas widget to match the new dimensions
|
||||
self.canvas.setFixedSize(*self.dimensions)
|
||||
|
||||
def paintEvent(self, event):
|
||||
# Get a painter object for painting on the canvas
|
||||
painter = QPainter(self.canvas)
|
||||
|
||||
# Set the pen color and width
|
||||
painter.pen = QPen(Qt.black, 2)
|
||||
|
||||
# Draw a line across the canvas using the dimensions from the user input
|
||||
painter.drawLine(0, 0, *self.dimensions)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = CADWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
@ -1,38 +0,0 @@
|
||||
from PySide6 import QtGui
|
||||
from PySide6.Qt3DCore import Qt3DCore
|
||||
from PySide6.Qt3DExtras import Qt3DExtras
|
||||
from PySide6.QtWidgets import QApplication, QMainWindow
|
||||
import sys
|
||||
|
||||
class My3DWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super(My3DWindow, self).__init__()
|
||||
|
||||
# Create the 3D scene
|
||||
scene = Qt3DCore.QEntity()
|
||||
|
||||
# Create a 3D view container
|
||||
view = Qt3DExtras.Qt3DWindow()
|
||||
view.defaultFrameGraph().setClearColor(QtGui.QColor(0, 0, 0, 0))
|
||||
|
||||
# Set root entity for the scene
|
||||
view.setRootEntity(scene)
|
||||
|
||||
# Create a 3D transform
|
||||
transform = Qt3DCore.QTransform()
|
||||
transform.setScale3D(QtGui.QVector3D(1.0, 1.0, 1.0))
|
||||
|
||||
# Set components for the mesh entity
|
||||
mesh_entity.addComponent(material)
|
||||
mesh_entity.addComponent(transform)
|
||||
|
||||
# Add the mesh entity to the scene
|
||||
scene.addComponent(mesh_entity)
|
||||
|
||||
self.setCentralWidget(view)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = My3DWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
Binary file not shown.
@ -2,6 +2,7 @@ import math
|
||||
import re
|
||||
from copy import copy
|
||||
|
||||
import numpy as np
|
||||
from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QInputDialog
|
||||
from PySide6.QtGui import QPainter, QPen, QColor
|
||||
from PySide6.QtCore import Qt, QPoint, QPointF, Signal
|
||||
@ -47,6 +48,81 @@ class SketchWidget(QWidget):
|
||||
def create_workplane(self):
|
||||
self.wp = self.solv.create_2d_base()
|
||||
|
||||
def create_workplane_projected(self):
|
||||
self.wp = self.solv.create_2d_base()
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
self.slv_points_main.append(relation_point)
|
||||
|
||||
print("points", self.slv_points_main)
|
||||
print("lines", self.slv_lines_main)
|
||||
print("lines", 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.wp = self.solv.add_work_plane(origin_handle, slv_normal)
|
||||
print(self.wp)
|
||||
|
||||
def get_handle_nr(self, input_str: str) -> int:
|
||||
# Define the regex pattern to extract the handle number
|
||||
pattern = r"handle=(\d+)"
|
||||
|
@ -5,6 +5,7 @@ import vtk
|
||||
from PySide6 import QtCore, QtWidgets
|
||||
from PySide6.QtCore import Signal
|
||||
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
|
||||
from vtkmodules.util.numpy_support import vtk_to_numpy, numpy_to_vtk
|
||||
|
||||
|
||||
class VTKWidget(QtWidgets.QWidget):
|
||||
@ -12,6 +13,10 @@ class VTKWidget(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.access_selected_points = []
|
||||
self.selected_normal = None
|
||||
self.selected_edges = []
|
||||
self.cell_normals = None
|
||||
self.vtk_widget = QVTKRenderWindowInteractor(self)
|
||||
|
||||
# Create layout and add VTK widget
|
||||
@ -26,12 +31,12 @@ class VTKWidget(QtWidgets.QWidget):
|
||||
|
||||
# Set up the camera
|
||||
self.camera = self.renderer.GetActiveCamera()
|
||||
self.camera.SetPosition(5, 5, 5)
|
||||
self.camera.SetPosition(5, 5, 100)
|
||||
self.camera.SetFocalPoint(0, 0, 0)
|
||||
|
||||
# Set up picking
|
||||
self.picker = vtk.vtkCellPicker()
|
||||
self.picker.SetTolerance(0.0005)
|
||||
self.picker.SetTolerance(0.005)
|
||||
|
||||
# Create a mapper and actor for picked cells
|
||||
self.picked_mapper = vtk.vtkDataSetMapper()
|
||||
@ -47,78 +52,109 @@ class VTKWidget(QtWidgets.QWidget):
|
||||
# Add observer for mouse clicks
|
||||
self.interactor.AddObserver("RightButtonPressEvent", self.on_click)
|
||||
|
||||
def create_cube_mesh(self):
|
||||
cube_source = vtk.vtkCubeSource()
|
||||
print(cube_source)
|
||||
@staticmethod
|
||||
def compute_normal_from_lines(line1, line2):
|
||||
vec1 = line1[1] - line1[0]
|
||||
vec2 = line2[1] - line2[0]
|
||||
normal = np.cross(vec1, vec2)
|
||||
print(normal)
|
||||
normal = normal / np.linalg.norm(normal)
|
||||
return normal
|
||||
|
||||
def load_interactor_mesh(self, edges):
|
||||
# Create vtkPoints to store all points
|
||||
points = vtk.vtkPoints()
|
||||
|
||||
# Create vtkCellArray to store the lines
|
||||
lines = vtk.vtkCellArray()
|
||||
|
||||
for edge in edges:
|
||||
# Add points for this edge
|
||||
point_id1 = points.InsertNextPoint(edge[0])
|
||||
point_id2 = points.InsertNextPoint(edge[1])
|
||||
|
||||
# Create a line using the point IDs
|
||||
line = vtk.vtkLine()
|
||||
line.GetPointIds().SetId(0, point_id1)
|
||||
line.GetPointIds().SetId(1, point_id2)
|
||||
|
||||
# Add the line to the cell array
|
||||
lines.InsertNextCell(line)
|
||||
|
||||
# Create vtkPolyData to store the geometry
|
||||
polydata = vtk.vtkPolyData()
|
||||
polydata.SetPoints(points)
|
||||
polydata.SetLines(lines)
|
||||
|
||||
# Verify that the polydata is not empty
|
||||
if polydata.GetNumberOfPoints() == 0 or polydata.GetNumberOfCells() == 0:
|
||||
print("Error: PolyData is empty")
|
||||
sys.exit(1)
|
||||
|
||||
# Create a mapper and actor
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputConnection(cube_source.GetOutputPort())
|
||||
mapper.SetInputData(polydata)
|
||||
|
||||
actor = vtk.vtkActor()
|
||||
actor.SetMapper(mapper)
|
||||
actor.GetProperty().SetColor(0.0, 0.0, 1.0) # Set color to red
|
||||
actor.GetProperty().SetLineWidth(2) # Set line width
|
||||
|
||||
# Add the actor to the scene
|
||||
self.renderer.AddActor(actor)
|
||||
#self.renderer.SetBackground(0.1, 0.2, 0.4) # Set background color
|
||||
|
||||
def simplify_mesh(self, input_mesh, target_reduction):
|
||||
# Create the quadric decimation filter
|
||||
decimate = vtk.vtkDecimatePro()
|
||||
decimate.SetInputData(input_mesh)
|
||||
# Render and interact
|
||||
# Force an update of the pipeline
|
||||
# mapper.Update()
|
||||
self.vtk_widget.GetRenderWindow().Render()
|
||||
|
||||
# Set the reduction factor (0 to 1, where 1 means maximum reduction)
|
||||
decimate.SetTargetReduction(target_reduction)
|
||||
def render_from_points_direct_with_faces(self, vertices, faces):
|
||||
points = vtk.vtkPoints()
|
||||
for i in range(vertices.shape[0]):
|
||||
points.InsertNextPoint(vertices[i])
|
||||
|
||||
# Optional: Preserve topology (if needed)
|
||||
decimate.PreserveTopologyOn()
|
||||
# Create a vtkCellArray to store the triangles
|
||||
triangles = vtk.vtkCellArray()
|
||||
for i in range(faces.shape[0]):
|
||||
triangle = vtk.vtkTriangle()
|
||||
triangle.GetPointIds().SetId(0, faces[i, 0])
|
||||
triangle.GetPointIds().SetId(1, faces[i, 1])
|
||||
triangle.GetPointIds().SetId(2, faces[i, 2])
|
||||
triangles.InsertNextCell(triangle)
|
||||
|
||||
# Perform the decimation
|
||||
decimate.Update()
|
||||
# Create a polydata object
|
||||
polydata = vtk.vtkPolyData()
|
||||
polydata.SetPoints(points)
|
||||
polydata.SetPolys(triangles)
|
||||
|
||||
return decimate.GetOutput()
|
||||
# Calculate normals
|
||||
normalGenerator = vtk.vtkPolyDataNormals()
|
||||
normalGenerator.SetInputData(polydata)
|
||||
normalGenerator.ComputePointNormalsOn()
|
||||
normalGenerator.ComputeCellNormalsOn()
|
||||
normalGenerator.Update()
|
||||
|
||||
def combine_coplanar_faces(self, input_polydata, tolerance=0.001):
|
||||
# Clean the polydata to merge duplicate points
|
||||
clean = vtk.vtkCleanPolyData()
|
||||
clean.SetInputData(input_polydata)
|
||||
clean.SetTolerance(tolerance)
|
||||
clean.Update()
|
||||
self.cell_normals = vtk_to_numpy(normalGenerator.GetOutput().GetCellData().GetNormals())
|
||||
|
||||
# Generate normals and merge coplanar polygons
|
||||
normals = vtk.vtkPolyDataNormals()
|
||||
normals.SetInputConnection(clean.GetOutputPort())
|
||||
normals.SplittingOff() # Disable splitting of sharp edges
|
||||
normals.ConsistencyOn() # Ensure consistent polygon ordering
|
||||
normals.AutoOrientNormalsOn() # Automatically orient normals
|
||||
normals.ComputePointNormalsOff() # We only need face normals
|
||||
normals.ComputeCellNormalsOn() # Compute cell normals
|
||||
normals.Update()
|
||||
# Create a mapper and actor
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputData(polydata)
|
||||
|
||||
return normals.GetOutput()
|
||||
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
|
||||
|
||||
def poisson_reconstruction(self, points):
|
||||
# Create a polydata object from points
|
||||
point_polydata = vtk.vtkPolyData()
|
||||
point_polydata.SetPoints(points)
|
||||
|
||||
# Create a surface reconstruction filter
|
||||
surf = vtk.vtkSurfaceReconstructionFilter()
|
||||
surf.SetInputData(point_polydata)
|
||||
surf.Update()
|
||||
# (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
|
||||
|
||||
# Create a contour filter to extract the surface
|
||||
cf = vtk.vtkContourFilter()
|
||||
cf.SetInputConnection(surf.GetOutputPort())
|
||||
cf.SetValue(0, 0.0)
|
||||
cf.Update()
|
||||
|
||||
# Reverse normals
|
||||
reverse = vtk.vtkReverseSense()
|
||||
reverse.SetInputConnection(cf.GetOutputPort())
|
||||
reverse.ReverseCellsOn()
|
||||
reverse.ReverseNormalsOn()
|
||||
reverse.Update()
|
||||
|
||||
return reverse.GetOutput()
|
||||
|
||||
def load_interactor_mesh(self, simp_mesh):
|
||||
vertices, faces = simp_mesh
|
||||
self.load_custom_mesh(vertices, faces)
|
||||
# Force an update of the pipeline
|
||||
#mapper.Update()
|
||||
self.vtk_widget.GetRenderWindow().Render()
|
||||
|
||||
def load_custom_mesh(self, vertices, faces):
|
||||
### Load meshes by own module
|
||||
@ -154,277 +190,114 @@ class VTKWidget(QtWidgets.QWidget):
|
||||
mapper.Update()
|
||||
self.vtk_widget.GetRenderWindow().Render()
|
||||
|
||||
def create_simplified_outline(self, polydata):
|
||||
# 1. Extract the outer surface
|
||||
surface_filter = vtk.vtkDataSetSurfaceFilter()
|
||||
surface_filter.SetInputData(polydata)
|
||||
surface_filter.Update()
|
||||
|
||||
# 2. Extract feature edges (only boundary edges)
|
||||
feature_edges = vtk.vtkFeatureEdges()
|
||||
feature_edges.SetInputConnection(surface_filter.GetOutputPort())
|
||||
feature_edges.BoundaryEdgesOn()
|
||||
feature_edges.FeatureEdgesOff()
|
||||
feature_edges.NonManifoldEdgesOff()
|
||||
feature_edges.ManifoldEdgesOff()
|
||||
feature_edges.Update()
|
||||
|
||||
# 3. Clean the edges to merge duplicate points
|
||||
cleaner = vtk.vtkCleanPolyData()
|
||||
cleaner.SetInputConnection(feature_edges.GetOutputPort())
|
||||
cleaner.Update()
|
||||
|
||||
# 4. Optional: Smooth the outline
|
||||
smooth = vtk.vtkSmoothPolyDataFilter()
|
||||
smooth.SetInputConnection(cleaner.GetOutputPort())
|
||||
smooth.SetNumberOfIterations(15)
|
||||
smooth.SetRelaxationFactor(0.1)
|
||||
smooth.FeatureEdgeSmoothingOff()
|
||||
smooth.BoundarySmoothingOn()
|
||||
smooth.Update()
|
||||
|
||||
return smooth
|
||||
|
||||
def render_from_points_direct_with_faces(self, points):
|
||||
# Create a vtkPoints object and store the points in it
|
||||
vtk_points = vtk.vtkPoints()
|
||||
for point in points:
|
||||
vtk_points.InsertNextPoint(point)
|
||||
|
||||
# Create a vtkCellArray to store the triangles
|
||||
triangles = vtk.vtkCellArray()
|
||||
|
||||
# Assuming points are organized as triplets forming triangles
|
||||
for i in range(0, len(points), 3):
|
||||
triangle = vtk.vtkTriangle()
|
||||
triangle.GetPointIds().SetId(0, i)
|
||||
triangle.GetPointIds().SetId(1, i + 1)
|
||||
triangle.GetPointIds().SetId(2, i + 2)
|
||||
triangles.InsertNextCell(triangle)
|
||||
|
||||
# Create a polydata object
|
||||
polydata = vtk.vtkPolyData()
|
||||
polydata.SetPoints(vtk_points)
|
||||
polydata.SetPolys(triangles)
|
||||
|
||||
# Optional: Merge duplicate points
|
||||
cleaner = vtk.vtkCleanPolyData()
|
||||
cleaner.SetInputData(polydata)
|
||||
cleaner.Update()
|
||||
|
||||
# Optional: Combine coplanar faces
|
||||
normals = vtk.vtkPolyDataNormals()
|
||||
normals.SetInputConnection(cleaner.GetOutputPort())
|
||||
normals.SplittingOff()
|
||||
normals.ConsistencyOn()
|
||||
normals.AutoOrientNormalsOn()
|
||||
normals.ComputePointNormalsOff()
|
||||
normals.ComputeCellNormalsOn()
|
||||
normals.Update()
|
||||
|
||||
# Create a mapper and actor
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputConnection(normals.GetOutputPort())
|
||||
|
||||
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
|
||||
|
||||
feature_edges = self.create_simplified_outline(polydata)
|
||||
|
||||
# Create a mapper for the feature edges
|
||||
edge_mapper = vtk.vtkPolyDataMapper()
|
||||
# Already wiht output
|
||||
edge_mapper.SetInputConnection(feature_edges.GetOutputPort())
|
||||
|
||||
# Create an actor for the feature edges
|
||||
edge_actor = vtk.vtkActor()
|
||||
edge_actor.SetMapper(edge_mapper)
|
||||
|
||||
# Set the properties of the edge actor
|
||||
edge_actor.GetProperty().SetColor(1, 0, 0) # Set color (red in this case)
|
||||
edge_actor.GetProperty().SetLineWidth(2) # Set line width
|
||||
|
||||
# Optionally, if you want to keep the original mesh visible:
|
||||
# (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
|
||||
self.renderer.AddActor(edge_actor)
|
||||
|
||||
# Force an update of the pipeline
|
||||
mapper.Update()
|
||||
self.vtk_widget.GetRenderWindow().Render()
|
||||
|
||||
# Print statistics
|
||||
print(f"Original points: {len(points)}")
|
||||
print(f"Number of triangles: {triangles.GetNumberOfCells()}")
|
||||
print(f"Final number of points: {normals.GetOutput().GetNumberOfPoints()}")
|
||||
print(f"Final number of cells: {normals.GetOutput().GetNumberOfCells()}")
|
||||
|
||||
def render_from_points_direct(self, points):
|
||||
### Rendermethod for SDF mesh (output)
|
||||
# Create a vtkPoints object and store the points in it
|
||||
vtk_points = vtk.vtkPoints()
|
||||
for point in points:
|
||||
vtk_points.InsertNextPoint(point)
|
||||
|
||||
# Create a polydata object
|
||||
point_polydata = vtk.vtkPolyData()
|
||||
point_polydata.SetPoints(vtk_points)
|
||||
|
||||
# Surface reconstruction
|
||||
surf = vtk.vtkSurfaceReconstructionFilter()
|
||||
surf.SetInputData(point_polydata)
|
||||
surf.Update()
|
||||
|
||||
# Create a contour filter to extract the surface
|
||||
cf = vtk.vtkContourFilter()
|
||||
cf.SetInputConnection(surf.GetOutputPort())
|
||||
cf.SetValue(0, 0.0)
|
||||
cf.Update()
|
||||
|
||||
# Reverse the normals
|
||||
reverse = vtk.vtkReverseSense()
|
||||
reverse.SetInputConnection(cf.GetOutputPort())
|
||||
reverse.ReverseCellsOn()
|
||||
reverse.ReverseNormalsOn()
|
||||
reverse.Update()
|
||||
|
||||
# Get the reconstructed mesh
|
||||
reconstructed_mesh = reverse.GetOutput()
|
||||
|
||||
"""# Simplify the mesh
|
||||
target_reduction = 1 # Adjust this value as needed
|
||||
simplified_mesh = self.simplify_mesh(reconstructed_mesh, target_reduction)
|
||||
|
||||
combinded_faces = self.combine_coplanar_faces(simplified_mesh, 0.001)"""
|
||||
|
||||
# Create a mapper and actor for the simplified mesh
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputData(reconstructed_mesh)
|
||||
|
||||
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
|
||||
|
||||
# Add the actor to the renderer
|
||||
self.renderer.AddActor(actor)
|
||||
|
||||
# Force an update of the pipeline
|
||||
#mapper.Update()
|
||||
self.vtk_widget.GetRenderWindow().Render()
|
||||
|
||||
# Print statistics
|
||||
print(f"Original points: {len(points)}")
|
||||
print(
|
||||
f"Reconstructed mesh: {reconstructed_mesh.GetNumberOfPoints()} points, {reconstructed_mesh.GetNumberOfCells()} cells")
|
||||
"""print(
|
||||
f"Simplified mesh: {simplified_mesh.GetNumberOfPoints()} points, {simplified_mesh.GetNumberOfCells()} cells")"""
|
||||
|
||||
def on_click(self, obj, event):
|
||||
click_pos = self.interactor.GetEventPosition()
|
||||
|
||||
# Perform pick
|
||||
self.picker.Pick(click_pos[0], click_pos[1], 0, self.renderer)
|
||||
|
||||
# Get picked cell
|
||||
# Get picked cell ID
|
||||
cell_id = self.picker.GetCellId()
|
||||
|
||||
if cell_id != -1:
|
||||
print(f"Picked face ID: {cell_id}")
|
||||
print(f"Picked cell ID: {cell_id}")
|
||||
|
||||
# Get the polydata and the picked cell
|
||||
polydata = self.picker.GetActor().GetMapper().GetInput()
|
||||
cell = polydata.GetCell(cell_id)
|
||||
|
||||
# Project2D
|
||||
renderer = self.vtk_widget.GetRenderWindow().GetRenderers().GetFirstRenderer()
|
||||
camera = renderer.GetActiveCamera()
|
||||
# Ensure it's a line
|
||||
if cell.GetCellType() == vtk.VTK_LINE:
|
||||
# Get the two points of the line
|
||||
point_id1 = cell.GetPointId(0)
|
||||
point_id2 = cell.GetPointId(1)
|
||||
|
||||
# Get cell type
|
||||
cell_type = cell.GetCellType()
|
||||
print(f"Cell type: {cell_type}")
|
||||
proj_point1 = polydata.GetPoint(point_id1)
|
||||
proj_point2 = polydata.GetPoint(point_id2)
|
||||
|
||||
# Get points of the cell
|
||||
points = cell.GetPoints()
|
||||
num_points = points.GetNumberOfPoints()
|
||||
print(f"Number of points in the cell: {num_points}")
|
||||
self.access_selected_points.append((proj_point1, proj_point2))
|
||||
|
||||
vec_points = []
|
||||
point1 = np.array(proj_point1)
|
||||
point2 = np.array(proj_point2)
|
||||
|
||||
# Get coordinates of each point
|
||||
for i in range(num_points):
|
||||
point = points.GetPoint(i)
|
||||
print(f"Point {i}: {point}")
|
||||
vec_points.append(point)
|
||||
print(f"Line starts at: {point1}")
|
||||
print(f"Line ends at: {point2}")
|
||||
|
||||
# Get normal of the cell (if it's a polygon)
|
||||
if cell_type == vtk.VTK_TRIANGLE:
|
||||
normal = [0, 0, 0]
|
||||
vtk.vtkPolygon.ComputeNormal(points, normal)
|
||||
print(f"Face normal: {normal}")
|
||||
# Store this line for later use if needed
|
||||
self.selected_edges.append((point1, point2))
|
||||
|
||||
# Get cell data
|
||||
cell_data = polydata.GetCellData()
|
||||
if cell_data:
|
||||
num_arrays = cell_data.GetNumberOfArrays()
|
||||
print(f"Number of cell data arrays: {num_arrays}")
|
||||
for i in range(num_arrays):
|
||||
array = cell_data.GetArray(i)
|
||||
array_name = array.GetName()
|
||||
num_components = array.GetNumberOfComponents()
|
||||
value = [0] * num_components
|
||||
array.GetTuple(cell_id, value)
|
||||
print(f"Cell data '{array_name}': {value}")
|
||||
if len(self.selected_edges) == 2:
|
||||
# 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]
|
||||
self.selected_normal = np.cross(edge1, edge2)
|
||||
self.selected_normal = self.selected_normal / np.linalg.norm(self.selected_normal)
|
||||
print("Computed normal:", self.selected_normal)
|
||||
|
||||
# Get point data (average of all points in the cell)
|
||||
point_data = polydata.GetPointData()
|
||||
if point_data:
|
||||
num_arrays = point_data.GetNumberOfArrays()
|
||||
print(f"Number of point data arrays: {num_arrays}")
|
||||
for i in range(num_arrays):
|
||||
array = point_data.GetArray(i)
|
||||
array_name = array.GetName()
|
||||
num_components = array.GetNumberOfComponents()
|
||||
avg_value = np.zeros(num_components)
|
||||
for j in range(num_points):
|
||||
point_id = cell.GetPointId(j)
|
||||
value = [0] * num_components
|
||||
array.GetTuple(point_id, value)
|
||||
avg_value += np.array(value)
|
||||
avg_value /= num_points
|
||||
print(f"Average point data '{array_name}': {avg_value}")
|
||||
# Create a transform for projection
|
||||
transform = vtk.vtkTransform()
|
||||
transform.Identity()
|
||||
|
||||
if num_points and cell_data:
|
||||
face_orient = {'cell_data': cell_data, 'points': vec_points }
|
||||
print(face_orient)
|
||||
self.face_data.emit(face_orient)
|
||||
# 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
|
||||
|
||||
# Highlight picked face (your existing code)
|
||||
ids = vtk.vtkIdTypeArray()
|
||||
ids.SetNumberOfComponents(1)
|
||||
ids.InsertNextValue(cell_id)
|
||||
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])
|
||||
|
||||
selection_node = vtk.vtkSelectionNode()
|
||||
selection_node.SetFieldType(vtk.vtkSelectionNode.CELL)
|
||||
selection_node.SetContentType(vtk.vtkSelectionNode.INDICES)
|
||||
selection_node.SetSelectionList(ids)
|
||||
# Apply scaling to flatten
|
||||
transform.Scale(1, 1, 0)
|
||||
|
||||
selection = vtk.vtkSelection()
|
||||
selection.AddNode(selection_node)
|
||||
# Apply the inverse rotation to bring it back to original orientation
|
||||
transform.RotateWXYZ(-rotation_angle, rotation_axis[0], rotation_axis[1], rotation_axis[2])
|
||||
|
||||
extract_selection = vtk.vtkExtractSelection()
|
||||
extract_selection.SetInputData(0, polydata)
|
||||
extract_selection.SetInputData(1, selection)
|
||||
extract_selection.Update()
|
||||
# Apply the transform to the polydata
|
||||
transformFilter = vtk.vtkTransformPolyDataFilter()
|
||||
transformFilter.SetInputData(polydata)
|
||||
transformFilter.SetTransform(transform)
|
||||
transformFilter.Update()
|
||||
|
||||
self.picked_mapper.SetInputData(extract_selection.GetOutput())
|
||||
# Get the projected polydata
|
||||
projected_polydata = transformFilter.GetOutput()
|
||||
|
||||
# Create a mapper and actor for the projected data
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputData(projected_polydata)
|
||||
|
||||
actor = vtk.vtkActor()
|
||||
actor.SetMapper(mapper)
|
||||
actor.GetProperty().SetColor(0.0, 1.0, 0.0) # Set color to green
|
||||
actor.GetProperty().SetLineWidth(4) # Set line width
|
||||
|
||||
# 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(0, 0, 0)
|
||||
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()
|
||||
|
||||
# Reset selected edges
|
||||
self.selected_edges = []
|
||||
|
||||
elif len(self.access_selected_points) > 2:
|
||||
self.access_selected_points = []
|
||||
else:
|
||||
print("Selected cell is not a line")
|
||||
def start(self):
|
||||
self.interactor.Initialize()
|
||||
self.interactor.Start()
|
||||
|
337
drawing_modules/vtk_widget_alt_methods.py
Normal file
337
drawing_modules/vtk_widget_alt_methods.py
Normal file
@ -0,0 +1,337 @@
|
||||
def are_coplanar(self, normal1, normal2, point1, point2, tolerance=1e-6):
|
||||
# Check if normals are parallel
|
||||
if np.abs(np.dot(normal1, normal2)) < 1 - tolerance:
|
||||
return False
|
||||
|
||||
# Check if points lie on the same plane
|
||||
diff = point2 - point1
|
||||
return np.abs(np.dot(diff, normal1)) < tolerance
|
||||
|
||||
|
||||
def merge_coplanar_triangles(self, polydata):
|
||||
# Compute normals
|
||||
normalGenerator = vtk.vtkPolyDataNormals()
|
||||
normalGenerator.SetInputData(polydata)
|
||||
normalGenerator.ComputePointNormalsOff()
|
||||
normalGenerator.ComputeCellNormalsOn()
|
||||
normalGenerator.Update()
|
||||
|
||||
mesh = normalGenerator.GetOutput()
|
||||
n_cells = mesh.GetNumberOfCells()
|
||||
|
||||
# Create a map to store merged triangles
|
||||
merged = {}
|
||||
|
||||
for i in range(n_cells):
|
||||
if i in merged:
|
||||
continue
|
||||
|
||||
cell = mesh.GetCell(i)
|
||||
normal = np.array(mesh.GetCellData().GetNormals().GetTuple(i))
|
||||
point = np.array(cell.GetPoints().GetPoint(0))
|
||||
|
||||
merged[i] = [i]
|
||||
|
||||
for j in range(i + 1, n_cells):
|
||||
if j in merged:
|
||||
continue
|
||||
|
||||
cell_j = mesh.GetCell(j)
|
||||
normal_j = np.array(mesh.GetCellData().GetNormals().GetTuple(j))
|
||||
point_j = np.array(cell_j.GetPoints().GetPoint(0))
|
||||
|
||||
if self.are_coplanar(normal, normal_j, point, point_j):
|
||||
merged[i].append(j)
|
||||
|
||||
# Create new polygons
|
||||
new_polygons = vtk.vtkCellArray()
|
||||
for group in merged.values():
|
||||
if len(group) > 1:
|
||||
polygon = vtk.vtkPolygon()
|
||||
points = set()
|
||||
for idx in group:
|
||||
cell = mesh.GetCell(idx)
|
||||
for j in range(3):
|
||||
point_id = cell.GetPointId(j)
|
||||
points.add(point_id)
|
||||
polygon.GetPointIds().SetNumberOfIds(len(points))
|
||||
for j, point_id in enumerate(points):
|
||||
polygon.GetPointIds().SetId(j, point_id)
|
||||
new_polygons.InsertNextCell(polygon)
|
||||
else:
|
||||
new_polygons.InsertNextCell(mesh.GetCell(group[0]))
|
||||
|
||||
# Create new polydata
|
||||
new_polydata = vtk.vtkPolyData()
|
||||
new_polydata.SetPoints(mesh.GetPoints())
|
||||
new_polydata.SetPolys(new_polygons)
|
||||
|
||||
return new_polydata
|
||||
|
||||
|
||||
def create_cube_mesh(self):
|
||||
# cube_source = vtk.vtkSuperquadricSource()
|
||||
|
||||
reader = vtk.vtkSTLReader()
|
||||
reader.SetFileName("case.stl") # Replace with your mesh file path
|
||||
reader.Update()
|
||||
|
||||
featureEdges = vtk.vtkFeatureEdges()
|
||||
featureEdges.SetInputConnection(reader.GetOutputPort())
|
||||
featureEdges.BoundaryEdgesOn()
|
||||
featureEdges.FeatureEdgesOn()
|
||||
featureEdges.ManifoldEdgesOff()
|
||||
featureEdges.NonManifoldEdgesOff()
|
||||
featureEdges.Update()
|
||||
|
||||
# print(cube_source)
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputConnection(reader.GetOutputPort())
|
||||
actor = vtk.vtkActor()
|
||||
actor.SetMapper(mapper)
|
||||
self.renderer.AddActor(actor)
|
||||
|
||||
mapper_edge = vtk.vtkPolyDataMapper()
|
||||
mapper_edge.SetInputConnection(featureEdges.GetOutputPort())
|
||||
actor = vtk.vtkActor()
|
||||
actor.SetMapper(mapper_edge)
|
||||
self.renderer.AddActor(actor)
|
||||
|
||||
|
||||
def simplify_mesh(self, input_mesh, target_reduction):
|
||||
# Create the quadric decimation filter
|
||||
decimate = vtk.vtkDecimatePro()
|
||||
decimate.SetInputData(input_mesh)
|
||||
|
||||
# Set the reduction factor (0 to 1, where 1 means maximum reduction)
|
||||
decimate.SetTargetReduction(target_reduction)
|
||||
|
||||
# Optional: Preserve topology (if needed)
|
||||
decimate.PreserveTopologyOn()
|
||||
|
||||
# Perform the decimation
|
||||
decimate.Update()
|
||||
|
||||
return decimate.GetOutput()
|
||||
|
||||
|
||||
def combine_coplanar_faces(self, input_polydata, tolerance=0.001):
|
||||
# Clean the polydata to merge duplicate points
|
||||
clean = vtk.vtkCleanPolyData()
|
||||
clean.SetInputData(input_polydata)
|
||||
clean.SetTolerance(tolerance)
|
||||
clean.Update()
|
||||
|
||||
# Generate normals and merge coplanar polygons
|
||||
normals = vtk.vtkPolyDataNormals()
|
||||
normals.SetInputConnection(clean.GetOutputPort())
|
||||
normals.SplittingOff() # Disable splitting of sharp edges
|
||||
normals.ConsistencyOn() # Ensure consistent polygon ordering
|
||||
normals.AutoOrientNormalsOn() # Automatically orient normals
|
||||
normals.ComputePointNormalsOff() # We only need face normals
|
||||
normals.ComputeCellNormalsOn() # Compute cell normals
|
||||
normals.Update()
|
||||
|
||||
return normals.GetOutput()
|
||||
|
||||
|
||||
def poisson_reconstruction(self, points):
|
||||
# Create a polydata object from points
|
||||
point_polydata = vtk.vtkPolyData()
|
||||
point_polydata.SetPoints(points)
|
||||
|
||||
# Create a surface reconstruction filter
|
||||
surf = vtk.vtkSurfaceReconstructionFilter()
|
||||
surf.SetInputData(point_polydata)
|
||||
surf.Update()
|
||||
|
||||
# Create a contour filter to extract the surface
|
||||
cf = vtk.vtkContourFilter()
|
||||
cf.SetInputConnection(surf.GetOutputPort())
|
||||
cf.SetValue(0, 0.0)
|
||||
cf.Update()
|
||||
|
||||
# Reverse normals
|
||||
reverse = vtk.vtkReverseSense()
|
||||
reverse.SetInputConnection(cf.GetOutputPort())
|
||||
reverse.ReverseCellsOn()
|
||||
reverse.ReverseNormalsOn()
|
||||
reverse.Update()
|
||||
|
||||
return reverse.GetOutput()
|
||||
|
||||
|
||||
def create_simplified_outline(self, polydata):
|
||||
featureEdges = vtk.vtkFeatureEdges()
|
||||
featureEdges.SetInputData(polydata)
|
||||
featureEdges.BoundaryEdgesOn()
|
||||
featureEdges.FeatureEdgesOn()
|
||||
featureEdges.ManifoldEdgesOff()
|
||||
featureEdges.NonManifoldEdgesOff()
|
||||
featureEdges.Update()
|
||||
|
||||
"""# 3. Clean the edges to merge duplicate points
|
||||
cleaner = vtk.vtkCleanPolyData()
|
||||
cleaner.SetInputConnection(feature_edges.GetOutputPort())
|
||||
cleaner.Update()
|
||||
|
||||
# 4. Optional: Smooth the outline
|
||||
smooth = vtk.vtkSmoothPolyDataFilter()
|
||||
smooth.SetInputConnection(cleaner.GetOutputPort())
|
||||
smooth.SetNumberOfIterations(15)
|
||||
smooth.SetRelaxationFactor(0.1)
|
||||
smooth.FeatureEdgeSmoothingOff()
|
||||
smooth.BoundarySmoothingOn()
|
||||
smooth.Update()"""
|
||||
|
||||
return featureEdges
|
||||
|
||||
|
||||
def render_from_points_direct_with_faces(self, vertices, faces):
|
||||
points = vtk.vtkPoints()
|
||||
for i in range(vertices.shape[0]):
|
||||
points.InsertNextPoint(vertices[i])
|
||||
|
||||
# Create a vtkCellArray to store the triangles
|
||||
triangles = vtk.vtkCellArray()
|
||||
for i in range(faces.shape[0]):
|
||||
triangle = vtk.vtkTriangle()
|
||||
triangle.GetPointIds().SetId(0, faces[i, 0])
|
||||
triangle.GetPointIds().SetId(1, faces[i, 1])
|
||||
triangle.GetPointIds().SetId(2, faces[i, 2])
|
||||
triangles.InsertNextCell(triangle)
|
||||
|
||||
"""vtk_points = vtk.vtkPoints()
|
||||
for point in points:
|
||||
vtk_points.InsertNextPoint(point)
|
||||
|
||||
# Create a vtkCellArray to store the triangles
|
||||
triangles = vtk.vtkCellArray()
|
||||
|
||||
# Assuming points are organized as triplets forming triangles
|
||||
for i in range(0, len(points), 3):
|
||||
triangle = vtk.vtkTriangle()
|
||||
triangle.GetPointIds().SetId(0, i)
|
||||
triangle.GetPointIds().SetId(1, i + 1)
|
||||
triangle.GetPointIds().SetId(2, i + 2)
|
||||
triangles.InsertNextCell(triangle)"""
|
||||
|
||||
# Create a polydata object
|
||||
polydata = vtk.vtkPolyData()
|
||||
polydata.SetPoints(points)
|
||||
polydata.SetPolys(triangles)
|
||||
|
||||
# Calculate normals
|
||||
normalGenerator = vtk.vtkPolyDataNormals()
|
||||
normalGenerator.SetInputData(polydata)
|
||||
normalGenerator.ComputePointNormalsOn()
|
||||
normalGenerator.ComputeCellNormalsOn()
|
||||
normalGenerator.Update()
|
||||
|
||||
self.cell_normals = vtk_to_numpy(normalGenerator.GetOutput().GetCellData().GetNormals())
|
||||
|
||||
# merged_polydata = self.merge_coplanar_triangles(polydata)
|
||||
|
||||
# Create a mapper and actor
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputData(polydata)
|
||||
|
||||
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
|
||||
|
||||
feature_edges = self.create_simplified_outline(polydata)
|
||||
|
||||
# Create a mapper for the feature edges
|
||||
edge_mapper = vtk.vtkPolyDataMapper()
|
||||
# Already wiht output
|
||||
edge_mapper.SetInputConnection(feature_edges.GetOutputPort())
|
||||
|
||||
# Create an actor for the feature edges
|
||||
edge_actor = vtk.vtkActor()
|
||||
edge_actor.SetMapper(edge_mapper)
|
||||
|
||||
# Set the properties of the edge actor
|
||||
edge_actor.GetProperty().SetColor(1, 0, 0) # Set color (red in this case)
|
||||
edge_actor.GetProperty().SetLineWidth(2) # Set line width
|
||||
|
||||
# Optionally, if you want to keep the original mesh visible:
|
||||
# (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
|
||||
self.renderer.AddActor(edge_actor)
|
||||
|
||||
# Force an update of the pipeline
|
||||
mapper.Update()
|
||||
self.vtk_widget.GetRenderWindow().Render()
|
||||
|
||||
"""# Print statistics
|
||||
print(f"Original points: {len(points)}")
|
||||
print(f"Number of triangles: {triangles.GetNumberOfCells()}")
|
||||
print(f"Final number of points: {normals.GetOutput().GetNumberOfPoints()}")
|
||||
print(f"Final number of cells: {normals.GetOutput().GetNumberOfCells()}")"""
|
||||
|
||||
|
||||
def render_from_points_direct(self, points):
|
||||
### Rendermethod for SDF mesh (output)
|
||||
# Create a vtkPoints object and store the points in it
|
||||
vtk_points = vtk.vtkPoints()
|
||||
for point in points:
|
||||
vtk_points.InsertNextPoint(point)
|
||||
|
||||
# Create a polydata object
|
||||
point_polydata = vtk.vtkPolyData()
|
||||
point_polydata.SetPoints(vtk_points)
|
||||
|
||||
# Surface reconstruction
|
||||
surf = vtk.vtkSurfaceReconstructionFilter()
|
||||
surf.SetInputData(point_polydata)
|
||||
surf.Update()
|
||||
|
||||
# Create a contour filter to extract the surface
|
||||
cf = vtk.vtkContourFilter()
|
||||
cf.SetInputConnection(surf.GetOutputPort())
|
||||
cf.SetValue(0, 0.0)
|
||||
cf.Update()
|
||||
|
||||
# Reverse the normals
|
||||
reverse = vtk.vtkReverseSense()
|
||||
reverse.SetInputConnection(cf.GetOutputPort())
|
||||
reverse.ReverseCellsOn()
|
||||
reverse.ReverseNormalsOn()
|
||||
reverse.Update()
|
||||
|
||||
# Get the reconstructed mesh
|
||||
reconstructed_mesh = reverse.GetOutput()
|
||||
|
||||
"""# Simplify the mesh
|
||||
target_reduction = 1 # Adjust this value as needed
|
||||
simplified_mesh = self.simplify_mesh(reconstructed_mesh, target_reduction)
|
||||
|
||||
combinded_faces = self.combine_coplanar_faces(simplified_mesh, 0.001)"""
|
||||
|
||||
# Create a mapper and actor for the simplified mesh
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputData(reconstructed_mesh)
|
||||
|
||||
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
|
||||
|
||||
# Add the actor to the renderer
|
||||
self.renderer.AddActor(actor)
|
||||
|
||||
# Force an update of the pipeline
|
||||
# mapper.Update()
|
||||
self.vtk_widget.GetRenderWindow().Render()
|
||||
|
||||
# Print statistics
|
||||
print(f"Original points: {len(points)}")
|
||||
print(
|
||||
f"Reconstructed mesh: {reconstructed_mesh.GetNumberOfPoints()} points, {reconstructed_mesh.GetNumberOfCells()} cells")
|
||||
"""print(
|
||||
f"Simplified mesh: {simplified_mesh.GetNumberOfPoints()} points, {simplified_mesh.GetNumberOfCells()} cells")"""
|
57
fluency.py
57
fluency.py
@ -1,57 +0,0 @@
|
||||
from math import pi, sin, cos
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
from direct.task import Task
|
||||
from direct.gui.DirectGui import DirectEntry, DirectButton
|
||||
from sdf import *
|
||||
|
||||
|
||||
class MyApp(ShowBase):
|
||||
def __init__(self):
|
||||
ShowBase.__init__(self)
|
||||
|
||||
"""# Load the environment model.
|
||||
self.scene = self.loader.loadModel("station_alpha_ripped.glb")
|
||||
# Reparent the model to render.
|
||||
self.scene.reparentTo(self.render)
|
||||
# Apply scale and position transforms on the model.
|
||||
self.scene.setScale(1, 1, 1)
|
||||
self.scene.setPos(0, 0, 0)"""
|
||||
|
||||
# Add the spinCameraTask procedure to the task manager.
|
||||
self.taskMgr.add(self.spinCameraTask, "SpinCameraTask")
|
||||
|
||||
# Create a DirectEntry widget for text input
|
||||
self.textEntry = DirectEntry(
|
||||
text="", scale=0.05, command=self.setText, initialText="Type here"
|
||||
)
|
||||
self.textEntry.reparentTo(self.aspect2d)
|
||||
self.textEntry.setPos(-1.1, 0, -0.95)
|
||||
|
||||
self.apply = DirectButton(text="Make Cube",scale=0.05, command=self.buttonClicked)
|
||||
self.apply.reparentTo(self.aspect2d)
|
||||
self.apply.setPos(-1, -1, 0.8)
|
||||
|
||||
def setText(self, textEntered):
|
||||
print("Text entered:", textEntered)
|
||||
|
||||
def buttonClicked(self):
|
||||
print("Clciked")
|
||||
obj = box(0.9)
|
||||
model = obj.save("model.stl" , step=0.01)
|
||||
self.scene = self.loader.loadModel("model.stl")
|
||||
self.scene.reparentTo(self.render)
|
||||
self.scene.setScale(1, 1, 1)
|
||||
self.scene.setPos(0, 0, 0)
|
||||
|
||||
|
||||
# Define a procedure to move the camera.
|
||||
def spinCameraTask(self, task):
|
||||
angleDegrees = task.time * 6.0
|
||||
angleRadians = angleDegrees * (pi / 180.0)
|
||||
self.camera.setPos(20 * sin(angleRadians), -20 * cos(angleRadians), 3)
|
||||
self.camera.setHpr(angleDegrees, 0, 0)
|
||||
return Task.cont
|
||||
|
||||
|
||||
app = MyApp()
|
||||
app.run()
|
51
fluencyb.py
51
fluencyb.py
@ -1,51 +0,0 @@
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
|
||||
class Button:
|
||||
def __init__(self, text, position, size):
|
||||
self.text = text
|
||||
self.font = pygame.font.Font(None, 36)
|
||||
self.position = position
|
||||
self.size = size
|
||||
self.rect = pygame.Rect(position, size)
|
||||
self.clicked = False
|
||||
|
||||
def draw(self, surface):
|
||||
pygame.draw.rect(surface, (255, 255, 255), self.rect, 2)
|
||||
text_surface = self.font.render(self.text, True, (255, 255, 255))
|
||||
text_rect = text_surface.get_rect(center=self.rect.center)
|
||||
surface.blit(text_surface, text_rect)
|
||||
|
||||
def is_clicked(self, pos):
|
||||
if self.rect.collidepoint(pos):
|
||||
self.clicked = True
|
||||
return True
|
||||
return False
|
||||
|
||||
def main():
|
||||
pygame.init()
|
||||
display = (800, 600)
|
||||
screen = pygame.display.set_mode(display)
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
button = Button("Click Me!", (300, 250), (200, 50))
|
||||
|
||||
running = True
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == QUIT:
|
||||
running = False
|
||||
elif event.type == MOUSEBUTTONDOWN:
|
||||
if event.button == 1:
|
||||
if button.is_clicked(event.pos):
|
||||
print("Button Clicked!")
|
||||
|
||||
screen.fill((0, 0, 0))
|
||||
button.draw(screen)
|
||||
pygame.display.flip()
|
||||
clock.tick(60)
|
||||
|
||||
pygame.quit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
75
main.py
75
main.py
@ -8,7 +8,8 @@ from drawing_modules.vysta_widget import PyVistaWidget
|
||||
from drawing_modules.draw_widget2d import SketchWidget
|
||||
from sdf import *
|
||||
from python_solvespace import SolverSystem, ResultFlag
|
||||
from mesh_modules import simple_mesh
|
||||
from mesh_modules import simple_mesh, vesta_mesh, interactor_mesh
|
||||
|
||||
|
||||
# main, draw_widget, gl_widget
|
||||
|
||||
@ -44,7 +45,8 @@ class MainWindow(QMainWindow):
|
||||
self.ui.sketch_list.itemChanged.connect(self.draw_mesh)
|
||||
|
||||
### Sketches
|
||||
self.ui.pb_origin_wp.pressed.connect(self.add_new_sketch)
|
||||
self.ui.pb_origin_wp.pressed.connect(self.add_new_sketch_origin)
|
||||
self.ui.pb_origin_face.pressed.connect(self.add_new_sketch_wp)
|
||||
|
||||
self.ui.pb_nw_sktch.pressed.connect(self.add_sketch)
|
||||
self.ui.pb_del_sketch.pressed.connect(self.del_sketch)
|
||||
@ -66,10 +68,21 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.sketchWidget.constrain_done.connect(self.draw_op_complete)
|
||||
|
||||
def add_new_sketch(self):
|
||||
def add_new_sketch_origin(self):
|
||||
self.sketchWidget.clear_sketch()
|
||||
self.sketchWidget.create_workplane()
|
||||
|
||||
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.access_selected_points
|
||||
normal = self.custom_3D_Widget.selected_normal
|
||||
|
||||
self.sketchWidget.create_workplane_projected()
|
||||
self.sketchWidget.create_proj_lines(edges)
|
||||
|
||||
#self.sketchWidget.create_workplane_space(edges, normal)
|
||||
|
||||
def act_line_mode(self):
|
||||
if not self.ui.pb_linetool.isChecked():
|
||||
self.sketchWidget.mouse_mode = 'line'
|
||||
@ -77,7 +90,6 @@ class MainWindow(QMainWindow):
|
||||
self.sketchWidget.mouse_mode = None
|
||||
self.sketchWidget.line_draw_buffer = [None, None]
|
||||
|
||||
|
||||
def act_constrain_pt_pt_mode(self):
|
||||
if not self.ui.pb_con_ptpt.isChecked():
|
||||
self.sketchWidget.mouse_mode = 'pt_pt'
|
||||
@ -126,24 +138,24 @@ class MainWindow(QMainWindow):
|
||||
self.sketchWidget.mouse_mode = None
|
||||
self.sketchWidget.reset_buffers()
|
||||
|
||||
def calc_sketch_projection_3d(self, depth):
|
||||
name = self.ui.sketch_list.currentItem().text()
|
||||
#print("selected_for disp", name)
|
||||
model = self.model['sketch'][name]['sketch_points']
|
||||
#print("sketch points from model", model)
|
||||
simp_mesh = simple_mesh.generate_mesh(model, depth)
|
||||
print("Generated model", simp_mesh)
|
||||
self.custom_3D_Widget. load_interactor_mesh(simp_mesh) #draw_interactor(simp_mesh)
|
||||
def calc_sketch_projection_3d(self, lines, z_origin, depth):
|
||||
print(f"Gemetries {lines}, {z_origin}, {depth}")
|
||||
|
||||
edges = interactor_mesh.generate_mesh(lines, z_origin, depth)
|
||||
print("final_mesh", edges)
|
||||
self.custom_3D_Widget.load_interactor_mesh(edges)
|
||||
|
||||
def draw_mesh(self):
|
||||
#print("Update")
|
||||
name = self.ui.body_list.currentItem().text()
|
||||
#print("selected_for disp", name)
|
||||
print("selected_for disp", name)
|
||||
model = self.model['operation'][name]['sdf_object']
|
||||
mesh = model.generate()
|
||||
#print("Mesh sdf", mesh)
|
||||
#self.custom_3D_Widget.render_from_points_direct_with_faces(mesh)
|
||||
self.custom_3D_Widget.render_from_points_direct_with_faces(mesh)
|
||||
|
||||
vesta = vesta_mesh
|
||||
model_data = vesta.generate_mesh_from_sdf(model, resolution=64, threshold=0)
|
||||
|
||||
vertices, faces = model_data
|
||||
vesta.save_mesh_as_stl(vertices, faces, 'test.stl')
|
||||
self.custom_3D_Widget.render_from_points_direct_with_faces(vertices, faces)
|
||||
|
||||
def on_item_changed(self, current_item, previous_item):
|
||||
if current_item:
|
||||
@ -158,6 +170,19 @@ class MainWindow(QMainWindow):
|
||||
|
||||
return points_for_sdf
|
||||
|
||||
def convert_lines_for_interactor(self):
|
||||
points_for_interact = []
|
||||
for point_to_poly in self.sketchWidget.slv_lines_main:
|
||||
start, end = point_to_poly['ui_points']
|
||||
start_draw = self.translate_points_tup(start)
|
||||
end_draw = self.translate_points_tup(end)
|
||||
line = start_draw, end_draw
|
||||
points_for_interact.append(line)
|
||||
|
||||
print("packed_lines", points_for_interact)
|
||||
|
||||
return points_for_interact
|
||||
|
||||
def add_sketch(self):
|
||||
|
||||
name = f"sketch-{str(names.get_first_name())}"
|
||||
@ -226,6 +251,7 @@ class MainWindow(QMainWindow):
|
||||
print(f"Item '{item_name}' not found in either 'sketch' or 'operation' dictionary.")
|
||||
else:
|
||||
print("No item selected.")
|
||||
|
||||
def update_body(self):
|
||||
pass
|
||||
|
||||
@ -258,13 +284,12 @@ 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
|
||||
del points[-1]
|
||||
|
||||
print(points)
|
||||
|
||||
length, ok = QInputDialog.getDouble(self, 'Extrude Length', 'Enter a mm value:', decimals=2)
|
||||
#TODO : Implement cancel
|
||||
|
||||
@ -278,20 +303,20 @@ class MainWindow(QMainWindow):
|
||||
'type': 'extrude',
|
||||
'sdf_object': f,
|
||||
}
|
||||
print(element)
|
||||
#print(element)
|
||||
|
||||
self.model['operation'][name_op] = element
|
||||
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.draw_mesh()
|
||||
#self.calc_sketch_projection_3d(length)
|
||||
self.calc_sketch_projection_3d(lines, 0, length)
|
||||
#self.draw_mesh()
|
||||
|
||||
def send_cut(self):
|
||||
name = self.ui.body_list.currentItem().text()
|
||||
points = self.model['operation'][name]['sdf_object']
|
||||
self.list_selected.append(points)
|
||||
print(self.list_selected)
|
||||
#print(self.list_selected)
|
||||
|
||||
if len(self.list_selected) > 1:
|
||||
geo = Geometry()
|
||||
@ -308,7 +333,7 @@ class MainWindow(QMainWindow):
|
||||
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.draw_mesh()
|
||||
else:
|
||||
print("mindestens 2!")
|
||||
|
||||
|
44
mesh_modules/interactor_mesh.py
Normal file
44
mesh_modules/interactor_mesh.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Draw simple boundary based on the lines and depth
|
||||
|
||||
def generate_mesh(lines: list, z_origin: float, depth: float, symmetric: bool = True):
|
||||
if symmetric:
|
||||
depth1 = depth/2
|
||||
depth2 = -depth/2
|
||||
origin = create_3D(lines, z_origin, depth1)
|
||||
extruded = create_3D(lines, z_origin, depth2)
|
||||
else:
|
||||
origin = create_3D(lines, z_origin, 0)
|
||||
extruded = create_3D(lines, z_origin, depth)
|
||||
|
||||
vert_lines = create_vert_lines(origin, extruded)
|
||||
|
||||
print(f"Result = {origin} / {extruded} / {vert_lines}")
|
||||
|
||||
return origin + vert_lines + extruded
|
||||
|
||||
|
||||
def create_vert_lines(origin, extruded):
|
||||
vert_lines = []
|
||||
for d3_point_o, d3point_e in zip(origin, extruded):
|
||||
for sp3d_1, sp3d_2 in zip(d3_point_o, d3point_e):
|
||||
new_line = sp3d_1, sp3d_2
|
||||
vert_lines.append(new_line)
|
||||
return vert_lines
|
||||
|
||||
|
||||
def create_3D(lines, z_origin, depth):
|
||||
line_loop = []
|
||||
for coordinate2d in lines:
|
||||
start, end = coordinate2d
|
||||
|
||||
xs,ys = start
|
||||
coordinate3d_start_orig = xs, ys, z_origin + depth
|
||||
|
||||
xe, ye = end
|
||||
coordinate3d_end_orig = xe, ye, z_origin + depth
|
||||
|
||||
line3d_orig = coordinate3d_start_orig, coordinate3d_end_orig
|
||||
|
||||
line_loop.append(line3d_orig)
|
||||
|
||||
return line_loop
|
@ -1,9 +1,172 @@
|
||||
import numpy as np
|
||||
from scipy.spatial import ConvexHull
|
||||
from stl import mesh
|
||||
from scipy.spatial import Delaunay, ConvexHull
|
||||
from shapely.geometry import Polygon, Point
|
||||
|
||||
|
||||
def generate_mesh(points, depth):
|
||||
def alpha_shape(points, alpha):
|
||||
"""
|
||||
Compute the alpha shape (concave hull) of a set of points.
|
||||
"""
|
||||
|
||||
def add_edge(edges, edge_points, points, i, j):
|
||||
"""Add a line between the i-th and j-th points if not in the list already"""
|
||||
if (i, j) in edges or (j, i) in edges:
|
||||
return
|
||||
edges.add((i, j))
|
||||
edge_points.append(points[[i, j]])
|
||||
|
||||
tri = Delaunay(points)
|
||||
edges = set()
|
||||
edge_points = []
|
||||
|
||||
# Loop over triangles:
|
||||
for ia, ib, ic in tri.simplices:
|
||||
pa = points[ia]
|
||||
pb = points[ib]
|
||||
pc = points[ic]
|
||||
# Lengths of sides of triangle
|
||||
a = np.sqrt((pa[0] - pb[0]) ** 2 + (pa[1] - pb[1]) ** 2)
|
||||
b = np.sqrt((pb[0] - pc[0]) ** 2 + (pb[1] - pc[1]) ** 2)
|
||||
c = np.sqrt((pc[0] - pa[0]) ** 2 + (pc[1] - pa[1]) ** 2)
|
||||
# Semiperimeter of triangle
|
||||
s = (a + b + c) / 2.0
|
||||
# Area of triangle by Heron's formula
|
||||
area = np.sqrt(s * (s - a) * (s - b) * (s - c))
|
||||
circum_r = a * b * c / (4.0 * area)
|
||||
# Here's the radius filter.
|
||||
if circum_r < 1.0 / alpha:
|
||||
add_edge(edges, edge_points, points, ia, ib)
|
||||
add_edge(edges, edge_points, points, ib, ic)
|
||||
add_edge(edges, edge_points, points, ic, ia)
|
||||
|
||||
m = np.array(edge_points)
|
||||
return m
|
||||
|
||||
|
||||
def generate_mesh(points, depth, alpha=0.1):
|
||||
"""
|
||||
Generate a mesh by extruding a 2D shape along the Z-axis, automatically detecting holes.
|
||||
|
||||
:param points: List of (x, y) tuples representing all points of the 2D shape, including potential holes.
|
||||
:param depth: Extrusion depth along the Z-axis.
|
||||
:param alpha: Alpha value for the alpha shape algorithm (controls the "tightness" of the boundary).
|
||||
:return: Tuple of vertices and faces.
|
||||
"""
|
||||
# Convert points to a numpy array
|
||||
points_2d = np.array(points)
|
||||
|
||||
# Compute the alpha shape (outer boundary)
|
||||
boundary_edges = alpha_shape(points_2d, alpha)
|
||||
|
||||
# Create a Polygon from the boundary
|
||||
boundary_polygon = Polygon(boundary_edges)
|
||||
|
||||
# Separate points into boundary and interior
|
||||
boundary_points = []
|
||||
interior_points = []
|
||||
for point in points:
|
||||
if Point(point).touches(boundary_polygon) or Point(point).within(boundary_polygon):
|
||||
if Point(point).touches(boundary_polygon):
|
||||
boundary_points.append(point)
|
||||
else:
|
||||
interior_points.append(point)
|
||||
|
||||
# Perform Delaunay triangulation on all points
|
||||
tri = Delaunay(points_2d)
|
||||
|
||||
# Generate the top and bottom faces
|
||||
bottom_face = np.hstack((tri.points, np.zeros((tri.points.shape[0], 1))))
|
||||
top_face = np.hstack((tri.points, np.ones((tri.points.shape[0], 1)) * depth))
|
||||
|
||||
# Combine top and bottom vertices
|
||||
vertices_array = np.vstack((bottom_face, top_face))
|
||||
|
||||
# Create faces
|
||||
faces = []
|
||||
|
||||
# Bottom face triangulation
|
||||
for simplex in tri.simplices:
|
||||
faces.append(simplex.tolist())
|
||||
|
||||
# Top face triangulation (with an offset)
|
||||
top_offset = len(tri.points)
|
||||
for simplex in tri.simplices:
|
||||
faces.append([i + top_offset for i in simplex])
|
||||
|
||||
# Side faces for the outer boundary
|
||||
for i in range(len(boundary_points)):
|
||||
next_i = (i + 1) % len(boundary_points)
|
||||
current = points.index(boundary_points[i])
|
||||
next_point = points.index(boundary_points[next_i])
|
||||
faces.append([current, top_offset + current, top_offset + next_point])
|
||||
faces.append([current, top_offset + next_point, next_point])
|
||||
|
||||
# Convert vertices to the desired format: list of tuples
|
||||
vertices = [tuple(vertex) for vertex in vertices_array]
|
||||
|
||||
return vertices, faces
|
||||
|
||||
def generate_mesh_wholes(points, holes, depth):
|
||||
"""
|
||||
Generate a mesh by extruding a 2D shape along the Z-axis, including holes.
|
||||
|
||||
:param points: List of (x, y) tuples representing the outer boundary of the 2D shape.
|
||||
:param holes: List of lists, where each inner list contains (x, y) tuples representing a hole.
|
||||
:param depth: Extrusion depth along the Z-axis.
|
||||
:return: Tuple of vertices and faces.
|
||||
"""
|
||||
# Convert points to a numpy array
|
||||
points_2d = np.array(points)
|
||||
|
||||
# Prepare points for triangulation
|
||||
triangulation_points = points_2d.tolist()
|
||||
for hole in holes:
|
||||
triangulation_points.extend(hole)
|
||||
|
||||
# Perform Delaunay triangulation
|
||||
tri = Delaunay(np.array(triangulation_points))
|
||||
|
||||
# Generate the top and bottom faces
|
||||
bottom_face = np.hstack((tri.points, np.zeros((tri.points.shape[0], 1))))
|
||||
top_face = np.hstack((tri.points, np.ones((tri.points.shape[0], 1)) * depth))
|
||||
|
||||
# Combine top and bottom vertices
|
||||
vertices_array = np.vstack((bottom_face, top_face))
|
||||
|
||||
# Create faces
|
||||
faces = []
|
||||
|
||||
# Bottom face triangulation
|
||||
for simplex in tri.simplices:
|
||||
faces.append(simplex.tolist())
|
||||
|
||||
# Top face triangulation (with an offset)
|
||||
top_offset = len(tri.points)
|
||||
for simplex in tri.simplices:
|
||||
faces.append([i + top_offset for i in simplex])
|
||||
|
||||
# Side faces
|
||||
for i in range(len(points)):
|
||||
next_i = (i + 1) % len(points)
|
||||
faces.append([i, top_offset + i, top_offset + next_i])
|
||||
faces.append([i, top_offset + next_i, next_i])
|
||||
|
||||
# Side faces for holes
|
||||
start_index = len(points)
|
||||
for hole in holes:
|
||||
for i in range(len(hole)):
|
||||
current = start_index + i
|
||||
next_i = start_index + (i + 1) % len(hole)
|
||||
faces.append([current, top_offset + next_i, top_offset + current])
|
||||
faces.append([current, next_i, top_offset + next_i])
|
||||
start_index += len(hole)
|
||||
|
||||
# Convert vertices to the desired format: list of tuples
|
||||
vertices = [tuple(vertex) for vertex in vertices_array]
|
||||
|
||||
return vertices, faces
|
||||
|
||||
def generate_mesh_simple(points, depth):
|
||||
"""
|
||||
Generate a mesh by extruding a 2D shape along the Z-axis.
|
||||
|
||||
|
119
mesh_modules/vesta_mesh.py
Normal file
119
mesh_modules/vesta_mesh.py
Normal file
@ -0,0 +1,119 @@
|
||||
import numpy as np
|
||||
from skimage import measure
|
||||
import multiprocessing
|
||||
from functools import partial
|
||||
from multiprocessing.pool import ThreadPool
|
||||
import itertools
|
||||
import time
|
||||
|
||||
|
||||
def _cartesian_product(*arrays):
|
||||
la = len(arrays)
|
||||
dtype = np.result_type(*arrays)
|
||||
arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype)
|
||||
for i, a in enumerate(np.ix_(*arrays)):
|
||||
arr[..., i] = a
|
||||
return arr.reshape(-1, la)
|
||||
|
||||
|
||||
class VESTA:
|
||||
def __init__(self, sdf, bounds=None, resolution=64, threshold=0.0, workers=None):
|
||||
self.sdf = sdf
|
||||
self.bounds = bounds
|
||||
self.resolution = resolution
|
||||
self.threshold = threshold
|
||||
self.workers = workers or multiprocessing.cpu_count()
|
||||
|
||||
def _estimate_bounds(self):
|
||||
s = 16
|
||||
x0 = y0 = z0 = -1e9
|
||||
x1 = y1 = z1 = 1e9
|
||||
prev = None
|
||||
for i in range(32):
|
||||
X = np.linspace(x0, x1, s)
|
||||
Y = np.linspace(y0, y1, s)
|
||||
Z = np.linspace(z0, z1, s)
|
||||
d = np.array([X[1] - X[0], Y[1] - Y[0], Z[1] - Z[0]])
|
||||
threshold = np.linalg.norm(d) / 2
|
||||
if threshold == prev:
|
||||
break
|
||||
prev = threshold
|
||||
P = _cartesian_product(X, Y, Z)
|
||||
volume = self.sdf(P).reshape((len(X), len(Y), len(Z)))
|
||||
where = np.argwhere(np.abs(volume) <= threshold)
|
||||
if where.size == 0:
|
||||
continue
|
||||
x1, y1, z1 = (x0, y0, z0) + where.max(axis=0) * d + d / 2
|
||||
x0, y0, z0 = (x0, y0, z0) + where.min(axis=0) * d - d / 2
|
||||
if prev is None:
|
||||
raise ValueError("Failed to estimate bounds. No points found within any threshold.")
|
||||
return ((x0, y0, z0), (x1, y1, z1))
|
||||
|
||||
def _vesta_worker(self, chunk):
|
||||
x0, x1, y0, y1, z0, z1 = chunk
|
||||
X = np.linspace(x0, x1, self.resolution)
|
||||
Y = np.linspace(y0, y1, self.resolution)
|
||||
Z = np.linspace(z0, z1, self.resolution)
|
||||
P = _cartesian_product(X, Y, Z)
|
||||
V = self.sdf(P).reshape((self.resolution, self.resolution, self.resolution))
|
||||
|
||||
try:
|
||||
verts, faces, _, _ = measure.marching_cubes(V, self.threshold)
|
||||
except RuntimeError:
|
||||
# Return empty arrays if marching_cubes fails
|
||||
return np.array([]), np.array([])
|
||||
|
||||
# Scale and translate vertices to match the chunk's bounds
|
||||
verts = verts / (self.resolution - 1)
|
||||
verts[:, 0] = verts[:, 0] * (x1 - x0) + x0
|
||||
verts[:, 1] = verts[:, 1] * (y1 - y0) + y0
|
||||
verts[:, 2] = verts[:, 2] * (z1 - z0) + z0
|
||||
|
||||
return verts, faces
|
||||
|
||||
def _merge_meshes(self, results):
|
||||
all_verts = []
|
||||
all_faces = []
|
||||
offset = 0
|
||||
for verts, faces in results:
|
||||
if len(verts) > 0 and len(faces) > 0:
|
||||
all_verts.append(verts)
|
||||
all_faces.append(faces + offset)
|
||||
offset += len(verts)
|
||||
if not all_verts or not all_faces:
|
||||
return np.array([]), np.array([])
|
||||
return np.vstack(all_verts), np.vstack(all_faces)
|
||||
|
||||
def generate_mesh(self):
|
||||
if self.bounds is None:
|
||||
self.bounds = self._estimate_bounds()
|
||||
|
||||
(x0, y0, z0), (x1, y1, z1) = self.bounds
|
||||
chunks = [
|
||||
(x0, x1, y0, y1, z0, z1)
|
||||
]
|
||||
|
||||
with ThreadPool(self.workers) as pool:
|
||||
results = pool.map(self._vesta_worker, chunks)
|
||||
|
||||
verts, faces = self._merge_meshes(results)
|
||||
return verts, faces
|
||||
|
||||
|
||||
def generate_mesh_from_sdf(sdf, bounds=None, resolution=64, threshold=0.0, workers=None):
|
||||
vesta = VESTA(sdf, bounds, resolution, threshold, workers)
|
||||
return vesta.generate_mesh()
|
||||
|
||||
|
||||
# Helper function to save the mesh as an STL file
|
||||
def save_mesh_as_stl(vertices, faces, filename):
|
||||
from stl import mesh
|
||||
|
||||
# Create the mesh
|
||||
cube = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
|
||||
for i, f in enumerate(faces):
|
||||
for j in range(3):
|
||||
cube.vectors[i][j] = vertices[f[j], :]
|
||||
|
||||
# Write the mesh to file
|
||||
cube.save(filename)
|
170
side_fluency.py
170
side_fluency.py
@ -1,170 +0,0 @@
|
||||
import sys
|
||||
from PySide6.QtOpenGLWidgets import QOpenGLWidget
|
||||
from PySide6.QtWidgets import (QApplication, QMainWindow, QHBoxLayout, QVBoxLayout, QWidget, QPushButton, QGroupBox,
|
||||
QTextEdit, QSizePolicy)
|
||||
from PySide6.QtCore import QSize, Qt, QPoint
|
||||
from OpenGL.GL import *
|
||||
from OpenGL.GLU import *
|
||||
from stl import mesh
|
||||
|
||||
|
||||
class OpenGLWidget(QOpenGLWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.stl_file = "out.stl" # Replace with your STL file path
|
||||
self.lastPos = QPoint()
|
||||
self.xRot = 0
|
||||
self.yRot = 0
|
||||
self.zoom = -10.0
|
||||
|
||||
def load_stl(self, filename):
|
||||
try:
|
||||
stl_mesh = mesh.Mesh.from_file(filename)
|
||||
return stl_mesh.vectors
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File {filename} not found.")
|
||||
except Exception as e:
|
||||
print(f"Error loading {filename}: {e}")
|
||||
return []
|
||||
|
||||
def initializeGL(self):
|
||||
glClearColor(0, 0, 0, 1)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
|
||||
def resizeGL(self, w, h):
|
||||
glViewport(0, 0, w, h)
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
gluPerspective(45, w/h, 0.1, 100.0)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
|
||||
def paintGL(self):
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||
glLoadIdentity()
|
||||
glTranslatef(0.0, 0.0, self.zoom)
|
||||
glRotatef(self.xRot, 1.0, 0.0, 0.0)
|
||||
glRotatef(self.yRot, 0.0, 1.0, 0.0)
|
||||
glColor3f(1.0, 1.0, 1.0)
|
||||
|
||||
mesh_data = self.load_stl(self.stl_file)
|
||||
if mesh_data.any():
|
||||
self.draw_stl(mesh_data)
|
||||
|
||||
def draw_stl(self, vertices):
|
||||
glEnable(GL_LIGHTING)
|
||||
glEnable(GL_LIGHT0)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glEnable(GL_COLOR_MATERIAL)
|
||||
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
|
||||
|
||||
glLightfv(GL_LIGHT0, GL_POSITION, (0, 1, 1, 0))
|
||||
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.6, 0.6, 0.6, 0.6))
|
||||
|
||||
glBegin(GL_TRIANGLES)
|
||||
for triangle in vertices:
|
||||
for vertex in triangle:
|
||||
glVertex3fv(vertex)
|
||||
glEnd()
|
||||
|
||||
# Draw outer vertices as points
|
||||
glDisable(GL_LIGHTING)
|
||||
glColor3f(1.0, 0.0, 0.0) # Set color to red
|
||||
glPointSize(5.0) # Set point size
|
||||
glBegin(GL_POINTS)
|
||||
for triangle in vertices:
|
||||
for vertex in triangle:
|
||||
glVertex3fv(vertex)
|
||||
glEnd()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self.lastPos = event.pos()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
dx = event.x() - self.lastPos.x()
|
||||
dy = event.y() - self.lastPos.y()
|
||||
|
||||
if event.buttons() & Qt.LeftButton:
|
||||
self.xRot += 0.5 * dy
|
||||
self.yRot += 0.5 * dx
|
||||
self.update()
|
||||
|
||||
self.lastPos = event.pos()
|
||||
|
||||
def wheelEvent(self, event):
|
||||
delta = event.angleDelta().y()
|
||||
self.zoom += delta / 120
|
||||
self.update()
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.setWindowTitle("fluencyCAD")
|
||||
self.height = 1080
|
||||
self.width = 1920
|
||||
self.setFixedSize(QSize(self.width, self.height))
|
||||
|
||||
# Main central widget
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
|
||||
layout = QHBoxLayout(central_widget)
|
||||
|
||||
# Left tools area
|
||||
left_tools_layout = QVBoxLayout()
|
||||
|
||||
self.left_tools = QGroupBox("Left Tools")
|
||||
self.left_tools.setFixedSize(100, self.height)
|
||||
|
||||
self.draw_rectangle = QPushButton("Draw Rectangle")
|
||||
self.draw_rectangle.setFixedSize(80, 30)
|
||||
left_tools_layout.addWidget(self.draw_rectangle)
|
||||
|
||||
self.draw_rectangle = QPushButton("Draw Rectangle2")
|
||||
self.draw_rectangle.setFixedSize(80, 30)
|
||||
left_tools_layout.addWidget(self.draw_rectangle) # Align the button to the top
|
||||
|
||||
self.draw_rectangle = QPushButton("Draw Rectangle3")
|
||||
self.draw_rectangle.setFixedSize(80, 30)
|
||||
left_tools_layout.addWidget(self.draw_rectangle)
|
||||
|
||||
self.left_tools.setLayout(left_tools_layout)
|
||||
self.left_tools.setAlignment(Qt.AlignTop)
|
||||
layout.addWidget(self.left_tools)
|
||||
|
||||
# Center OpenGL widget and QTextEdit
|
||||
center_layout = QVBoxLayout()
|
||||
|
||||
self.openGLWidget = OpenGLWidget()
|
||||
center_layout.addWidget(self.openGLWidget)
|
||||
self.openGLWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
self.enter_code = QTextEdit()
|
||||
self.enter_code.setFixedSize(QSize(self.width, self.height // 4))
|
||||
center_layout.addWidget(self.enter_code)
|
||||
|
||||
self.code_tools = QGroupBox("Code Tools")
|
||||
self.code_tools.setFixedSize(QSize(self.width, self.height // 18))
|
||||
|
||||
code_tools_layout = QHBoxLayout() # Layout for code tools
|
||||
|
||||
self.apply_code = QPushButton("Apply Code") # Creating QPushButton
|
||||
self.apply_code.setFixedSize(80,30)
|
||||
|
||||
code_tools_layout.addWidget(self.apply_code) # Adding QPushButton to QHBoxLayout
|
||||
|
||||
self.code_tools.setLayout(code_tools_layout) # Setting QHBoxLayout to QGroupBox
|
||||
self.code_tools.setAlignment(Qt.AlignLeft)
|
||||
|
||||
center_layout.addWidget(self.code_tools) # Adding QGroupBox to the center layout
|
||||
|
||||
layout.addLayout(center_layout) # Adding center layout to the main layout
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
|
37
vulkan.py
37
vulkan.py
@ -1,37 +0,0 @@
|
||||
import vtk
|
||||
|
||||
def loadStl(fname):
|
||||
"""Load the given STL file, and return a vtkPolyData object for it."""
|
||||
reader = vtk.vtkSTLReader()
|
||||
reader.SetFileName(fname)
|
||||
reader.Update()
|
||||
polydata = reader.GetOutput()
|
||||
return polydata
|
||||
|
||||
def polyDataToActor(polydata):
|
||||
"""Wrap the provided vtkPolyData object in a mapper and an actor, returning the actor."""
|
||||
mapper = vtk.vtkPolyDataMapper()
|
||||
mapper.SetInputData(polydata)
|
||||
actor = vtk.vtkActor()
|
||||
actor.SetMapper(mapper)
|
||||
return actor
|
||||
|
||||
# Create a rendering window and renderer
|
||||
ren = vtk.vtkRenderer()
|
||||
renWin = vtk.vtkRenderWindow()
|
||||
renWin.AddRenderer(ren)
|
||||
|
||||
# Create a RenderWindowInteractor to permit manipulating the camera
|
||||
iren = vtk.vtkRenderWindowInteractor()
|
||||
iren.SetRenderWindow(renWin)
|
||||
style = vtk.vtkInteractorStyleTrackballCamera()
|
||||
iren.SetInteractorStyle(style)
|
||||
|
||||
stlFilename = "out.stl"
|
||||
polydata = loadStl(stlFilename)
|
||||
ren.AddActor(polyDataToActor(polydata))
|
||||
|
||||
# Enable user interface interactor
|
||||
iren.Initialize()
|
||||
renWin.Render()
|
||||
iren.Start()
|
Loading…
x
Reference in New Issue
Block a user