Files
fluencyCAD/doc/SKETCHER_TECHNICAL_DOCUMENTATION.md
2025-11-16 17:48:05 +01:00

31 KiB

Fluency CAD - Improved Sketcher Technical Documentation

Table of Contents

  1. Overview
  2. Architecture
  3. Core Components
  4. Geometry System
  5. Constraint Solving
  6. Coordinate Systems
  7. Interaction System
  8. Rendering System
  9. Snapping System
  10. Working Plane Integration
  11. API Reference
  12. Performance Considerations
  13. Troubleshooting

Overview

The ImprovedSketchWidget is a parametric 2D sketching system built for Fluency CAD. It provides constraint-based geometric modeling with real-time solving, integrated snapping, and seamless integration with 3D working planes. The system is built on top of the SolverSpace constraint solver and PySide6 for the user interface.

Key Features

  • Parametric Geometry: All geometry is constraint-driven and automatically updates
  • Real-time Solving: Constraints are solved dynamically as geometry is modified
  • Advanced Snapping: Multi-mode snapping system (points, midpoints, grid, angles)
  • Construction Geometry: Support for helper/construction geometry
  • Working Plane Integration: Seamless 2D/3D workflow with projected geometry
  • Interactive Dragging: Smooth point dragging with constraint preservation
  • Multiple Drawing Modes: Lines, rectangles, circles, arcs, and points

Architecture

┌─────────────────────────────────────────────────────────┐
│                ImprovedSketchWidget                     │
│  ┌─────────────────┐  ┌─────────────────────────────┐   │
│  │   User Interface │  │      Rendering System      │   │
│  │   - Mouse Events │  │   - Coordinate Transform    │   │
│  │   - Keyboard     │  │   - Geometry Drawing       │   │
│  │   - Mode Control │  │   - UI Overlays            │   │
│  └─────────────────┘  └─────────────────────────────┘   │
│           │                         │                   │
│           └─────────┬───────────────┘                   │
│                     │                                   │
│  ┌─────────────────────────────────────────────────┐   │
│  │            Interaction System               │   │
│  │  - Snapping Engine                             │   │
│  │  - Dragging Logic                              │   │
│  │  - Selection Management                        │   │
│  └─────────────────────────────────────────────────┘   │
│                     │                                   │
│  ┌─────────────────────────────────────────────────┐   │
│  │              Geometry System                   │   │
│  │  ┌─────────────┐  ┌─────────────────────────┐   │   │
│  │  │ Point2D     │  │      Line2D             │   │   │
│  │  │ Circle2D    │  │      Arc2D (future)     │   │   │
│  │  └─────────────┘  └─────────────────────────┘   │   │
│  └─────────────────────────────────────────────────┘   │
│                     │                                   │
│  ┌─────────────────────────────────────────────────┐   │
│  │            ImprovedSketch                      │   │
│  │  (Enhanced SolverSystem)                       │   │
│  │  - Constraint Management                       │   │
│  │  - Solver Integration                          │   │
│  │  - Geometry Storage                            │   │
│  └─────────────────────────────────────────────────┘   │
│                     │                                   │
│  ┌─────────────────────────────────────────────────┐   │
│  │          SolverSpace Library                   │   │
│  │  - Constraint Solving Engine                   │   │
│  │  - Geometric Relationships                     │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

Core Components

1. ImprovedSketchWidget

The main widget class that handles user interaction and rendering.

Key Responsibilities:

  • Mouse and keyboard event handling
  • Mode management (line, circle, constraint modes, etc.)
  • Coordinate system transformations
  • Rendering pipeline orchestration
  • Integration with external systems (working planes)

2. ImprovedSketch

Enhanced wrapper around SolverSpace's SolverSystem.

Key Responsibilities:

  • Geometry storage and management
  • Constraint system integration
  • Solver result processing
  • Handle management for solver objects

3. Geometry Classes

Type-safe geometry representations with validation.

Classes:

  • Point2D: 2D points with solver integration
  • Line2D: 2D lines with constraint tracking
  • Circle2D: 2D circles with radius constraints

Geometry System

Point2D Class

class Point2D:
    def __init__(self, x: float, y: float, is_construction: bool = False):
        self.id = uuid.uuid4()           # Unique identifier
        self.x = float(x)                # X coordinate
        self.y = float(y)                # Y coordinate
        self.ui_point = QPoint(int(x), int(y))  # Qt UI point
        self.handle = None               # SolverSpace handle
        self.handle_nr = None            # Handle number
        self.is_helper = is_construction # Construction geometry flag

Key Features:

  • Automatic coordinate validation
  • SolverSpace handle integration
  • Construction/normal geometry support
  • Distance calculations and equality testing

Line2D Class

class Line2D:
    def __init__(self, start_point: Point2D, end_point: Point2D, is_construction: bool = False):
        self.id = uuid.uuid4()
        self.start = start_point         # Start point reference
        self.end = end_point            # End point reference
        self.handle = None              # SolverSpace handle
        self.constraints = []           # Applied constraints list
        self.is_helper = is_construction

Key Features:

  • Automatic degenerate line detection
  • Length, midpoint, and angle calculations
  • Point-on-line testing with tolerance
  • Constraint tracking and annotation

Circle2D Class

class Circle2D:
    def __init__(self, center: Point2D, radius: float, is_construction: bool = False):
        self.id = uuid.uuid4()
        self.center = center            # Center point reference
        self.radius = float(radius)     # Radius value
        self.handle = None              # SolverSpace handle
        self.constraints = []           # Applied constraints
        self.is_helper = is_construction

Constraint Solving

SolverSpace Integration

The system uses the python-solvespace library for constraint solving. The ImprovedSketch class wraps the SolverSpace API and provides:

  1. Automatic Handle Management: Each geometry object gets a unique handle
  2. Error Handling: Robust error handling for solver failures
  3. Position Updates: Automatic geometry position updates after solving

Constraint Types

Geometric Constraints

  • Coincident: Point-to-point or point-to-line coincidence
  • Horizontal: Forces lines to be horizontal
  • Vertical: Forces lines to be vertical
  • Distance: Fixes distance between points or line length
  • Parallel: Makes lines parallel (future implementation)
  • Perpendicular: Makes lines perpendicular (future implementation)

Constraint Application Workflow

def _handle_distance_constraint(self, pos: QPoint):
    line = self.sketch.get_line_near(pos)
    if line and line.handle:
        # Get user input for distance
        distance, ok = QInputDialog.getDouble(...)
        if ok:
            # Apply constraint to solver
            self.sketch.distance(line.start.handle, line.end.handle, distance, self.sketch.wp)
            # Solve system
            result = self.sketch.solve_system()
            if result == ResultFlag.OKAY:
                line.constraints.append(f"L={distance:.2f}")

Solver Workflow

  1. Constraint Addition: Constraints are added to the solver system
  2. System Solving: The solver attempts to find a valid solution
  3. Result Processing: If successful, geometry positions are updated
  4. UI Updates: The display is refreshed to show new positions

Coordinate Systems

The sketcher uses multiple coordinate systems that must be properly transformed between:

1. Sketch Coordinates (Local)

  • Origin at sketch center
  • Y-axis points up (mathematical convention)
  • Units in millimeters
  • Range: typically -1000 to +1000

2. Viewport Coordinates (Screen)

  • Origin at top-left of widget
  • Y-axis points down (computer graphics convention)
  • Units in pixels
  • Range: 0 to widget dimensions

3. Working Plane Coordinates (3D)

  • 3D coordinates projected onto 2D working plane
  • Transformation handled by external VTK system
  • Converted to sketch coordinates for display

Coordinate Transformations

Viewport to Local (Mouse Input)

def _viewport_to_local(self, viewport_pos: QPoint) -> QPoint:
    # Step 1: Subtract widget center
    center_x = self.width() / 2
    center_y = self.height() / 2
    
    # Step 2: Apply pan offset
    viewport_x = viewport_pos.x() - center_x - (self.pan_offset.x() * self.zoom_factor)
    viewport_y = viewport_pos.y() - center_y - (self.pan_offset.y() * self.zoom_factor)
    
    # Step 3: Apply inverse zoom and Y-flip
    local_x = viewport_x / self.zoom_factor
    local_y = -viewport_y / self.zoom_factor
    
    return QPoint(int(local_x), int(local_y))

Rendering Transform Setup

def _setup_coordinate_system(self, painter: QPainter):
    transform = QTransform()
    
    # Translate to center and apply pan
    center = QPointF(self.width() / 2, self.height() / 2)
    transform.translate(center.x() + self.pan_offset.x() * self.zoom_factor, 
                      center.y() + self.pan_offset.y() * self.zoom_factor)
    
    # Apply zoom and flip Y-axis
    transform.scale(self.zoom_factor, -self.zoom_factor)
    
    painter.setTransform(transform)

Interaction System

Mode-Based Interaction

