Replicating the "Joint Origin Tool" selection behaviour

Replicating the "Joint Origin Tool" selection behaviour

Jekannadar
Explorer Explorer
675 Views
6 Replies
Message 1 of 7

Replicating the "Joint Origin Tool" selection behaviour

Jekannadar
Explorer
Explorer

Hi! I'm trying to make the selectionInput display "snapping points", in the same fashion as creating a Joint Origin or Joint works in the regular Fusion360 user interface. 

 

The use case is interactively creating Joint Origins that follow special naming conventions, so quintessentially I would like to closely replicate the Joint Origin tool. 

 

I am unsure how to go about this. Is this something that has to be done in a preSelectMouseMove hook on a selectionInput? Computing the necessary snap points should be fairly straightforward, but I am a bit stumped on how one would display them and make them actually selectable, to e.g. then use the JointOrigin API to create a suitable Joint Origin. 

If something like "embedding" or "inheriting" from the regular Joint Origin tool is possible, that would be the absolute best case, but I could find no info on that. 

 

Thanks in advance for any help! 

0 Likes
676 Views
6 Replies
Replies (6)
Message 2 of 7

BrianEkins
Mentor
Mentor

It's not possible to re-use the interface from the Joint or Joint Origin command. That functionality is built-in to those commands. You should be able to display glyphs using custom graphics, which can be selectable. Theoretically, I believe it should all work but I haven't attempted anything quite like that before.

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

Jekannadar
Explorer
Explorer

Thank you for your answer! 

 

It is good to have someone confirm that no amount of hacky python code will let me mess with the inbuilt commands.

By now I have just opted to make the Add-Ins workflow a tad more tedious by making creating the joint origins a prerequisite.  Howver, in case it is relevant to anyone else in the future, some general findings: 

 

Replicating the snapping behaviour of the Joint and JointOrigin commands does not seem to be possible, even when using CustomGraphics. Apart from the large amount of effort needed to replicate the orientation being dependent on the relative mouse position as well as the actual snapping, it also isn't possible to select CustomGraphics created during the selection process. 

 

Creating the CustomGraphics during a PreSelect hook works just fine, and the user interface also shows them as aligible for selection. However on clicking them, the CustomGraphics get removed and no selection is made. This seems to be inline with all CustomGraphics created during selection also being removed upon canceling/ending the selection process in general. 

 

This seems to be intentional, as in contrast, CustomGraphics that have previously been created in an "execute" Method can be properly selected, so the issue does seem to be them being created during the preSelect. Probably this is some intentional lifecycle thing, I found a post where someone clarified they are also only intended for display purposes. 

 

It would be nice though if someone could confirm that CustomGraphics created during any of the Selection hooks can not be selected themselves. 

 

Here is a MWE adapted from the Add-In default template that illustrates this, just in case it isn't intentional/someone wants to tinker with it.

 

import adsk.core
import os
from ...lib import fusion360utils as futil
from ... import config

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

CMD_ID = f'{config.COMPANY_NAME}_{config.ADDIN_NAME}_type_joint'
CMD_NAME = 'Snap Point Example'
CMD_Description = 'Illustrate custom graphics behaviour.'
IS_PROMOTED = True

WORKSPACE_ID = 'FusionSolidEnvironment'
PANEL_ID = 'TYPES'
COMMAND_BESIDE_ID = 'ScriptsManagerCommand'

# Resource location
ICON_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources', '')

local_handlers = []

def start():
    # Command Definition.
    cmd_def = ui.commandDefinitions.addButtonDefinition(CMD_ID, CMD_NAME, CMD_Description, ICON_FOLDER)
    futil.add_handler(cmd_def.commandCreated, command_created)

    # Register
    workspace = ui.workspaces.itemById(WORKSPACE_ID)
    panel = workspace.toolbarPanels.itemById(PANEL_ID)

    control2 = panel.controls.addCommand(ui.commandDefinitions.itemById("JointOrigin"))
    control2.isPromoted = True

    control = panel.controls.addCommand(cmd_def, COMMAND_BESIDE_ID, False)
    control.isPromoted = IS_PROMOTED

    


# Executed when add-in is stopped.
def stop():
    workspace = ui.workspaces.itemById(WORKSPACE_ID)
    panel = workspace.toolbarPanels.itemById(PANEL_ID)
    command_definition = ui.commandDefinitions.itemById(CMD_ID)

    for i in range(panel.controls.count):
        if panel.controls.item(0):
            panel.controls.item(0).deleteMe()

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

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

    # Handlers
    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.destroy, command_destroy, local_handlers=local_handlers)

    #futil.add_handler(args.command.preSelectMouseMove, command_preSelectMouseMove, local_handlers=local_handlers)
    futil.add_handler(args.command.preSelect, command_preSelect, local_handlers=local_handlers)
    futil.add_handler(args.command.preSelectEnd, command_preSelectEnd, local_handlers=local_handlers)
    futil.add_handler(args.command.executePreview, command_executePreview, local_handlers=local_handlers)
    futil.add_handler(args.command.select, command_select, local_handlers=local_handlers)

    inputs = args.command.commandInputs

    # UI DEF
    selectionInput = inputs.addSelectionInput('selection', 'Select', 'Select')
    selectionInput.setSelectionLimits(0)

