Problems adding a dropdown menu to my add-in

Problems adding a dropdown menu to my add-in

en9y37
Advocate Advocate
1,044 Views
6 Replies
Message 1 of 7

Problems adding a dropdown menu to my add-in

en9y37
Advocate
Advocate

Hi dear community!

 

I'd like to give my add-ins the ability to interact with the user trough text input boxes, dropdowns menus, etc. I thought these would be much easier but I'm finding this quite tricky and it's seeming to me pretty hard to understand.

 

Here I leave you an example on which I'm working right now. It's a simple dropdown menu in which depending on the selection, several events will be triggered. The point is that nothing at all happens when I run the add-in, the dropdown doesn't appear and the rest of the code is not executed. The code is trimmed to make it easier to understand.

 

Any idea about what I'm doing wrong? Thanks in advance

 

 

import adsk.core, adsk.fusion
import os
from ...lib import fusion360utils as futil
app = adsk.core.Application.get()
ui = app.userInterface


# TODO *** Specify the command identity information. ***
CMD_ID = f'actualizaTodoPostprocesa'
CMD_NAME = 'Actualiza todo y postprocesa' 
CMD_Description = 'Actualiza y postprocessa el diseño y sus subensamblajes con los datos de Google Sheets'

# Specify that the command will be promoted to the panel.
IS_PROMOTED = True

# TODO *** Define the location where the command button will be created. ***
# This is done by specifying the workspace, the tab, and the panel, and the 
# command it will be inserted beside. Not providing the command to position it
# will insert it at the end.
WORKSPACE_ID = 'FusionSolidEnvironment'
PANEL_ID = 'SolidScriptsAddinsPanel'
COMMAND_BESIDE_ID = 'ScriptsManagerCommand'

# Resource location for command icons, here we assume a sub folder in this directory named "resources".
ICON_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources', '')

# Local list of event handlers used to maintain a reference so
# they are not released and garbage collected.
local_handlers = []


# Executed when add-in is run.
def start():
    # Create a command Definition.
    cmd_def = ui.commandDefinitions.addButtonDefinition(CMD_ID, CMD_NAME, CMD_Description, ICON_FOLDER)

    # Define an event handler for the command created event. It will be called when the button is clicked.
    futil.add_handler(cmd_def.commandCreated, command_execute)

    # ******** Add a button into the UI so the user can run the command. ********
    # Get the target workspace the button will be created in.
    workspace = ui.workspaces.itemById(WORKSPACE_ID)

    # Get the panel the button will be created in.
    panel = workspace.toolbarPanels.itemById(PANEL_ID)

    # Create the button command control in the UI after the specified existing command.
    control = panel.controls.addCommand(cmd_def, COMMAND_BESIDE_ID, False)

    # Specify if the command is promoted to the main toolbar. 
    control.isPromoted = IS_PROMOTED


# Executed when add-in is stopped.
def stop():
    # Get the various UI elements for this command
    workspace = ui.workspaces.itemById(WORKSPACE_ID)
    panel = workspace.toolbarPanels.itemById(PANEL_ID)
    command_control = panel.controls.itemById(CMD_ID)
    command_definition = ui.commandDefinitions.itemById(CMD_ID)

    # Delete the button command control
    if command_control:
        command_control.deleteMe()

    # Delete the command definition
    if command_definition:
        command_definition.deleteMe()

# Function that is called when a user clicks the corresponding button in the UI.
# This defines the contents of the command dialog and connects to the command related events.

