Merge pull request 'structure' (#7) from structure into master

Reviewed-on: BKLronin/fluency#7
This commit is contained in:
BKLronin 2025-06-01 10:05:29 +02:00
commit 0a9d557ce0
7 changed files with 391 additions and 180 deletions

31
Gui.py
View File

@ -419,6 +419,7 @@ class Ui_fluencyCAD(object):
self.pb_con_perp = QPushButton(self.groupBox_3) self.pb_con_perp = QPushButton(self.groupBox_3)
self.pb_con_perp.setObjectName(u"pb_con_perp") self.pb_con_perp.setObjectName(u"pb_con_perp")
self.pb_con_perp.setCheckable(True) self.pb_con_perp.setCheckable(True)
self.pb_con_perp.setAutoExclusive(False)
self.gridLayout_4.addWidget(self.pb_con_perp, 1, 1, 1, 1) self.gridLayout_4.addWidget(self.pb_con_perp, 1, 1, 1, 1)
@ -454,6 +455,7 @@ class Ui_fluencyCAD(object):
self.pb_con_mid = QPushButton(self.groupBox_3) self.pb_con_mid = QPushButton(self.groupBox_3)
self.pb_con_mid.setObjectName(u"pb_con_mid") self.pb_con_mid.setObjectName(u"pb_con_mid")
self.pb_con_mid.setCheckable(True) self.pb_con_mid.setCheckable(True)
self.pb_con_mid.setAutoExclusive(False)
self.gridLayout_4.addWidget(self.pb_con_mid, 1, 0, 1, 1) self.gridLayout_4.addWidget(self.pb_con_mid, 1, 0, 1, 1)
@ -469,11 +471,11 @@ class Ui_fluencyCAD(object):
self.tabWidget.setSizePolicy(sizePolicy5) self.tabWidget.setSizePolicy(sizePolicy5)
self.tabWidget.setMaximumSize(QSize(200, 16777215)) self.tabWidget.setMaximumSize(QSize(200, 16777215))
self.tabWidget.setTabPosition(QTabWidget.South) self.tabWidget.setTabPosition(QTabWidget.South)
self.widget = QWidget() self.snaps = QWidget()
self.widget.setObjectName(u"widget") self.snaps.setObjectName(u"snaps")
self.verticalLayout_3 = QVBoxLayout(self.widget) self.verticalLayout_3 = QVBoxLayout(self.snaps)
self.verticalLayout_3.setObjectName(u"verticalLayout_3") self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.groupBox_5 = QGroupBox(self.widget) self.groupBox_5 = QGroupBox(self.snaps)
self.groupBox_5.setObjectName(u"groupBox_5") self.groupBox_5.setObjectName(u"groupBox_5")
sizePolicy6 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) sizePolicy6 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
sizePolicy6.setHorizontalStretch(0) sizePolicy6.setHorizontalStretch(0)
@ -491,6 +493,7 @@ class Ui_fluencyCAD(object):
self.pb_snap_vert = QPushButton(self.groupBox_5) self.pb_snap_vert = QPushButton(self.groupBox_5)
self.pb_snap_vert.setObjectName(u"pb_snap_vert") self.pb_snap_vert.setObjectName(u"pb_snap_vert")
self.pb_snap_vert.setCheckable(True) self.pb_snap_vert.setCheckable(True)
self.pb_snap_vert.setAutoExclusive(False)
self.gridLayout_11.addWidget(self.pb_snap_vert, 2, 1, 1, 1) self.gridLayout_11.addWidget(self.pb_snap_vert, 2, 1, 1, 1)
@ -516,12 +519,14 @@ class Ui_fluencyCAD(object):
self.pushButton_7 = QPushButton(self.groupBox_5) self.pushButton_7 = QPushButton(self.groupBox_5)
self.pushButton_7.setObjectName(u"pushButton_7") self.pushButton_7.setObjectName(u"pushButton_7")
self.pushButton_7.setCheckable(True) self.pushButton_7.setCheckable(True)
self.pushButton_7.setAutoExclusive(False)
self.gridLayout_11.addWidget(self.pushButton_7, 3, 0, 1, 1) self.gridLayout_11.addWidget(self.pushButton_7, 3, 0, 1, 1)
self.pb_snap_horiz = QPushButton(self.groupBox_5) self.pb_snap_horiz = QPushButton(self.groupBox_5)
self.pb_snap_horiz.setObjectName(u"pb_snap_horiz") self.pb_snap_horiz.setObjectName(u"pb_snap_horiz")
self.pb_snap_horiz.setCheckable(True) self.pb_snap_horiz.setCheckable(True)
self.pb_snap_horiz.setAutoExclusive(False)
self.gridLayout_11.addWidget(self.pb_snap_horiz, 2, 0, 1, 1) self.gridLayout_11.addWidget(self.pb_snap_horiz, 2, 0, 1, 1)
@ -534,28 +539,32 @@ class Ui_fluencyCAD(object):
self.pushButton_8 = QPushButton(self.groupBox_5) self.pushButton_8 = QPushButton(self.groupBox_5)
self.pushButton_8.setObjectName(u"pushButton_8") self.pushButton_8.setObjectName(u"pushButton_8")
self.pushButton_8.setCheckable(True)
self.pushButton_8.setAutoExclusive(False)
self.gridLayout_11.addWidget(self.pushButton_8, 0, 0, 1, 1) self.gridLayout_11.addWidget(self.pushButton_8, 0, 0, 1, 1)
self.pb_snap_midp = QPushButton(self.groupBox_5) self.pb_snap_midp = QPushButton(self.groupBox_5)
self.pb_snap_midp.setObjectName(u"pb_snap_midp") self.pb_snap_midp.setObjectName(u"pb_snap_midp")
self.pb_snap_midp.setCheckable(True) self.pb_snap_midp.setCheckable(True)
self.pb_snap_midp.setAutoExclusive(False)
self.gridLayout_11.addWidget(self.pb_snap_midp, 0, 1, 1, 1) self.gridLayout_11.addWidget(self.pb_snap_midp, 0, 1, 1, 1)
self.pb_snap_angle = QPushButton(self.groupBox_5) self.pb_snap_angle = QPushButton(self.groupBox_5)
self.pb_snap_angle.setObjectName(u"pb_snap_angle") self.pb_snap_angle.setObjectName(u"pb_snap_angle")
self.pb_snap_angle.setCheckable(True) self.pb_snap_angle.setCheckable(True)
self.pb_snap_angle.setAutoExclusive(False)
self.gridLayout_11.addWidget(self.pb_snap_angle, 3, 1, 1, 1) self.gridLayout_11.addWidget(self.pb_snap_angle, 3, 1, 1, 1)
self.verticalLayout_3.addWidget(self.groupBox_5) self.verticalLayout_3.addWidget(self.groupBox_5)
self.tabWidget.addTab(self.widget, "") self.tabWidget.addTab(self.snaps, "")
self.widget1 = QWidget() self.settings = QWidget()
self.widget1.setObjectName(u"widget1") self.settings.setObjectName(u"settings")
self.tabWidget.addTab(self.widget1, "") self.tabWidget.addTab(self.settings, "")
self.gridLayout.addWidget(self.tabWidget, 3, 0, 1, 1) self.gridLayout.addWidget(self.tabWidget, 3, 0, 1, 1)
@ -582,7 +591,7 @@ class Ui_fluencyCAD(object):
self.retranslateUi(fluencyCAD) self.retranslateUi(fluencyCAD)
self.InputTab.setCurrentIndex(0) self.InputTab.setCurrentIndex(0)
self.tabWidget.setCurrentIndex(1) self.tabWidget.setCurrentIndex(0)
QMetaObject.connectSlotsByName(fluencyCAD) QMetaObject.connectSlotsByName(fluencyCAD)
@ -710,8 +719,8 @@ class Ui_fluencyCAD(object):
self.pushButton_8.setText(QCoreApplication.translate("fluencyCAD", u"Pnt", None)) self.pushButton_8.setText(QCoreApplication.translate("fluencyCAD", u"Pnt", None))
self.pb_snap_midp.setText(QCoreApplication.translate("fluencyCAD", u"MidP", None)) self.pb_snap_midp.setText(QCoreApplication.translate("fluencyCAD", u"MidP", None))
self.pb_snap_angle.setText(QCoreApplication.translate("fluencyCAD", u"Angles", None)) self.pb_snap_angle.setText(QCoreApplication.translate("fluencyCAD", u"Angles", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.widget), QCoreApplication.translate("fluencyCAD", u"Setg 1", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.snaps), QCoreApplication.translate("fluencyCAD", u"Setg 1", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.widget1), QCoreApplication.translate("fluencyCAD", u"Setg 2", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.settings), QCoreApplication.translate("fluencyCAD", u"Setg 2", None))
self.menuFile.setTitle(QCoreApplication.translate("fluencyCAD", u"File", None)) self.menuFile.setTitle(QCoreApplication.translate("fluencyCAD", u"File", None))
self.menuSettings.setTitle(QCoreApplication.translate("fluencyCAD", u"Settings", None)) self.menuSettings.setTitle(QCoreApplication.translate("fluencyCAD", u"Settings", None))
# retranslateUi # retranslateUi