def command_executePreview(args: adsk.core.CommandEventHandler):
    app = adsk.core.Application.get()

def command_select(args: adsk.core.SelectionEventArgs):
    app = adsk.core.Application.get()
    

def command_preSelectMouseMove(args: adsk.core.SelectionEventArgs):
    app = adsk.core.Application.get() 

def command_preSelect(args: adsk.core.SelectionEventArgs):
    selectedFace = adsk.fusion.BRepFace.cast(args.selection.entity)
    design = adsk.fusion.Design.cast(app.activeProduct)
    graphics = design.rootComponent.customGraphicsGroups.add()
    if selectedFace:
        coord_array = [selectedFace.centroid.x,selectedFace.centroid.y,selectedFace.centroid.z]
        coords = adsk.fusion.CustomGraphicsCoordinates.create(coord_array)
        points = graphics.addPointSet(coords, [0], 
                   adsk.fusion.CustomGraphicsPointTypes.UserDefinedCustomGraphicsPointType,
                   ICON_FOLDER + '/64x64.png')
        tmatrix = adsk.core.Matrix3D.create()
    #if args.selection.entity.classType() != adsk.fusion.CustomGraphicsPointSet.classType() and args.selection.entity.classType() != adsk.fusion.BRepFace.classType():
    # Only allow Faces or CustomGraphics
    #        args.isSelectable = False

def command_preSelectEnd(args: adsk.core.SelectionEventArgs):        
    app = adsk.core.Application.get()



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

def command_preview(args: adsk.core.CommandEventArgs):
    inputs = args.command.commandInputs
    futil.log(f'{CMD_NAME} Command Preview Event')


def command_input_changed(args: adsk.core.InputChangedEventArgs):
    changed_input = args.input
    inputs = args.inputs
    futil.log(f'{CMD_NAME} Input Changed Event fired from a change to {changed_input.id}')


def command_destroy(args: adsk.core.CommandEventArgs):
    global local_handlers
    local_handlers = []
    futil.log(f'{CMD_NAME} Command Destroy Event')

 

0 Likes
Message 4 of 7

BrianEkins
Mentor
Mentor

I think what you're running into is the expected behavior of a command. Anything you create in reaction to the command events is aborted every time any of the command inputs are modified. The exception to this is the execute event where everything is saved. This behavior applies to everything, not just custom graphics. The big difference between custom graphics and persistent entities like sketches and features, is that custom graphics are not written into the document, so if you draw custom graphics, save the document, close it, and open it, the custom graphics will be gone.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
0 Likes
Message 5 of 7

Jekannadar
Explorer
Explorer

That seems to be the case. After some more days of tinkering, I conclude there is no way to replicate the snapping behaviour of the Joint and Joint Origin tools in a satisfactory way. Closely observing how they work, they also don't actually select a snapping point, but select actual geometry and remember the selected nsapping option (e.g. start, end or center of line, etc.), and the rest  ist graphical magic that makes it look nice. 

 

The reason I think it can't be replicated properly is because preSelectMouseMove doesn't seem to be working as expected. The event does not continously fire during moving the mouse over a selectable entity. The following snippet only results in "Fired" being printed when the mouse moves to a different face, when one would expect it to continously fire as per Documentation. Additionally, the point on the selection obtainable from "args.selection.point" only updates on actual mouse clicks. I think preSelectMouseMove as a name implies that it should probably be the point that the mouse is currently hovering over. 

 

futil.add_handler(args.command.preSelectMouseMove, command_preSelectMouseMove, local_handlers=local_handlers)

def command_preSelectMouseMove(args: adsk.core.SelectionEventArgs):
    print("Fired")

 

 

As a result,  the behaviour expected of preselectMouseMove needs to be replicated with the regular MouseMove. This necessitates calling args.viewport.refresh(), which leads to a very choppy display of the moving indicator that can be seen in the normal JointOrigin command, as far as my testing goes. 

As far as I can tell, preSelect and preSelectMouseMove act exactly the same. I think this might just be an actual bug, but I might also have misunderstood something. 

0 Likes
Message 6 of 7

BrianEkins
Mentor
Mentor

I had forgotten about a bug in the preSelectMouseMove event. You should test this again in the update coming out later this month. I believe it is supposed to be fixed.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
0 Likes
Message 7 of 7

Jekannadar
Explorer
Explorer

Thanks for the info! Guess I'll get back to it when the update comes out then.

0 Likes