Help: Scripting mouse press and release events? (Sketch)

Hi,

I’m working on a tool that lets you draw ‘freehand’ more precisely and with more control than the sketch tool.

Certain posts in the forum require it:
Sketch tool settings
Sketch tool smoothness control
iPad sketching for rhino

Here are two scripts that enable you to draw degree 1 or 3 curves by capturing very precise points and offering the option of simplifying the curve directly as a function of a percentage of the total number of points.

SketchD1Rebuild.py (5.9 KB)
SketchD3Rebuild.py (6.1 KB)

import Rhino
import rhinoscriptsyntax as rs
from System.Drawing import Color

drawing_started = False
temporary_point_id = None
points = []


def simplify_points(points, tolerance):
    if not points or len(points) < 2:
        return points
    simplified = [points[0]]
    for pt in points[1:]:
        if rs.Distance(simplified[-1], pt) > tolerance:
            simplified.append(pt)
    return simplified


def rebuild_polyline_proportional(polyline_id, reduction_percentage):
    if not rs.IsCurve(polyline_id):
        print("The selected object is not a polyline.")
        return None
    points = rs.CurvePoints(polyline_id)
    if not points or len(points) < 2:
        print("Unable to retrieve points from the polyline.")
        return None
    initial_points = len(points)
    new_point_count = max(2, int(initial_points * (reduction_percentage / 100.0)))
    reduced_points = rs.DivideCurve(polyline_id, new_point_count - 2, create_points=False)
    if not reduced_points:
        print("Failed to reduce points.")
        return None
    final_points = [points[0]] + reduced_points + [points[-1]]
    rebuilt_polyline = rs.AddPolyline(final_points)
    if rebuilt_polyline:
        print("Polyline rebuilt with {} points.".format(len(final_points)))
        rs.DeleteObject(polyline_id)
        print("Original polyline deleted.")
        return rebuilt_polyline
    else:
        print("Failed to rebuild the polyline.")
        return None


def dynamic_draw(sender, args):
    global drawing_started, points
    if drawing_started:
        current_point = args.CurrentPoint
        if isinstance(current_point, Rhino.Geometry.Point3d):
            points.append(current_point)
            if len(points) > 1:
                args.Display.DrawPolyline(points, Color.Blue, 2)
            args.Display.DrawPoint(current_point, Color.Red)


def start_and_stop_drawing():
    global drawing_started, temporary_point_id, points
    start_point = rs.GetPoint("Click to start drawing.")
    if not start_point:
        print("Drawing canceled.")
        return False
    if not isinstance(start_point, Rhino.Geometry.Point3d):
        start_point = Rhino.Geometry.Point3d(start_point)
    temporary_point_id = rs.AddPoint(start_point)
    if not temporary_point_id:
        print("Unable to add the temporary point.")
        return False
    points.append(start_point)
    drawing_started = True
    gp = Rhino.Input.Custom.GetPoint()
    gp.AcceptNothing(True)
    gp.DynamicDraw += dynamic_draw
    while True:
        result = gp.Get()
        if result == Rhino.Input.GetResult.Point:
            end_point = gp.Point()
            points.append(end_point)
            print("Drawing finished with a click.")
            break
        elif result == Rhino.Input.GetResult.Nothing:
            print("Validation with Enter.")
            break
        else:
            print("Drawing canceled or interrupted.")
            return False
    drawing_started = False
    if temporary_point_id:
        rs.DeleteObject(temporary_point_id)
    return True


def sketch_with_proportional_simplification():
    global points
    tol = 0.1
    print("Click to start drawing. Move the mouse to sketch. Click or press 'Enter' to finish.")
    if not start_and_stop_drawing():
        return
    if len(points) < 2:
        print("Not enough points captured to create a polyline.")
        return
    simplified_points = simplify_points(points, tol)
    polyline_id = rs.AddPolyline(simplified_points)
    if not polyline_id:
        print("Failed to create the polyline.")
        return
    print("Sketch completed with a tolerance of {}.".format(tol))
    reduction_percentage = rs.GetReal("Enter the percentage of points to retain (0-100%)", 100, 0, 100)
    if reduction_percentage is None:
        print("Simplification canceled.")
        return
    rebuilt_polyline = rebuild_polyline_proportional(polyline_id, reduction_percentage)
    if rebuilt_polyline:
        print("The final polyline has been proportionally simplified with {}% of points retained.".format(reduction_percentage))
    else:
        print("Failed to simplify the polyline.")


if __name__ == "__main__":
    sketch_with_proportional_simplification()

This works well, but I have a problem: I can’t get the curve to be drawn by holding down the mouse and validating the curve when the mouse is released. I’d like a Click, Hold and Drag function to draw the curve. Like the sketch tool. Is this possible? Does anyone have an idea of how to do this?

It would be great to have some help on this!
Thanks,

Hi @Pipouille,

