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: 

Use TextCommands2

5 REPLIES 5
Reply
Message 1 of 6
kandennti
1238 Views, 5 Replies

Use TextCommands2

I don't understand English, so I use a translation site to create my sentences.
So, please understand that there may be inappropriate expressions.

 

This is a continuation of our efforts here.

https://forums.autodesk.com/t5/fusion-360-api-and-scripts/use-textcommands/m-p/9645717#M10758 

I myself have a better understanding than before.

 

There are a number of commands that are not provided as an API, but can be used in conjunction with TextCommands.
One of them is a group of bodies.

This is illustrated by the data in this state.
1.png
Make the Bodies of RootComponent (marked in red) selected and execute the following TextCommands.

Commands.Start FusionCreateSurfaceGroupCommand

 

2.png

You can make a group without problems.

Then execute the TextCommands in the same way with the "Component1:1" Bodies (marked in blue) selected.
3.png
This one is also created without problems.

To perform these same processes, we created the following script.

 

        # occ
        occ :adsk.fusion.Occurrence = root.occurrences[0]
        
        if occ.nativeObject:
            print('{} has nativeObject'.format(occ.name))
        else:
            print('{} has not nativeObject'.format(occ.name))

        createBodiesGroup(occ)

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

def createBodiesGroup(
    root_occ :adsk.core.Base
    ):

    # select bodies
    sels :adsk.core.Selections = _ui.activeSelections
    sels.clear()
    sels.add(root_occ.bRepBodies)

    # execute TextCommands
    _app.executeTextCommand('Commands.Start FusionCreateSurfaceGroupCommand')


Unfortunately, RootComponent creates it, but Occurrence(Component1:1) results in an error and cannot be created.
4.png
I do not know the cause of this error.
(Maybe it's because I don't understand NativeObject and Proxy correctly.)

However, I found that you can select correctly by using the TextCommands here.

Selections.Add <Paths>

https://github.com/kantoku-code/Fusion360_Small_Tools_for_Developers/blob/master/TextCommands/TextCo... 

API's Selections.Add method can only accommodate what is provided as an object in the API, but
In the case of the TextCommands is possible to state selected, even in entity that is not provided as an object in the API.


Eventually you need to get the <paths>.
To verify the <paths>, select the Bodies of "Component1:1" in the GUI and execute the following text command.

Selections.List

https://github.com/kantoku-code/Fusion360_Small_Tools_for_Developers/blob/master/TextCommands/TextCo... 

5.png


The numbers displayed may be different.
Each of these numbers seems to be represented as <EntityRef><EntityID>.

We could use this one to see the properties of the entity.

PEntity.Properties <EntityRef>

https://github.com/kantoku-code/Fusion360_Small_Tools_for_Developers/blob/master/TextCommands/TextCo... 

 

Let's try it out with "57".
6.png
The return value seems to be in JSON format.
Since it is tedious to check them one by one, we will create a script like this one to check them.

# Fusion360API Python script
import adsk.core, adsk.fusion, traceback
import json
import os
import neu_server

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

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

        # check select count
        sels :adsk.core.Selections = _ui.activeSelections
        if sels.count < 1:
            _ui.messageBox('Pre-select the elements to examine.')
            return

        if sels.count > 1:
            _ui.messageBox('There should be only one entity to choose.')
            return

        dumpMsg('-- select info --')

        # get paths
        linesep = os.linesep
        paths :str = _app.executeTextCommand(u'Selections.List').rstrip(linesep)
        dumpMsg('<paths> : {}\n'.format(paths))

        # split paths
        ids = paths.split(':')
        if len(ids) < 1:
            return

        # loop all ID
        for id in ids:
            try:
                dumpMsg('prop -> : {}'.format(id))

                # get property
                # prop_txt = _app.executeTextCommand(u'PEntity.Properties {}'.format(id))
                prop_txt = neu_server.get_entity_properties(id)
                if len(prop_txt) < 1: continue

                # to json
                prop =  json.dumps(prop_txt, indent=2)
                dumpMsg(str(prop) + '\n')
            except:
                pass

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

def dumpMsg(msg :str):
    _ui.palettes.itemById('TextCommands').writeText(str(msg))

7.png

In my environment

prop_txt = _app.executeTextCommand(u'PEntity.Properties {}'.format(id))


with the same function because the output was not clean in

import neu_server
・・・
prop_txt = neu_server.get_entity_properties(id)


to use.
Since it is not practical to write out all the information because there is a lot of information to be output, this is what happens when you only write out the "interfaceId" key.
Also, I would like to add my prediction based on the name of the key.

57 : "Ns::Comp::ComponentInstance" - Occurrence?
3 : "Na::FusionComponent" - Component?
4 : "Ns::Comp::ComponentInstances" - Occurrences?
224 : "Ns::Comp::ComponentInstance" - Occurrence?
173 : "Na::FusionComponent" - Component?
176 : "Ns::BREP::Bodies" - bodies?


I didn't expect there to be a kind of Occurrence in the RootComponent as well, but it feels more natural.
(If you select only the root component and run the script, it only returns "57")

Also, if you look for the following entityID from each property, you will find it with a key like this

57 : "Ns::Comp::ComponentInstance"['rTargetComponent']['entityId'] -> 3
3 : "Na::FusionComponent"['rComponentBaseInstances']['entityId'] -> 4
4 : "Ns::Comp::ComponentInstances"['children']['entityId'] -> 224
224 : "Ns::Comp::ComponentInstance"['rTargetComponent']['entityId'] -> 173
173 : "Na::FusionComponent"['rBodies']['entityId'] -> 176
176 : "Ns::BREP::Bodies"


This way you have relative information.

The image in my head looks like this.
8.png
I could be wrong.

Red is the elements that can be selected in the GUI (mainly occlusion) and green is practically not shown.
Reference source (component). A collection of each element that is not even shown in blue.
I was surprised to find that the root component also has an occurrence.
Such a command also exists.
9.png
Also, entityID is

PAsset
PBody
PComponent
PDocument
PEntity
PInterfaces
PInstance


These commands are also available. (I'm sure there are others)

5 REPLIES 5
Message 2 of 6
kandennti
in reply to: kandennti

Now the main preparation is ready.

Select using the API.

Selections.Add <bodies>

Then get the <paths> using the TextCommands.

Selections.List

In TextCommands, we can use this <paths> to make a selection, but as shown in the first part, we cannot select the body of Occurrence(Component1:1) in the API selection.

So we select a selectable Occurrence(Component1:1) and get the <paths>.

Selections.List 
57:3:4:224


Based on the entityID of "224", you can run "PEntity.Properties" to get the next required entityID.

・・・

"rOwningDeriveFeature": {},
"rTargetComponent": {
"segId": 25,
"entityId": 173,
"rootId": ""
},
"transform": 
・・・


For TargetComponent, it is also valid here.
10.png

Based on these, let's fix the erroneous script.

# Fusion360API Python script
import adsk.core, adsk.fusion, traceback
import json
import os
import neu_server

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

def run(context):
    try:
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface
        des :adsk.fusion.Design = _app.activeProduct
        root :adsk.fusion.Component = des.rootComponent

        # root
        createBodiesGroup(root)

        # occ
        occ :adsk.fusion.Occurrence = root.occurrences[0]
        
        if occ.nativeObject:
            print('{} has nativeObject'.format(occ.name))
        else:
            print('{} has not nativeObject'.format(occ.name))

        createBodiesGroup(occ)

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

def createBodiesGroup(
    root_occ :adsk.core.Base
    ):

    try:
        global _app, _ui

        # get Selections
        sels :adsk.core.Selections = _ui.activeSelections
        sels.clear()

        # select root or occ
        sels.add(root_occ)

        # get root_occ <paths>
        linesep = os.linesep
        occPaths = _app.executeTextCommand(u'Selections.List').rstrip(linesep)

        # get root_occ entityID
        occId = str(occPaths).split(':')[-1]

        # get root_occ property
        propTxt = neu_server.get_entity_properties(occId)
        prop =  json.loads(json.dumps(propTxt))

        # get target component entityID
        compId = prop['rTargetComponent']['entityId']

        # get component property
        propTxt = neu_server.get_entity_properties(compId)
        prop =  json.loads(json.dumps(propTxt))

        # get bodies entityID
        bodiesId = prop['rBodies']['entityId']

        # create bodies paths
        bodiesPaths = '{}:{}:{}'.format(occPaths, compId, bodiesId)

        # select bodies
        sels.clear()
        _app.executeTextCommand(u'Selections.add {}'.format(bodiesPaths))

        # execute TextCommands
        _app.executeTextCommand('Commands.Start FusionCreateSurfaceGroupCommand')

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

When we did this, the group was created successfully.

11.png

Message 3 of 6
kandennti
in reply to: kandennti

So far, we've served our purpose by preselecting the objects provided by the API and executing the commands. Next, let's look at an example using an object not provided by the API.

Since there have been inquiries on the forum in the past, we decide to make and modify the circular pattern of the sketch.

The actual operation in the GUI. First make a sketch and draw one circle.
Select the circle and the sketch origin to make the circle a circular pattern with the sketch origin as the center point.
If we call the circular pattern command in this state, unfortunately, both of them are captured as objects.
13.png
You can see that "preselect + command invocation" fails, but now we know how to make it work.

 

〇Find the CommandID
First, check the command ID.
You can also check it by the method previously described here.

https://forums.autodesk.com/t5/fusion-360-api-and-scripts/use-textcommands/m-p/9645688 

This time, I will use this add-in that I made myself.

https://github.com/kantoku-code/Fusion360_Small_Tools_for_Developers/tree/master/CommandLogger 

It's a simple matter of displaying the "Command Name : Command ID" called by the GUI operation in the TextCommands palette after launching the add-in, but I like it because I can check it as I go along.
12.png
The command ID for the circular pattern of the sketch is "CircularSketchPatternCommand".

 

 

〇Find the InputID of a dialog.
We then examine the InputID of each of the command dialogs.
Previously, we used this one from TextCommands, but some items were not displayed.

UI.CurrentCommandInfo


I searched for it and found that this command also displays the InputID.

Toolkit.cmdDialog

https://github.com/kantoku-code/Fusion360_Small_Tools_for_Developers/blob/master/TextCommands/TextCo... 

14.png

We can see that the InputID used in this case looks like this

Objects -> Geometries
Center -> CircularAxis
Quantity -> infoCircularGridEntryCount

 

 

〇Set the entity or value to a dialog

The way to do this without pre-selection was introduced here in the past using "ONK".

https://forums.autodesk.com/t5/fusion-360-api-and-scripts/use-textcommands/m-p/9645717#M10758 

 

I have found another way to do it.

UI.EnableCommandInput <InputID>

https://github.com/kantoku-code/Fusion360_Small_Tools_for_Developers/blob/master/TextCommands/TextCo... 

 

Using this function, you can move the focus to the specified InputID.
(However, it is limited to the Input for selection)
15.png
Move the focus to an arbitrary Input and add

userInterface.activeSelections.add(<API Object>)


to select the desired element, and you have assigned it to the Input.

    # get selections
    sels :adsk.core.Selections = _ui.activeSelections
    sels.clear()

・・・

    # set Objects
    _app.executeTextCommand(u'UI.EnableCommandInput {}'.format('Geometries'))
    sels.add(tragetEntity)


The "Objects" and "Center Point" is resolved in this.


As for the "Quantity," we already solved that last time, so here it is.

    # set Quantity
    _app.executeTextCommand(u'Commands.SetDouble infoCircularGridEntryCount 5')

After that, just run it and the circular pattern will be created successfully.

 

 

〇Find the circular pattern entityID.

From here, we will modify the circular pattern created in the above steps.
The procedure to modify is "preselect + call modify command".

The command ID for the modification was done using the same add-in as in the previous step.
16.png
The CommandID is "SketchPatternCircularEdit".

Next, we need to select the "Circular Pattern", which unfortunately is not provided as an object in the API.
We need to find <paths> as we did when we selected bodies.

I used the following procedure to find it.
17.png
First, select "Circular Pattern" in the GUI, then use the TextCommands to get <paths>.
It will be ①.

Selections.List 
57:3:12:173:211

(The numbers themselves may be different from yours)
The entityID of the "Circular Pattern" is "211". We can also see that its parent entityID is "173".

For now, we'll check the "211" property and find "173", which means we can get the parent entity with the "rParentNode" key. It will be ②.

Next, let's check the "173" property." interfaceId" is "SKs::Geometry::MSketch".
So, this is probably the sketch. It will be ③.

Find out "211" from this property. It will be ④.
You can see that it is in the "constraints" key. It will be ⑤.

    # select Sketch
    sels.clear()
    sels.add(skt)

    # Get <paths> from Sketch
    linesep = os.linesep # Get the line feed code for each os.
    sktPaths = _app.executeTextCommand(u'Selections.List').rstrip(linesep)

    # Get entityID from Sketch
    oktId = str(sktPaths).split(':')[-1] # The last number is the entityID.

    # Get Property
    propTxt = neu_server.get_entity_properties(oktId)
    sktProp =  json.loads(json.dumps(propTxt))

    # Get CircularSketchPattern entityID
    # Normally, you should check "interfaceId" and so on, but we have simplified it.
    circularSketchPatternId = sktProp['constraints'][-1]['entityId']

There is only one "circular pattern" in this sketch, so it is displayed as shown in the image, but the "constraints" key is essentially a list, and multiple values can exist.
If you create another circular pattern and look at the properties, you will see the output like this.
18.png

 

 

〇Select a circular pattern.

Once you get to this point, the selection itself is easy.
Just attach the circular pattern entityID to the sketch <paths> and use the text command Selections.add.

        # CircularSketchPattern paths
        circularSketchPatternPaths = sktPaths + ':' + str(circularSketchPatternId)

        # Circular Pattern
        sels.clear()
        _app.executeTextCommand(u'Selections.add {}'.format(circularSketchPatternPaths))

 

 

〇Modify the settings
After that, call "SketchPatternCircularEdit" to display the command dialog.
You can modify the Input as you did when you created the circular pattern.

Message 4 of 6
kandennti
in reply to: kandennti

〇summary

The whole process can be summarized as follows.

# Fusion360API Python script
import adsk.core, adsk.fusion, traceback
import os
import json
import neu_server

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

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

        # get active sketch
        skt :adsk.fusion.Sketch = des.activeEditObject
        if not skt:
            return

        # get entity
        tragetEntity :adsk.fusion.SketchEntity = skt.sketchCurves.sketchCircles[0]
        circularAxis :adsk.fusion.SketchPoint = skt.originPoint

        # get selections
        sels :adsk.core.Selections = _ui.activeSelections
        sels.clear()

        # call CircularSketchPatternCommand
        _app.executeTextCommand(u'Commands.Start CircularSketchPatternCommand')

        # set Objects
        _app.executeTextCommand(u'UI.EnableCommandInput {}'.format('Geometries'))
        sels.add(tragetEntity)

        # set Center Point
        _app.executeTextCommand(u'UI.EnableCommandInput {}'.format('CircularAxis'))
        sels.add(circularAxis)

        # set Quantity
        _app.executeTextCommand(u'Commands.SetDouble infoCircularGridEntryCount 5')

        # Comimit Command
        _app.executeTextCommand(u'NuCommands.CommitCmd')

        # Message
        _ui.messageBox('Modify the Circular Pattern.')

        # -- Start editing --
        # select Sketch
        sels.clear()
        sels.add(skt)

        # Get <paths> from Sketch
        linesep = os.linesep # Get the line feed code for each os.
        sktPaths = _app.executeTextCommand(u'Selections.List').rstrip(linesep)

        # Get entityID from Sketch
        oktId = str(sktPaths).split(':')[-1] # The last number is the entityID.

        # Get Property
        propTxt = neu_server.get_entity_properties(oktId)
        sktProp =  json.loads(json.dumps(propTxt))

        # Get CircularSketchPattern entityID
        # Normally, you should check "interfaceId" and so on, but we have simplified it.
        circularSketchPatternId = sktProp['constraints'][-1]['entityId']

        # CircularSketchPattern paths
        circularSketchPatternPaths = sktPaths + ':' + str(circularSketchPatternId)

        # Circular Pattern
        sels.clear()
        _app.executeTextCommand(u'Selections.add {}'.format(circularSketchPatternPaths))

        # Call Edit Circular Pattern command
        _app.executeTextCommand(u'Commands.Start SketchPatternCircularEdit')

        # Change quantity
        _app.executeTextCommand(u'Commands.SetDouble infoCircularGridEntryCount 7')

        # Comimit Command
        _app.executeTextCommand(u'NuCommands.CommitCmd')

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

19.png

It is much more time consuming than API development alone, but it also allows you to select entity that are not provided as objects in the API, allowing you to create things that you could not create with the API alone.

 

Thank you for reading my long explanation until the end. I sincerely hope it will be useful for any add-ins you create.

It is thanks to @thomasa88  that I was able to deepen my understanding so far.
Thank you very much for the great hint you gave me.

 

Message 5 of 6
kandennti
in reply to: kandennti

I found out how to change the value of "eDropDownButton", which I hadn't known how to do for a long time.
"eDropDownButton" is a CommandInputs like this.

e1.png

 

The final step is to modify the pipe command here.

https://forums.autodesk.com/t5/fusion-360-api-and-scripts/use-textcommands/m-p/9645688 

 

 

Find out the ID of Inputs in advance.
Execute this command while the command dialog is displayed.

Toolkit.cmdDialog

For more information about "Toolkit.cmdDialog", please refer to here.

https://forums.autodesk.com/t5/fusion-360-api-and-scripts/use-textcommands2/m-p/9937199#M12120 

 

You will see that the Inputs ID looks like this.

e2.png

 

The value of the "eDropDownButton" can be changed with the following command

Commands.SetString <InputsID> <MenuItems ID>

 

For example, if you want the "section" to be a square, you would use

Commands.SetString infoSectionType infoRectangular

 

 

Finally, we modify the pipe commands to be more controlled.

#Fusion360API python script
#Author-kantoku
#Description-create PipeFeature sample

import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

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

        # new doc
        _app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)
        des  :adsk.fusion.Design = _app.activeProduct
        des.designType = adsk.fusion.DesignTypes.ParametricDesignType
        root :adsk.fusion.Component = des.rootComponent

        # create sketch
        crv = initSktCircle(root)

        # create pipe
        initPipe(crv, 'infoRectangular', 'infoNewBodyType')

        # create sketch
        crv = initSktSpline(root)

        # create pipe
        initPipe(crv, 'infoTriangular', 'infoNewBodyType')

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

def initSktSpline(comp :adsk.fusion.Component):
    skt :adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)

    poss = [[-1,2,0], [2,1,0], [-3,-4,0]]

    pnt3D = adsk.core.Point3D
    objs = adsk.core.ObjectCollection.create()
    [objs.add(pnt3D.create(x,y,z)) for (x,y,z) in poss]
        
    crvs :adsk.fusion.SketchCurves = skt.sketchCurves
    crv = crvs.sketchFittedSplines.add(objs)

    return crv

def initSktCircle(comp :adsk.fusion.Component):
    skt :adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)

    pnt3D = adsk.core.Point3D
    crvs :adsk.fusion.SketchCurves = skt.sketchCurves
    crv = crvs.sketchCircles.addByCenterRadius(pnt3D.create(-5.0,-5,0), 4.0)

    return crv

def initPipe(
    path,
    infoSectionType = 'infoCircular',
    infoBooleanType = 'infoNewBodyType'):

    sels :adsk.core.Selections = _ui.activeSelections
    sels.clear()
    sels.add(path)

    txtCmds = [
        u'Commands.Start PrimitivePipe', # show dialog
        u'Commands.SetDouble SWEEP_POP_ALONG 1.0', # input distance
        u'Commands.SetDouble SectionRadius 0.5', # input radius
        u'Commands.SetString infoSectionType {}'.format(infoSectionType),
        u'Commands.SetString infoBooleanType {}'.format(infoBooleanType),
        u'NuCommands.CommitCmd' # execute command
    ]
    
    for cmd in txtCmds:
        _app.executeTextCommand(cmd)

    sels.clear()

The result is shown here.

e3.png

 

The "eDropDownButton" is used in so many command dialogs that not being able to change it would be very inconvenient.
This may allow you to incorporate functionality into your scripts/add-ins that you have previously given up on.

 

 

Message 6 of 6
kandennti
in reply to: kandennti

When I want to display a small message while a script/add-in is running, I output it to the TextCommands palette (Application.log Method) because MessageBox stops the process and ProgressDialog takes time to prepare.

 

However, for a long time I could not figure out how to clear the TextCommands palette.

 

I knew that executing the TextCommand here in the GUI would clear it.

https://github.com/kantoku-code/Fusion360_Small_Tools_for_Developers/blob/master/TextCommands/TextCo... 

Window.Clear 

However, the executeTextCommand method does not clear it.

 

 

 

Recently, I found a way to clear it.

app.log(
    app.executeTextCommand(u'window.Clear')
)

or

app.log(u'TextCommandWindow.Clear')

 

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