Skip to content

rendering more than 30000 points... #1965

Description

@jon1enforce

There is a rendering limit - by DRAW_TIME in CNCCanvas.py, that result in an error, and limitations, also at modern hardware.
Detailed logging of the rendering and an escape from the limitations would be great for complex and big data above 1mb.

DRAW_TIME = 180 # Maximum draw time permitted

`

----------------------------------------------------------------------

Draw the paths for the whole gcode file

----------------------------------------------------------------------

def drawPaths(self):
    if not self.draw_paths:
        print("DEBUG: draw_paths is disabled, skipping rendering")
        for block in self.gcode.blocks:
            block.resetPath()
        return

    print("=" * 60)
    print("DEBUG: START drawPaths()")
    print(f"DEBUG: Total blocks: {len(self.gcode.blocks)}")
    print(f"DEBUG: DRAW_TIME setting: {DRAW_TIME} seconds")
    
    total_lines = sum(len(block) for block in self.gcode.blocks)
    print(f"DEBUG: Total lines to process: {total_lines}")
    
    # Performance Monitoring
    startTime = time.time()
    lines_processed = 0
    paths_created = 0
    errors_encountered = 0
    
    try:
        # Adaptive performance settings based on file size
        if total_lines > 50000:
            update_frequency = 10000  # Very large files
            gui_update_interval = 5.0  # Update GUI every 5 seconds
            print("DEBUG: Using HIGH performance mode for large file")
        elif total_lines > 10000:
            update_frequency = 5000   # Large files
            gui_update_interval = 3.0
            print("DEBUG: Using MEDIUM performance mode")
        else:
            update_frequency = 1000   # Normal files
            gui_update_interval = 1.0
            print("DEBUG: Using NORMAL performance mode")
        
        n = update_frequency
        last_gui_update = time.time()
        self.cnc.resetAllMargins()
        drawG = self.draw_rapid or self.draw_paths or self.draw_margin
        
        # Disable timeout completely - let it run as long as needed
        # if time.time() - startTime > DRAW_TIME:
        #    raise AlarmException()
        
        block_start_time = time.time()
        
        for i, block in enumerate(self.gcode.blocks):
            print(f"DEBUG: Processing block {i}: '{block.name()}' with {len(block)} lines")
            
            start = True  # start location found
            block.resetPath()
            block_lines_processed = 0
            block_paths_created = 0

            # Draw block
            for j, line in enumerate(block):
                lines_processed += 1
                block_lines_processed += 1
                
                n -= 1
                current_time = time.time()
                
                # Progress reporting for large files
                if lines_processed % update_frequency == 0:
                    elapsed = current_time - startTime
                    rate = lines_processed / elapsed if elapsed > 0 else 0
                    print(f"DEBUG: Progress: {lines_processed}/{total_lines} lines "
                          f"({lines_processed/total_lines*100:.1f}%) - "
                          f"Rate: {rate:.1f} lines/sec - "
                          f"Paths: {paths_created} - "
                          f"Errors: {errors_encountered}")
                
                # GUI update (less frequent for better performance)
                if n == 0 or current_time - last_gui_update > gui_update_interval:
                    if current_time - last_gui_update > gui_update_interval:
                        try:
                            self.update()
                            last_gui_update = current_time
                            print(f"DEBUG: GUI updated - {lines_processed} lines processed")
                        except Exception as e:
                            print(f"DEBUG: GUI update failed: {e}")
                    
                    # Reset counter with adaptive frequency
                    if total_lines - lines_processed > update_frequency * 10:
                        n = update_frequency
                    else:
                        n = max(100, update_frequency // 10)  # More frequent near end
                
                # Process line with comprehensive error handling
                cmd = None
                try:
                    # Compile and evaluate line
                    compiled_line = CNC.compileLine(line)
                    if compiled_line is not None:
                        cmd = self.gcode.evaluate(compiled_line, self.app)
                        
                        # Handle different command types
                        if isinstance(cmd, tuple):
                            cmd = None  # Special commands not for drawing
                        elif cmd is not None:
                            cmd = CNC.breakLine(cmd)
                    
                except AlarmException as ae:
                    print(f"DEBUG: AlarmException at block {i}, line {j}: {ae}")
                    errors_encountered += 1
                    # Continue with next line instead of stopping
                    continue
                except MemoryError as me:
                    print(f"DEBUG: MEMORY ERROR at block {i}, line {j}: {me}")
                    errors_encountered += 1
                    # Try to recover by forcing garbage collection
                    import gc
                    gc.collect()
                    continue
                except Exception as e:
                    print(f"DEBUG: Exception at block {i}, line {j}: {type(e).__name__}: {e}")
                    print(f"DEBUG: Problematic line: {line}")
                    errors_encountered += 1
                    # Continue with next line
                    continue
                
                # Skip if no command to draw or drawing disabled
                if cmd is None or not drawG:
                    block.addPath(None)
                    continue
                
                # Create path with robust error handling
                try:
                    path = self.drawPath(block, cmd)
                    if path is not None:
                        self._items[path] = (i, j)
                        block.addPath(path)
                        paths_created += 1
                        block_paths_created += 1
                        
                        # Mark start position for first valid path in block
                        if start and self.cnc.gcode in (1, 2, 3):
                            block.startPath(self.cnc.x, self.cnc.y, self.cnc.z)
                            start = False
                    else:
                        block.addPath(None)
                        
                except MemoryError as me:
                    print(f"DEBUG: MEMORY ERROR in drawPath at block {i}, line {j}: {me}")
                    errors_encountered += 1
                    block.addPath(None)
                    # Force cleanup
                    import gc
                    gc.collect()
                except Exception as e:
                    print(f"DEBUG: Error in drawPath at block {i}, line {j}: {type(e).__name__}: {e}")
                    errors_encountered += 1
                    block.addPath(None)
            
            # Block completion stats
            block_time = time.time() - block_start_time
            print(f"DEBUG: Block {i} completed: {block_lines_processed} lines, "
                  f"{block_paths_created} paths, {block_time:.2f}s")
            
            # Set end position for block
            try:
                block.endPath(self.cnc.x, self.cnc.y, self.cnc.z)
            except Exception as e:
                print(f"DEBUG: Error setting block end path: {e}")
            
            block_start_time = time.time()
        
        # Final statistics
        total_time = time.time() - startTime
        print("=" * 60)
        print(f"DEBUG: RENDERING COMPLETED SUCCESSFULLY")
        print(f"DEBUG: Total time: {total_time:.2f} seconds")
        print(f"DEBUG: Lines processed: {lines_processed}")
        print(f"DEBUG: Paths created: {paths_created}")
        print(f"DEBUG: Errors encountered: {errors_encountered}")
        print(f"DEBUG: Processing rate: {lines_processed/total_time:.1f} lines/sec")
        print("=" * 60)
        
        # Update status
        self.status(_("Rendering completed: {} paths from {} lines in {:.1f}s").format(
            paths_created, lines_processed, total_time))
            
    except AlarmException as ae:
        # This should never happen now, but just in case
        total_time = time.time() - startTime
        print(f"DEBUG: UNEXPECTED AlarmException after {total_time:.2f}s: {ae}")
        print(f"DEBUG: Processed {lines_processed} lines before interruption")
        self.status(_("Rendering interrupted after {:.1f}s ({} lines processed)").format(
            total_time, lines_processed))
            
    except Exception as e:
        # Catch any other unexpected exceptions
        total_time = time.time() - startTime
        print(f"DEBUG: CRITICAL ERROR in drawPaths: {type(e).__name__}: {e}")
        import traceback
        traceback.print_exc()
        self.status(_("Rendering failed after {:.1f}s: {}").format(total_time, str(e)))
    
    finally:
        # Always ensure GUI is updated
        try:
            self.update()
        except:
            pass

# ----------------------------------------------------------------------
# Create path for one g command - Optimized version
# ----------------------------------------------------------------------
def drawPath(self, block, cmds):
    try:
        self.cnc.motionStart(cmds)
        xyz = self.cnc.motionPath()
        self.cnc.motionEnd()
        
        if not xyz:
            return None
        
        # Performance optimization: Simplify paths with many points
        if len(xyz) > 500:  # Very long paths
            # Reduce point density while maintaining shape
            step = max(1, len(xyz) // 200)  # Target ~200 points max
            simplified_xyz = xyz[::step]
            # Always include first and last points
            if simplified_xyz[-1] != xyz[-1]:
                simplified_xyz.append(xyz[-1])
            xyz = simplified_xyz
            print(f"DEBUG: Simplified path from {len(xyz)*step} to {len(xyz)} points")
        
        # Calculate statistics
        self.cnc.pathLength(block, xyz)
        if self.cnc.gcode in (1, 2, 3):
            block.pathMargins(xyz)
            self.cnc.pathMargins(block)
        
        # Handle enable/disable state
        if block.enable:
            if self.cnc.gcode == 0 and self.draw_rapid:
                xyz[0] = self._last
            self._last = xyz[-1]
        else:
            if self.cnc.gcode == 0:
                return None
        
        # Convert to canvas coordinates
        coords = self.plotCoords(xyz)
        if not coords:
            return None
        
        # Create canvas line with appropriate style
        if block.enable:
            fill = block.color if block.color else ENABLE_COLOR
        else:
            fill = DISABLE_COLOR
            
        if self.cnc.gcode == 0:
            if self.draw_rapid:
                return self.create_line(coords, fill=fill, width=0, dash=(4, 3))
            else:
                return None
        elif self.draw_paths:
            return self.create_line(coords, fill=fill, width=0, cap="projecting")
        else:
            return None
            
    except MemoryError:
        print("DEBUG: Memory error in drawPath - skipping")
        raise
    except Exception as e:
        print(f"DEBUG: Error in drawPath: {e}")
        return None

`

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions