f3d loading change

f3d loading change

espablo
Enthusiast Enthusiast
863 Views
8 Replies
Message 1 of 9

f3d loading change

espablo
Enthusiast
Enthusiast

I wrote a script to load the f3d file I selected and change the user parameter values. The program loads the file 'file1.f3d' or 'file2.f3d' of my choice and immediately updates the user parameters ("width", "depth", "height"). Up to this point everything is OK. The problem is when I only change parameters. I get this message: AttributeError: 'NoneType' object has no attribute 'expression'. I displayed all_parameters.count while changing the value and it turns out that the MyExecutePreviewHandler call deletes the loaded file and user parameters. I don't know if I'm doing something wrong or it's a bug

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

_app = adsk.core.Application.get()
_ui = _app.userInterface

# Global set of event handlers to keep them referenced for the duration of the command
_handlers = []
CMD_ID = "f3dTest"
RES_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "")

old_file = None


# Event handler for the inputChanged event.
class MyInputChangedHandler(adsk.core.InputChangedEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        try:
            eventArgs = adsk.core.InputChangedEventArgs.cast(args)

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


# Event handler for the execute event.
class MyExecuteHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        try:
            eventArgs = adsk.core.InputChangedEventArgs.cast(args)

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


# Event handler for the destroy event.
class MyDestroyHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        adsk.terminate()


class MyExecutePreviewHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args: adsk.core.CommandEventArgs):
        try:
            eventArgs = adsk.core.CommandEventArgs.cast(args)
            inputs = eventArgs.command.commandInputs

            file = inputs.itemById(CMD_ID + "f3d_file_input").selectedItem.name
            _app.log(f"{file = }")
            global old_file
            if old_file != file:
                old_file = file
                load_local_file(RES_FOLDER, file)

            product = _app.activeProduct
            design = adsk.fusion.Design.cast(product)
            all_parameters = design.allParameters
            _app.log(f"{all_parameters.count = }")

            width = str(int(inputs.itemById(CMD_ID + "width").value * 10))
            param_width = all_parameters.itemByName("width")
            _app.log(f"{width = }")

            param_width.expression = width

            depth = str(int(inputs.itemById(CMD_ID + "depth").value * 10))
            param_depth = all_parameters.itemByName("depth")
            param_depth.expression = depth

            height = str(int(inputs.itemById(CMD_ID + "height").value * 10))
            param_height = all_parameters.itemByName("height")
            param_height.expression = height

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


# Event handler for the commandCreated event.
class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        try:

            eventArgs = adsk.core.CommandCreatedEventArgs.cast(args)

            inputs = adsk.core.CommandInputs.cast(eventArgs.command.commandInputs)
            globalUnits = _app.activeProduct.unitsManager.defaultLengthUnits

            f3d_file = adsk.core.DropDownStyles.LabeledIconDropDownStyle
            f3d_file_input = inputs.addDropDownCommandInput(
                CMD_ID + "f3d_file_input", "f3d file", f3d_file
            )
            f3d_file_items = f3d_file_input.listItems

            f3d_file_items.add("file1", True)
            f3d_file_items.add("file2", False)

            width = adsk.core.ValueInput.createByString("1000")
            input_width = inputs.addValueInput(
                CMD_ID + "width",
                "width",
                globalUnits,
                width,
            )

            depth = adsk.core.ValueInput.createByString("2000")
            input_depth = inputs.addValueInput(
                CMD_ID + "depth",
                "depth",
                globalUnits,
                depth,
            )

            height = adsk.core.ValueInput.createByString("3000")
            input_height = inputs.addValueInput(
                CMD_ID + "height",
                "height",
                globalUnits,
                height,
            )

            # Connect to command execute.
            onExecute = MyExecuteHandler()
            eventArgs.command.execute.add(onExecute)
            _handlers.append(onExecute)

            # Connect to input changed.
            onInputChanged = MyInputChangedHandler()
            eventArgs.command.inputChanged.add(onInputChanged)
            _handlers.append(onInputChanged)

            onExecutePreview = MyExecutePreviewHandler()
            eventArgs.command.executePreview.add(onExecutePreview)
            _handlers.append(onExecutePreview)

            # Connect to the command terminate.
            onDestroy = MyDestroyHandler()
            eventArgs.command.destroy.add(onDestroy)
            _handlers.append(onDestroy)

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


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

        # Create a command.
        cmd = _ui.commandDefinitions.itemById("f3dTest")
        if cmd:
            cmd.deleteMe()

        cmd = _ui.commandDefinitions.addButtonDefinition(
            "f3dTest", "f3d load test", "Load Test", ""
        )

        # Connect to the command create event.
        onCommandCreated = MyCommandCreatedHandler()
        cmd.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)

        # Execute the command.
        cmd.execute()

        # Set this so the script continues to run.
        adsk.autoTerminate(False)
    except:
        if _ui:
            _ui.messageBox("Failed:\n{}".format(traceback.format_exc()))


