Bug? - Attributes disappearing

Bug? - Attributes disappearing

pludikar
Collaborator Collaborator
1,658 Views
6 Replies
Message 1 of 7

Bug? - Attributes disappearing

pludikar
Collaborator
Collaborator

It appears that any attribute added to an entity during an open dialog box disappears before onExecute is processed after the OK button is pressed. If I'm doing something wrong, please enlighten me - otherwise this has to be a bug!  I initially thought this was related to components, but it happens with rootComponents as well.

 

  • Create a root box, and if you want, a component box.
  • Run the addon - found at the bottom of CREATE menu.
  • click on the box and then OK

this will create a attribTest.log file

 

-Before the OK button is pressed, the log show that attributes were created. 

-After the OK button press the attributes have been cleared. 

-Next time addon is activated - the log shows that the attributes were persistent, but the ones created before the last OK, do not reappeared. 

 

Attached is also a log file.

 

from collections import defaultdict
from pprint import pformat
import logging
import os

import adsk.core, adsk.fusion
import traceback

from . import utils

appPath = os.path.dirname(os.path.abspath(__file__))
logger = logging.getLogger('aaaa')

#clean up log handlers - seems to be an issue with f360 not playing nice with python logging
for handler in logger.handlers:
    handler.flush()
    handler.close()

formatter = logging.Formatter('%(lineno)d; %(funcName)s ; %(message)s')
logHandler = logging.FileHandler(os.path.join(appPath, 'attribTest.log'), mode='a')
logHandler.setFormatter(formatter)
logHandler.flush()
logger.addHandler(logHandler)
logger.setLevel(logging.DEBUG)


class AttribTestCommand(object):
    COMMAND_ID = "attribTestBtn"

    def __init__(self):
        self.app = adsk.core.Application.get()
        self.ui = self.app.userInterface
        self.handlers = HandlerHelper()
        self.body = None

    def addButton(self):
        # clean up any crashed instances of the button if existing
        try:
            self.removeButton()
        except:
            pass

        # add add-in to UI
        buttonAttribTest = self.ui.commandDefinitions.addButtonDefinition(
            self.COMMAND_ID, 'attribTest', 'adds attributes while dialog is open and after it is executed')

        buttonAttribTest.commandCreated.add(self.handlers.make_handler(adsk.core.CommandCreatedEventHandler,
                                                                    self.onCreate))

        createPanel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        buttonControl = createPanel.controls.addCommand(buttonAttribTest, 'attribTestBtn')

        # Make the button available in the panel.
        buttonControl.isPromotedByDefault = True
        buttonControl.isPromoted = True

    def removeButton(self):
        cmdDef = self.ui.commandDefinitions.itemById(self.COMMAND_ID)
        if cmdDef:
            cmdDef.deleteMe()
        createPanel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        cntrl = createPanel.controls.itemById(self.COMMAND_ID)
        if cntrl:
            cntrl.deleteMe()

    def onCreate(self, args):
        inputs = args.command.commandInputs

        selection = inputs.addSelectionInput(
            'selBody', 'Body',
            'Select a body')
        selection.addSelectionFilter('Bodies')
        selection.setSelectionLimits(1,0)
        cmd = adsk.core.Command.cast(args.command)
        # Add handlers to this command.
        cmd.execute.add(self.handlers.make_handler(adsk.core.CommandEventHandler, self.onExecute))
        cmd.inputChanged.add(
            self.handlers.make_handler(adsk.core.InputChangedEventHandler, self.onChange))
        logger.debug('============================================\nattributes on creation: \n{}'.format(getAttribs(self.design)))
        self.clearAttribs('AttribTest')
        logger.debug('attributes cleared: \n{}'.format(getAttribs(self.design)))
        
    def onChange(self, args):
        changedInput = adsk.core.CommandInput.cast(args.input)
        self.body = changedInput.selection(0).entity
        logger.debug('entity: {}'.format('Component' if self.body.assemblyContext else 'Root'))
        self.setAttribBefore()
        logger.debug('attributes set on change: \n{}'.format(getAttribs(self.design)))
        collection = adsk.core.ObjectCollection.create()
        collection.add(self.body)
        self.ui.activeSelections.all = collection
        
    def clearAttribs(self, name):  #not used - can be called manually during debugging
        attribs = self.design.findAttributes(name, '')
        for attrib in attribs:
            attrib.deleteMe()

    def onExecute(self, args):
        logger.debug('attributes immediately onExecute:\n{}'.format(getAttribs(self.design)))
        self.setAttribAfter()
        logger.debug('attributes after:\n{}\n***********************************************\n'.format(getAttribs(self.design)))

    @property
    def design(self):
        return self.app.activeProduct

    @property
    def rootComp(self):
        return self.design.rootComponent

    def setAttribBefore(self):
        for face in self.body.faces:
            face.nativeObject.attributes.add("AttribTest", "Native before",'') if face.assemblyContext else face.attributes.add("AttribTest", "before",'')
            face.attributes.add("AttribTest", "before",'') #should be 2 sets of attributes for component and 1 set for root
            
    def setAttribAfter(self):
        for face in self.body.faces:
            face.nativeObject.attributes.add("AttribTest", "Native after",'') if face.assemblyContext else face.attributes.add("AttribTest", "after",'')
            face.attributes.add("AttribTest", "after",'')  #should be 2 sets of attributes for component and 1 set for root
            
class HandlerHelper(object):

    def __init__(self):
        self.handlers = []  # needed to prevent GC of SWIG objects

    def make_handler(self, handler_cls, notify_method, catch_exceptions=True):
        class _Handler(handler_cls):
            def notify(self, args):
                if catch_exceptions:
                    try:
                        notify_method(args)
                    except:
                        messageBox('Failed:\n{}'.format(traceback.format_exc()))
                else:
                    notify_method(args)
        h = _Handler()
        self.handlers.append(h)
        return h

attribTest = AttribTestCommand()

def run(context):
    try:
        attribTest.addButton()
    except:
        messageBox(traceback.format_exc())

def messageBox(*args):
    adsk.core.Application.get().userInterface.messageBox(*args)
    
def getAttribs(design):
    return pformat( list(map(lambda x:x.name, design.findAttributes('AttribTest', '')))) 

def stop(context):
    try:
        attribTest.removeButton()
        for handler in logger.handlers:
            handler.flush()
            handler.close()
            logger.removeHandler(handler)
    except:
        messageBox(traceback.format_exc())

 

Peter

I'm not an expert, but I know enough to be very, very dangerous.

Life long R&D Engineer (retired after 30+ years in Military Communications, Aerospace Robotics and Transport Automation).
0 Likes
1,659 Views
6 Replies
Replies (6)
Message 2 of 7

BrianEkins
Mentor
Mentor

You're running into the transaction behavior of a command.  Each time you make a change to any of the command inputs, Fusion aborts any previous changes before firing the changed event.  It's only the changes that you made in the last change event or the command execute event that are captured in the command transaction and are saved.

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

pludikar
Collaborator
Collaborator

Hi Brian,

I understand the transactional behaviour, however, the changes I made prior to onExecute were being done in the onChange event.  What am I missing?

 

Peter 

I'm not an expert, but I know enough to be very, very dangerous.

Life long R&D Engineer (retired after 30+ years in Military Communications, Aerospace Robotics and Transport Automation).
0 Likes
Message 4 of 7

BrianEkins
Mentor
Mentor

I misspoke in my last reponse.  It's not the changed event but the execute preview event.  And even then, it only uses what you create in execute preview event if you also set the isValidResult property to True in the event.  If you do that then Fusion uses the last results from the execute preview event and skips the execute event.  Otherwise it's whatever you create in the execute event that's used.  I assume you've read this, but it goes over the different command related events and how they should be used.

http://help.autodesk.com/view/fusion360/ENU/?guid=GUID-3922697A-7BF1-4799-9A5B-C8539DF57051 

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

pludikar
Collaborator
Collaborator

Hi Brian,

Many thanks - that was a useful insight.  I've read the link you provided many times - but it has some details that don't become apparent until you hit an issue.

 

I just found these 2 sentences buried in the command description, nearly at the bottom of the article.

"When you do any creation or edit operations in response to the executePreview event, all of those changes are captured within a single transaction. The next time the executePreview event is fired, Fusion 360 automatically aborts the previous transaction which means the document goes back to its state before any of those changes, and then you perform the creation or edit actions all over again." 

 

To be honest, even these sentences only focus on the executePreview event but miss that this concept applies in general.  I would venture to suggest that a more focused description in the overview section wouldn't go amiss.

 

Thanks again

 

I'm not an expert, but I know enough to be very, very dangerous.

Life long R&D Engineer (retired after 30+ years in Military Communications, Aerospace Robotics and Transport Automation).
0 Likes
Message 6 of 7

BrianEkins
Mentor
Mentor

Every time I use the documentation I find a little typo or something that could be explained better.  I was squeezing in time to write the user manual topics just before a release so there wasn't ever much time for reviews and almost never time to go back and work on old topics.

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

pludikar
Collaborator
Collaborator
It’s no easy task to write a manual, for the most part what you’ve done is highly useful - so kudos. It sounds like AD neither has the personnel nor the inclination to improve on what’s already been done. I can’t say that I’ve observed/noticed any changes to the user manual since I started using it a couple of years ago. Maybe time for AD to make this open to anyone to edit and add to, just as Wikipedia has done.


I'm not an expert, but I know enough to be very, very dangerous.

Life long R&D Engineer (retired after 30+ years in Military Communications, Aerospace Robotics and Transport Automation).
0 Likes