/**
* Custom Event Sample
* Demonstrates the ability to call into the main thread from a worker thread. This sample is an add-in . To use it, use the Scripts and Add-Ins command to create a new add-in. Delete all of the code in the newly created...
*/
import { adsk } from "@adsk/fusion";
const TEST_PREFIX = "CustomEventTest";
const DEFAULT_TIMEOUT_MS = 5000;
function assert(condition: boolean, message: string): void {
if (!condition) {
throw new Error(`${TEST_PREFIX} FAIL: ${message}`);
}
}
/** `fireCustomEvent` queues work (see FireCustomEventTask); pump until `pred` holds or timeout. */
function pumpUntil(pred: () => boolean, label: string, timeoutMs: number = DEFAULT_TIMEOUT_MS): void {
const start = Date.now();
while (!pred() && Date.now() - start < timeoutMs) {
adsk.doEvents();
}
if (!pred()) {
throw new Error(`${TEST_PREFIX} FAIL: "${label}" did not complete within ${timeoutMs}ms`);
}
}
function pumpFor(durationMs: number): void {
const start = Date.now();
while (Date.now() - start < durationMs) {
adsk.doEvents();
}
}
function fireAndWait(
app: any,
eventId: string,
additionalInfo: string,
pred: () => boolean,
label: string,
timeoutMs: number = DEFAULT_TIMEOUT_MS
): void {
(app as any).fireCustomEvent(eventId, additionalInfo);
pumpUntil(pred, label, timeoutMs);
}
function fireAndSettle(app: any, eventId: string, additionalInfo: string, settleMs: number = 200): void {
(app as any).fireCustomEvent(eventId, additionalInfo);
pumpFor(settleMs);
}
// ---------------------------------------------------------------------------
// 1. Basic lifecycle: register → add handler → fire → verify → remove → unregister
// ---------------------------------------------------------------------------
function testBasicLifecycle(): void {
adsk.log(`${TEST_PREFIX}: basic lifecycle...`);
const app = adsk.core.Application.get();
const eventId = "CustomEventTest_Basic";
const customEvent = (app as any).registerCustomEvent(eventId);
assert(
customEvent !== null && customEvent !== undefined,
"registerCustomEvent must return a non-null event object"
);
let notifyCount = 0;
let lastInfo = "";
const handler: any = {
notify: (args: any) => {
notifyCount++;
try {
if (args && args.additionalInfo) {
lastInfo = args.additionalInfo;
}
} catch (_) {
/* additionalInfo may not be available */
}
adsk.log(`${TEST_PREFIX}: handler notified (count=${notifyCount})`);
},
};
customEvent.add(handler);
fireAndWait(app, eventId, "payload-1", () => notifyCount >= 1, "basic lifecycle first notify");
assert(notifyCount >= 1, `handler must fire at least once, got ${notifyCount}`);
fireAndWait(app, eventId, "payload-2", () => notifyCount >= 2, "basic lifecycle second notify");
assert(notifyCount >= 2, `handler must fire at least twice, got ${notifyCount}`);
customEvent.remove(handler);
const countAfterRemove = notifyCount;
fireAndSettle(app, eventId, "post-remove");
assert(
notifyCount === countAfterRemove,
"handler must not fire after remove"
);
(app as any).unregisterCustomEvent(eventId);
adsk.log(`${TEST_PREFIX}: basic lifecycle PASSED`);
}
// ---------------------------------------------------------------------------
// 2. Duplicate add ΓÇö same handler object added twice is a no-op
// ---------------------------------------------------------------------------
function testDuplicateAdd(): void {
adsk.log(`${TEST_PREFIX}: duplicate add...`);
const app = adsk.core.Application.get();
const eventId = "CustomEventTest_DupAdd";
const customEvent = (app as any).registerCustomEvent(eventId);
let notifyCount = 0;
const handler: any = {
notify: () => {
notifyCount++;
},
};
customEvent.add(handler);
customEvent.add(handler);
fireAndWait(app, eventId, "dup-add", () => notifyCount >= 1, "duplicate add notify");
assert(
notifyCount === 1,
`duplicate add: second add is a no-op (${notifyCount} notifies ΓÇö expected 1; if 2, native dedupe may be missing)`
);
customEvent.remove(handler);
(app as any).unregisterCustomEvent(eventId);
adsk.log(`${TEST_PREFIX}: duplicate add PASSED`);
}
// ---------------------------------------------------------------------------
// 3. Remove-after-remove ΓÇö second remove is a safe no-op
// ---------------------------------------------------------------------------
function testRemoveAfterRemove(): void {
adsk.log(`${TEST_PREFIX}: remove after remove...`);
const app = adsk.core.Application.get();
const eventId = "CustomEventTest_RemRem";
const customEvent = (app as any).registerCustomEvent(eventId);
const handler: any = { notify: () => {} };
customEvent.add(handler);
customEvent.remove(handler);
customEvent.remove(handler);
(app as any).unregisterCustomEvent(eventId);
adsk.log(`${TEST_PREFIX}: remove after remove PASSED`);
}
// ---------------------------------------------------------------------------
// 4a. Multiple distinct handlers on the same event (concurrent contract)
// ---------------------------------------------------------------------------
function testMultipleHandlersConcurrent(): void {
adsk.log(`${TEST_PREFIX}: multiple handlers (concurrent)...`);
const app = adsk.core.Application.get();
const eventId = "CustomEventTest_MultiConcurrent";
const customEvent = (app as any).registerCustomEvent(eventId);
let count1 = 0;
let count2 = 0;
const handler1: any = { notify: () => { count1++; } };
const handler2: any = { notify: () => { count2++; } };
customEvent.add(handler1);
customEvent.add(handler2);
fireAndWait(
app,
eventId,
"multi-concurrent-1",
() => count1 >= 1 && count2 >= 1,
"concurrent handlers first notify"
);
assert(count1 >= 1, `handler1 must fire, got ${count1}`);
assert(count2 >= 1, `handler2 must fire, got ${count2}`);
customEvent.remove(handler1);
const prevCount1 = count1;
const prevCount2 = count2;
fireAndWait(
app,
eventId,
"multi-concurrent-2",
() => count1 === prevCount1 && count2 > prevCount2,
"concurrent handlers after removing handler1"
);
assert(count1 === prevCount1, "handler1 must not fire after removal");
assert(count2 > prevCount2, "handler2 must still fire after handler1 removal");
customEvent.remove(handler2);
(app as any).unregisterCustomEvent(eventId);
adsk.log(`${TEST_PREFIX}: multiple handlers (concurrent) PASSED`);
}
// ---------------------------------------------------------------------------
// 4b. Distinct handlers can each be attached and notified on one event
// ---------------------------------------------------------------------------
function testMultipleHandlersSequential(): void {
adsk.log(`${TEST_PREFIX}: multiple handlers (sequential)...`);
const app = adsk.core.Application.get();
const eventId = "CustomEventTest_MultiSequential";
const customEvent = (app as any).registerCustomEvent(eventId);
let count1 = 0;
let count2 = 0;
const handler1: any = { notify: () => { count1++; } };
const handler2: any = { notify: () => { count2++; } };
customEvent.add(handler1);
fireAndWait(app, eventId, "multi-sequential-1", () => count1 >= 1, "sequential handler1 notify");
assert(count1 >= 1, `handler1 must fire, got ${count1}`);
customEvent.remove(handler1);
customEvent.add(handler2);
fireAndWait(app, eventId, "multi-sequential-2", () => count2 >= 1, "sequential handler2 notify");
assert(
count2 >= 1,
`handler2 must fire when added after handler1 removal, got ${count2}`
);
customEvent.remove(handler2);
const prevCount1 = count1;
const prevCount2 = count2;
fireAndSettle(app, eventId, "multi-sequential-3");
assert(count1 === prevCount1, "handler1 must not fire after removal");
assert(count2 === prevCount2, "handler2 must not fire after removal");
(app as any).unregisterCustomEvent(eventId);
adsk.log(`${TEST_PREFIX}: multiple handlers (sequential) PASSED`);
}
// ---------------------------------------------------------------------------
// 5. Handler left registered ΓÇö unregisterCustomEvent should clean up
// ---------------------------------------------------------------------------
function testUnregisterCleansUp(): void {
adsk.log(`${TEST_PREFIX}: unregister cleans up...`);
const app = adsk.core.Application.get();
const eventId = "CustomEventTest_Unreg";
const customEvent = (app as any).registerCustomEvent(eventId);
let notifyCount = 0;
const handler: any = { notify: () => { notifyCount++; } };
customEvent.add(handler);
fireAndWait(app, eventId, "unreg", () => notifyCount >= 1, "unregister cleanup precondition");
assert(notifyCount >= 1, "handler must fire before unregister");
(app as any).unregisterCustomEvent(eventId);
adsk.log(`${TEST_PREFIX}: unregister cleans up PASSED`);
}
// ---------------------------------------------------------------------------
// Run all tests
// ---------------------------------------------------------------------------
adsk.log(`${TEST_PREFIX}: starting tests...`);
testBasicLifecycle();
testDuplicateAdd();
testRemoveAfterRemove();
testMultipleHandlersConcurrent();
testMultipleHandlersSequential();
testUnregisterCleansUp();
adsk.log(`${TEST_PREFIX}: all tests PASSED`);
#Author-
#Description-
import adsk.core, adsk.fusion, adsk.cam, traceback
import threading, random, json
app = None
ui = adsk.core.UserInterface.cast(None)
handlers = []
stopFlag = None
myCustomEvent = 'MyCustomEventId'
customEvent = None
# The event handler that responds to the custom event being fired.
class ThreadEventHandler(adsk.core.CustomEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
# Make sure a command isn't running before changes are made.
if ui.activeCommand != 'SelectCommand':
ui.commandDefinitions.itemById('SelectCommand').execute()
# Get the value from the JSON data passed through the event.
eventArgs = json.loads(args.additionalInfo)
newValue = float(eventArgs['Value'])
# Set the parameter value.
design = adsk.fusion.Design.cast(app.activeProduct)
param = design.rootComponent.modelParameters.itemByName('Length')
param.value = newValue
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
adsk.autoTerminate(False)
# The class for the new thread.
class MyThread(threading.Thread):
def __init__(self, event):
threading.Thread.__init__(self)
self.stopped = event
def run(self):
# Every five seconds fire a custom event, passing a random number.
while not self.stopped.wait(2):
args = {'Value': random.randint(1000, 10000)/1000}
app.fireCustomEvent(myCustomEvent, json.dumps(args))
def run(context):
global ui
global app
try:
app = adsk.core.Application.get()
ui = app.userInterface
# Register the custom event and connect the handler.
global customEvent
customEvent = app.registerCustomEvent(myCustomEvent)
onThreadEvent = ThreadEventHandler()
customEvent.add(onThreadEvent)
handlers.append(onThreadEvent)
# Create a new thread for the other processing.
global stopFlag
stopFlag = threading.Event()
myThread = MyThread(stopFlag)
myThread.start()
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
def stop(context):
try:
if handlers.count:
customEvent.remove(handlers[0])
stopFlag.set()
app.unregisterCustomEvent(myCustomEvent)
ui.messageBox('Stop addin')
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))