View File

@ -32,4 +32,4 @@ So far these are the elements:
- Code: A special type that directly builds bodys from sdfCAD code. - Code: A special type that directly builds bodys from sdfCAD code.
- Body: The 3D meshed result from sdfCAD - Body: The 3D meshed result from sdfCAD
- Sketch: The base to draw new entities. - Sketch: The base to draw new entities.
- Interactor: A special component mesh that is used to manipulate the bodys in 3d view. - Interactor (edges): A special component mesh that is used to manipulate the bodys in 3d view.

View File

@ -24,15 +24,60 @@ class SketchWidget(QWidget):
self.hovered_point = None self.hovered_point = None
self.selected_line = None self.selected_line = None
### Display Settings
self.snapping_range = 20 # Range in pixels for snapping self.snapping_range = 20 # Range in pixels for snapping
self.zoom = 1 self.zoom = 1
# Mouse Input
self.setMouseTracking(True) self.setMouseTracking(True)
self.mouse_mode = False self.mouse_mode = False
self.is_construct = False
self.snap_mode = {
"point": False,
"mpoint": False,
"horiz": False,
"vert":False,
"grid": False,
"angle": False
}
# Solver
self.solv = SolverSystem() self.solv = SolverSystem()
self.sketch = Sketch2d() self.sketch = Sketch2d()
def act_line_mode(self, checked):
if checked:
self.mouse_mode = 'line'
print(self.mouse_mode)
else:
self.mouse_mode = None
def act_constrain_pt_pt_mode(self):
self.mouse_mode = 'pt_pt'
def act_constrain_pt_line_mode(self):
self.mouse_mode = 'pt_line'
def act_constrain_horiz_line_mode(self):
self.mouse_mode = 'horiz'
def act_constrain_vert_line_mode(self):
self.mouse_mode = 'vert'
def act_constrain_distance_mode(self):
self.mouse_mode = 'distance'
def act_constrain_mid_point_mode(self):
self.mouse_mode = 'pb_con_mid'
def on_snap_mode_change(self, helper_type: str, value: bool):
self.snap_mode[helper_type] = value
def on_construct_change(self, checked):
self.is_construct = checked
def create_sketch(self, sketch_in ): def create_sketch(self, sketch_in ):
self.sketch = Sketch2d() self.sketch = Sketch2d()
self.sketch.id = sketch_in.id self.sketch.id = sketch_in.id
@ -46,6 +91,7 @@ class SketchWidget(QWidget):
return self.sketch return self.sketch
def reset_buffers(self): def reset_buffers(self):
self.mouse_mode = None
self.line_draw_buffer = [None, None] self.line_draw_buffer = [None, None]
self.drag_buffer = [None, None] self.drag_buffer = [None, None]
self.main_buffer = [None, None] self.main_buffer = [None, None]
@ -235,11 +281,12 @@ class SketchWidget(QWidget):
Go through solversystem and check points2d for changes in position after solving Go through solversystem and check points2d for changes in position after solving
:return: List with points that now have a different position :return: List with points that now have a different position
""" """
old_points_ui = [] old_points_ui = []
new_points_ui = [] new_points_ui = []
for old_point_ui in self.sketch.points: for index, old_point_ui in enumerate(self.sketch.points):
old_points_ui.append(old_point_ui.ui_point) old_points_ui.append((index, old_point_ui.ui_point))
for i in range(self.sketch.entity_len()): for i in range(self.sketch.entity_len()):
# Iterate though full length because mixed list from SS # Iterate though full length because mixed list from SS
@ -255,9 +302,10 @@ class SketchWidget(QWidget):
if len(old_points_ui) != len(new_points_ui): if len(old_points_ui) != len(new_points_ui):
print(f"Length mismatch {len(old_points_ui)} - {len(new_points_ui)}") print(f"Length mismatch {len(old_points_ui)} - {len(new_points_ui)}")
for index, (old_point, new_point) in enumerate(zip(old_points_ui, new_points_ui)): for (old_index, old_point), new_point in zip(old_points_ui, new_points_ui):
if old_point != new_point: if old_point != new_point:
differences.append((index, old_point, new_point)) #print(old_point)
differences.append((old_index, old_point, new_point))
return differences return differences
@ -266,13 +314,20 @@ class SketchWidget(QWidget):
# print("Initial slv_points_main:", self.slv_points_main) # print("Initial slv_points_main:", self.slv_points_main)
print("Change list:", point_list) print("Change list:", point_list)
if len(point_list) > 0: """for point in point_list:
new = Point2D(point.x(), point.y())
self.sketch.points.append(new)"""
if len(point_list) > 1:
for tbu_points_idx in point_list: for tbu_points_idx in point_list:
# Each tbu_points_idx is a tuple: (index, old_point, new_point) # Each tbu_points_idx is a tuple: (index, old_point, new_point)
index, old_point, new_point = tbu_points_idx index, old_point, new_point = tbu_points_idx
# Update the point in slv_points_main # Update the point in slv_points_main
self.sketch.points[index].ui_point = new_point self.sketch.points[index].ui_point = new_point
"""for points in self.sketch.points:
print(points.ui_point)"""
# Print updated state # Print updated state
# print("Updated slv_points_main:", self.slv_points_main) # print("Updated slv_points_main:", self.slv_points_main)
@ -291,7 +346,7 @@ class SketchWidget(QWidget):
if event.button() == Qt.LeftButton and not self.mouse_mode: if event.button() == Qt.LeftButton and not self.mouse_mode:
self.drag_buffer[1] = local_event_pos self.drag_buffer[1] = local_event_pos
print("Le main buffer", self.drag_buffer) #print("Le main buffer", self.drag_buffer)
if not None in self.main_buffer and len(self.main_buffer) == 2: if not None in self.main_buffer and len(self.main_buffer) == 2:
entry = self.drag_buffer[0] entry = self.drag_buffer[0]
@ -300,9 +355,9 @@ class SketchWidget(QWidget):
self.sketch.solve() self.sketch.solve()
points_need_update = self.check_all_points() #points_need_update = self.check_all_points()
self.update_ui_points(points_need_update) #self.update_ui_points(points_need_update)
self.check_all_lines_and_update(points_need_update) #self.check_all_lines_and_update(points_need_update)
self.update() self.update()
self.drag_buffer = [None, None] self.drag_buffer = [None, None]
@ -315,6 +370,7 @@ class SketchWidget(QWidget):
self.drag_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) self.drag_buffer[0] = self.get_handle_from_ui_point(self.hovered_point)
if event.button() == Qt.RightButton and self.mouse_mode: if event.button() == Qt.RightButton and self.mouse_mode:
self.constrain_done.emit()
self.reset_buffers() self.reset_buffers()
if event.button() == Qt.LeftButton and self.mouse_mode == "line": if event.button() == Qt.LeftButton and self.mouse_mode == "line":
@ -329,6 +385,10 @@ class SketchWidget(QWidget):
v = clicked_pos.y() v = clicked_pos.y()
point = Point2D(u,v) point = Point2D(u,v)
print("construct", self.is_construct )
# Construction mode
point.is_helper = self.is_construct
self.sketch.add_point(point) self.sketch.add_point(point)
self.line_draw_buffer[0] = point self.line_draw_buffer[0] = point
@ -337,17 +397,25 @@ class SketchWidget(QWidget):
u = clicked_pos.x() u = clicked_pos.x()
v = clicked_pos.y() v = clicked_pos.y()
print("construct", self.is_construct)
point = Point2D(u, v) point = Point2D(u, v)
# Construction mode
point.is_helper = self.is_construct
self.sketch.add_point(point) self.sketch.add_point(point)
self.line_draw_buffer[1] = point self.line_draw_buffer[1] = point
print("Buffer state", self.line_draw_buffer) #print("Buffer state", self.line_draw_buffer)
if self.line_draw_buffer[0] and self.line_draw_buffer[1]: if self.line_draw_buffer[0] and self.line_draw_buffer[1]:
line = Line2D(self.line_draw_buffer[0], self.line_draw_buffer[1]) line = Line2D(self.line_draw_buffer[0], self.line_draw_buffer[1])
# Construction mode
line.is_helper = self.is_construct
self.sketch.add_line(line) self.sketch.add_line(line)
# Reset the buffer for the next line segment # Reset the buffer for the next line segment
@ -367,7 +435,7 @@ class SketchWidget(QWidget):
self.main_buffer[1] = self.get_handle_from_ui_point(self.hovered_point) self.main_buffer[1] = self.get_handle_from_ui_point(self.hovered_point)
if self.main_buffer[0] and self.main_buffer[1]: if self.main_buffer[0] and self.main_buffer[1]:
print("buf", self.main_buffer) # print("buf", self.main_buffer)
self.sketch.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.wp) self.sketch.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.wp)
@ -387,7 +455,7 @@ class SketchWidget(QWidget):
self.main_buffer = [None, None] self.main_buffer = [None, None]
if event.button() == Qt.LeftButton and self.mouse_mode == "pt_line": if event.button() == Qt.LeftButton and self.mouse_mode == "pt_line":
print("ptline") #print("ptline")
line_selected = None line_selected = None
if self.hovered_point and not self.main_buffer[1]: if self.hovered_point and not self.main_buffer[1]:
@ -418,9 +486,10 @@ class SketchWidget(QWidget):
self.main_buffer = [None, None] self.main_buffer = [None, None]
if event.button() == Qt.LeftButton and self.mouse_mode == "pb_con_mid": if event.button() == Qt.LeftButton and self.mouse_mode == "pb_con_mid":
print("ptline") #print("ptline")
line_selected = None line_selected = None
if self.hovered_point and not self.main_buffer[1]: if self.hovered_point and not self.main_buffer[1]:
self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point)
@ -433,6 +502,12 @@ class SketchWidget(QWidget):
if self.sketch.solve() == ResultFlag.OKAY: if self.sketch.solve() == ResultFlag.OKAY:
print("Fuck yeah") print("Fuck yeah")
for idx, line in enumerate(self.sketch.lines):
# print(line.crd1.ui_point)
if self.is_point_on_line(local_event_pos, line.crd1.ui_point, line.crd2.ui_point):
self.sketch.lines[idx].constraints.append("mid")
print(self.sketch.lines[idx].constraints)
elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE:
print("Solve_failed - Converge") print("Solve_failed - Converge")
@ -450,12 +525,21 @@ class SketchWidget(QWidget):
line_selected = self.get_line_handle_from_ui_point(local_event_pos) line_selected = self.get_line_handle_from_ui_point(local_event_pos)
if line_selected: if line_selected:
self.sketch.horizontal(line_selected, self.sketch.wp) self.sketch.horizontal(line_selected, self.sketch.wp)
if self.sketch.solve() == ResultFlag.OKAY: if self.sketch.solve() == ResultFlag.OKAY:
print("Fuck yeah") print("Fuck yeah")
# Add succesful constraint to constrain draw list so it gets drawn in paint function
for idx, line in enumerate(self.sketch.lines):
#print(line.crd1.ui_point)
if self.is_point_on_line(local_event_pos, line.crd1.ui_point, line.crd2.ui_point):
self.sketch.lines[idx].constraints.append("hrz")
#print(self.sketch.lines[idx].constraints)
elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE:
print("Solve_failed - Converge") print("Solve_failed - Converge")
@ -473,6 +557,11 @@ class SketchWidget(QWidget):
if self.sketch.solve() == ResultFlag.OKAY: if self.sketch.solve() == ResultFlag.OKAY:
print("Fuck yeah") print("Fuck yeah")
for idx, line in enumerate(self.sketch.lines):
# print(line.crd1.ui_point)
if self.is_point_on_line(local_event_pos, line.crd1.ui_point, line.crd2.ui_point):
self.sketch.lines[idx].constraints.append("vrt")
# print(self.sketch.lines[idx].constraints)
elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE:
print("Solve_failed - Converge") print("Solve_failed - Converge")
@ -490,7 +579,7 @@ class SketchWidget(QWidget):
e2 = None e2 = None
if self.hovered_point: if self.hovered_point:
print("buf point") # print("buf point")
# Get the point as UI point as buffer # Get the point as UI point as buffer
self.main_buffer[0] = self.hovered_point self.main_buffer[0] = self.hovered_point
@ -514,6 +603,11 @@ class SketchWidget(QWidget):
if self.sketch.solve() == ResultFlag.OKAY: if self.sketch.solve() == ResultFlag.OKAY:
print("Fuck yeah") print("Fuck yeah")
for idx, line in enumerate(self.sketch.lines):
# print(line.crd1.ui_point)
if self.is_point_on_line(local_event_pos, line.crd1.ui_point, line.crd2.ui_point):
if "dist" not in self.sketch.lines[idx].constraints:
self.sketch.lines[idx].constraints.append("dst")
elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE:
print("Solve_failed - Converge") print("Solve_failed - Converge")
@ -532,6 +626,8 @@ class SketchWidget(QWidget):
self.update_ui_points(points_need_update) self.update_ui_points(points_need_update)
self.check_all_lines_and_update(points_need_update) self.check_all_lines_and_update(points_need_update)
self.update() self.update()
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
@ -540,7 +636,7 @@ class SketchWidget(QWidget):
closest_point = None closest_point = None
min_distance = float('inf') min_distance = float('inf')
threshold = 10 # Distance threshold for highlighting threshold = 15 # Distance threshold for highlighting
if self.mouse_mode == "line" and self.line_draw_buffer[0]: if self.mouse_mode == "line" and self.line_draw_buffer[0]:
# Update the current cursor position as the second point # Update the current cursor position as the second point
@ -562,7 +658,7 @@ class SketchWidget(QWidget):
if closest_point != self.hovered_point: if closest_point != self.hovered_point:
self.hovered_point = closest_point self.hovered_point = closest_point
print(self.hovered_point) #print(self.hovered_point)
for line in self.sketch.lines: for line in self.sketch.lines:
p1 = line.crd1.ui_point p1 = line.crd1.ui_point
@ -571,11 +667,14 @@ class SketchWidget(QWidget):
if self.is_point_on_line(local_event_pos, p1, p2): if self.is_point_on_line(local_event_pos, p1, p2):
self.selected_line = p1, p2 self.selected_line = p1, p2
# Midpointsnap only in drawer not solver if self.snap_mode.get("mpoint"):
mid = self.calculate_midpoint(p1, p2) # Midpointsnap only in drawer not solver
distance = (local_event_pos - mid).manhattanLength() mid = self.calculate_midpoint(p1, p2)
if distance < threshold and distance < min_distance: distance = (local_event_pos - mid).manhattanLength()
self.hovered_point = mid if distance < threshold and distance < min_distance:
self.hovered_point = mid
break
break break
else: else:
self.selected_line = None self.selected_line = None
@ -678,6 +777,103 @@ class SketchWidget(QWidget):
widget_y = -point.y() widget_y = -point.y()
return QPoint(int(widget_x), int(widget_y)) return QPoint(int(widget_x), int(widget_y))
def draw_measurement(self,painter, start_point, end_point):
pen_normal = QPen(Qt.gray)
pen_normal.setWidthF(2 / self.zoom)
pen_planned = QPen(Qt.gray)
pen_planned.setStyle(Qt.PenStyle.DotLine)
pen_planned.setWidthF(2 / self.zoom)
pen_construct = QPen(Qt.cyan)
pen_construct.setStyle(Qt.PenStyle.DotLine)
pen_construct.setWidthF(1 / self.zoom)
pen_solver = QPen(Qt.green)
pen_solver.setWidthF(2 / self.zoom)
pen_text = QPen(Qt.white)
pen_text.setWidthF(1 / self.zoom)
# Calculate the direction of the line
dx = end_point.x() - start_point.x()
dy = end_point.y() - start_point.y()
# Swap and negate to get a perpendicular vector
perp_dx = -dy
perp_dy = dx
# Normalize the perpendicular vector
length = (perp_dx ** 2 + perp_dy ** 2) ** 0.5
if length == 0: # Prevent division by zero
return
perp_dx /= length
perp_dy /= length
# Fixed length for the perpendicular lines
fixed_length = 40 # Adjust as needed
half_length = fixed_length # fixed_length / 2
# Calculate endpoints for the perpendicular line at the start
start_perp_start_x = start_point.x() + perp_dx
start_perp_start_y = start_point.y() + perp_dy
start_perp_end_x = start_point.x() + perp_dx * half_length * (1 / self.zoom)
start_perp_end_y = start_point.y() + perp_dy * half_length * (1 / self.zoom)
start_perp_end_x_conn_line = start_point.x() + perp_dx * half_length * 0.75 * (1 / self.zoom)
start_perp_end_y_conn_line = start_point.y() + perp_dy * half_length * 0.75 * (1 / self.zoom)
# Draw the perpendicular line at the start
painter.setPen(pen_construct) # Different color for the perpendicular line
painter.drawLine(
QPointF(start_perp_start_x, start_perp_start_y),
QPointF(start_perp_end_x, start_perp_end_y)
)
# Calculate endpoints for the perpendicular line at the end
end_perp_start_x = end_point.x() + perp_dx
end_perp_start_y = end_point.y() + perp_dy
end_perp_end_x = end_point.x() + perp_dx * half_length * (1 / self.zoom)
end_perp_end_y = end_point.y() + perp_dy * half_length * (1 / self.zoom)
end_perp_end_x_conn_line = end_point.x() + perp_dx * half_length * .75 * (1 / self.zoom)
end_perp_end_y_conn_line = end_point.y() + perp_dy * half_length * .75 * (1 / self.zoom)
# Draw the perpendicular line at the end
painter.drawLine(
QPointF(end_perp_start_x, end_perp_start_y),
QPointF(end_perp_end_x, end_perp_end_y)
)
painter.drawLine(
QPointF(end_perp_end_x_conn_line, end_perp_end_y_conn_line),
QPointF(start_perp_end_x_conn_line, start_perp_end_y_conn_line)
)
# Save painter state
painter.save()
painter.setPen(pen_text)
# Calculate the distance and midpoint
dis = self.distance(start_point, end_point)
mid = self.calculate_midpoint(QPointF(start_perp_end_x_conn_line, start_perp_end_y_conn_line),
QPointF(end_perp_end_x_conn_line, end_perp_end_y_conn_line))
# mid = self.calculate_midpoint(start_point, end_point)
# Transform for text
painter.translate(mid.x(), mid.y()) # Move to the midpoint
painter.scale(1, -1) # Flip y-axis back to make text readable
# Draw the text
painter.drawText(0, 0, str(round(dis, 2))) # Draw text at transformed position
# Restore painter state
painter.restore()
def paintEvent(self, event): def paintEvent(self, event):
painter = QPainter(self) painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.Antialiasing)
@ -700,6 +896,10 @@ class SketchWidget(QWidget):
pen_normal = QPen(Qt.gray) pen_normal = QPen(Qt.gray)
pen_normal.setWidthF(2 / self.zoom) pen_normal.setWidthF(2 / self.zoom)
pen_planned = QPen(Qt.gray)
pen_planned.setStyle(Qt.PenStyle.DotLine)
pen_planned.setWidthF(2 / self.zoom)
pen_construct = QPen(Qt.cyan) pen_construct = QPen(Qt.cyan)
pen_construct.setStyle(Qt.PenStyle.DotLine) pen_construct.setStyle(Qt.PenStyle.DotLine)
pen_construct.setWidthF(1 / self.zoom) pen_construct.setWidthF(1 / self.zoom)
@ -726,26 +926,10 @@ class SketchWidget(QWidget):
if self.mouse_mode == "line" and self.line_draw_buffer[0] and self.dynamic_line_end is not None: if self.mouse_mode == "line" and self.line_draw_buffer[0] and self.dynamic_line_end is not None:
start_point = self.line_draw_buffer[0].ui_point start_point = self.line_draw_buffer[0].ui_point
end_point = self.dynamic_line_end end_point = self.dynamic_line_end
painter.setPen(Qt.red) # Use a different color for the dynamic line painter.setPen(pen_planned) # Use a different color for the dynamic line
painter.drawLine(start_point, end_point) painter.drawLine(start_point, end_point)
# Save painter state self.draw_measurement(painter, start_point, end_point)
painter.save()
painter.setPen(pen_text)
# Calculate the distance and midpoint
dis = self.distance(start_point, end_point)
mid = self.calculate_midpoint(start_point, end_point)
# Transform for text
painter.translate(mid.x(), mid.y()) # Move to the midpoint
painter.scale(1, -1) # Flip y-axis back to make text readable
# Draw the text
painter.drawText(0, 0, str(round(dis, 2))) # Draw text at transformed position
# Restore painter state
painter.restore()
for line in self.sketch.lines: for line in self.sketch.lines:
if line.is_helper: if line.is_helper:
@ -759,6 +943,17 @@ class SketchWidget(QWidget):
p2 = line.crd2.ui_point p2 = line.crd2.ui_point
painter.drawLine(p1, p2) painter.drawLine(p1, p2)
if not self.selected_line:
painter.save()
midp = self.calculate_midpoint(p1, p2)
painter.translate(midp)
painter.scale(1, -1)
for i, text in enumerate(line.constraints):
painter.drawText(0, i * 15, f"> {text} <")
painter.restore()
# Draw all solver points # Draw all solver points
if self.sketch.entity_len(): if self.sketch.entity_len():
painter.setPen(pen_solver) painter.setPen(pen_solver)
@ -782,6 +977,9 @@ class SketchWidget(QWidget):
painter.setPen(QPen(Qt.red, 2 / self.zoom)) painter.setPen(QPen(Qt.red, 2 / self.zoom))
painter.drawLine(p1, p2) painter.drawLine(p1, p2)
self.draw_measurement(painter, p1, p2)
"""for cross in self.sketch.proj_points: """for cross in self.sketch.proj_points:
self.draw_cross(painter, cross, 10 / self.zoom) self.draw_cross(painter, cross, 10 / self.zoom)
@ -825,6 +1023,9 @@ class Line2D:
# Construction Geometry # Construction Geometry
self.is_helper: bool = False self.is_helper: bool = False
# String list with applied constraints
self.constraints: list = []
class Sketch2d(SolverSystem): class Sketch2d(SolverSystem):
""" """

