# Fluency CAD - Improved Sketcher Technical Documentation ## Table of Contents 1. [Overview](#overview) 2. [Architecture](#architecture) 3. [Core Components](#core-components) 4. [Geometry System](#geometry-system) 5. [Constraint Solving](#constraint-solving) 6. [Coordinate Systems](#coordinate-systems) 7. [Interaction System](#interaction-system) 8. [Rendering System](#rendering-system) 9. [Snapping System](#snapping-system) 10. [Working Plane Integration](#working-plane-integration) 11. [API Reference](#api-reference) 12. [Performance Considerations](#performance-considerations) 13. [Troubleshooting](#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 ```python 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 ```python 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 ```python 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 ```python 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) ```python 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 ```python 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: #### 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 ### Mouse Event Handling #### Click Processing Flow ```python def mousePressEvent(self, event): local_pos = self._viewport_to_local(event.pos()) if event.button() == Qt.LeftButton: if self.current_mode == SketchMode.NONE: # Check for point dragging point = self.sketch.get_point_near(local_pos) if point: self._start_point_drag(point, local_pos) else: # Handle drawing modes self._handle_left_click(local_pos) ``` ### 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 ```python 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: ```python @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 ```python 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 ```python @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: ```python 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: ```python 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:** ```python widget = ImprovedSketchWidget() widget.show() ``` **Mode Control:** ```python widget.set_mode(SketchMode.LINE) widget.set_construction_mode(True) ``` **Snapping Control:** ```python widget.set_snap_mode(SnapMode.POINT, True) widget.toggle_snap_mode(SnapMode.MIDPOINT, enabled) ``` **View Control:** ```python widget.zoom_to_fit() ``` **Sketch Access:** ```python sketch = widget.get_sketch() widget.set_sketch(imported_sketch) ``` ### Sketch Management #### ImprovedSketch **Geometry Addition:** ```python 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:** ```python # 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: ```python # 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 #### 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: ```python 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 --- ## 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.