How can I create an event?

Thanks @nathanletwory,

I’ll take a look at c# events, more how they are created.

I can compile ironpython into dll but I’ve no idea how could I reference the resulting mess of a dll. :smiley:

Look up this forum for clr.AddReference :slight_smile:

no, I know that.

but have you looked a compiled IronPython with ILspy?

Nope, I haven’t used IronPython outside of Rhino.

This is decompiled empty winform with title “hello world” compiled to dll:

using IronPython.Compiler;
using IronPython.Runtime;
using IronPython.Runtime.Operations;
using Microsoft.Scripting;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Runtime;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

public class DLRCachedCode
{
	[CachedOptimizedCode(new string[]
	{
		"__name__",
		"__file__",
		"__doc__",
		"__path__",
		"__builtins__",
		"__package__",
		"clr",
		"Application",
		"Form",
		"HelloWorldForm",
		"form"
	})]
	public static object helloworld$1(CodeContext $globalContext, FunctionCode $functionCode)
	{
		object[] expr_06 = new object[1];
		StrongBox<object[]> strongBox = expr_06[0] = new StrongBox<object[]>();
		object[] locals = expr_06;
		object[] value = new object[10];
		strongBox.Value = value;
		strongBox.Value[9] = CallSite<Func<CallSite, object, string, object>>.Create(PythonOps.MakeSetAction($globalContext, "Name"));
		strongBox.Value[8] = CallSite<Func<CallSite, object, string, object>>.Create(PythonOps.MakeSetAction($globalContext, "Text"));
		strongBox.Value[7] = PythonOps.MakeFunctionCode($globalContext, "__init__", null, new string[]
		{
			"self"
		}, 0, 152, 245, "helloworld", new Func<PythonFunction, object, object>(new Closure(null, locals).__init__$1), null, null, null, new string[]
		{
			"self"
		}, 1);
		strongBox.Value[6] = new string[]
		{
			"__module__",
			"__init__"
		};
		strongBox.Value[5] = CallSite<Func<CallSite, object, CodeContext, object>>.Create(PythonOps.MakeGetAction($globalContext, "Run", false));
		strongBox.Value[4] = CallSite<Func<CallSite, CodeContext, object, object, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(1)));
		strongBox.Value[3] = CallSite<Func<CallSite, CodeContext, object, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(0)));
		strongBox.Value[2] = PythonOps.MakeFunctionCode($globalContext, "HelloWorldForm", null, new string[0], 0, 115, 251, "helloworld", new Func<CodeContext, CodeContext>(new Closure(null, locals).HelloWorldForm$2), null, null, null, new string[]
		{
			"__module__",
			"__init__"
		}, 2);
		strongBox.Value[1] = CallSite<Func<CallSite, object, CodeContext, object>>.Create(PythonOps.MakeGetAction($globalContext, "AddReference", false));
		strongBox.Value[0] = CallSite<Func<CallSite, CodeContext, object, string, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(1)));
		PythonGlobal[] globalArrayFromContext = PythonOps.GetGlobalArrayFromContext($globalContext);
		try
		{
			object obj = PythonOps.PublishModule($globalContext, "helloworld");
			try
			{
				globalArrayFromContext[1].set_CurrentValue((object)"helloworld");
				globalArrayFromContext[0].set_CurrentValue((object)"helloworld");
				PythonOps.ModuleStarted($globalContext, 8);
				globalArrayFromContext[2].set_CurrentValue(null);
				int num = 1;
				globalArrayFromContext[6].set_CurrentValue(LightExceptions.CheckAndThrow(PythonOps.ImportTop($globalContext, "clr", -1)));
				num = 3;
				CallSite<Func<CallSite, CodeContext, object, string, object>> arg;
				CallSite<Func<CallSite, object, CodeContext, object>> arg2;
				(arg = (CallSite<Func<CallSite, CodeContext, object, string, object>>)strongBox.Value[0]).Target(arg, $globalContext, (arg2 = (CallSite<Func<CallSite, object, CodeContext, object>>)strongBox.Value[1]).Target(arg2, globalArrayFromContext[6].get_CurrentValue(), $globalContext), "System.Windows.Forms");
				num = 7;
				object obj2 = LightExceptions.CheckAndThrow(PythonOps.ImportWithNames($globalContext, "System.Windows.Forms", new string[]
				{
					"Application",
					"Form"
				}, -1));
				globalArrayFromContext[7].set_CurrentValue(PythonOps.ImportFrom($globalContext, obj2, "Application"));
				globalArrayFromContext[8].set_CurrentValue(PythonOps.ImportFrom($globalContext, obj2, "Form"));
				num = 11;
				globalArrayFromContext[9].set_CurrentValue(PythonOps.MakeClass((FunctionCode)strongBox.Value[2], null, $globalContext, "HelloWorldForm", new object[]
				{
					globalArrayFromContext[8].get_CurrentValue()
				}, "Text,Name"));
				num = 23;
				CallSite<Func<CallSite, CodeContext, object, object>> arg3;
				globalArrayFromContext[10].set_CurrentValue((arg3 = (CallSite<Func<CallSite, CodeContext, object, object>>)strongBox.Value[3]).Target(arg3, $globalContext, globalArrayFromContext[9].get_CurrentValue()));
				num = 25;
				CallSite<Func<CallSite, CodeContext, object, object, object>> arg4;
				CallSite<Func<CallSite, object, CodeContext, object>> arg5;
				(arg4 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[4]).Target(arg4, $globalContext, (arg5 = (CallSite<Func<CallSite, object, CodeContext, object>>)strongBox.Value[5]).Target(arg5, globalArrayFromContext[7].get_CurrentValue(), $globalContext), globalArrayFromContext[10].get_CurrentValue());
			}
			catch (Exception ex)
			{
				int num;
				PythonOps.UpdateStackTrace(ex, $globalContext, $functionCode, num);
				throw;
			}
		}
		catch (Exception)
		{
			object obj;
			PythonOps.RemoveModule($globalContext, "helloworld", obj);
			throw;
		}
		return null;
	}

	private static object __init__$1(Closure closure, PythonFunction $function, object self)
	{
		object[] locals = closure.Locals;
		StrongBox<object[]> strongBox = (StrongBox<object[]>)locals[0];
		CodeContext globalContext = PythonOps.GetGlobalContext(PythonOps.GetParentContextFromFunction($function));
		PythonGlobal[] globalArrayFromContext = PythonOps.GetGlobalArrayFromContext(globalContext);
		try
		{
			try
			{
				List<FunctionStack> list = PythonOps.PushFrame(globalContext, $function.get___code__());
				int num = 17;
				CallSite<Func<CallSite, object, string, object>> arg;
				(arg = (CallSite<Func<CallSite, object, string, object>>)strongBox.Value[8]).Target(arg, self, "Hello World!");
				num = 19;
				CallSite<Func<CallSite, object, string, object>> arg2;
				(arg2 = (CallSite<Func<CallSite, object, string, object>>)strongBox.Value[9]).Target(arg2, self, "Hello World!");
			}
			finally
			{
				List<FunctionStack> list;
				list.RemoveAt(list.Count + -1);
			}
		}
		catch (Exception ex)
		{
			int num;
			PythonOps.UpdateStackTrace(ex, globalContext, (FunctionCode)strongBox.Value[7], num);
			throw;
		}
		return null;
	}

	private static CodeContext HelloWorldForm$2(Closure closure, CodeContext $parentContext)
	{
		object[] locals = closure.Locals;
		CodeContext globalContext = PythonOps.GetGlobalContext($parentContext);
		PythonGlobal[] globalArrayFromContext = PythonOps.GetGlobalArrayFromContext(globalContext);
		ClosureCell closureCell = PythonOps.MakeClosureCell();
		ClosureCell closureCell2 = PythonOps.MakeClosureCell();
		CodeContext codeContext = PythonOps.CreateLocalContext($parentContext, new MutableTuple<ClosureCell, ClosureCell>(closureCell, closureCell2), (string[])((StrongBox<object[]>)locals[0]).Value[6]);
		closureCell.Value = globalArrayFromContext[0].get_CurrentValue();
		closureCell2.Value = PythonOps.MakeFunction(codeContext, (FunctionCode)((StrongBox<object[]>)locals[0]).Value[7], globalArrayFromContext[0].get_RawValue(), null);
		return codeContext;
	}

	[CachedOptimizedCode(new string[]
	{
		"__name__",
		"__file__",
		"__doc__",
		"__path__",
		"__builtins__",
		"__package__",
		"clr",
		"Application",
		"Form",
		"HelloWorldForm",
		"form"
	})]
	public static object __main__$2(CodeContext $globalContext, FunctionCode $functionCode)
	{
		object[] expr_06 = new object[1];
		StrongBox<object[]> strongBox = expr_06[0] = new StrongBox<object[]>();
		object[] locals = expr_06;
		object[] value = new object[10];
		strongBox.Value = value;
		strongBox.Value[9] = CallSite<Func<CallSite, object, string, object>>.Create(PythonOps.MakeSetAction($globalContext, "Name"));
		strongBox.Value[8] = CallSite<Func<CallSite, object, string, object>>.Create(PythonOps.MakeSetAction($globalContext, "Text"));
		strongBox.Value[7] = PythonOps.MakeFunctionCode($globalContext, "__init__", null, new string[]
		{
			"self"
		}, 0, 152, 245, "helloworld.py", new Func<PythonFunction, object, object>(new Closure(null, locals).__init__$2), null, null, null, new string[]
		{
			"self"
		}, 1);
		strongBox.Value[6] = new string[]
		{
			"__module__",
			"__init__"
		};
		strongBox.Value[5] = CallSite<Func<CallSite, object, CodeContext, object>>.Create(PythonOps.MakeGetAction($globalContext, "Run", false));
		strongBox.Value[4] = CallSite<Func<CallSite, CodeContext, object, object, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(1)));
		strongBox.Value[3] = CallSite<Func<CallSite, CodeContext, object, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(0)));
		strongBox.Value[2] = PythonOps.MakeFunctionCode($globalContext, "HelloWorldForm", null, new string[0], 0, 115, 251, "helloworld.py", new Func<CodeContext, CodeContext>(new Closure(null, locals).HelloWorldForm$3), null, null, null, new string[]
		{
			"__module__",
			"__init__"
		}, 2);
		strongBox.Value[1] = CallSite<Func<CallSite, object, CodeContext, object>>.Create(PythonOps.MakeGetAction($globalContext, "AddReference", false));
		strongBox.Value[0] = CallSite<Func<CallSite, CodeContext, object, string, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(1)));
		PythonGlobal[] globalArrayFromContext = PythonOps.GetGlobalArrayFromContext($globalContext);
		try
		{
			object obj = PythonOps.PublishModule($globalContext, "__main__");
			try
			{
				globalArrayFromContext[1].set_CurrentValue((object)"helloworld.py");
				globalArrayFromContext[0].set_CurrentValue((object)"__main__");
				PythonOps.ModuleStarted($globalContext, 8);
				globalArrayFromContext[2].set_CurrentValue(null);
				int num = 1;
				globalArrayFromContext[6].set_CurrentValue(LightExceptions.CheckAndThrow(PythonOps.ImportTop($globalContext, "clr", -1)));
				num = 3;
				CallSite<Func<CallSite, CodeContext, object, string, object>> arg;
				CallSite<Func<CallSite, object, CodeContext, object>> arg2;
				(arg = (CallSite<Func<CallSite, CodeContext, object, string, object>>)strongBox.Value[0]).Target(arg, $globalContext, (arg2 = (CallSite<Func<CallSite, object, CodeContext, object>>)strongBox.Value[1]).Target(arg2, globalArrayFromContext[6].get_CurrentValue(), $globalContext), "System.Windows.Forms");
				num = 7;
				object obj2 = LightExceptions.CheckAndThrow(PythonOps.ImportWithNames($globalContext, "System.Windows.Forms", new string[]
				{
					"Application",
					"Form"
				}, -1));
				globalArrayFromContext[7].set_CurrentValue(PythonOps.ImportFrom($globalContext, obj2, "Application"));
				globalArrayFromContext[8].set_CurrentValue(PythonOps.ImportFrom($globalContext, obj2, "Form"));
				num = 11;
				globalArrayFromContext[9].set_CurrentValue(PythonOps.MakeClass((FunctionCode)strongBox.Value[2], null, $globalContext, "HelloWorldForm", new object[]
				{
					globalArrayFromContext[8].get_CurrentValue()
				}, "Text,Name"));
				num = 23;
				CallSite<Func<CallSite, CodeContext, object, object>> arg3;
				globalArrayFromContext[10].set_CurrentValue((arg3 = (CallSite<Func<CallSite, CodeContext, object, object>>)strongBox.Value[3]).Target(arg3, $globalContext, globalArrayFromContext[9].get_CurrentValue()));
				num = 25;
				CallSite<Func<CallSite, CodeContext, object, object, object>> arg4;
				CallSite<Func<CallSite, object, CodeContext, object>> arg5;
				(arg4 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[4]).Target(arg4, $globalContext, (arg5 = (CallSite<Func<CallSite, object, CodeContext, object>>)strongBox.Value[5]).Target(arg5, globalArrayFromContext[7].get_CurrentValue(), $globalContext), globalArrayFromContext[10].get_CurrentValue());
			}
			catch (Exception ex)
			{
				int num;
				PythonOps.UpdateStackTrace(ex, $globalContext, $functionCode, num);
				throw;
			}
		}
		catch (Exception)
		{
			object obj;
			PythonOps.RemoveModule($globalContext, "__main__", obj);
			throw;
		}
		return null;
	}

	private static object __init__$2(Closure closure, PythonFunction $function, object self)
	{
		object[] locals = closure.Locals;
		StrongBox<object[]> strongBox = (StrongBox<object[]>)locals[0];
		CodeContext globalContext = PythonOps.GetGlobalContext(PythonOps.GetParentContextFromFunction($function));
		PythonGlobal[] globalArrayFromContext = PythonOps.GetGlobalArrayFromContext(globalContext);
		try
		{
			try
			{
				List<FunctionStack> list = PythonOps.PushFrame(globalContext, $function.get___code__());
				int num = 17;
				CallSite<Func<CallSite, object, string, object>> arg;
				(arg = (CallSite<Func<CallSite, object, string, object>>)strongBox.Value[8]).Target(arg, self, "Hello World!");
				num = 19;
				CallSite<Func<CallSite, object, string, object>> arg2;
				(arg2 = (CallSite<Func<CallSite, object, string, object>>)strongBox.Value[9]).Target(arg2, self, "Hello World!");
			}
			finally
			{
				List<FunctionStack> list;
				list.RemoveAt(list.Count + -1);
			}
		}
		catch (Exception ex)
		{
			int num;
			PythonOps.UpdateStackTrace(ex, globalContext, (FunctionCode)strongBox.Value[7], num);
			throw;
		}
		return null;
	}

	private static CodeContext HelloWorldForm$3(Closure closure, CodeContext $parentContext)
	{
		object[] locals = closure.Locals;
		CodeContext globalContext = PythonOps.GetGlobalContext($parentContext);
		PythonGlobal[] globalArrayFromContext = PythonOps.GetGlobalArrayFromContext(globalContext);
		ClosureCell closureCell = PythonOps.MakeClosureCell();
		ClosureCell closureCell2 = PythonOps.MakeClosureCell();
		CodeContext codeContext = PythonOps.CreateLocalContext($parentContext, new MutableTuple<ClosureCell, ClosureCell>(closureCell, closureCell2), (string[])((StrongBox<object[]>)locals[0]).Value[6]);
		closureCell.Value = globalArrayFromContext[0].get_CurrentValue();
		closureCell2.Value = PythonOps.MakeFunction(codeContext, (FunctionCode)((StrongBox<object[]>)locals[0]).Value[7], globalArrayFromContext[0].get_RawValue(), null);
		return codeContext;
	}

	[DlrCachedCode]
	public static MutableTuple<Type[], Delegate[][], string[][], string[][]> GetScriptCodeInfo()
	{
		return new MutableTuple<Type[], Delegate[][], string[][], string[][]>(new Type[]
		{
			typeof(PythonContext)
		}, new Delegate[][]
		{
			new Delegate[]
			{
				new LookupCompilationDelegate(DLRCachedCode.helloworld$1),
				new LookupCompilationDelegate(DLRCachedCode.__main__$2)
			}
		}, new string[][]
		{
			new string[]
			{
				"helloworld",
				"helloworld.py"
			}
		}, new string[][]
		{
			new string[]
			{
				"helloworld",
				"__main__"
			}
		});
	}
}

helloworld.py (298 Bytes)

I had some free time and I was interested in this anyway, so here is an extremely hacky and dangerous (no error checking) but seemingly working example of a custom event firing from one script to another, based on the above example problem of point containment by a brep.

Summary:

I used the event watcher sample and added a custom event (and caller) with pyevent, to the class.
In the init, the class asks the user to select a point of interest and a brep to test with.
I used the ReplaceObject doc event to check if the replaced object matches the point of interest. If so, then use the new RhinoObject from the event args as the point for the containment test. If true then call the custom event caller with a dictionary for e, the event args.

Once that is running (from the sticky), I run a another script that gets the reference to the event watcher from the sticky, subscribes a function to the custom event made previously, then runs a function to allow the user to move the point of interest. If the point moves inside the brep then subscribed function fires.

Here is the event watcher sample with mods:

################################################################################
# SampleEventHandler.py
# Copyright (c) 2018 Robert McNeel & Associates.
# See License.md in the root of this repository for details.
################################################################################
################################################################################
# Modified for custom event example on discourse
################################################################################
import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs
import pyevent

################################################################################
# SampleEventHandler class
################################################################################
class SampleEventHandler():
    
    # Initializer
    def __init__(self):
        # Enable the event handlers
        Rhino.RhinoDoc.AddRhinoObject += self.OnAddRhinoObject
        Rhino.RhinoDoc.ReplaceRhinoObject += self.OnReplaceRhinoObject
        Rhino.RhinoDoc.DeleteRhinoObject += self.OnDeleteRhinoObject
        Rhino.RhinoDoc.UndeleteRhinoObject += self.OnUndeleteRhinoObject
        
        # make the event and caller with pyevent
        self.brep_contains_point_event, self.brep_contains_point_caller = pyevent.make_event()
        
        self.Enabled = True
        
        # get the guid for the point of interest (the one you will move later)
        self.point_guid_of_interest = rs.GetObject('select point of interest')
        # get the guid for the brep to test inclusion with
        self.brep_guid = rs.GetObject('select brep')
    
    # Disables the event handlers
    def Disable(self):
        Rhino.RhinoDoc.AddRhinoObject -= self.OnAddRhinoObject
        Rhino.RhinoDoc.ReplaceRhinoObject -= self.OnReplaceRhinoObject
        Rhino.RhinoDoc.DeleteRhinoObject -= self.OnDeleteRhinoObject
        Rhino.RhinoDoc.UndeleteRhinoObject -= self.OnUndeleteRhinoObject
        
        self.Enabled = False
    
    # Returns the enabled state
    def IsEnabled(self):
        return self.Enabled
    
    # AddRhinoObject event handler
    def OnAddRhinoObject(self, sender, e):
        # print '> SampleEventHandler.OnAddRhinoObject'
        pass
    
    # ReplaceRhinoObject event handler
    def OnReplaceRhinoObject(self, sender, e):
        print '> SampleEventHandler.OnReplaceRhinoObject'
        try:
            # print(e.OldRhinoObject.Id, e.NewRhinoObject.Id, e.ObjectId)
            if e.ObjectId == self.point_guid_of_interest:
                print('point moved')
                # we need the new location, not the old, or the one associated with the Id
                # as it also seems to be the old location
                point3d = e.NewRhinoObject.Geometry.Location
                # get the brep geometry for inclusion test
                brep = rs.coercebrep(self.brep_guid)
                if brep.IsPointInside(point3d, sc.doc.ModelAbsoluteTolerance, True):
                    print('point inside brep, emitting event')
                    # event args can be anything, so I would use a dictionary
                    # and populate with items.  For example just an empty is OK.
                    event_args = {}
                    # here you call the caller of the event (made in __init__)
                    # you pass in the sender (self) and the event args
                    self.brep_contains_point_caller(self, event_args)
                    
        except Exception as exp:
            print(exp)
    
    # DeleteRhinoObject event handler
    def OnDeleteRhinoObject(self, sender, e):
        # print '> SampleEventHandler.OnDeleteRhinoObject'
        pass
    
    # UndeleteRhinoObject event handler
    def OnUndeleteRhinoObject(self, sender, e):
        # print '> SampleEventHandler.OnUndeleteRhinoObject'   
        pass
        
################################################################################
# TestSampleEventHandler function
################################################################################
def TestSampleEventHandler():
    # See if we already have a handler
    if sc.sticky.has_key('sample_event_handler'):
        # Delete the handler
        handler = sc.sticky.pop('sample_event_handler', None)
        if handler:
            handler.Disable()
            handler = None
            print ' SampleEventHandler disabled'  
    else:
        # Create handler
        handler = SampleEventHandler()
        # Add the handler to the sticky dictionary so it
        # survives when the main function ends.
        sc.sticky['sample_event_handler'] = handler       
        print ' SampleEventHandler enabled'  
    
################################################################################
# Check to see if this file is being executed as the 'main' python
# script instead of being used as a module by some other python script
# This allows us to use the module which ever way we want.
################################################################################
if __name__ == '__main__':
    TestSampleEventHandler() 

Here is the second script to test the custom event:

import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs

emitter = sc.sticky.get('sample_event_handler', None)

class EventListener:
    def __init__(self):
        if emitter:
            # subscribe a function to the event
            emitter.brep_contains_point_event += self.react_to_event
            # call the function to move a point
            self.move_point()
        
        
    def move_point(self):
        point_guid = rs.GetObject('select point to move')
        if point_guid:
            dest = rs.GetPoint('select destination')
            if dest:
                vect = dest - rs.coerce3dpoint(point_guid)
                rs.MoveObject(point_guid, vect)
                again = rs.GetString('Again?', strings=['yes', 'no'])
                if again == 'yes':
                    self.move_point()
        # disconnect event before quitting, otherwise they linger
        print('disconnecting event')
        emitter.brep_contains_point_event -= self.react_to_event
        
    def react_to_event(self, sender, e):
        print('reacting to event')
        print(sender, e)
        
        
if __name__ == '__main__':
    el = EventListener()

For testing I put a brep cube and a point in the doc, ran the event listener selecting said point and brep when prompted, then ran the second script and selected the same point to move around. The subscribed ‘react_to_event’ function ran when the point was moved into the brep volume, so I think it is working, at least here.

@ivelin.peychev : Don’t know if this is exactly what you want but perhaps it may help or provide an interim solution. Perhaps there are ways to make custom Rhino.DocObjects that are capable of emitting events but themselves but I don’t know about it.

1 Like

Thanks for sharing this @nathancoatney,

Very interesting indeed.
I’ll try to understand it and see if I can apply it.

Meanwhile I started looking into csharp again, after @nathanletwory mentioned that perhaps this is the proper way to go. I’ve forgotten a lot of stuff about C# and everything is happening so slow. :confounded: