Custom Event Handler and Threading
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
I've been playing around with the Fusion360 API a bit and now have an actual reason to use it. I've built a delta robot, but it's not of the standard symmetrical kinematic and as such it's really nasty math to solve the inverse and forward kinematics. I would like to drive the joints and use Fusion to solve the forward kinematics. I can then iteratively solve for the inverse kinematics. If you're unfamiliar with what a delta robot is, just google delta robot.
So here's where I am. I am able to drive the joints in my model using the API. There is a caveat here in that when I drive one joint, I have to lock the others because otherwise driving one makes the others move a bit. I can also measure the location of my end effector. In embedded systems programming, I can make the LED blink so I'm 90% of the way there, but I'm afraid 90% of the work remains and I need some guidance.
What I would like to do is in a triple nested for loop move each joint, then measure where the end effector is thereby mapping out the forward kinematics (from angles of the shoulder joints -> position of end effector). The end goal here is to optimize the lengths of the upper and lower arms of my delta model to optimize for the work volume I need.
When I've tried this directly in the main thread so to speak... or at least I think that's what I've tried it locks up fusion360 and everything crashes. I assume Fusion360 doesn't like having someone take over 100% of the processing time from it's various tasks. So now I'm trying to do this with my own worker thread that every so often reports back to the main thread a set of three angles to put the joints at.
Using this script, which I've mostly copied from various other people I'm able to move one of the shoulders.
import adsk.core, adsk.fusion, traceback, math
pi = math.pi
# global set of event handlers to keep them referenced for the duration of the command
handlers = []
app = adsk.core.Application.get()
ui = None
if app:
ui = app.userInterface
sliderInput = None
class MyTestCommandExecuteHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
root = app.activeProduct.rootComponent
joint0 = root.joints.itemByName('Shoulder0')
joint1 = root.joints.itemByName('Shoulder1')
joint2 = root.joints.itemByName('Shoulder2')
if not (joint1.isLocked):
joint1._set_isLocked(True)
if not (joint2.isLocked):
joint2._set_isLocked(True)
revoluteMotion = adsk.fusion.RevoluteJointMotion.cast(joint0.jointMotion)
revoluteMotion.rotationValue = pi * 2 * sliderInput.valueOne / 180.0
args.isValidResult = True
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
class MyTestCommandDestroyHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
# when the command is done, terminate the script
# this will release all globals which will remove all event handlers
adsk.terminate()
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
class MyTestCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
cmd = args.command
cmd.isRepeatable = False
onExecute = MyTestCommandExecuteHandler()
cmd.execute.add(onExecute)
onExecutePreview = MyTestCommandExecuteHandler()
cmd.executePreview.add(onExecutePreview)
onDestroy = MyTestCommandDestroyHandler()
cmd.destroy.add(onDestroy)
# keep the handler referenced beyond this function
handlers.append(onExecute)
handlers.append(onExecutePreview)
handlers.append(onDestroy)
#define the inputs
inputs = cmd.commandInputs
global sliderInput
sliderInput = inputs.addIntegerSliderCommandInput('sliderCommandInput', 'Slider', -20, 20, False)
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
def run(context):
try:
product = app.activeProduct
design = adsk.fusion.Design.cast(product)
if not design:
ui.messageBox('It is not supported in current workspace, please change to MODEL workspace and try again.')
return
commandDefinitions = ui.commandDefinitions
#check the command exists or not
cmdDef = commandDefinitions.itemById('MyTest')
if not cmdDef:
cmdDef = commandDefinitions.addButtonDefinition('MyTest','My Test','My Test','') # relative resource file path is specified
onCommandCreated = MyTestCommandCreatedHandler()
cmdDef.commandCreated.add(onCommandCreated)
# keep the handler referenced beyond this function
handlers.append(onCommandCreated)
inputs = adsk.core.NamedValues.create()
cmdDef.execute(inputs)
# prevent this module from being terminate when the script returns, because we are waiting for event handlers to fire
adsk.autoTerminate(False)
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
Now, instead of using sliders to move the joints I'd like to drive them from a for loop as mentioned above. It seems like the way to do this is with a custom thread. Here's what I've got. It's not working all as it doesn't seem to even be hitting my ThreadEventHandler where presumably I'm going to move the joints.
import adsk.core, adsk.fusion, traceback, math import threading, random, json pi = math.pi # global set of event handlers to keep them referenced for the duration of the command app = None ui = adsk.core.UserInterface.cast(None) handlers = [] stopFlag = None myCustomEvent = 'MyCustomEventId' customEvent = None class MyThread(threading.Thread): def __init__(self, event): threading.Thread.__init__(self) self.stopped = event def run(self): theta = -20 while not self.stopped.wait(0.5): args = {'Angle':theta} app.fireCustomEvent(myCustomEvent, json.dumps(args)) theta = theta+1 if theta > 19: theta = -20 class ThreadEventHandler(adsk.core.CustomEventHandler): def __init__(self): super().__init__() def notify(self, args): try: 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) theta = float(eventArgs['Angle']) #use the value to set a specific parameter root = app.activeProduct.rootComponent joint0 = root.joints.itemByName('Shoulder0') joint1 = root.joints.itemByName('Shoulder1') joint2 = root.joints.itemByName('Shoulder2') if not (joint1.isLocked): joint1._set_isLocked(True) if not (joint2.isLocked): joint2._set_isLocked(True) revoluteMotion = adsk.fusion.RevoluteJointMotion.cast(joint0.jointMotion) revoluteMotion.rotationValue = pi * 2 * theta / 180.0 args.isValidResult = True except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) 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()))
Any help would be greatly appreciated. In fact, I'd be willing to pay for about an hour of someone's time where we could do a google hangout or something if they're a pro and can get me over some of the conceptual humps here.
Thanks
-Marc
PS: random idea for the Fusion360 team. Make some API experts on your end available for hire to help people get started. The tutorials are good, but they're not quite enough... for an idiot like me.