Rather than using rhinoscriptsyntax, use RhinoCommon’s GetPoint class. Inherit your own class from this and override it’s OnMouseMove and OnDynamicDraw methods.

– Dale

Hi Dale,

Thanks for your help, I’m really a beginner helping myself a lot with AI, so I may not be able to get that far. I’ve tried something, but it’s as if the MouseUp isn’t recognised, doesn’t react.
So it’s impossible to click, drag with the mouse clicked down to draw the curve and record the points and then release the mouse click up to record the last point and stop the curve.

import Rhino
from Rhino.Geometry import Point3d, Polyline
from System.Drawing import Color


class DragCurveDrawer(Rhino.Input.Custom.GetPoint):
    def __init__(self):
        super(DragCurveDrawer, self).__init__()
        self.points = []
        self.is_drawing = False

    def OnMouseDown(self, e):
        """Start drawing when the mouse is pressed."""
        self.is_drawing = True
        self.points = [e.Point]  # Add the initial point
        super(DragCurveDrawer, self).OnMouseDown(e)

    def OnMouseMove(self, e):
        """Collect points while dragging the mouse."""
        if self.is_drawing:
            self.points.append(e.Point)
        super(DragCurveDrawer, self).OnMouseMove(e)

    def OnMouseUp(self, e):
        """Finalize the curve when the mouse is released."""
        if self.is_drawing:
            self.points.append(e.Point)  # Add the final point
            self.is_drawing = False
            self.SetCommandResult(Rhino.Commands.Result.Success)
        super(DragCurveDrawer, self).OnMouseUp(e)

    def OnDynamicDraw(self, e):
        """Draw the polyline dynamically."""
        if len(self.points) > 1:
            dynamic_points = self.points + [e.CurrentPoint]
            e.Display.DrawPolyline(dynamic_points, Color.Blue, 2)
        super(DragCurveDrawer, self).OnDynamicDraw(e)


def simplify_points(points, tolerance):
    """Simplify points by removing close duplicates."""
    if len(points) < 2:
        return points
    simplified = [points[0]]
    for pt in points[1:]:
        if pt.DistanceTo(simplified[-1]) > tolerance:
            simplified.append(pt)
    return simplified


def rebuild_polyline_proportional(points, reduction_percentage):
    """Rebuild the polyline with a proportional reduction in points."""
    if len(points) < 2:
        return points

    new_count = max(2, int(len(points) * (reduction_percentage / 100.0)))
    interval = len(points) / float(new_count - 1)

    reduced_points = [points[int(round(i * interval))] for i in range(new_count)]
    return reduced_points


def sketch_with_drag():
    """Main function to sketch a curve by dragging the mouse."""
    print("Click and hold to draw. Release to finish.")

    # Instantiate the custom curve drawer
    drawer = DragCurveDrawer()
    drawer.SetCommandPrompt("Click and hold to draw. Release to finish.")

    # Wait for user input
    result = drawer.Get()

    if result != Rhino.Commands.Result.Success or not drawer.points:
        print("Drawing canceled or failed.")
        return

    points = drawer.points
    if len(points) < 2:
        print("Not enough points captured.")
        return

    # Simplify the points
    tol = 0.1
    simplified_points = simplify_points(points, tol)
    if len(simplified_points) < 2:
        print("Simplified points are insufficient to create a polyline.")
        return

    # Create the polyline using RhinoCommon
    polyline = Polyline(simplified_points)
    if not polyline.IsValid:
        print("Failed to create a valid polyline.")
        return

    # Add the polyline to the document
    polyline_curve = polyline.ToNurbsCurve()
    if polyline_curve and polyline_curve.IsValid:
        Rhino.RhinoDoc.ActiveDoc.Objects.AddCurve(polyline_curve)
        print("Polyline added to the document.")
    else:
        print("Failed to add the polyline.")

    # Rebuild the polyline proportionally
    reduction_percentage = Rhino.Input.RhinoGet.GetNumber(
        "Enter the percentage of points to retain (0-100%)", True, 100
    )[1]
    if reduction_percentage is None:
        print("Simplification canceled.")
        return

    rebuilt_points = rebuild_polyline_proportional(simplified_points, reduction_percentage)
    rebuilt_polyline = Polyline(rebuilt_points)
    rebuilt_curve = rebuilt_polyline.ToNurbsCurve()

    if rebuilt_curve and rebuilt_curve.IsValid:
        Rhino.RhinoDoc.ActiveDoc.Objects.AddCurve(rebuilt_curve)
        print(f"Rebuilt polyline with {len(rebuilt_points)} points added to the document.")
    else:
        print("Failed to rebuild the polyline.")


if __name__ == "__main__":
    sketch_with_drag()

I wanted to do something similar and translated this script (from the master Dale himself) to python to use as a starting point. Haven’t looked through your code, but this might give you some insight.