def command_created(args: adsk.core.CommandCreatedEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Created Event')

    # https://help.autodesk.com/view/fusion360/ENU/?contextId=CommandInputs
    inputs = args.command.commandInputs

    # TODO Define the dialog for your command by adding different inputs to the command.

    listaItems = inputs.addDropDownCommandInput('elegirPost','Postprocesador',0).listItems
    listaItems.add("Mucobal",True)
    listaItems.add("Zoroa",False)

    # TODO Connect to the events that are needed by this command.
    futil.add_handler(args.command.execute, command_execute, local_handlers=local_handlers)
    futil.add_handler(args.command.inputChanged, command_input_changed, local_handlers=local_handlers)
    futil.add_handler(args.command.executePreview, command_preview, local_handlers=local_handlers)
    futil.add_handler(args.command.validateInputs, command_validate_input, local_handlers=local_handlers)
    futil.add_handler(args.command.destroy, command_destroy, local_handlers=local_handlers)
    
# This event handler is called when the user clicks the OK button in the command dialog or 
# is immediately called after the created event not command inputs were created for the dialog.
def command_execute(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Execute Event')

   # Get a reference to your command's inputs.
    inputs = args.command.commandInputs
    postUsuario: adsk.core.DropDownCommandInput = inputs.itemById('elegirPost')
    nombrePost = postUsuario.selectedItem.name
    
    ui.messageBox(nombrePost)
   

# This event handler is called when the command needs to compute a new preview in the graphics window.
def command_preview(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Preview Event')
    inputs = args.command.commandInputs


# This event handler is called when the user changes anything in the command dialog
# allowing you to modify values of other inputs based on that change.
def command_input_changed(args: adsk.core.InputChangedEventArgs):
    changed_input = args.input
    inputs = args.inputs

    # General logging for debug.
    futil.log(f'{CMD_NAME} Input Changed Event fired from a change to {changed_input.id}')


# This event handler is called when the user interacts with any of the inputs in the dialog
# which allows you to verify that all of the inputs are valid and enables the OK button.
def command_validate_input(args: adsk.core.ValidateInputsEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Validate Input Event')

    inputs = args.inputs
    
    True
    # Verify the validity of the input values. This controls if the OK button is enabled or not.
    valueInput = inputs.itemById('value_input')
    if valueInput.value >= 0:
        inputs.areInputsValid = True
    else:
        inputs.areInputsValid = False
        

# This event handler is called when the command terminates.
def command_destroy(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Destroy Event')

    global local_handlers
    local_handlers = []
0 Likes
1,045 Views
6 Replies
Replies (6)
Message 2 of 7

Jorge_Jaramillo
Collaborator
Collaborator

Hi @en9y37 ,

 

I had found some problems in your code:

1) Rename start() by run(context) # this is the entry-fn the API calls to start the addIn

2) Add cmd_def.execute() at the end of run() function  # this is how all commands get to run

3) In the command_validate_input() function the sentence valueInput = inputs.itemById('elegirPost')  gives an error since there is not an command with id 'elegirPost' defined.

 

At least with the first to fixes, your addIn will run, and you can start debbuging it.

 

Hope this help.

 

Regards,

Jorge

 

0 Likes
Message 3 of 7

en9y37
Advocate
Advocate

Hola Jorge @Jorge_Jaramillo, gracias por la respuesta!

 

I've been trying with your ideas but my problem still remains. Anyway, I'm quite stubborn and I've been testing several things. The question is that my issues come from other side, I'll try to explain....

 

In my example I trimmed part of the code to make it more handy, but at same time I hid the real reason of my issues. In my code I call several commands with this function:

 

app.executeTextCommand(u'Commands.Start ContextUpdateAllFromParentCmd')

 

Well, everything works fine as long as the command is a "Fusion native" command, as the listed above. The problem comes when I use this function with my own commands or others from external applications:

 

app.executeTextCommand(u'Commands.Start actualizarGsheet') #My own command
app.executeTextCommand(u'Commands.Start JoinerCadSculptCmd') #Command from JoinerCAD add-in

 

So, everytime one of these lines is executed, the flow of the executions returns to this point:

 

inputs = args.command.commandInputs

 

In the context of the code, when I debug, I see that when the line 30 is executed, it returns to line 8 directly and starts a non-ending loop, and obviously it crashes.

 

def command_execute(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Execute Event')

    # TODO ******************************** Your code here ********************************
    try:
        # Get a reference to your command's inputs.
        inputs = args.command.commandInputs
        postUsuario: adsk.core.DropDownCommandInput = inputs.itemById('elegirPost')
        nombrePost = postUsuario.selectedItem.name
        
        # NIVEL ROOT_______________________________________________________________________________________
        app = adsk.core.Application.get()
        ui = app.userInterface
        product = app.activeProduct
        design = adsk.fusion.Design.cast(product)
        dibujo_WS = ui.workspaces.itemById("FusionSolidEnvironment")
        cam_WS = ui.workspaces.itemById("CAMEnvironment")
                      
        dibujo_WS.activate()
        
        listadoNumero = []   
               
        numeroRoot = design.activeComponent.partNumber
        numero0 = ""
        numeroCNC = numeroRoot + "_"
        listadoNumero.append(numeroCNC)
        
        app.executeTextCommand(u'Commands.Start ContextUpdateAllFromParentCmd')
        app.executeTextCommand(u'Commands.Start actualizarGsheet')

 

I hope this time everyone handles more information to focus on which my problems are.

 

Thanks all!

0 Likes
Message 4 of 7

Jorge_Jaramillo
Collaborator
Collaborator

Hi @en9y37,

 

Could you check in line 3 what do you get in CommandEventArgs.firingEvent.name? like so:

futil.log(f'{CMD_NAME} Command Execute Event {args.firingEvent.name}')

This will help you identify the command that is being fired.

 

By the way, do you have a single command execute handler for all yours commands?
What is the command executed handler defined for "ContextUpdateAllFromParentCmd" and "actualizarGsheet" commands?
I believe this is the cause of the endless-loop you is experiencing.

 

Regards,
Jorge

0 Likes
Message 5 of 7

en9y37
Advocate
Advocate

Hi again @Jorge_Jaramillo.

 

Could you check in line 3 what do you get in CommandEventArgs.firingEvent.name? like so:


I guess you mean this...

 

en9y37_0-1659025006302.png

 

By the way, do you have a single command execute handler for all yours commands?
What is the command executed handler defined for "ContextUpdateAllFromParentCmd" and "actualizarGsheet" commands?


I think I have just one command execute handler and I didnt define anyone for those commands. But I couldn't say much more, this thing of the handlers overpasses me I hardly understand their purpose. Do you mean this?

 



def command_created(args: adsk.core.CommandCreatedEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Created Event')

    # https://help.autodesk.com/view/fusion360/ENU/?contextId=CommandInputs
    inputs = args.command.commandInputs

    # TODO Define the dialog for your command by adding different inputs to the command.

    listaItems = inputs.addDropDownCommandInput('elegirPost','Postprocesador',0).listItems
    listaItems.add("Mucobal",True)
    listaItems.add("Zoroa",False)

    # TODO Connect to the events that are needed by this command.
    futil.add_handler(args.command.execute, command_execute, local_handlers=local_handlers)
    futil.add_handler(args.command.inputChanged, command_input_changed, local_handlers=local_handlers)
    futil.add_handler(args.command.executePreview, command_preview, local_handlers=local_handlers)
    futil.add_handler(args.command.validateInputs, command_validate_input, local_handlers=local_handlers)
    futil.add_handler(args.command.destroy, command_destroy, local_handlers=local_handlers)
 
Anyway it's quite weird that when I execute with  app.executeTextCommand() a native command everything works fine with no needs of the handlers thing, but not when the command called is from an Add-in.
 
Thanks again for yyour time.
 
0 Likes
Message 6 of 7

Jorge_Jaramillo
Collaborator
Collaborator
Hi,
Please share the source code where you define the "actualizarGsheet" command.
And in the debug you made, also display this property: args.command.parentCommandDefinition.id
0 Likes
Message 7 of 7

en9y37
Advocate
Advocate

Hi again @Jorge_Jaramillo 

 

Here I leave you the whole code of the entry.py file from the command which calls the 

app.executeTextCommand(u'Commands.Start actualizarGsheet')
 

 

import adsk.core, adsk.fusion, traceback, time
import os
from ...lib import fusion360utils as futil
app = adsk.core.Application.get()
ui = app.userInterface


# TODO *** Specify the command identity information. ***
CMD_ID = f'actualizaTodoPostprocesa'
CMD_NAME = 'Actualiza todo y postprocesa' 
CMD_Description = 'Actualiza y postprocessa el diseño y sus subensamblajes con los datos de Google Sheets'

# Specify that the command will be promoted to the panel.
IS_PROMOTED = True

# TODO *** Define the location where the command button will be created. ***
# This is done by specifying the workspace, the tab, and the panel, and the 
# command it will be inserted beside. Not providing the command to position it
# will insert it at the end.
WORKSPACE_ID = 'FusionSolidEnvironment'
PANEL_ID = 'SolidScriptsAddinsPanel'
COMMAND_BESIDE_ID = 'ScriptsManagerCommand'

# Resource location for command icons, here we assume a sub folder in this directory named "resources".
ICON_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources', '')

# Local list of event handlers used to maintain a reference so
# they are not released and garbage collected.
local_handlers = []


# Executed when add-in is run.
def start():
    # Create a command Definition.
    cmd_def = ui.commandDefinitions.addButtonDefinition(CMD_ID, CMD_NAME, CMD_Description, ICON_FOLDER)

    # Define an event handler for the command created event. It will be called when the button is clicked.
    futil.add_handler(cmd_def.commandCreated, command_created)

    # ******** Add a button into the UI so the user can run the command. ********
    # Get the target workspace the button will be created in.
    workspace = ui.workspaces.itemById(WORKSPACE_ID)

    # Get the panel the button will be created in.
    panel = workspace.toolbarPanels.itemById(PANEL_ID)

    # Create the button command control in the UI after the specified existing command.
    control = panel.controls.addCommand(cmd_def, COMMAND_BESIDE_ID, False)

    # Specify if the command is promoted to the main toolbar. 
    control.isPromoted = IS_PROMOTED


# Executed when add-in is stopped.
def stop():
    # Get the various UI elements for this command
    workspace = ui.workspaces.itemById(WORKSPACE_ID)
    panel = workspace.toolbarPanels.itemById(PANEL_ID)
    command_control = panel.controls.itemById(CMD_ID)
    command_definition = ui.commandDefinitions.itemById(CMD_ID)

    # Delete the button command control
    if command_control:
        command_control.deleteMe()

    # Delete the command definition
    if command_definition:
        command_definition.deleteMe()

# Function that is called when a user clicks the corresponding button in the UI.
# This defines the contents of the command dialog and connects to the command related events.

def command_created(args: adsk.core.CommandCreatedEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Created Event')

    # https://help.autodesk.com/view/fusion360/ENU/?contextId=CommandInputs
    inputs = args.command.commandInputs

    # TODO Define the dialog for your command by adding different inputs to the command.

    listaItems = inputs.addDropDownCommandInput('elegirPost','Postprocesador',0).listItems
    listaItems.add("Mucobal",True)
    listaItems.add("Zoroa",False)

    # TODO Connect to the events that are needed by this command.
    futil.add_handler(args.command.execute, command_execute, local_handlers=local_handlers)
    futil.add_handler(args.command.inputChanged, command_input_changed, local_handlers=local_handlers)
    futil.add_handler(args.command.executePreview, command_preview, local_handlers=local_handlers)
    futil.add_handler(args.command.validateInputs, command_validate_input, local_handlers=local_handlers)
    futil.add_handler(args.command.destroy, command_destroy, local_handlers=local_handlers)

# This event handler is called when the user clicks the OK button in the command dialog or 
# is immediately called after the created event not command inputs were created for the dialog.
def command_execute(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Execute Event {args.firingEvent.name}')

    # TODO ******************************** Your code here ********************************
    try:
        # Get a reference to your command's inputs.
        inputs = args.command.commandInputs
        postUsuario: adsk.core.DropDownCommandInput = inputs.itemById('elegirPost')
        nombrePost = postUsuario.selectedItem.name
        
        app = adsk.core.Application.get()
        ui = app.userInterface
         
        app.executeTextCommand(u'Commands.Start ContextUpdateAllFromParentCmd')
        app.executeTextCommand(u'Commands.Start actualizarGsheet')
        ui.messageBox("Si se muestra esto, no ha entrado en bucle")

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

# This event handler is called when the command needs to compute a new preview in the graphics window.
def command_preview(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Preview Event')
    inputs = args.command.commandInputs


# This event handler is called when the user changes anything in the command dialog
# allowing you to modify values of other inputs based on that change.
def command_input_changed(args: adsk.core.InputChangedEventArgs):
    changed_input = args.input
    inputs = args.inputs

    # General logging for debug.
    futil.log(f'{CMD_NAME} Input Changed Event fired from a change to {changed_input.id}')


# This event handler is called when the user interacts with any of the inputs in the dialog
# which allows you to verify that all of the inputs are valid and enables the OK button.
def command_validate_input(args: adsk.core.ValidateInputsEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Validate Input Event')

    inputs = args.inputs
    
    # Verify the validity of the input values. This controls if the OK button is enabled or not.
    valueInput = inputs.itemById('value_input')
    if valueInput.value >= 0:
        inputs.areInputsValid = True
    else:
        inputs.areInputsValid = False
        

# This event handler is called when the command terminates.
def command_destroy(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Destroy Event')

    global local_handlers
    local_handlers = []

 

 

And the whole code of the entry.py from actualizarGsheet:

 

 

import adsk.core, adsk.fusion, traceback
import os
from ...lib import fusion360utils as futil


app = adsk.core.Application.get()
ui = app.userInterface


# TODO *** Specify the command identity information. ***
CMD_ID = f'actualizarGsheet'
CMD_NAME = 'Actualizar de Google Sheets'
CMD_Description = 'Actualiza el estado del diseño con los datos de Google Sheets, solo en el diseño actual'

# Specify that the command will be promoted to the panel.
IS_PROMOTED = False

# TODO *** Define the location where the command button will be created. ***
# This is done by specifying the workspace, the tab, and the panel, and the 
# command it will be inserted beside. Not providing the command to position it
# will insert it at the end.
WORKSPACE_ID = 'FusionSolidEnvironment'
PANEL_ID = 'SolidScriptsAddinsPanel'
COMMAND_BESIDE_ID = 'ScriptsManagerCommand'

# Resource location for command icons, here we assume a sub folder in this directory named "resources".
ICON_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources', '')

# Local list of event handlers used to maintain a reference so
# they are not released and garbage collected.
local_handlers = []


# Executed when add-in is run.
def start():
    # Create a command Definition.
    cmd_def = ui.commandDefinitions.addButtonDefinition(CMD_ID, CMD_NAME, CMD_Description, ICON_FOLDER)

    # Define an event handler for the command created event. It will be called when the button is clicked.
    futil.add_handler(cmd_def.commandCreated, command_created) #command_execute ejecuta la secuencia sin pasar por dialogo previo

    # ******** Add a button into the UI so the user can run the command. ********
    # Get the target workspace the button will be created in.
    workspace = ui.workspaces.itemById(WORKSPACE_ID)

    # Get the panel the button will be created in.
    panel = workspace.toolbarPanels.itemById(PANEL_ID)

    # Create the button command control in the UI after the specified existing command.
    control = panel.controls.addCommand(cmd_def, COMMAND_BESIDE_ID, False)

    # Specify if the command is promoted to the main toolbar. 
    control.isPromoted = IS_PROMOTED


# Executed when add-in is stopped.
def stop():
    # Get the various UI elements for this command
    workspace = ui.workspaces.itemById(WORKSPACE_ID)
    panel = workspace.toolbarPanels.itemById(PANEL_ID)
    command_control = panel.controls.itemById(CMD_ID)
    command_definition = ui.commandDefinitions.itemById(CMD_ID)

    # Delete the button command control
    if command_control:
        command_control.deleteMe()

    # Delete the command definition
    if command_definition:
        command_definition.deleteMe()

# Function that is called when a user clicks the corresponding button in the UI.
# This defines the contents of the command dialog and connects to the command related events.

def command_created(args: adsk.core.CommandCreatedEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Created Event')

    # https://help.autodesk.com/view/fusion360/ENU/?contextId=CommandInputs
    inputs = args.command.commandInputs

    # TODO Define the dialog for your command by adding different inputs to the command.

    # TODO Connect to the events that are needed by this command.
    futil.add_handler(args.command.execute, command_execute, local_handlers=local_handlers)
    futil.add_handler(args.command.inputChanged, command_input_changed, local_handlers=local_handlers)
    futil.add_handler(args.command.executePreview, command_preview, local_handlers=local_handlers)
    futil.add_handler(args.command.validateInputs, command_validate_input, local_handlers=local_handlers)
    futil.add_handler(args.command.destroy, command_destroy, local_handlers=local_handlers)

# This event handler is called when the user clicks the OK button in the command dialog or 
# is immediately called after the created event not command inputs were created for the dialog.
def command_execute(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Execute Event')

    # TODO ******************************** Your code here ********************************
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        ui.messageBox("Se ha ejecutado actualizar Gsheet")
   
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
       


# This event handler is called when the command needs to compute a new preview in the graphics window.
def command_preview(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Preview Event')
    inputs = args.command.commandInputs


# This event handler is called when the user changes anything in the command dialog
# allowing you to modify values of other inputs based on that change.
def command_input_changed(args: adsk.core.InputChangedEventArgs):
    changed_input = args.input
    inputs = args.inputs

    # General logging for debug.
    futil.log(f'{CMD_NAME} Input Changed Event fired from a change to {changed_input.id}')


# This event handler is called when the user interacts with any of the inputs in the dialog
# which allows you to verify that all of the inputs are valid and enables the OK button.
def command_validate_input(args: adsk.core.ValidateInputsEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Validate Input Event')

    inputs = args.inputs
    
    # Verify the validity of the input values. This controls if the OK button is enabled or not.
    valueInput = inputs.itemById('value_input')
    if valueInput.value >= 0:
        inputs.areInputsValid = True
    else:
        inputs.areInputsValid = False
        

# This event handler is called when the command terminates.
def command_destroy(args: adsk.core.CommandEventArgs):
    # General logging for debug.
    futil.log(f'{CMD_NAME} Command Destroy Event')

    global local_handlers
    local_handlers = []

 

Both codes are reduced to its minimum and they have enough information to replicate the error.

 

This property: args.command.parentCommandDefinition.id = actualizaTodoPostprocesa, it remains this value along the whole execution.

 

Other thing I've noticed... when I started to develop my add-ins, since I never used dialogs, instead of removing the dialog samples from the template, I made this change in line 40 in any of my codes:

 

 

futil.add_handler(cmd_def.commandCreated, command_created)
#Changed command_created to command_execute
futil.add_handler(cmd_def.commandCreated, command_execute)

 

 

I don't know why, but the point is that my codes, (which include tons of "app.executeTextCommand()")  work properly with this simple change, as long I don't need use any dialog, because this way they are passed trough. If I leave the template as it is, the endlees loop comes again.

 

I think the problem is quite isolated, something related using app.executeTextCommand() to external commands, but I can't figure out which te solution should be.

 

Muchas gracias por tu tiempo Jorge.

0 Likes