Community
Fusion API and Scripts
Got a new add-in to share? Need something specialized to be scripted? Ask questions or share what you’ve discovered with the community.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Focus CommandInput

3 REPLIES 3
Reply
Message 1 of 4
thomasa88
328 Views, 3 Replies

Focus CommandInput

I have created a Command with a StringValueCommandInput. To make it easy for the user, I want this input to be focused when the command opens. Is this possible? I'm using the Python API.

 

Currently, the user has to press TAB to get to the command input. And sometimes that doesn't work either (the Command is not focused?)

3 REPLIES 3
Message 2 of 4
nnikbin
in reply to: thomasa88

Hi @thomasa88 

 

I wish every software developer care about user experience as much as you do.

 

I guess there is no high-level solution, but you can use solutions like low-level firing of keyboard events.

 

I used a platform-dependent solution for Windows from here and it worked. At the end of CommandCreatedEventHandler I called PressKey(VK_TAB) to fire the TAB keydown event. You can use platform-independent solutions like pyautogui.

 

Here is the sample script:

 

import adsk.core, adsk.fusion, traceback

import ctypes
from ctypes import wintypes
import time

user32 = ctypes.WinDLL('user32', use_last_error=True)

INPUT_KEYBOARD = 1

KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP       = 0x0002
KEYEVENTF_UNICODE     = 0x0004
KEYEVENTF_SCANCODE    = 0x0008

MAPVK_VK_TO_VSC = 0

# msdn.microsoft.com/en-us/library/dd375731
VK_TAB  = 0x09
VK_MENU = 0x12

# C struct definitions
wintypes.ULONG_PTR = wintypes.WPARAM

class MOUSEINPUT(ctypes.Structure):
    _fields_ = (("dx",          wintypes.LONG),
                ("dy",          wintypes.LONG),
                ("mouseData",   wintypes.DWORD),
                ("dwFlags",     wintypes.DWORD),
                ("time",        wintypes.DWORD),
                ("dwExtraInfo", wintypes.ULONG_PTR))

class KEYBDINPUT(ctypes.Structure):
    _fields_ = (("wVk",         wintypes.WORD),
                ("wScan",       wintypes.WORD),
                ("dwFlags",     wintypes.DWORD),
                ("time",        wintypes.DWORD),
                ("dwExtraInfo", wintypes.ULONG_PTR))

    def __init__(self, *args, **kwds):
        super(KEYBDINPUT, self).__init__(*args, **kwds)
        # some programs use the scan code even if KEYEVENTF_SCANCODE
        # isn't set in dwFflags, so attempt to map the correct code.
        if not self.dwFlags & KEYEVENTF_UNICODE:
            self.wScan = user32.MapVirtualKeyExW(self.wVk,
                                                 MAPVK_VK_TO_VSC, 0)

class HARDWAREINPUT(ctypes.Structure):
    _fields_ = (("uMsg",    wintypes.DWORD),
                ("wParamL", wintypes.WORD),
                ("wParamH", wintypes.WORD))

class INPUT(ctypes.Structure):
    class _INPUT(ctypes.Union):
        _fields_ = (("ki", KEYBDINPUT),
                    ("mi", MOUSEINPUT),
                    ("hi", HARDWAREINPUT))
    _anonymous_ = ("_input",)
    _fields_ = (("type",   wintypes.DWORD),
                ("_input", _INPUT))

def PressKey(hexKeyCode):
    x = INPUT(type=INPUT_KEYBOARD,
              ki=KEYBDINPUT(wVk=hexKeyCode))
    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

_app = None
_ui  = None
_done = False
_Executed = True

_handlers = []

class MyCommandExecuteHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        global _done
        global _Executed
        try:
            product = _app.activeProduct
            design = adsk.fusion.Design.cast(_app.activeProduct)
            occurrences = design.rootComponent.occurrences
            _done = True
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
            
            
class MyCommandDestroyHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            #testDocuments()
            adsk.terminate()
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            cmd = adsk.core.Command.cast(args.command)

            onExecute = MyCommandExecuteHandler()
            cmd.execute.add(onExecute)
            _handlers.append(onExecute)
            
            onDestroy = MyCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            _handlers.append(onDestroy)
            
            onCommandTerminated = MyCommandTerminatedHandler()
            _ui.commandTerminated.add(onCommandTerminated)
            _handlers.append(onCommandTerminated)

            inputs = cmd.commandInputs
            
            stringInput = inputs.addStringValueInput('stringInput', "String", "")

            PressKey(VK_TAB)

        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

class MyCommandTerminatedHandler(adsk.core.ApplicationCommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        global _done
        global _Executed
        try:
            #_done = _done + 1
            #if _done == 3:
            if _done == True and args.terminationReason == adsk.core.CommandTerminationReason.CancelledTerminationReason:
                _done = False
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
        

def run(context):
    try:
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface

        cmdDef = _ui.commandDefinitions.itemById('setFocus')
        if not cmdDef:
            cmdDef = _ui.commandDefinitions.addButtonDefinition('setFocus', 'Set Focus', 'Set Focus')

        onCommandCreated = MyCommandCreatedHandler()
        cmdDef.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)

        cmdDef.execute()

        adsk.autoTerminate(False)
    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

 

 

Message 3 of 4
thomasa88
in reply to: nnikbin

Hey, that is quite clever!

Although, I would prefer not having to do it that way. I think I skip it for this add-in.

Since the command dialog does not always seem to get focus, in my experience, maybe a system call to activate the correct window (or click on it) would make it more waterproof.

pyautogui is probably the cross-platform way to go, as you say. I found that NoGesturesNoShift uses Autohotkey integrated in Python.

 

I got annoyed at not being able to Tab between the "Select" inputs, so I started writing an Autohotkey script that finds the current command window and clicks on the next control. It worked for basic things, like the two selects in a Revolve. However, since it is clicking, it clicks on e.g. check boxes and folding option groups makes it get out of alignment (it assumes a distance of 30px between each control.)

I'm attaching my script, for the curios. It uses F1 and Shift+F1 to tab up and down in a control. The code is not cleaned up, as I gave up.

 

I think any solution clicking in Fusion 360 should actually make use of inspection support in Qt. Then we could hopefully find and select the precise elements.

Message 4 of 4
BrianEkins
in reply to: thomasa88

This would be a good feature to add to the API.  The SelectionCommandInput object supports the hasFocus property to set which selection input is current active.  We need the same property on some of the other input types.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report