def load_local_file(folder, file_name):
    try:
        load_file = os.path.join(folder, f"{file_name}.f3d")

        if os.path.exists(load_file):
            design = _app.activeProduct
            root = design.rootComponent
            importManager = _app.importManager
            archiveOptions = importManager.createFusionArchiveImportOptions(load_file)

            importManager.importToTarget(archiveOptions, root)

    except:
        _app.log("Failed:\n{}".format(traceback.format_exc()))
0 Likes
Accepted solutions (1)
864 Views
8 Replies
Replies (8)
Message 2 of 9

kandennti
Mentor
Mentor

Hi @espablo -San.

 

I didn't know what it meant and had a hard time reproducing it. I wish you had at least attached the necessary files.

 

The executePreview you are using implements a transaction, which means that each time a condition is met that allows execution, the previous process is discarded and the process starts over from the beginning.

 

The only way to get executePreview to do what you want is to load f3d each time.

・・・
            file = inputs.itemById(CMD_ID + "f3d_file_input").selectedItem.name
            _app.log(f"{file = }")
            # global old_file
            # if old_file != file:
            #     old_file = file
            load_local_file(RES_FOLDER, file)
・・・

Of course, this would not be efficient.

0 Likes
Message 3 of 9

espablo
Enthusiast
Enthusiast

Thank you for your interest. I'm already explaining what effect I wanted to achieve. After running the script, I want to be able to choose which *.f3d file should be loaded into the document. Then I want to change the dimensions and finally save the document or export it to another f3d file. The test program shows that each call to executePreview, e.g. changing one dimension, reloaded the f3d file. That's why I applied the condition:

if old_file != file:
old_file = file
load_local_file(RES_FOLDER, file)


I want to avoid reloading the file when I only change dimensions. I didn't include the f3d files because it works with any file
I am sending the entire test add-on

0 Likes
Message 4 of 9

Jorge_Jaramillo
Collaborator
Collaborator

Hi,

 

This section of the API help is worth reading, as it explains (I believe) what you're looking for:

Jorge_Jaramillo_0-1723296676848.png

 

I hope this can help.

 

Regards,

Jorge Jaramillo

 

0 Likes
Message 5 of 9

kandennti
Mentor
Mentor

@Jorge_Jaramillo -San.

 

args.isValidResult = True


will only prevent the execute event from occurring and will result in an error.

 

Changing the number will trigger the inputChanged event, which will trigger the ExecutePreview event again, discarding any processing from the previous ExecutePreview event.
(The state returns to the state before f3d was loaded.)

 

Message 6 of 9

Jorge_Jaramillo
Collaborator
Collaborator
Hi Kandennti-San,
You are correct. It rolls back the file import operation after any change in the command dialog. This a way I used before but with light operations over the model.

I'm thinking about another option. Let's see if that works. I'll replay.
Message 7 of 9

espablo
Enthusiast
Enthusiast

If you have any solution to this problem, I would be grateful for your advice.

The documentation shows that each time EventPreview is recreated from its initial state, i.e. from the moment of EventPreview registration, e.g. if the document is empty, each call to EventPreview starts from an empty document, but if EventPreview is called when the document already contains some components, it always returns to the state in which the EventPreview was registered, i.e. the included components.
If you have any idea how to bypass this, I would be grateful for your advice and a piece of code. I came up with the idea of ​​running the script in a new document and when a new f3d file is loaded, it closes the old document and opens a new one. So far, I have managed to adapt the code so that it runs in a new document, but I am not yet able to close this document and open a new one when the f3d file is changed.

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