The sketcher supports multiple interaction modes with robust mode management:

Drawing Modes

  • SketchMode.LINE: Two-point line creation
  • SketchMode.RECTANGLE: Two-corner rectangle creation
  • SketchMode.CIRCLE: Center-radius circle creation
  • SketchMode.POINT: Single point creation

Constraint Modes

  • SketchMode.COINCIDENT_PT_PT: Point-to-point coincidence
  • SketchMode.HORIZONTAL: Horizontal line constraint
  • SketchMode.VERTICAL: Vertical line constraint
  • SketchMode.DISTANCE: Distance/length constraint

Selection Mode

  • SketchMode.NONE: Selection and manipulation mode (enables point dragging)

Selection and Deletion System

The sketcher now includes a comprehensive selection and deletion system that allows users to select and remove elements from the sketch.

Selection Methods

  1. Single Element Selection: Click on individual points or lines to select/deselect them
  2. Rectangle Selection: Click and drag to create a selection rectangle for multiple elements
  3. Visual Feedback: Selected elements are highlighted in yellow with increased size

Deletion Methods

  1. Keyboard Deletion: Press Delete or Backspace to remove selected elements
  2. Proper Cleanup: Elements are removed from both the sketch and constraint solver
  3. Dependency Handling: Lines are deleted before points to maintain geometric integrity

Implementation Details

The selection system is implemented through the following components:

  • Selection Tracking: selected_elements list tracks currently selected elements
  • Rectangle Selection: selection_rect_start and selection_rect_end track rectangle selection bounds
  • Visual Feedback: Modified drawing methods highlight selected elements in yellow
  • Keyboard Support: keyPressEvent handles Delete/Backspace keys
  • Deletion Method: delete_selected_elements handles removal of elements from sketch and solver

Selection Workflow

  1. Default Selection Mode: The sketcher defaults to selection mode when no drawing tool is active
  2. Element Selection:
    • Click on points or lines to select/deselect them (they turn yellow)
    • Click and drag to create a rectangle selection for multiple elements
  3. Element Deletion:
    • Press Delete or Backspace to remove all selected elements
    • Elements are removed from both the sketch and constraint solver
  4. Visual Feedback:
    • Selected elements are highlighted in yellow
    • Rectangle selection is shown with a yellow dashed border

Constraints Handling

When elements are deleted:

  • Lines are removed first to avoid issues with points being used by lines
  • Points are only removed if they are not used by any remaining lines
  • The constraint solver is re-run after deletion to update remaining constraints
  • Proper error handling ensures the UI remains responsive even if solver operations fail

Mode Management System

The mode system has been enhanced to provide intuitive selection and deletion functionality:

Mode Compatibility

  • Python None is automatically converted to SketchMode.NONE for backward compatibility
  • The set_mode() method ensures the mode is always a valid SketchMode enum value
  • Mode changes reset all interaction buffers and state

Default Selection Behavior

  • SketchMode.NONE now serves as the default selection mode
  • When no drawing tool is active, the sketcher is in selection mode by default
  • Users can click on elements to select/deselect them (they turn yellow)
  • Users can click and drag to create rectangle selections
  • Pressing Delete or Backspace removes all selected elements

Right-Click Behavior

  • Right-clicking always exits any active mode and returns to SketchMode.NONE
  • This enables point dragging and prevents unintended geometry creation
  • The mode reset happens directly in the sketcher, not through main app signals

Point Dragging Safety

  • Point dragging is only enabled when in SketchMode.NONE mode
  • Left-clicks in NONE mode check for draggable points first
  • If no point is found, the click is processed as a selection operation

Mouse Event Handling

Click Processing Flow

def mousePressEvent(self, event):
    local_pos = self._viewport_to_local(event.pos())
    
    if event.button() == Qt.LeftButton:
        self._handle_left_click(local_pos)
    elif event.button() == Qt.RightButton:
        self._handle_right_click(local_pos)
    elif event.button() == Qt.MiddleButton:
        self._start_panning(event.pos())

Enhanced Left-Click Handler

def _handle_left_click(self, pos: QPoint):
    # Safety check for NONE mode (dragging enabled)
    if self.current_mode == SketchMode.NONE or self.current_mode is None:
        point = self.sketch.get_point_near(pos, self.snap_settings.snap_distance)
        if point:
            self._start_point_drag(point, pos)
            return
        else:
            # No point found - ignore click to prevent unintended drawing
            return
    
    # Handle active drawing/constraint modes
    if self.current_mode == SketchMode.LINE:
        self._handle_line_creation(pos)
    elif self.current_mode == SketchMode.HORIZONTAL:
        self._handle_horizontal_constraint(pos)
    # ... other modes

