ValidateInputs broken with multiple SelectionInputs

ValidateInputs broken with multiple SelectionInputs

rory2WEHA
Explorer Explorer
1,128 Views
7 Replies
Message 1 of 8

ValidateInputs broken with multiple SelectionInputs

rory2WEHA
Explorer
Explorer

I've been trying to create a script that requires several SelectionInputs, and want to ensure that the OK button is greyed out until all of these inputs have a valid selection. I've been playing with the built in "Intersections.py" script which manages this with the following code inside the validateInputs class:

 

class IntersectionValidateInputHandler(adsk.core.ValidateInputsEventHandler):
    def __init__(self):
        super().__init__()
       
    def notify(self, args):
        try:
            sels = ui.activeSelections;
            if len(sels) == 2:
                args.areInputsValid = True
            else:
                args.areInputsValid = False
        except:
            if ui:
                ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

class IntersectionCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            cmd = args.command
            onExecute = IntersectionCommandExecuteHandler()
            cmd.execute.add(onExecute)
            onDestroy = IntersectionCommandDestroyHandler()
            cmd.destroy.add(onDestroy)

            onValidateInput = IntersectionValidateInputHandler()
            cmd.validateInputs.add(onValidateInput)
            # keep the handler referenced beyond this function
            handlers.append(onExecute)
            handlers.append(onDestroy)
            handlers.append(onValidateInput)
            #define the inputs
            inputs = cmd.commandInputs
            i1 = inputs.addSelectionInput('entity', 'Entity One', 'Please select a curve, planear entity or a BRepBody, Component, Occurrence')

            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Edges);
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.PlanarFaces);
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.SketchCurves);
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionLines);
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionPlanes);
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Bodies);
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Occurrences);
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.RootComponents);

            i2 = inputs.addSelectionInput('sectionentity', 'Entity Two', 'Please select a linear or planear entity')

            i2.addSelectionFilter(adsk.core.SelectionCommandInput.PlanarFaces);
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.LinearEdges);
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.SketchLines);
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionLines);
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionPlanes);

            #adding this line seems to break the validateInputs functionality
            inputs.addTextBoxCommandInput('readonly_textBox1', 'text', 'This is where the text will be', 2, True) 


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

 

This was working as intended, however after adding a tertiary (text box) input (present in code snippet above), ValidateInputs was not being called until an entry into the text box was detected. This meant that I could click OK with only one SelectionInput entered, rather than both.

 

Interestingly - after I reverted the code back to what it was originally (stock Intersections.py script as it came with the Fusion 360 install), the issue persisted. Restarting Fusion360 has not fixed it.

 

Can anyone explain what is happening?

0 Likes
Accepted solutions (2)
1,129 Views
7 Replies
Replies (7)
Message 2 of 8

kandennti
Mentor
Mentor
0 Likes
Message 3 of 8

rory2WEHA
Explorer
Explorer

@kandennti thanks for the response - I actually came across your original query in trying to solve this one, but it doesn't solve the problem for me.

 

The problems with this method are:

  • Without selection limits defined per selectionInput, a user couple make 2+ selections into one input and 0 in the other (which is an invalid entry) but the script will let the user click OK.
  • This doesn't resolve the underlying issue of why the script was working and now isn't, despite the code being the same as it was originally.

Furthermore, in my own code defining setSelectionLimits(0) doesn't work at all, possibly because each seletionInput is within its own group, but I'm not sure.

0 Likes
Message 4 of 8

kandennti
Mentor
Mentor
Accepted solution

@rory2WEHA .

 

When multiple SelectionCommandInputs are placed, the process becomes cumbersome.
I don't think there is an explanation of this area in the online documentation.

 

1) SetSelectionLimits(0).
2) Use the preSelect event to determine the selection.
3) Control the OK button with the validateInputs event.

If you do not use this combination, it will not perform as you would like.

 

I personally feel that the original ValidateInputsEventHandler is not a very good way to handle this, so I am rewriting it.

・・・
class IntersectionValidateInputHandler(adsk.core.ValidateInputsEventHandler):
    def __init__(self):
        super().__init__()
       
    def notify(self, args):
        adsk.core.Application.get().log(args.firingEvent.name)
        try:
            adsk.core.Application.get().log(args.firingEvent.name)

            # command inputs
            inputs: adsk.core.CommandInputs = args.inputs

            # Make sure that the respective SelectionCommandInput is selected.
            global _selIpt1Id, _selIpt2Id
            for id in [_selIpt1Id, _selIpt2Id]:
                selIpt: adsk.core.SelectionCommandInput = inputs.itemById(id)
                if not selIpt:
                    continue
                if selIpt.selectionCount != 1:
                    args.areInputsValid = False
                    return

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

class IntersectionCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            cmd = args.command
            onExecute = IntersectionCommandExecuteHandler()
            cmd.execute.add(onExecute)
            handlers.append(onExecute)

            onDestroy = IntersectionCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            handlers.append(onDestroy)

            onValidateInput = IntersectionValidateInputHandler()
            cmd.validateInputs.add(onValidateInput)
            handlers.append(onValidateInput)

            # Added
            onPreSelect = MyPreSelectHandler()
            cmd.preSelect.add(onPreSelect)
            handlers.append(onPreSelect)

            #define the inputs
            inputs = cmd.commandInputs

            global _selIpt1Id
            i1: adsk.core.SelectionCommandInput = inputs.addSelectionInput(
                _selIpt1Id,
                'Entity One',
                'Please select a curve, planear entity or a BRepBody, Component, Occurrence'
            )

            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Edges)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.PlanarFaces)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.SketchCurves)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionLines)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionPlanes)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Bodies)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Occurrences)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.RootComponents)

            i1.setSelectionLimits(0) # Added

            global _selIpt2Id
            i2 = inputs.addSelectionInput(
                _selIpt2Id,
                'Entity Two',
                'Please select a linear or planear entity'
            )

            i2.addSelectionFilter(adsk.core.SelectionCommandInput.PlanarFaces)
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.LinearEdges)
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.SketchLines)
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionLines)
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionPlanes)

            i2.setSelectionLimits(0) # Added

            #adding this line seems to break the validateInputs functionality
            inputs.addTextBoxCommandInput('readonly_textBox1', 'text', 'This is where the text will be', 2, True) 


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

class MyPreSelectHandler(adsk.core.SelectionEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args: adsk.core.SelectionEventArgs):
        adsk.core.Application.get().log(args.firingEvent.name)

        # Irrelevant except for SelectionCommandInput.
        if args.activeInput.classType() != adsk.core.SelectionCommandInput.classType():
            return

        # If the selection has already been made, do not allow the selection.
        if args.activeInput.selectionCount > 0:
            args.isSelectable = False

The other parts are not modified from "Intersections.py".

0 Likes
Message 5 of 8

rory2WEHA
Explorer
Explorer

Hi @kandennti , thanks for taking another look at this for me.

 

When I tried to execute the script with the changes you suggested I got an error:

 

Failed:

Traceback (most recent call last):

File "C:/Users/rory/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Scripts/Intersection-EDIT/Intersection-EDIT.py", line 103, in notify

_selIpt1Id,

NameError: name '_selIpt1Id' is not defined

 

Was I meant to declare the variable elsewhere? Sorry if I missed something obvious, I'm not an expert in Python. Here is a dump of the full script:

 

#Author-Autodesk Inc.
#Description-Caculate the intersections between the selected curve/surface/body/component/occurrence and curve/surface.
# non planar surface does not support for now

import adsk.core, adsk.fusion, traceback
import os

pi = 3.1415926
nearZero = 0.000001
# global set of event handlers to keep them referenced for the duration of the command
handlers = []

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

class IntersectionCommandExecuteHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            command = args.firingEvent.sender
            inputs = command.commandInputs

            input0 = inputs[0];
            sel0 = input0.selection(0);

            input1 = inputs[1];
            sel1 = input1.selection(0);

            intersections = Intersections();
            intersections.Execute(sel0.entity, sel1.entity);
        except:
            if ui:
                ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

class IntersectionCommandDestroyHandler(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 IntersectionValidateInputHandler(adsk.core.ValidateInputsEventHandler):
    def __init__(self):
        super().__init__()
       
    def notify(self, args):
        adsk.core.Application.get().log(args.firingEvent.name)
        try:
            adsk.core.Application.get().log(args.firingEvent.name)

            # command inputs
            inputs: adsk.core.CommandInputs = args.inputs

            # Make sure that the respective SelectionCommandInput is selected.
            global _selIpt1Id, _selIpt2Id
            for id in [_selIpt1Id, _selIpt2Id]:
                selIpt: adsk.core.SelectionCommandInput = inputs.itemById(id)
                if not selIpt:
                    continue
                if selIpt.selectionCount != 1:
                    args.areInputsValid = False
                    return

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

class IntersectionCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            cmd = args.command
            onExecute = IntersectionCommandExecuteHandler()
            cmd.execute.add(onExecute)
            handlers.append(onExecute)

            onDestroy = IntersectionCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            handlers.append(onDestroy)

            onValidateInput = IntersectionValidateInputHandler()
            cmd.validateInputs.add(onValidateInput)
            handlers.append(onValidateInput)

            # Added
            onPreSelect = MyPreSelectHandler()
            cmd.preSelect.add(onPreSelect)
            handlers.append(onPreSelect)

            #define the inputs
            inputs = cmd.commandInputs

            global _selIpt1Id
            i1: adsk.core.SelectionCommandInput = inputs.addSelectionInput(
                _selIpt1Id,
                'Entity One',
                'Please select a curve, planear entity or a BRepBody, Component, Occurrence'
            )

            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Edges)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.PlanarFaces)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.SketchCurves)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionLines)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionPlanes)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Bodies)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.Occurrences)
            i1.addSelectionFilter(adsk.core.SelectionCommandInput.RootComponents)

            i1.setSelectionLimits(0) # Added

            global _selIpt2Id
            i2 = inputs.addSelectionInput(
                _selIpt2Id,
                'Entity Two',
                'Please select a linear or planear entity'
            )

            i2.addSelectionFilter(adsk.core.SelectionCommandInput.PlanarFaces)
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.LinearEdges)
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.SketchLines)
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionLines)
            i2.addSelectionFilter(adsk.core.SelectionCommandInput.ConstructionPlanes)

            i2.setSelectionLimits(0) # Added

            #adding this line seems to break the validateInputs functionality
            inputs.addTextBoxCommandInput('readonly_textBox1', 'text', 'This is where the text will be', 2, True) 


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

class MyPreSelectHandler(adsk.core.SelectionEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args: adsk.core.SelectionEventArgs):
        adsk.core.Application.get().log(args.firingEvent.name)

        # Irrelevant except for SelectionCommandInput.
        if args.activeInput.classType() != adsk.core.SelectionCommandInput.classType():
            return

        # If the selection has already been made, do not allow the selection.
        if args.activeInput.selectionCount > 0:
            args.isSelectable = False

class Intersections:
    def Execute(self, entityOne, entityTwo):
        #caculate the intersections
        sectionResults = adsk.core.ObjectCollection.create()
        def getGeometry(entity):
                geom = entity
                if isinstance(entity, adsk.fusion.BRepFace) or \
                   isinstance(entity, adsk.fusion.BRepEdge) or \
                   isinstance(entity, adsk.fusion.ConstructionAxis) or\
                   isinstance(entity,adsk.fusion.ConstructionPlane):
                    geom = entity.geometry
                elif isinstance(entity, adsk.fusion.SketchCurve):
                    geom = entity.worldGeometry;
                return geom;

        def intersectWith(surfaceOrCurve, section):
            surfaceOrCurve = getGeometry(surfaceOrCurve)

            section = getGeometry(section)
            result = None
            if isinstance(surfaceOrCurve, adsk.core.Curve3D):
                result = section.intersectWithCurve(surfaceOrCurve)
            elif isinstance(section, adsk.core.Curve3D):
                result = surfaceOrCurve.intersectWithCurve(section)
            else:
                if surfaceOrCurve.surfaceType == adsk.core.SurfaceTypes.PlaneSurfaceType and section.surfaceType == adsk.core.SurfaceTypes.PlaneSurfaceType :
                    result = section.intersectWithPlane(surfaceOrCurve)
                    if result:
                        sectionResults.add(result)
                    return 
            if result:
                for resultI in result:
                    sectionResults.add(resultI)

        def intersectWithBody(body, section):
            fs = body.faces;
            for fsI in fs:
                intersectWith(fsI, section)

        def intersectWithComponent(comp, occ, section):
            if isinstance(comp,adsk.fusion.Component):
                bodies = comp.bRepBodies
                for body in bodies:
                    if(not body):
                        continue
                    if occ :
                        body = body.createForAssemblyContext(occ)

                    intersectWithBody(body, section)

            childOccs = None
            if occ :
                childOccs = occ.childOccurrences
            else:
                childOccs = comp.occurrences
            
            for childOcc in childOccs:
                if not childOcc:
                    continue
                intersectWithComponent(childOcc.component, childOcc, section);

        if isinstance(entityOne,adsk.fusion.Component):
            intersectWithComponent(entityOne, None, entityTwo)

        elif isinstance(entityOne,adsk.fusion.Occurrence):
            intersectWithComponent(entityOne.component, entityOne, entityTwo)

        elif isinstance(entityOne, adsk.fusion.BRepBody):
            intersectWithBody(entityOne, entityTwo)

        else:
            intersectWith(entityOne, entityTwo)

        if len(sectionResults) == 0:
            if ui:
                ui.messageBox('No intersection found')
            return

        def isPlanearEntity(entity):
            planearEnt = False
            if isinstance(entity, adsk.fusion.ConstructionPlane):
                planearEnt = True
            elif isinstance(entity, adsk.fusion.BRepFace):
                sur = entity.geometry

                if(sur.surfaceType == adsk.core.SurfaceTypes.PlaneSurfaceType):
                    planearEnt = True

            return planearEnt

        doc = app.activeDocument
        d = doc.design
        rootComp = d.rootComponent

        sketch = None
        if isPlanearEntity(entityTwo):
            sketch = rootComp.sketches.add(entityTwo)
        elif isPlanearEntity(entityOne):
            sketch = rootComp.sketches.add(entityOne)
        else:
            sketch = rootComp.sketches.add(rootComp.xYConstructionPlane)

        for geom in sectionResults:
            if not geom or not sketch:
                continue

            m = sketch.transform
            m.invert()
            geom.transformBy(m)
            if isinstance(geom,adsk.core.Point3D):
                sketch.sketchPoints.add(geom)
            elif isinstance(geom,adsk.core.Curve3D):
                sketchCurve = None
                if isinstance(geom, adsk.core.Line3D):
                    sketchCurve = sketch.sketchCurves.sketchLines.addByTwoPoints(geom.startPoint, geom.endPoint);

                elif isinstance(geom,adsk.core.Arc3D):
                    sweepAngle = 2 * pi if abs(geom.endAngle - geom.startAngle) < nearZero else geom.startAngle
                    sketchCurve = sketch.sketchCurves.sketchArcs.addByCenterStartSweep(geom.center, geom.startPoint, sweepAngle)

                elif isinstance(geom,adsk.core.Circle3D):
                    sketchCurve = sketch.sketchCurves.sketchCircles.addByCenterRadius(geom.center, geom.radius)

                elif isinstance (geom,adsk.core.Ellipse3D):
                    curveEva = geom.evaluator

                    startParameter = None
                    endParameter = None
                    curveEva.getParameterExtents(startParameter, endParameter)

                    pointOnCurve = None
                    curveEva.getPointAtParameter((startParameter + endParameter)/3, pointOnCurve)

                    majorAxisPoint = geom.center
                    majorAxisVec = geom.majorAxis

                    majorAxisVec.scaleBy(geom.majorRadius)
                    majorAxisPoint.translateBy(majorAxisVec)

                    sketchCurve = sketch.sketchCurves.sketchEllipses.add(geom.center, majorAxisPoint, pointOnCurve)

                elif isinstance(geom, adsk.core.NurbsCurve3D):
                    pts = geom.controlPoints

                    ptCol = adsk.core.ObjectCollection.create()

                    for ptsI in pts:
                        ptCol.add(ptsI)

                    sketchCurve = sketch.sketchCurves.SketchFittedSplines.add(ptCol)

                elif isinstance(geom,adsk.core.InfiniteLine3D):
                    start = geom.origin
                    end = geom.origin
                    dir = geom.direction
                    dir.scaleBy(10)
                    end.translateBy(dir)
                    sketchCurve = sketch.sketchCurves.sketchLines.addByTwoPoints(start, end)

                if sketchCurve:
                    sketchCurve.isConstruction = True

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('IntersectionCMDDef')
        if not cmdDef:
            resourceDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources') # absolute resource file path is specified
            cmdDef = commandDefinitions.addButtonDefinition('IntersectionCMDDef',
                    'Intersections',
                    'Calculate the intersections of two selected entities',
                    resourceDir)

        onCommandCreated = IntersectionCommandCreatedHandler()
        cmdDef.commandCreated.add(onCommandCreated)
        # keep the handler referenced beyond this function
        handlers.append(onCommandCreated)
        
        cmdDef.execute()

        # 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()))

 

 

0 Likes
Message 6 of 8

kandennti
Mentor
Mentor
Accepted solution

@rory2WEHA .

 

Sorry, two lines were missing.

 

・・・
pi = 3.1415926
nearZero = 0.000001
# global set of event handlers to keep them referenced for the duration of the command
handlers = []

_selIpt1Id = 'selIpt1Id' # <-Add here
_selIpt2Id = 'selIpt2Id' # <-Add here

app = adsk.core.Application.get()
・・・

 

0 Likes
Message 7 of 8

rory2WEHA
Explorer
Explorer

Thanks you @kandennti that fixed it! I think this will be something I can implement into my own script now, so I will accept the solution. 

One final question about the solution is around this line of code:

 

adsk.core.Application.get().log(args.firingEvent.name)

 

Can you explain, or give a link to somewhere that explains what this line is doing? I'm sure it is something obvious to but I'm still learning the object model of this software and am struggling to work it out myself.

0 Likes
Message 8 of 8

kandennti
Mentor
Mentor

@rory2WEHA .

 

The event is written out to the text command window to confirm that the event has occurred.

https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-6A2353AC-6C89-4032-AE65-69D45AF815BF 

1.png

 

This is for confirmation and has nothing to do with the operation.
There is no problem if you delete it.

 

0 Likes