33
gui.ui
View File

@ -733,6 +733,9 @@
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
@ -813,6 +816,9 @@
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -836,9 +842,9 @@
<enum>QTabWidget::South</enum> <enum>QTabWidget::South</enum>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>0</number>
</property> </property>
<widget class="QWidget" name=""> <widget class="QWidget" name="snaps">
<attribute name="title"> <attribute name="title">
<string>Setg 1</string> <string>Setg 1</string>
</attribute> </attribute>
@ -882,6 +888,9 @@
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="2"> <item row="4" column="0" colspan="2">
@ -919,6 +928,9 @@
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
@ -929,6 +941,9 @@
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="6" column="1">
@ -949,6 +964,12 @@
<property name="text"> <property name="text">
<string>Pnt</string> <string>Pnt</string>
</property> </property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
@ -959,6 +980,9 @@
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
@ -969,6 +993,9 @@
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -976,7 +1003,7 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name=""> <widget class="QWidget" name="settings">
<attribute name="title"> <attribute name="title">
<string>Setg 2</string> <string>Setg 2</string>
</attribute> </attribute>

16
license.md Normal file
View File

@ -0,0 +1,16 @@
Copyright (C) 2025 Thomas Herrmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

106
main.py
View File

@ -120,13 +120,13 @@ class MainWindow(QMainWindow):
self.ui.pb_flip_face.pressed.connect(self.on_flip_face) self.ui.pb_flip_face.pressed.connect(self.on_flip_face)
###Modes ###Modes
self.ui.pb_linetool.pressed.connect(self.act_line_mode) self.ui.pb_linetool.clicked.connect(self.sketchWidget.act_line_mode)
self.ui.pb_con_ptpt.pressed.connect(self.act_constrain_pt_pt_mode) self.ui.pb_con_ptpt.clicked.connect(self.sketchWidget.act_constrain_pt_pt_mode)
self.ui.pb_con_line.pressed.connect(self.act_constrain_pt_line_mode) self.ui.pb_con_line.clicked.connect(self.sketchWidget.act_constrain_pt_line_mode)
self.ui.pb_con_horiz.pressed.connect(self.act_constrain_horiz_line_mode) self.ui.pb_con_horiz.clicked.connect(self.sketchWidget.act_constrain_horiz_line_mode)
self.ui.pb_con_vert.pressed.connect(self.act_constrain_vert_line_mode) self.ui.pb_con_vert.clicked.connect(self.sketchWidget.act_constrain_vert_line_mode)
self.ui.pb_con_dist.pressed.connect(self.act_constrain_distance_mode) self.ui.pb_con_dist.clicked.connect(self.sketchWidget.act_constrain_distance_mode)
self.ui.pb_con_mid.pressed.connect(self.act_constrain_mid_point_mode) self.ui.pb_con_mid.clicked.connect(self.sketchWidget.act_constrain_mid_point_mode)
### Operations ### Operations
self.ui.pb_extrdop.pressed.connect(self.send_extrude) self.ui.pb_extrdop.pressed.connect(self.send_extrude)
@ -137,12 +137,18 @@ class MainWindow(QMainWindow):
self.setFocusPolicy(Qt.StrongFocus) self.setFocusPolicy(Qt.StrongFocus)
self.send_command.connect(self.custom_3D_Widget.on_receive_command) self.send_command.connect(self.custom_3D_Widget.on_receive_command)
self.ui.actionNew_Project.triggered.connect(self.new_project) self.ui.actionNew_Project.triggered.connect(self.new_project)
self.ui.pb_enable_construct.clicked.connect(self.sketchWidget.on_construct_change)
self.project = Project() self.project = Project()
self.new_project() self.new_project()
### SNAPS
self.ui.pb_snap_midp.toggled.connect(lambda checked: self.sketchWidget.on_snap_mode_change("mpoint", checked))
self.ui.pb_snap_horiz.toggled.connect(lambda checked: self.sketchWidget.on_snap_mode_change("horiz", checked))
self.ui.pb_snap_vert.toggled.connect(lambda checked: self.sketchWidget.on_snap_mode_change("vert", checked))
self.ui.pb_snap_angle.toggled.connect(lambda checked: self.sketchWidget.on_snap_mode_change("angle", checked))
self.ui.pb_enable_snap.toggled.connect(lambda checked: self.sketchWidget.on_snap_mode_change("point", checked))
### COMPOS ### COMPOS
### COMPOS ### COMPOS
@ -156,7 +162,6 @@ class MainWindow(QMainWindow):
self.project.timeline = timeline self.project.timeline = timeline
self.new_component() self.new_component()
def new_component(self): def new_component(self):
print("Creating a new component...") print("Creating a new component...")
@ -270,7 +275,7 @@ class MainWindow(QMainWindow):
sketch.original_sketch = sketch_from_widget sketch.original_sketch = sketch_from_widget
#Get parameters #Get parameters
points = sketch_from_widget.points points = [point for point in sketch_from_widget.points if hasattr(point, 'is_helper') and not point.is_helper]
sketch.convert_points_for_sdf(points) sketch.convert_points_for_sdf(points)
sketch.id = sketch_from_widget.id sketch.id = sketch_from_widget.id
@ -280,7 +285,7 @@ class MainWindow(QMainWindow):
# Register sketch to timeline # Register sketch to timeline
### Add selection compo here ### Add selection compo here
compo_id = self.get_activated_compo() compo_id = self.get_activated_compo()
print("newsketch_name", sketch.id) #print("newsketch_name", sketch.id)
self.project.timeline[compo_id].sketches[sketch.id] = sketch self.project.timeline[compo_id].sketches[sketch.id] = sketch
# Add Item to slection menu # Add Item to slection menu
@ -294,6 +299,7 @@ class MainWindow(QMainWindow):
self.ui.sketch_list.setCurrentItem(items) self.ui.sketch_list.setCurrentItem(items)
def on_compo_change(self): def on_compo_change(self):
'''This function redraws the sdf and helper mesh from available bodies and adds the names back to the list entries'''
self.custom_3D_Widget.clear_body_actors() self.custom_3D_Widget.clear_body_actors()
self.custom_3D_Widget.clear_actors_interactor() self.custom_3D_Widget.clear_actors_interactor()
self.custom_3D_Widget.clear_actors_projection() self.custom_3D_Widget.clear_actors_projection()
@ -303,27 +309,27 @@ class MainWindow(QMainWindow):
self.ui.sketch_list.clear() self.ui.sketch_list.clear()
self.ui.body_list.clear() self.ui.body_list.clear()
print("id", compo_id) #print("id", compo_id)
print("sketch_registry", self.project.timeline[compo_id].sketches) #print("sketch_registry", self.project.timeline[compo_id].sketches)
for sketch in self.project.timeline[compo_id].sketches: for sketch in self.project.timeline[compo_id].sketches:
print(sketch) #print(sketch)
self.ui.sketch_list.addItem(sketch) self.ui.sketch_list.addItem(sketch)
for body in self.project.timeline[compo_id].bodies: for body in self.project.timeline[compo_id].bodies:
self.ui.body_list.addItem(body) self.ui.body_list.addItem(body)
if self.project.timeline[compo_id].bodies: if self.project.timeline[compo_id].bodies:
item = self.ui.body_list.findItems(body , Qt.MatchExactly)[0] item = self.ui.body_list.findItems(body , Qt.MatchExactly)[0]
self.ui.body_list.setCurrentItem(item) self.ui.body_list.setCurrentItem(item)
self.draw_mesh() self.draw_mesh()
selected = self.ui.body_list.currentItem() selected = self.ui.body_list.currentItem()
name = selected.text() name = selected.text()
edges = self.project.timeline[compo_id].bodies[name].interactor.edges edges = self.project.timeline[compo_id].bodies[name].interactor.edges
offset_vec = self.project.timeline[compo_id].bodies[name].interactor.offset_vector offset_vec = self.project.timeline[compo_id].bodies[name].interactor.offset_vector
self.custom_3D_Widget.load_interactor_mesh(edges, offset_vec) self.custom_3D_Widget.load_interactor_mesh(edges, offset_vec)
def edit_sketch(self): def edit_sketch(self):
selected = self.ui.sketch_list.currentItem() selected = self.ui.sketch_list.currentItem()
@ -353,49 +359,6 @@ class MainWindow(QMainWindow):
def on_flip_face(self): def on_flip_face(self):
self.send_command.emit("flip") self.send_command.emit("flip")
def act_line_mode(self):
if not self.ui.pb_linetool.isChecked():
self.sketchWidget.mouse_mode = 'line'
else:
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'
else:
self.sketchWidget.mouse_mode = None
def act_constrain_pt_line_mode(self):
if not self.ui.pb_con_line.isChecked():
self.sketchWidget.mouse_mode = 'pt_line'
else:
self.sketchWidget.mouse_mode = None
def act_constrain_horiz_line_mode(self):
if not self.ui.pb_con_horiz.isChecked():
self.sketchWidget.mouse_mode = 'horiz'
else:
self.sketchWidget.mouse_mode = None
def act_constrain_vert_line_mode(self):
if not self.ui.pb_con_vert.isChecked():
self.sketchWidget.mouse_mode = 'vert'
else:
self.sketchWidget.mouse_mode = None
def act_constrain_distance_mode(self):
if not self.ui.pb_con_dist.isChecked():
self.sketchWidget.mouse_mode = 'distance'
else:
self.sketchWidget.mouse_mode = None
def act_constrain_mid_point_mode(self):
if not self.ui.pb_con_mid.isChecked():
self.sketchWidget.mouse_mode = 'pb_con_mid'
else:
self.sketchWidget.mouse_mode = None
def draw_op_complete(self): def draw_op_complete(self):
# safely disable the line modes # safely disable the line modes
self.ui.pb_linetool.setChecked(False) self.ui.pb_linetool.setChecked(False)
@ -420,7 +383,7 @@ class MainWindow(QMainWindow):
model_data = vesta.generate_mesh_from_sdf(model, resolution=64, threshold=0) model_data = vesta.generate_mesh_from_sdf(model, resolution=64, threshold=0)
vertices, faces = model_data vertices, faces = model_data
vesta.save_mesh_as_stl(vertices, faces, 'test.stl') #vesta.save_mesh_as_stl(vertices, faces, 'test.stl')
self.custom_3D_Widget.render_from_points_direct_with_faces(vertices, faces) self.custom_3D_Widget.render_from_points_direct_with_faces(vertices, faces)
def on_item_changed(self, current_item, previous_item): def on_item_changed(self, current_item, previous_item):
@ -464,8 +427,9 @@ class MainWindow(QMainWindow):
#print(sketch) #print(sketch)
points = sketch.sdf_points points = sketch.sdf_points
# detect loop that causes problems in mesh generation
if points[-1] == points[0]: if points[-1] == points[0]:
#detect loop that causes problems in mesh generation print("overlap")
del points[-1] del points[-1]
dialog = ExtrudeDialog(self) dialog = ExtrudeDialog(self)
@ -484,8 +448,8 @@ class MainWindow(QMainWindow):
centroid = self.custom_3D_Widget.centroid centroid = self.custom_3D_Widget.centroid
if centroid is None: if centroid is None:
centroid = [0, 0, 0] centroid = [0, 0, 0]
else: """else:
centroid = list(centroid) centroid = list(centroid)"""
#print("This centroid ", centroid) #print("This centroid ", centroid)
sketch.origin = centroid sketch.origin = centroid

View File

@ -1,66 +1,60 @@
certifi==2024.7.4 certifi==2025.1.31
cffi==1.16.0 cffi==1.17.1
charset-normalizer==3.3.2 charset-normalizer==3.4.1
contourpy==1.2.0 contourpy==1.3.1
cycler==0.12.1 cycler==0.12.1
fonttools==4.47.0 flexcache==0.3
freetype-py==2.4.0 flexparser==0.4
fonttools==4.56.0
freetype-py==2.5.1
hsluv==5.0.4 hsluv==5.0.4
idna==3.7 idna==3.10
imageio==2.33.1 imageio==2.37.0
kiwisolver==1.4.5 kiwisolver==1.4.8
lazy_loader==0.3 lazy_loader==0.4
markdown-it-py==3.0.0 markdown-it-py==3.0.0
matplotlib==3.8.2 matplotlib==3.10.1
mdurl==0.1.2 mdurl==0.1.2
meshio==5.3.4 meshio==5.3.5
names==0.3.0 names==0.3.0
networkx==3.2.1 networkx==3.4.2
Nuitka==2.2.1 Nuitka==2.6.9
numpy==1.26.2 numpy==2.2.4
numpy-stl==3.1.1 numpy-stl==3.2.0
ordered-set==4.1.0 ordered-set==4.1.0
packaging==23.2 packaging==24.2
panda3d-gltf==1.2.0 panda3d-gltf==1.3.0
panda3d-simplepbr==0.12.0 panda3d-simplepbr==0.13.0
Pillow==10.1.0 pillow==11.1.0
Pint==0.22 Pint==0.24.4
platformdirs==4.2.2 platformdirs==4.3.7
pooch==1.8.2 pooch==1.8.2
pycparser==2.21 pycparser==2.22
pygame==2.5.2 pygame==2.6.1
Pygments==2.17.2 Pygments==2.19.1
PyOpenGL==3.1.7 pyparsing==3.2.3
pyparsing==3.1.1 PySide6_Essentials==6.8.3
PyQt6==6.7.0 python-dateutil==2.9.0.post0
PyQt6-3D==6.7.0
PyQt6-3D-Qt6==6.7.0
PyQt6-Qt6==6.7.0
PyQt6-sip==13.6.0
PySide6==6.6.1
PySide6-Addons==6.6.1
PySide6-Essentials==6.6.1
python-dateutil==2.8.2
python-solvespace==3.0.8 python-solvespace==3.0.8
python-utils==3.8.2 python-utils==3.9.1
pyvista==0.43.10 pyvista==0.44.2
pyvistaqt==0.11.1 pyvistaqt==0.11.2
QtPy==2.4.1 QtPy==2.4.3
requests==2.32.3 requests==2.32.3
rich==13.7.0 rich==13.9.4
scikit-image==0.22.0 scikit-image==0.25.2
scipy==1.11.4 scipy==1.15.2
scooby==0.10.0 scooby==0.10.0
sdfcad @ git+https://gitlab.com/nobodyinperson/sdfCAD@9bd4e9021c6ee7e685ee28e8a3a5d2d2c028190c sdfcad @ git+https://gitlab.com/nobodyinperson/sdfCAD@9bd4e9021c6ee7e685ee28e8a3a5d2d2c028190c
shapely==2.0.4 shapely==2.1.0rc1
shiboken6==6.6.1 shiboken6==6.8.3
six==1.16.0 six==1.17.0
tifffile==2023.12.9 tifffile==2025.3.13
trimesh==4.3.2 trimesh==4.6.5
tripy==1.0.0 tripy==1.0.0
typing_extensions==4.9.0 typing_extensions==4.13.0
urllib3==2.2.2 urllib3==2.3.0
vispy==0.14.2 vispy==0.14.3
vtk==9.3.0 vtk==9.4.1
vulkan==1.3.275.0 vulkan==1.3.275.1
zstandard==0.22.0 zstandard==0.23.0