Right-Click Mode Reset

def _handle_right_click(self, pos: QPoint):
    # Reset interaction state
    self._reset_interaction_state()
    
    # Force mode to NONE to enable dragging
    self.current_mode = SketchMode.NONE
    
    # Emit signal to inform main app
    self.constraint_applied.emit()

Point Dragging System

The point dragging system is optimized for performance and maintains constraint consistency:

Drag Phases

  1. Drag Start (_start_point_drag):

    • Identifies dragged point
    • Stores initial position
    • Sets dragging state
  2. Drag Update (_handle_point_drag):

    • Updates point visual position only
    • Applies snapping
    • No solver execution (for performance)
  3. Drag End (_end_point_drag):

    • Updates solver parameters with final position
    • Runs constraint solver
    • Updates all connected geometry
    • Resets drag state
def _end_point_drag(self):
    if not self.dragging_point:
        return
    
    # Update solver parameters with final position
    if self.dragging_point.handle:
        new_x = self.dragging_point.x
        new_y = self.dragging_point.y
        self.sketch.set_params(self.dragging_point.handle.params, [new_x, new_y])
    
    # Run solver to update all connected geometry
    result = self.sketch.solve_system()
    if result == ResultFlag.OKAY:
        self.sketch_modified.emit()

Rendering System

Rendering Pipeline

The rendering system uses Qt's QPainter with a multi-layer approach:

  1. Coordinate System Setup: Apply zoom, pan, and Y-flip transforms
  2. Background Rendering: Grid, axes, and origin marker
  3. Geometry Rendering: Points, lines, circles with proper styling
  4. Dynamic Elements: Preview geometry during creation
  5. UI Overlays: Mode indicators, measurements, snap highlights

Rendering Layers

Layer 1: Background

  • Coordinate axes (dashed gray lines)
  • Grid (if enabled)
  • Origin marker (red circle)

Layer 2: Geometry

  • Construction geometry (green, dotted)
  • Normal geometry (gray, solid)
  • Constraint annotations

Layer 3: Interactive Elements

  • Hover highlights (red)
  • Dynamic previews (gray, dashed)
  • Measurements during creation

Layer 4: UI Overlays

  • Snap point indicators
  • Mode and zoom information
  • Status messages

Styling System

Rendering appearance is controlled by the RenderSettings class:

@dataclass
class RenderSettings:
    normal_pen_width: float = 2.0
    construction_pen_width: float = 1.0
    highlight_pen_width: float = 3.0
    
    normal_color = QColor(128, 128, 128)      # Gray
    construction_color = QColor(0, 255, 0)    # Green
    highlight_color = QColor(255, 0, 0)       # Red
    solver_color = QColor(0, 255, 0)          # Green
    dynamic_color = QColor(128, 128, 128)     # Gray
    text_color = QColor(255, 255, 255)        # White

Dynamic Previews

During geometry creation, dynamic previews show:

  • Line Creation: Dashed line from start to cursor with length annotation
  • Rectangle Creation: Dashed rectangle outline
  • Circle Creation: Dashed circle with radius line and annotation

Snapping System

Snap Modes

The snapping system supports multiple simultaneous snap modes:

SnapMode.POINT

  • Snaps to existing geometry points
  • Priority: Highest
  • Visual: Red circle highlight

SnapMode.MIDPOINT

  • Snaps to line midpoints
  • Priority: Medium
  • Visual: Red diamond highlight

SnapMode.GRID

  • Snaps to grid intersections
  • Priority: Lowest
  • Visual: Green cross highlight

SnapMode.HORIZONTAL/VERTICAL

  • Angular snapping (future implementation)
  • Constrains to horizontal/vertical directions

SnapMode.INTERSECTION

  • Snaps to line intersections (future implementation)

Snap Algorithm

def _get_snapped_position(self, pos: QPoint) -> QPoint:
    min_distance = float('inf')
    snapped_pos = pos
    snap_threshold = self.snap_settings.snap_distance
    
    # Point snapping (highest priority)
    if SnapMode.POINT in self.snap_settings.enabled_modes:
        for point in self.sketch.points:
            distance = math.sqrt((pos.x() - point.x)**2 + (pos.y() - point.y)**2)
            if distance < snap_threshold and distance < min_distance:
                snapped_pos = QPoint(int(point.x), int(point.y))
                min_distance = distance
    
    # Midpoint snapping (medium priority)
    if SnapMode.MIDPOINT in self.snap_settings.enabled_modes and min_distance > snap_threshold:
        for line in self.sketch.lines:
            midpoint = line.midpoint
            distance = math.sqrt((pos.x() - midpoint.x)**2 + (pos.y() - midpoint.y)**2)
            if distance < snap_threshold and distance < min_distance:
                snapped_pos = QPoint(int(midpoint.x), int(midpoint.y))
                min_distance = distance
    
    return snapped_pos

Snap Settings

@dataclass
class SnapSettings:
    snap_distance: float = 20.0      # Snap threshold in pixels
    angle_increment: float = 15.0    # Angular snap increment
    grid_spacing: float = 50.0       # Grid spacing
    enabled_modes: Set[SnapMode]     # Active snap modes

Working Plane Integration

Projected Geometry Workflow

The sketcher integrates with 3D working planes through projected geometry:

  1. 3D Geometry Selection: User selects 3D lines/points in VTK widget
  2. Plane Definition: System computes working plane from selections
  3. Geometry Projection: 3D geometry is projected onto 2D working plane
  4. Sketch Import: Projected geometry is imported as construction geometry

Projection Import Methods

convert_proj_points(proj_points)

Imports projected 3D points as 2D construction points:

def convert_proj_points(self, proj_points):
    for point_data in proj_points:
        if hasattr(point_data, 'x') and hasattr(point_data, 'y'):
            point = Point2D(point_data.x, point_data.y, True)  # Construction
            self.sketch.add_point(point)

convert_proj_lines(proj_lines)

Imports projected 3D lines as 2D construction lines:

def convert_proj_lines(self, proj_lines):
    for line_data in proj_lines:
        # Handle object format
        if hasattr(line_data, 'start') and hasattr(line_data, 'end'):
            x1, y1 = line_data.start.x, line_data.start.y
            x2, y2 = line_data.end.x, line_data.end.y
            
            # Skip degenerate lines
            if abs(x1 - x2) < 1e-6 and abs(y1 - y2) < 1e-6:
                continue
            
            start = Point2D(x1, y1, True)
            end = Point2D(x2, y2, True)
            self.sketch.add_point(start)
            self.sketch.add_point(end)
            line = Line2D(start, end, True)
            self.sketch.add_line(line)

Construction vs Normal Geometry

  • Construction Geometry:

    • Rendered in green with dotted lines
    • Used for reference and alignment
    • Created from projected 3D geometry
    • Flag: is_construction=True
  • Normal Geometry:

    • Rendered in gray with solid lines
    • Part of the actual sketch design
    • Created by user drawing actions
    • Flag: is_construction=False

API Reference

Main Widget Class

ImprovedSketchWidget

Initialization:

widget = ImprovedSketchWidget()
widget.show()

Mode Control:

# Set drawing modes
widget.set_mode(SketchMode.LINE)
widget.set_mode(SketchMode.NONE)  # Enable selection/dragging
widget.set_mode(None)  # Also converted to SketchMode.NONE

# Construction geometry
widget.set_construction_mode(True)

Snapping Control:

widget.set_snap_mode(SnapMode.POINT, True)
widget.toggle_snap_mode(SnapMode.MIDPOINT, enabled)

View Control:

widget.zoom_to_fit()

Sketch Access:

sketch = widget.get_sketch()
widget.set_sketch(imported_sketch)

Sketch Management

ImprovedSketch

Geometry Addition:

sketch = ImprovedSketch()
point = Point2D(10, 20)
line = Line2D(start_point, end_point)
circle = Circle2D(center_point, radius)

sketch.add_point(point)
sketch.add_line(line)
sketch.add_circle(circle)

Constraint Application:

# Distance constraint
sketch.distance(point1.handle, point2.handle, 50.0, sketch.wp)

# Coincident constraint
sketch.coincident(point1.handle, point2.handle, sketch.wp)

# Line constraints
sketch.horizontal(line.handle, sketch.wp)
sketch.vertical(line.handle, sketch.wp)

# Solve system
result = sketch.solve_system()

Signals

The widget emits several signals for integration:

# Emitted when constraint is successfully applied
widget.constraint_applied.connect(callback)

# Emitted when new geometry is created
widget.geometry_created.connect(callback)  # Parameter: geometry type string

# Emitted when sketch is modified
widget.sketch_modified.connect(callback)

Performance Considerations

