Fix sketcher mode handling to prevent unintended line creation during drag operations
Major changes: - Fixed right-click handler to directly set mode to NONE instead of relying on main app signal handling - Added safety checks in left-click handler to prevent drawing when no draggable point is found in NONE mode - Enhanced mode compatibility by treating Python None as SketchMode.NONE in set_mode() method - Added comprehensive debug logging for mode changes and interaction state tracking - Resolved integration issue where persistent constraint modes were prematurely reset by main app - Ensured point dragging is only enabled in NONE mode, preventing accidental polyline creation This fixes the reported issue where deactivating the line tool would still create lines when dragging, and ensures proper mode transitions between drawing tools and selection/drag mode.
This commit is contained in:
236
main.py
236
main.py
@@ -11,7 +11,7 @@ from Gui import Ui_fluencyCAD # Import the generated GUI module
|
||||
from drawing_modules.vtk_widget import VTKWidget
|
||||
import numpy as np
|
||||
|
||||
from drawing_modules.draw_widget_solve import SketchWidget
|
||||
from drawing_modules.improved_sketcher import ImprovedSketchWidget, SketchMode, SnapMode
|
||||
from sdf import *
|
||||
from python_solvespace import SolverSystem, ResultFlag
|
||||
from mesh_modules import simple_mesh, vesta_mesh, interactor_mesh
|
||||
@@ -93,7 +93,7 @@ class MainWindow(QMainWindow):
|
||||
size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
||||
#self.custom_3D_Widget.setSizePolicy(size_policy)
|
||||
|
||||
self.sketchWidget = SketchWidget()
|
||||
self.sketchWidget = ImprovedSketchWidget()
|
||||
layout2 = self.ui.sketch_tab.layout() # Get the layout of self.ui.gl_canvas
|
||||
layout2.addWidget(self.sketchWidget)
|
||||
size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
||||
@@ -120,36 +120,44 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.ui.pb_flip_face.pressed.connect(self.on_flip_face)
|
||||
|
||||
###Modes
|
||||
self.ui.pb_linetool.clicked.connect(self.sketchWidget.act_line_mode)
|
||||
self.ui.pb_con_ptpt.clicked.connect(self.sketchWidget.act_constrain_pt_pt_mode)
|
||||
self.ui.pb_con_line.clicked.connect(self.sketchWidget.act_constrain_pt_line_mode)
|
||||
self.ui.pb_con_horiz.clicked.connect(self.sketchWidget.act_constrain_horiz_line_mode)
|
||||
self.ui.pb_con_vert.clicked.connect(self.sketchWidget.act_constrain_vert_line_mode)
|
||||
self.ui.pb_con_dist.clicked.connect(self.sketchWidget.act_constrain_distance_mode)
|
||||
self.ui.pb_con_mid.clicked.connect(self.sketchWidget.act_constrain_mid_point_mode)
|
||||
###Modes - Updated for improved sketcher
|
||||
# Add a selection/drag mode button if available in UI, otherwise use existing button
|
||||
# For now, we'll assume there might be a selection button - adapt as needed
|
||||
# self.ui.pb_select.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.NONE))
|
||||
|
||||
self.ui.pb_linetool.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.LINE))
|
||||
self.ui.pb_rectool.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.RECTANGLE))
|
||||
self.ui.pb_circtool.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.CIRCLE))
|
||||
self.ui.pb_con_ptpt.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.COINCIDENT_PT_PT))
|
||||
self.ui.pb_con_line.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.COINCIDENT_PT_LINE))
|
||||
self.ui.pb_con_horiz.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.HORIZONTAL))
|
||||
self.ui.pb_con_vert.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.VERTICAL))
|
||||
self.ui.pb_con_dist.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.DISTANCE))
|
||||
self.ui.pb_con_mid.clicked.connect(lambda: self.sketchWidget.set_mode(SketchMode.MIDPOINT))
|
||||
|
||||
### Operations
|
||||
self.ui.pb_extrdop.pressed.connect(self.send_extrude)
|
||||
self.ui.pb_cutop.pressed.connect(self.send_cut)
|
||||
self.ui.pb_del_body.pressed.connect(self.del_body)
|
||||
|
||||
self.sketchWidget.constrain_done.connect(self.draw_op_complete)
|
||||
# Connect new sketcher signals
|
||||
self.sketchWidget.constraint_applied.connect(self.on_constraint_applied)
|
||||
self.sketchWidget.geometry_created.connect(self.on_geometry_created)
|
||||
self.sketchWidget.sketch_modified.connect(self.on_sketch_modified)
|
||||
self.setFocusPolicy(Qt.StrongFocus)
|
||||
|
||||
self.send_command.connect(self.custom_3D_Widget.on_receive_command)
|
||||
self.ui.actionNew_Project.triggered.connect(self.new_project)
|
||||
self.ui.pb_enable_construct.clicked.connect(self.sketchWidget.on_construct_change)
|
||||
self.ui.pb_enable_construct.clicked.connect(lambda checked: self.sketchWidget.set_construction_mode(checked))
|
||||
self.project = 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))
|
||||
self.ui.pb_snap_midp.toggled.connect(lambda checked: self.sketchWidget.set_snap_mode(SnapMode.MIDPOINT, checked))
|
||||
self.ui.pb_snap_horiz.toggled.connect(lambda checked: self.sketchWidget.set_snap_mode(SnapMode.HORIZONTAL, checked))
|
||||
self.ui.pb_snap_vert.toggled.connect(lambda checked: self.sketchWidget.set_snap_mode(SnapMode.VERTICAL, checked))
|
||||
self.ui.pb_snap_angle.toggled.connect(lambda checked: self.sketchWidget.set_snap_mode(SnapMode.ANGLE, checked))
|
||||
self.ui.pb_enable_snap.toggled.connect(lambda checked: self.sketchWidget.set_snap_mode(SnapMode.POINT, checked))
|
||||
### COMPOS
|
||||
### COMPOS
|
||||
|
||||
@@ -275,10 +283,8 @@ class MainWindow(QMainWindow):
|
||||
#Save original for editing later
|
||||
sketch.original_sketch = sketch_from_widget
|
||||
|
||||
#Get parameters
|
||||
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)
|
||||
# Use the new geometry conversion method that handles circles, lines, and points
|
||||
sketch.convert_geometry_for_sdf(sketch_from_widget)
|
||||
sketch.id = sketch_from_widget.id
|
||||
|
||||
sketch.filter_lines_for_interactor(sketch_from_widget.lines)
|
||||
@@ -294,7 +300,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# Deactivate drawing
|
||||
self.ui.pb_linetool.setChecked(False)
|
||||
self.sketchWidget.line_mode = False
|
||||
self.sketchWidget.set_mode(None)
|
||||
|
||||
items = self.ui.sketch_list.findItems(sketch.id, Qt.MatchExactly)[0]
|
||||
self.ui.sketch_list.setCurrentItem(items)
|
||||
@@ -361,16 +367,43 @@ class MainWindow(QMainWindow):
|
||||
self.send_command.emit("flip")
|
||||
|
||||
def draw_op_complete(self):
|
||||
# safely disable the line modes
|
||||
# safely disable all drawing and constraint modes
|
||||
self.ui.pb_linetool.setChecked(False)
|
||||
self.ui.pb_rectool.setChecked(False)
|
||||
self.ui.pb_circtool.setChecked(False)
|
||||
|
||||
# Disable all constraint buttons
|
||||
self.ui.pb_con_ptpt.setChecked(False)
|
||||
self.ui.pb_con_line.setChecked(False)
|
||||
self.ui.pb_con_horiz.setChecked(False)
|
||||
self.ui.pb_con_vert.setChecked(False)
|
||||
self.ui.pb_con_dist.setChecked(False)
|
||||
self.ui.pb_con_mid.setChecked(False)
|
||||
self.ui.pb_con_perp.setChecked(False)
|
||||
|
||||
self.sketchWidget.mouse_mode = None
|
||||
self.sketchWidget.reset_buffers()
|
||||
# Reset the sketch widget mode
|
||||
self.sketchWidget.set_mode(None)
|
||||
|
||||
def on_geometry_created(self, geometry):
|
||||
"""Handle geometry creation from the improved sketcher."""
|
||||
print(f"Geometry created: {geometry}")
|
||||
|
||||
def on_sketch_modified(self):
|
||||
"""Handle sketch modifications from the improved sketcher."""
|
||||
print("Sketch modified")
|
||||
|
||||
def on_constraint_applied(self):
|
||||
"""Handle constraint application - only reset UI if exiting constraint mode."""
|
||||
# Only call draw_op_complete if we're actually exiting constraint mode
|
||||
# This allows persistent constraint behavior - constraints stay active until right-click
|
||||
current_mode = self.sketchWidget.current_mode
|
||||
|
||||
# Only reset if we're going back to NONE mode (right-click exit)
|
||||
if current_mode == SketchMode.NONE:
|
||||
self.draw_op_complete()
|
||||
else:
|
||||
# We're still in a constraint mode, don't reset the UI buttons
|
||||
print(f"Constraint applied, staying in mode: {current_mode}")
|
||||
|
||||
def draw_mesh(self):
|
||||
|
||||
@@ -428,10 +461,8 @@ class MainWindow(QMainWindow):
|
||||
#print(sketch)
|
||||
points = sketch.sdf_points
|
||||
|
||||
# detect loop that causes problems in mesh generation
|
||||
if points[-1] == points[0]:
|
||||
print("overlap")
|
||||
del points[-1]
|
||||
# Note: Closed polygons should have first == last point for proper SDF generation
|
||||
# No need to remove 'overlapping' points as they're intentionally closed
|
||||
|
||||
dialog = ExtrudeDialog(self)
|
||||
if dialog.exec():
|
||||
@@ -694,11 +725,130 @@ class Sketch:
|
||||
def convert_points_for_sdf(self, points):
|
||||
points_for_sdf = []
|
||||
for point in points:
|
||||
if point.is_helper is False:
|
||||
if hasattr(point, 'is_helper') and point.is_helper is False:
|
||||
print("point", point)
|
||||
points_for_sdf.append(self.translate_points_tup(point.ui_point))
|
||||
# Handle improved sketcher Point2D objects
|
||||
if hasattr(point, 'x') and hasattr(point, 'y'):
|
||||
points_for_sdf.append((point.x, point.y))
|
||||
else:
|
||||
# Fallback for old-style point objects
|
||||
points_for_sdf.append(self.translate_points_tup(point.ui_point))
|
||||
|
||||
self.sdf_points = points_for_sdf
|
||||
|
||||
def convert_geometry_for_sdf(self, sketch):
|
||||
"""Convert sketch geometry (points, lines, circles) to SDF polygon points"""
|
||||
import math
|
||||
points_for_sdf = []
|
||||
|
||||
# Handle circles by converting them to polygons
|
||||
if hasattr(sketch, 'circles') and sketch.circles:
|
||||
for circle in sketch.circles:
|
||||
if not circle.is_helper:
|
||||
# Convert circle to polygon approximation
|
||||
num_segments = 32 # Number of segments for circle approximation
|
||||
center_x, center_y = circle.center.x, circle.center.y
|
||||
radius = circle.radius
|
||||
|
||||
for i in range(num_segments):
|
||||
angle = 2 * math.pi * i / num_segments
|
||||
x = center_x + radius * math.cos(angle)
|
||||
y = center_y + radius * math.sin(angle)
|
||||
points_for_sdf.append((x, y))
|
||||
|
||||
# Handle lines by creating ordered polygon from connected line segments
|
||||
if hasattr(sketch, 'lines') and sketch.lines:
|
||||
non_helper_lines = [line for line in sketch.lines if not line.is_helper]
|
||||
if non_helper_lines:
|
||||
# For connected shapes like rectangles, we need to trace the outline
|
||||
# to avoid duplicate corner points
|
||||
ordered_points = self._trace_connected_lines(non_helper_lines)
|
||||
points_for_sdf.extend(ordered_points)
|
||||
|
||||
# Handle individual points (if any)
|
||||
if hasattr(sketch, 'points') and sketch.points:
|
||||
for point in sketch.points:
|
||||
if hasattr(point, 'is_helper') and not point.is_helper:
|
||||
# Only add standalone points (not already part of lines/circles)
|
||||
is_standalone = True
|
||||
|
||||
# Check if point is part of any line
|
||||
if hasattr(sketch, 'lines'):
|
||||
for line in sketch.lines:
|
||||
if point == line.start or point == line.end:
|
||||
is_standalone = False
|
||||
break
|
||||
|
||||
# Check if point is center of any circle
|
||||
if hasattr(sketch, 'circles'):
|
||||
for circle in sketch.circles:
|
||||
if point == circle.center:
|
||||
is_standalone = False
|
||||
break
|
||||
|
||||
if is_standalone:
|
||||
points_for_sdf.append((point.x, point.y))
|
||||
|
||||
self.sdf_points = points_for_sdf
|
||||
print(f"Generated SDF points: {len(points_for_sdf)} points")
|
||||
if points_for_sdf:
|
||||
print(f"First point: {points_for_sdf[0]}, Last point: {points_for_sdf[-1]}")
|
||||
|
||||
def _trace_connected_lines(self, lines):
|
||||
"""Trace connected line segments to create ordered polygon points without duplicates"""
|
||||
if not lines:
|
||||
return []
|
||||
|
||||
# Start with the first line
|
||||
current_line = lines[0]
|
||||
# Keep Y coordinate as-is to match sketcher orientation
|
||||
ordered_points = [(current_line.start.x, current_line.start.y)]
|
||||
used_lines = {current_line}
|
||||
current_point = current_line.end
|
||||
|
||||
while len(used_lines) < len(lines):
|
||||
# Add the current transition point keeping Y coordinate as-is
|
||||
point_tuple = (current_point.x, current_point.y)
|
||||
if not ordered_points or ordered_points[-1] != point_tuple:
|
||||
ordered_points.append(point_tuple)
|
||||
|
||||
# Find the next connected line
|
||||
next_line = None
|
||||
for line in lines:
|
||||
if line in used_lines:
|
||||
continue
|
||||
|
||||
# Check if this line connects to current_point
|
||||
if self._points_equal(line.start, current_point):
|
||||
next_line = line
|
||||
current_point = line.end
|
||||
break
|
||||
elif self._points_equal(line.end, current_point):
|
||||
next_line = line
|
||||
current_point = line.start
|
||||
break
|
||||
|
||||
if next_line is None:
|
||||
# No more connected lines, might be separate line segments
|
||||
break
|
||||
|
||||
used_lines.add(next_line)
|
||||
|
||||
# If we didn't use all lines, add remaining line endpoints keeping Y coordinate as-is
|
||||
for line in lines:
|
||||
if line not in used_lines:
|
||||
start_point = (line.start.x, line.start.y)
|
||||
end_point = (line.end.x, line.end.y)
|
||||
if start_point not in ordered_points:
|
||||
ordered_points.append(start_point)
|
||||
if end_point not in ordered_points:
|
||||
ordered_points.append(end_point)
|
||||
|
||||
return ordered_points
|
||||
|
||||
def _points_equal(self, p1, p2, tolerance=1e-6):
|
||||
"""Check if two points are equal within tolerance"""
|
||||
return abs(p1.x - p2.x) < tolerance and abs(p1.y - p2.y) < tolerance
|
||||
|
||||
def filter_lines_for_interactor(self, lines):
|
||||
### Filter lines that are not meant to be drawn for the interactor like contruction lines
|
||||
@@ -792,16 +942,22 @@ class Interactor:
|
||||
Translates coordinates."""
|
||||
|
||||
points_for_interact = []
|
||||
for point_to_poly in input_lines:
|
||||
from_coord_start = window.sketchWidget.from_quadrant_coords_no_center(point_to_poly.crd1.ui_point)
|
||||
from_coord_end = window.sketchWidget.from_quadrant_coords_no_center(point_to_poly.crd2.ui_point)
|
||||
start_draw = self.translate_points_tup(from_coord_start)
|
||||
end_draw = self.translate_points_tup(from_coord_end)
|
||||
line = start_draw, end_draw
|
||||
points_for_interact.append(line)
|
||||
for line in input_lines:
|
||||
# Handle improved sketcher Line2D objects with start/end Point2D objects
|
||||
if hasattr(line, 'start') and hasattr(line, 'end'):
|
||||
start_point = (line.start.x, line.start.y)
|
||||
end_point = (line.end.x, line.end.y)
|
||||
else:
|
||||
# Fallback for old-style line objects
|
||||
from_coord_start = window.sketchWidget.from_quadrant_coords_no_center(line.crd1.ui_point)
|
||||
from_coord_end = window.sketchWidget.from_quadrant_coords_no_center(line.crd2.ui_point)
|
||||
start_point = self.translate_points_tup(from_coord_start)
|
||||
end_point = self.translate_points_tup(from_coord_end)
|
||||
|
||||
line_tuple = (start_point, end_point)
|
||||
points_for_interact.append(line_tuple)
|
||||
|
||||
print("packed_lines", points_for_interact)
|
||||
|
||||
self.lines = points_for_interact
|
||||
|
||||
@dataclass
|
||||
|
||||
Reference in New Issue
Block a user