_app = adsk.core.Application.get()
_ui = _app.userInterface

# Global set of event handlers to keep them referenced for the duration of the command
_handlers = []
CMD_ID = "f3dTest"
NEW_CMD_ID = "f3dTest2"
RES_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "")

old_file = "file1"
new_document = None


# Event handler for the inputChanged event.
class MyInputChangedHandler(adsk.core.InputChangedEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        try:
            eventArgs = adsk.core.InputChangedEventArgs.cast(args)

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


# Event handler for the execute event.
class MyExecuteHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        try:
            eventArgs = adsk.core.InputChangedEventArgs.cast(args)

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


# Event handler for the destroy event.
class MyDestroyHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        adsk.terminate()
        global new_document
        if new_document:
            new_document.close(False)
            new_document = None


class MyExecutePreviewHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args: adsk.core.CommandEventArgs):
        try:
            eventArgs = adsk.core.CommandEventArgs.cast(args)
            inputs = eventArgs.command.commandInputs

            file = inputs.itemById(CMD_ID + "f3d_file_input").selectedItem.name
            _app.log(f"{file = }")
            global old_file
            if old_file != file:
                old_file = file
                load_local_file(RES_FOLDER, file)

            product = _app.activeProduct
            design = adsk.fusion.Design.cast(product)
            all_parameters = design.allParameters
            _app.log(f"{all_parameters.count = }")

            width = str(int(inputs.itemById(CMD_ID + "width").value * 10))
            param_width = all_parameters.itemByName("width")
            _app.log(f"{width = }")

            param_width.expression = width

            depth = str(int(inputs.itemById(CMD_ID + "depth").value * 10))
            param_depth = all_parameters.itemByName("depth")
            param_depth.expression = depth

            height = str(int(inputs.itemById(CMD_ID + "height").value * 10))
            param_height = all_parameters.itemByName("height")
            param_height.expression = height

            product = _app.activeProduct
            design = adsk.fusion.Design.cast(product)

            eventArgs.isValidResult = True

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


# Event handler for the commandCreated event.
class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        try:
            app = adsk.core.Application.get()
            ui = app.userInterface

            # Sprawdź, czy komenda z tym identyfikatorem już istnieje i usuń ją, jeśli istnieje.
            existingCmdDef = ui.commandDefinitions.itemById(NEW_CMD_ID)
            if existingCmdDef:
                existingCmdDef.deleteMe()

            global new_document
            new_document = app.documents.add(
                adsk.core.DocumentTypes.FusionDesignDocumentType
            )
            # Teraz można bezpiecznie utworzyć nową komendę.
            newCmdDef = ui.commandDefinitions.addButtonDefinition(
                NEW_CMD_ID, "New Command", "This is a new command."
            )

            # Connect to the new command created event
            newCommandCreated = NewCommandCreatedEventHandler()
            newCmdDef.commandCreated.add(newCommandCreated)
            _handlers.append(newCommandCreated)

            # Execute the new command
            newCmdDef.execute()
        except:
            if _ui:
                _ui.messageBox("Failed:\n{}".format(traceback.format_exc()))


class NewCommandCreatedEventHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        try:

            eventArgs = adsk.core.CommandCreatedEventArgs.cast(args)

            inputs = adsk.core.CommandInputs.cast(eventArgs.command.commandInputs)
            globalUnits = _app.activeProduct.unitsManager.defaultLengthUnits

            f3d_file = adsk.core.DropDownStyles.LabeledIconDropDownStyle
            f3d_file_input = inputs.addDropDownCommandInput(
                CMD_ID + "f3d_file_input", "f3d file", f3d_file
            )
            f3d_file_items = f3d_file_input.listItems

            f3d_file_items.add("file1", True)
            f3d_file_items.add("file2", False)

            load_local_file(RES_FOLDER, old_file)

            width = adsk.core.ValueInput.createByString("1000")
            input_width = inputs.addValueInput(
                CMD_ID + "width",
                "width",
                globalUnits,
                width,
            )

            depth = adsk.core.ValueInput.createByString("2000")
            input_depth = inputs.addValueInput(
                CMD_ID + "depth",
                "depth",
                globalUnits,
                depth,
            )

            height = adsk.core.ValueInput.createByString("3000")
            input_height = inputs.addValueInput(
                CMD_ID + "height",
                "height",
                globalUnits,
                height,
            )

            # Connect to command execute.
            onExecute = MyExecuteHandler()
            eventArgs.command.execute.add(onExecute)
            _handlers.append(onExecute)

            # Connect to input changed.
            onInputChanged = MyInputChangedHandler()
            eventArgs.command.inputChanged.add(onInputChanged)
            _handlers.append(onInputChanged)

            onExecutePreview = MyExecutePreviewHandler()
            eventArgs.command.executePreview.add(onExecutePreview)
            _handlers.append(onExecutePreview)

            # Connect to the command terminate.
            onDestroy = MyDestroyHandler()
            eventArgs.command.destroy.add(onDestroy)
            _handlers.append(onDestroy)

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


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

        # Create a command.
        cmd = _ui.commandDefinitions.itemById("f3dTest")
        if cmd:
            cmd.deleteMe()

        cmd = _ui.commandDefinitions.addButtonDefinition(
            "f3dTest", "f3d load test", "Load Test", ""
        )

        # Connect to the command create event.
        onCommandCreated = MyCommandCreatedHandler()
        cmd.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)

        # Execute the command.
        cmd.execute()

        # Set this so the script continues to run.
        adsk.autoTerminate(False)
    except:
        if _ui:
            _ui.messageBox("Failed:\n{}".format(traceback.format_exc()))


def load_local_file(folder, file_name, new_doc=False):
    app = adsk.core.Application.get()
    try:
        load_file = os.path.join(folder, f"{file_name}.f3d")

        if os.path.exists(load_file):
            design = _app.activeProduct
            root = design.rootComponent
            importManager = _app.importManager
            archiveOptions = importManager.createFusionArchiveImportOptions(load_file)

        newdoc = None
        if new_doc:
            # return importManager.importToNewDocument(archiveOptions)
            newdoc = app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)

        importManager.importToTarget(archiveOptions, root)

        return newdoc
    except:
        _app.log("Failed:\n{}".format(traceback.format_exc()))
0 Likes
Message 8 of 9

Jorge_Jaramillo
Collaborator
Collaborator
Accepted solution

Hi,

 

I have a version that worked in my environment, which is a mix between ExecutePreview, a custom event and re-launch the command every time a file import is required.

 

The idea how it works is the following:

- everytime a change is made in one the fields in the command, an InputChangedEvent is fired; it use its handler to keep the field name that what changed and save it in a global variable.

- following a ExecutePreviewEvent is fired, but since within it's handler there is no way to identify which field was changed, here is where the variable of the previous step comes to work, because it will indicate what to do in this handler from the following options:

a) import a design, if a different file was selected

b) update parameters and let the user visualize the changes, if any of the 3 values was updated

- because the model shouldn't be update inside any of the command handlers, a CustomEvent is create for it.  This way the command won't rollback the model with every update to the fields.

- when the file is selected, the following steps are made:

a) generate a CustomEvent to indicate a file import operation, sending a short json message with the name of the file to be loaded.

the following steps happen in the execution of the custom event:

b) close the current command

c) rolls back the timeline if any previous file was imported before; also remove user parameter if they exists because I notice the timeline's rollback doesn't remove the parameters

d) import the design file

e) look for user parameters recently loaded and update global variables with its values

f) start a new command with parameter-values it has in the global variables

- at the end, if the user choses OK, the model is update with the parameter values from the command fields.

- also notice that the script updates all 3 parameter values everytime one of them is updated, because the ExecutePreviewEvent rolls back any change that was made.

 

Please find attached the script, copy it in your root project, then add the following in your script to invoke it:

from . import _t04_import_design as t04
t04.import_design()

(you can later rename it as you want)

 

Give it a try and let me know if it works for you.

 

Regards,

Jorge Jaramillo

 

Message 9 of 9

espablo
Enthusiast
Enthusiast

Jorge Jaramillo you are great! This is exactly what I expected