Optimization Strategies

  1. Lazy Solving: Solver only runs when necessary (after constraints or drag end)
  2. Efficient Rendering: Uses Qt's optimized drawing primitives
  3. Smart Updates: Only redraws affected regions when possible
  4. Handle Caching: SolverSpace handles are cached to avoid recreation

Memory Management

  • Geometry objects use weak references where possible
  • SolverSpace handles are properly cleaned up
  • Qt objects follow parent-child hierarchy for automatic cleanup

Scalability Limits

  • Recommended maximum: ~1000 geometric entities
  • Solver performance degrades with complex constraint networks
  • Rendering remains smooth up to ~10,000 entities

Troubleshooting

Common Issues

Mode Handling Problems

Symptoms: Unintended line creation when dragging, tools not deactivating properly Causes: Mode not properly reset to NONE, Python None vs SketchMode.NONE confusion Solutions:

  • Always right-click to exit active modes
  • Ensure set_mode(None) is converted to SketchMode.NONE
  • Verify mode state after tool deactivation in main app

Point Dragging Issues

Symptoms: Cannot drag points, dragging creates unwanted lines Causes: Mode not set to NONE, safety checks preventing drag detection Solutions:

  • Verify current mode is SketchMode.NONE before attempting to drag
  • Right-click to ensure proper mode exit from drawing tools
  • Check that point detection threshold is appropriate

Solver Failures

Symptoms: Constraints not applied, geometry not updating Causes: Over-constrained systems, conflicting constraints Solutions:

  • Check constraint compatibility
  • Verify geometry validity
  • Use ResultFlag inspection for error details

Coordinate Transform Issues

Symptoms: Mouse clicks don't match visual geometry Causes: Incorrect transform calculations, zoom/pan state corruption Solutions:

  • Verify _viewport_to_local and _setup_coordinate_system consistency
  • Reset view with zoom_to_fit()

Performance Problems

Symptoms: Slow dragging, UI lag Causes: Solver running during drag, excessive redraws Solutions:

  • Ensure solver only runs in _end_point_drag
  • Check render loop efficiency
  • Profile with Qt performance tools

Snap Behavior Issues

Symptoms: Inconsistent snapping, incorrect snap points Causes: Priority conflicts, threshold settings, coordinate errors Solutions:

  • Adjust snap threshold in SnapSettings
  • Verify snap priority order
  • Check coordinate conversion in snap calculations

Debug Logging

Enable detailed logging for troubleshooting:

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('improved_sketcher')

Key log messages include:

  • Geometry addition/removal
  • Constraint application results
  • Solver execution status
  • Coordinate transformations
  • Snap calculations

Testing Guidelines

Unit Testing

  • Test geometry classes with edge cases
  • Verify coordinate transformations
  • Test constraint application logic

Integration Testing

  • Test with various sketch sizes
  • Verify working plane integration
  • Test complex constraint networks

Performance Testing

  • Measure solver execution time
  • Profile rendering performance
  • Test with large geometry sets

Recent Improvements (2025-08-16)

Mode Handling Enhancements

Significant improvements have been made to the mode management system:

Fixed Issues

  1. Unintended Line Creation: Resolved issue where dragging with line tool deactivated would still create lines
  2. Mode Reset Reliability: Right-click now reliably exits any active mode and returns to NONE
  3. Backward Compatibility: Python None mode values are automatically converted to SketchMode.NONE
  4. Safety Checks: Added comprehensive checks to prevent drawing operations in NONE mode

Implementation Details

  • Enhanced _handle_right_click() to directly set mode to NONE
  • Added safety checks in _handle_left_click() for NONE mode behavior
  • Improved set_mode() method to handle None input gracefully
  • Added comprehensive debug logging for mode transitions

Integration Improvements

  • Fixed main app integration where constraint modes were prematurely reset
  • Ensured persistent constraint behavior until explicit user cancellation
  • Maintained UI button state consistency with actual sketcher mode

These improvements ensure reliable mode transitions and prevent common user frustrations with unintended geometry creation.

Conclusion

The ImprovedSketchWidget provides a robust, extensible foundation for 2D parametric sketching in Fluency CAD. Its architecture separates concerns effectively, uses proven libraries (SolverSpace, PySide6), and provides rich interaction capabilities while maintaining good performance characteristics.

The system is designed for extensibility - new geometry types, constraint types, and interaction modes can be added following the established patterns. The comprehensive API allows for both direct use and integration with larger CAD systems.

With the recent mode handling improvements, the sketcher now provides a more reliable and intuitive user experience, with proper separation between drawing modes and selection/manipulation operations.