From 5932a24c67a64f64c63949acbd2042f4752999d7 Mon Sep 17 00:00:00 2001 From: "t.bassi" Date: Fri, 11 Apr 2025 17:51:43 +0200 Subject: [PATCH] Initial Commit --- .vscode/launch.json | 20 + .vscode/settings.json | 5 + AddInIcon.svg | 2576 +++++++++++++++++ LocalSave.py | 142 + README.md | 24 +- commands/__init__.py | 30 + commands/commandDialog/__init__.py | 0 commands/commandDialog/entry.py | 158 + commands/commandDialog/resources/16x16.png | Bin 0 -> 415 bytes commands/commandDialog/resources/32x32.png | Bin 0 -> 701 bytes commands/commandDialog/resources/64x64.png | Bin 0 -> 1077 bytes commands/paletteSend/__init__.py | 0 commands/paletteSend/entry.py | 149 + commands/paletteSend/resources/16x16.png | Bin 0 -> 474 bytes commands/paletteSend/resources/32x32.png | Bin 0 -> 1067 bytes commands/paletteSend/resources/64x64.png | Bin 0 -> 1972 bytes commands/paletteShow/__init__.py | 0 commands/paletteShow/entry.py | 193 ++ commands/paletteShow/resources/16x16.png | Bin 0 -> 340 bytes commands/paletteShow/resources/32x32.png | Bin 0 -> 587 bytes commands/paletteShow/resources/64x64.png | Bin 0 -> 1165 bytes .../paletteShow/resources/html/index.html | 39 + .../resources/html/static/palette.js | 48 + config.py | 21 + resources/32x32-panel.png | Bin 0 -> 397 bytes resources/32x32-saveas.png | Bin 0 -> 467 bytes 26 files changed, 3404 insertions(+), 1 deletion(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 AddInIcon.svg create mode 100644 LocalSave.py create mode 100644 commands/__init__.py create mode 100644 commands/commandDialog/__init__.py create mode 100644 commands/commandDialog/entry.py create mode 100644 commands/commandDialog/resources/16x16.png create mode 100644 commands/commandDialog/resources/32x32.png create mode 100644 commands/commandDialog/resources/64x64.png create mode 100644 commands/paletteSend/__init__.py create mode 100644 commands/paletteSend/entry.py create mode 100644 commands/paletteSend/resources/16x16.png create mode 100644 commands/paletteSend/resources/32x32.png create mode 100644 commands/paletteSend/resources/64x64.png create mode 100644 commands/paletteShow/__init__.py create mode 100644 commands/paletteShow/entry.py create mode 100644 commands/paletteShow/resources/16x16.png create mode 100644 commands/paletteShow/resources/32x32.png create mode 100644 commands/paletteShow/resources/64x64.png create mode 100644 commands/paletteShow/resources/html/index.html create mode 100644 commands/paletteShow/resources/html/static/palette.js create mode 100644 config.py create mode 100644 resources/32x32-panel.png create mode 100644 resources/32x32-saveas.png diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1c69300 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + "version": "0.2.0", + "configurations": [{ + "name": "Python: Attach", + "type": "python", + "request": "attach", + "pathMappings": [{ + "localRoot": "${workspaceRoot}", + "remoteRoot": "${workspaceRoot}" + }], + "osx": { + "filePath": "${file}" + }, + "windows": { + "filePath": "${file}" + }, + "port": 9000, + "host": "localhost" + }] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f1a38ec --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.autoComplete.extraPaths": ["C:/Users/t.bassi/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.analysis.extraPaths": ["C:/Users/t.bassi/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.defaultInterpreterPath": "C:/Users/t.bassi/AppData/Local/Autodesk/webdeploy/production/b4ebb90d69b5fc8cf013f75341ee2a1192c9da8e/Python/python.exe" +} \ No newline at end of file diff --git a/AddInIcon.svg b/AddInIcon.svg new file mode 100644 index 0000000..dc82408 --- /dev/null +++ b/AddInIcon.svg @@ -0,0 +1,2576 @@ + + + + + + Fusion Add-In Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LocalSave.py b/LocalSave.py new file mode 100644 index 0000000..4d5b658 --- /dev/null +++ b/LocalSave.py @@ -0,0 +1,142 @@ +import adsk.core, adsk.fusion, adsk.cam, traceback +import os +import uuid + +app = None +ui = None +handlers = [] + +def onExecuteSaveAs(args): + try: + doc = app.activeDocument + design = app.activeProduct + + if not hasattr(design, 'exportManager'): + ui.messageBox("Current document cannot be exported.") + return + + export_mgr = design.exportManager + + # Show file save dialog + file_dialog = ui.createFileDialog() + file_dialog.title = "Export As" + file_dialog.filter = "Fusion 360 Files (*.f3d)" + + if doc.dataFile: + initial_dir = os.path.dirname(doc.dataFile.name) + file_dialog.initialDirectory = initial_dir + + if file_dialog.showSave() != adsk.core.DialogResults.DialogOK: + return + + file_path = file_dialog.filename + file_ext = os.path.splitext(file_path)[1].lower() + + # Verify file extension + if file_ext not in ['.f3d']: + ui.messageBox("Invalid file type. Please export as .f3d") + return + + # Create appropriate export options based on file extension + options = export_mgr.createFusionArchiveExportOptions(file_path) + + export_mgr.execute(options) + + ui.messageBox(f"File exported to: {file_path}") + except Exception as e: + if ui: + ui.messageBox(f"Failed to export file: {e}") + +class SaveCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): + def __init__(self, execute_function): + super().__init__() + self.execute_function = execute_function + + def notify(self, args): + try: + command = args.command + on_execute_handler = SaveCommandExecuteHandler(self.execute_function) + command.execute.add(on_execute_handler) + handlers.append(on_execute_handler) + except Exception as e: + if ui: + ui.messageBox(f"Failed to create command: {e}") + +class SaveCommandExecuteHandler(adsk.core.CommandEventHandler): + def __init__(self, execute_function): + super().__init__() + self.execute_function = execute_function + + def notify(self, args): + try: + self.execute_function(args) + except Exception as e: + if ui: + ui.messageBox(f"Failed to execute command: {e}") + +def run(context): + global app, ui + app = adsk.core.Application.get() + ui = app.userInterface + + try: + tab_id = f'LocalSaveTab_{uuid.uuid4()}' + panel_id = f'LocalSavePanel_{uuid.uuid4()}' + save_as_command_id = f"SaveAsLocalCommand_{uuid.uuid4()}" + + current_dir = os.path.dirname(os.path.abspath(__file__)) + save_as_icon_path = os.path.join(current_dir, 'resources', '32x32-saveas.png') + panel_icon_path = os.path.join(current_dir, 'resources', '32x32-panel.png') + + workspace = ui.workspaces.itemById('FusionSolidEnvironment') + + for tab in workspace.toolbarTabs: + if tab.id.startswith('LocalSaveTab'): + tab.deleteMe() + break + + tab = workspace.toolbarTabs.add(tab_id, 'Local Save') + panel = tab.toolbarPanels.add(panel_id, 'Save Options', panel_icon_path) + + try: + save_as_command_def = ui.commandDefinitions.addButtonDefinition( + save_as_command_id, + 'Save', + 'Save the file locally. Use this for initial save and override of file.', + save_as_icon_path + ) + + save_as_created_handler = SaveCommandCreatedHandler(onExecuteSaveAs) + save_as_command_def.commandCreated.add(save_as_created_handler) + + handlers.append(save_as_created_handler) + + panel.controls.addCommand(save_as_command_def) + + except Exception as e: + ui.messageBox(f"Error creating commands: {e}") + return + + except Exception as e: + if ui: + ui.messageBox(f"Failed to run the plugin: {e}") + +def stop(context): + try: + workspace = ui.workspaces.itemById('FusionSolidEnvironment') + if workspace: + tab = workspace.toolbarTabs.itemById('LocalSaveTab') + if tab: + tab.deleteMe() + + command_definitions_to_delete = [] + for cmd_def in ui.commandDefinitions: + if cmd_def.id.startswith('SaveAsLocalCommand_'): + command_definitions_to_delete.append(cmd_def) + + for cmd_def in command_definitions_to_delete: + cmd_def.deleteMe() + + except Exception as e: + if ui: + ui.messageBox(f"Failed to stop the plugin: {e}") \ No newline at end of file diff --git a/README.md b/README.md index 8420fc5..383113a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# Fusion360-LocalSave +Fusion 360 Local Save Extension +This Fusion 360 extension brings back easy local saving by letting you export .f3d files directly to your computer, bypassing the cloud-only model almost completely. Conversion and native export are still needed for f3z archives. +## Features +Adds a "Local Save" tab with a "Save" button + +Saves the current document as a local .f3d file + +Ideal for backups, version control, and offline work + +## How to Use +Copy the script folder into your Fusion 360 Add-Ins folder: +`~/Library/Application Support/Autodesk/Autodesk Fusion 360/API/AddIns` (macOS) + +`C:\Users\\AppData\Roaming\Autodesk\Autodesk Fusion 360\API\AddIns\` (Windows) + +Open Fusion 360 → Tools → Scripts and Add-Ins + +Find the extension under the `My Add-Ins` tab and click Run + +Use the new "Local Save" tab in the toolbar to export files + +## Output +Exports files in .f3d format, compatible with Fusion 360 for re-import or sharing. \ No newline at end of file diff --git a/commands/__init__.py b/commands/__init__.py new file mode 100644 index 0000000..0499301 --- /dev/null +++ b/commands/__init__.py @@ -0,0 +1,30 @@ +# Here you define the commands that will be added to your add-in. + +# TODO Import the modules corresponding to the commands you created. +# If you want to add an additional command, duplicate one of the existing directories and import it here. +# You need to use aliases (import "entry" as "my_module") assuming you have the default module named "entry". +from .commandDialog import entry as commandDialog +from .paletteShow import entry as paletteShow +from .paletteSend import entry as paletteSend + +# TODO add your imported modules to this list. +# Fusion will automatically call the start() and stop() functions. +commands = [ + commandDialog, + paletteShow, + paletteSend +] + + +# Assumes you defined a "start" function in each of your modules. +# The start function will be run when the add-in is started. +def start(): + for command in commands: + command.start() + + +# Assumes you defined a "stop" function in each of your modules. +# The stop function will be run when the add-in is stopped. +def stop(): + for command in commands: + command.stop() \ No newline at end of file diff --git a/commands/commandDialog/__init__.py b/commands/commandDialog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commands/commandDialog/entry.py b/commands/commandDialog/entry.py new file mode 100644 index 0000000..5a4c514 --- /dev/null +++ b/commands/commandDialog/entry.py @@ -0,0 +1,158 @@ +import adsk.core +import os +from ...lib import fusionAddInUtils as futil +from ... import config +app = adsk.core.Application.get() +ui = app.userInterface + + +# TODO *** Specify the command identity information. *** +CMD_ID = f'{config.COMPANY_NAME}_{config.ADDIN_NAME}_cmdDialog' +CMD_NAME = 'Command Dialog Sample' +CMD_Description = 'A Fusion Add-in Command with a dialog' + +# Specify that the command will be promoted to the panel. +IS_PROMOTED = True + +# TODO *** Define the location where the command button will be created. *** +# This is done by specifying the workspace, the tab, and the panel, and the +# command it will be inserted beside. Not providing the command to position it +# will insert it at the end. +WORKSPACE_ID = 'FusionSolidEnvironment' +PANEL_ID = 'SolidScriptsAddinsPanel' +COMMAND_BESIDE_ID = 'ScriptsManagerCommand' + +# Resource location for command icons, here we assume a sub folder in this directory named "resources". +ICON_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources', '') + +# Local list of event handlers used to maintain a reference so +# they are not released and garbage collected. +local_handlers = [] + + +# Executed when add-in is run. +def start(): + # Create a command Definition. + cmd_def = ui.commandDefinitions.addButtonDefinition(CMD_ID, CMD_NAME, CMD_Description, ICON_FOLDER) + + # Define an event handler for the command created event. It will be called when the button is clicked. + futil.add_handler(cmd_def.commandCreated, command_created) + + # ******** Add a button into the UI so the user can run the command. ******** + # Get the target workspace the button will be created in. + workspace = ui.workspaces.itemById(WORKSPACE_ID) + + # Get the panel the button will be created in. + panel = workspace.toolbarPanels.itemById(PANEL_ID) + + # Create the button command control in the UI after the specified existing command. + control = panel.controls.addCommand(cmd_def, COMMAND_BESIDE_ID, False) + + # Specify if the command is promoted to the main toolbar. + control.isPromoted = IS_PROMOTED + + +# Executed when add-in is stopped. +def stop(): + # Get the various UI elements for this command + workspace = ui.workspaces.itemById(WORKSPACE_ID) + panel = workspace.toolbarPanels.itemById(PANEL_ID) + command_control = panel.controls.itemById(CMD_ID) + command_definition = ui.commandDefinitions.itemById(CMD_ID) + + # Delete the button command control + if command_control: + command_control.deleteMe() + + # Delete the command definition + if command_definition: + command_definition.deleteMe() + + +# Function that is called when a user clicks the corresponding button in the UI. +# This defines the contents of the command dialog and connects to the command related events. +def command_created(args: adsk.core.CommandCreatedEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME} Command Created Event') + + # https://help.autodesk.com/view/fusion360/ENU/?contextId=CommandInputs + inputs = args.command.commandInputs + + # TODO Define the dialog for your command by adding different inputs to the command. + + # Create a simple text box input. + inputs.addTextBoxCommandInput('text_box', 'Some Text', 'Enter some text.', 1, False) + + # Create a value input field and set the default using 1 unit of the default length unit. + defaultLengthUnits = app.activeProduct.unitsManager.defaultLengthUnits + default_value = adsk.core.ValueInput.createByString('1') + inputs.addValueInput('value_input', 'Some Value', defaultLengthUnits, default_value) + + # TODO Connect to the events that are needed by this command. + 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.validateInputs, command_validate_input, local_handlers=local_handlers) + futil.add_handler(args.command.destroy, command_destroy, local_handlers=local_handlers) + + +# This event handler is called when the user clicks the OK button in the command dialog or +# is immediately called after the created event not command inputs were created for the dialog. +def command_execute(args: adsk.core.CommandEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME} Command Execute Event') + + # TODO ******************************** Your code here ******************************** + + # Get a reference to your command's inputs. + inputs = args.command.commandInputs + text_box: adsk.core.TextBoxCommandInput = inputs.itemById('text_box') + value_input: adsk.core.ValueCommandInput = inputs.itemById('value_input') + + # Do something interesting + text = text_box.text + expression = value_input.expression + msg = f'Your text: {text}
Your value: {expression}' + ui.messageBox(msg) + + +# This event handler is called when the command needs to compute a new preview in the graphics window. +def command_preview(args: adsk.core.CommandEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME} Command Preview Event') + inputs = args.command.commandInputs + + +# This event handler is called when the user changes anything in the command dialog +# allowing you to modify values of other inputs based on that change. +def command_input_changed(args: adsk.core.InputChangedEventArgs): + changed_input = args.input + inputs = args.inputs + + # General logging for debug. + futil.log(f'{CMD_NAME} Input Changed Event fired from a change to {changed_input.id}') + + +# This event handler is called when the user interacts with any of the inputs in the dialog +# which allows you to verify that all of the inputs are valid and enables the OK button. +def command_validate_input(args: adsk.core.ValidateInputsEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME} Validate Input Event') + + inputs = args.inputs + + # Verify the validity of the input values. This controls if the OK button is enabled or not. + valueInput = inputs.itemById('value_input') + if valueInput.value >= 0: + args.areInputsValid = True + else: + args.areInputsValid = False + + +# This event handler is called when the command terminates. +def command_destroy(args: adsk.core.CommandEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME} Command Destroy Event') + + global local_handlers + local_handlers = [] diff --git a/commands/commandDialog/resources/16x16.png b/commands/commandDialog/resources/16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..03babdc056577fea50543b03d15c480d88c89f4c GIT binary patch literal 415 zcmV;Q0bu@#P)KA9k4D0+<1qc$SunL_18+0mJ|no~1oAf13dfUQ*u%01+5>;@Y z)*+9ui?QG#{8m7wjtRZ_7;GpXT!7Xr);$MsL(ih)&$~U6?q5HKp5f86w1-XuNy<+m ziM)=y6Z(%>u-E%a+b3{!z~(0Dcce}w4hGl2o zy!Y*!_hU9;+cvg|2T^S6rX^)vFJ%&8b=}+Q=c_!Rn^p=y6@XDMWu{S-Z=yr$0lx7w zfD`uQ?@^2g>JWm^v+>X~-24*Dhu;%x1PJ3R3<2zK=m1JqAmKlP zDq$QUjH_f1xeXV70tRP!SLksYasu2n+8vPyj)4_;EL;NEjmh{7Ku(s5R3S27}iUf9JR=g?(3!*!+J;9 zUT_wma2h1zM_2-vG0C{tdN&M9fIR<@GLZ?KsX8NH3l#%YA*w1m9+ieEZyG*X{=mcA zg}c6U`{&Ev_0^}zWO8K~#>XH6%#Gg_#E1M#R{@vf?{50-efxleXHSKVola*wo6X)d z8jW}Cohn3AUOuU6$P8U52s$Vhi^5#D+huOKD3{A%Bm@GUJ3(B1fPzC}jX=PKBo-VW zYmdg`k<&K@gTY(B2UHi5Sa5Xo;xPa=P^;C17+}rfd7M|WX0wUWXe0>a^LgjFMu4T3 zZdzI#m(ql7{{(Pf(=;p=3qdfQPNU!Ni&-Ec;l0VQVd$nMB~UMA+;&^(-TY>OoXuuc zo|31bhs6KCB90w|u j%CBTHsr;pz&*%6H8=VB7i~>0q00000NkvXXu0mjfNzF8J literal 0 HcmV?d00001 diff --git a/commands/commandDialog/resources/64x64.png b/commands/commandDialog/resources/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..dd285fd1c9a47ff154e426da5c90fcdd96239c2c GIT binary patch literal 1077 zcmV-51j_q~P)ZWvYv= zx{iE7uCu6eC_iv#i2_oDd_h3U2V4qV&?G1gA|e+p8iVi7XYZ_c_iU3VU16PXXWqN@ z?9A+XW-u7wA!GJIvj_0lmLWfkSW_)^9A4)TT$bSUz1i%S7VI6)LtqJ=8o~!!779c@B##BTgKfaKCsQSSY zAccGkt0L(1Lpl@8Fq*{WXiOky8UVIrog(z;+3p6e^Q)*IlOQF4q|v1DB@vW}_)E#( zW#It8o*>2aBUgl60hr{=hFlRc1)%ABk_a@l6JcTy^uuVr2ILV3#z|jJ6o3Lx!p^A!-OI|=a6RemBQsf0#sW1)tPT4Ix zkR~snQVxss^n(T}KLGR-*Gp8@6~)9QFAXXJ>=krSq_yp|#KaFHA;LB-TdMiT9*rRd zR$H8sqo0b7oe9Te3KX&(b;MNus$f&v&$~x}+qY-nP2f7Lz?1|%4t?D)7bTX)8G~s7-qHr9GAI)?)||C*KA=q`RM9orn&+0o z9fdtUs1`&8>>W@cl?$!_VyEbvf<6Td zj*eLOZ)|J~U1tg)c3J%}LYYN4K0apMKPrHU6Cxtzd_}*})fg4&^9vK_6Qq_w#!N^a zNfV!r=98iw?{L_M_4Re;u)~n91tdgBJ&#A%+kP0a1PEM5s+~4ErMXZ!A(A!?rO)P; z4eVklA;M+V$qOjGF+z{SDdh=`X_7a(3}!))77YxQeSe)Wz6@qz+T?Y>+KZE>U~L&J vE37Vqb%eDUVC!9AE-Lr>yytoP90u?YjV+lgrL2(V00000NkvXXu0mjf+LYiK literal 0 HcmV?d00001 diff --git a/commands/paletteSend/__init__.py b/commands/paletteSend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commands/paletteSend/entry.py b/commands/paletteSend/entry.py new file mode 100644 index 0000000..dfdc9d5 --- /dev/null +++ b/commands/paletteSend/entry.py @@ -0,0 +1,149 @@ +import json +import adsk.core +import os +from ...lib import fusionAddInUtils as futil +from ... import config + +app = adsk.core.Application.get() +ui = app.userInterface + +# TODO ********************* Change these names ********************* +CMD_ID = f'{config.COMPANY_NAME}_{config.ADDIN_NAME}_palette_send' +CMD_NAME = 'Send to Palette' +CMD_Description = 'Send some information to the palette' +IS_PROMOTED = False + +# Using "global" variables by referencing values from /config.py +PALETTE_ID = config.sample_palette_id + +# TODO *** Define the location where the command button will be created. *** +# This is done by specifying the workspace, the tab, and the panel, and the +# command it will be inserted beside. Not providing the command to position it +# will insert it at the end. +WORKSPACE_ID = 'FusionSolidEnvironment' +PANEL_ID = 'SolidScriptsAddinsPanel' +COMMAND_BESIDE_ID = 'ScriptsManagerCommand' + +# Resource location for command icons, here we assume a sub folder in this directory named "resources". +ICON_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources', '') + +# Local list of event handlers used to maintain a reference so +# they are not released and garbage collected. +local_handlers = [] + + +# Executed when add-in is run. +def start(): + # Create a command Definition. + cmd_def = ui.commandDefinitions.addButtonDefinition(CMD_ID, CMD_NAME, CMD_Description, ICON_FOLDER) + + # Add command created handler. The function passed here will be executed when the command is executed. + futil.add_handler(cmd_def.commandCreated, command_created) + + # ******** Add a button into the UI so the user can run the command. ******** + # Get the target workspace the button will be created in. + workspace = ui.workspaces.itemById(WORKSPACE_ID) + + # Get the panel the button will be created in. + panel = workspace.toolbarPanels.itemById(PANEL_ID) + + # Create the button command control in the UI after the specified existing command. + control = panel.controls.addCommand(cmd_def, COMMAND_BESIDE_ID, False) + + # Specify if the command is promoted to the main toolbar. + control.isPromoted = IS_PROMOTED + + +# Executed when add-in is stopped. +def stop(): + # Get the various UI elements for this command + workspace = ui.workspaces.itemById(WORKSPACE_ID) + panel = workspace.toolbarPanels.itemById(PANEL_ID) + command_control = panel.controls.itemById(CMD_ID) + command_definition = ui.commandDefinitions.itemById(CMD_ID) + + # Delete the button command control + if command_control: + command_control.deleteMe() + + # Delete the command definition + if command_definition: + command_definition.deleteMe() + + +# Event handler that is called when the user clicks the command button in the UI. +# To have a dialog, you create the desired command inputs here. If you don't need +# a dialog, don't create any inputs and the execute event will be immediately fired. +# You also need to connect to any command related events here. +def command_created(args: adsk.core.CommandCreatedEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME} Command Created Event') + + # TODO Create the event handlers you will need for this instance of the command + 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) + + # Create the user interface for your command by adding different inputs to the CommandInputs object + # https://help.autodesk.com/view/fusion360/ENU/?contextId=CommandInputs + inputs = args.command.commandInputs + + # TODO ******************************** Define your UI Here ******************************** + + # Simple text input box + inputs.addTextBoxCommandInput('text_input', 'Text Message', 'Enter some text', 1, False) + + # To create a numerical input with units, we need to get the current units and create a "ValueInput" + # https://help.autodesk.com/view/fusion360/ENU/?contextId=ValueInput + users_current_units = app.activeProduct.unitsManager.defaultLengthUnits + default_value = adsk.core.ValueInput.createByString(f'1 {users_current_units}') + inputs.addValueInput('value_input', 'Value Message', users_current_units, default_value) + + +# This function will be called when the user hits the OK button in the command dialog +def command_execute(args: adsk.core.CommandEventArgs): + # General logging for debug + futil.log(f'{CMD_NAME} Command Execute Event') + + inputs = args.command.commandInputs + + # TODO ******************************** Your code here ******************************** + + # Get a reference to your command's inputs + text_input: adsk.core.TextBoxCommandInput = inputs.itemById('text_input') + value_input: adsk.core.ValueCommandInput = inputs.itemById('value_input') + + # Construct a message + message_action = 'updateMessage' + message_data = { + 'myValue': f'{value_input.value} cm', + 'myExpression': value_input.expression, + 'myText': text_input.formattedText + } + # JSON strings are a useful way to translate between javascript objects and python dictionaries + message_json = json.dumps(message_data) + + # Get a reference to the palette and send the message to the palette javascript + palette = ui.palettes.itemById(PALETTE_ID) + palette.sendInfoToHTML(message_action, message_json) + + +# This function will be called when the command needs to compute a new preview in the graphics window +def command_preview(args: adsk.core.CommandEventArgs): + inputs = args.command.commandInputs + futil.log(f'{CMD_NAME} Command Preview Event') + + +# This function will be called when the user changes anything in the command dialog +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}') + + +# This event handler is called when the command terminates. +def command_destroy(args: adsk.core.CommandEventArgs): + global local_handlers + local_handlers = [] + futil.log(f'{CMD_NAME} Command Destroy Event') diff --git a/commands/paletteSend/resources/16x16.png b/commands/paletteSend/resources/16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..c18250a935fb739d5651b499e03ede8a65f0e9b4 GIT binary patch literal 474 zcmV<00VV#4P)K+Q5MPI7Ji_pD7rS1AX{v<5J6%g zscbC$0a_T?EiDzIVAvTaA2Z{z7>S8SOb#5*xrck+``lLnrhzD}#kq*20aPF=BC%<0 zwdw6YsV#u20O4@BMS#~(0dl!qOG}EP)C)My6SnO%_oCYNp1`tfils7h*)9O~C-e^m zI=vn51ZkQY#WjHv>pc^K-VXo_j8PyH5?m;$~w*vNkl&LL&Z|T3T-5HDV18fX^oZCv_?%V z>j$>d7V_eQv{1A)U_Yp}YJ-%bplN8@qQ$h$=O#90=YIIGGm~s~b~j799=LG1bC~m+ z|2gN*y#`(&@mcZL>ab~ap z1(Z?I{+)5ZcGCJ?EIED?Rq~J&In-J@DwB@+!_Z5V0tSF#=5)a{@F#GGd6xzclR&~A*$T!2Lkk5*_O?t~_iq0% zF>t)o4t|Gun^7U`p^9bej#V#jnW$L0_9XBWP{X_wYppQ~1W*Ai1s()ON#@*@{r>>) l1EnNuYKCO(q{*(e=06uyS_{OSTi^fy002ovPDHLkV1h6_?}z{Z literal 0 HcmV?d00001 diff --git a/commands/paletteSend/resources/64x64.png b/commands/paletteSend/resources/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..ed1ae276ca4eb5a06999f3e340cabec2f571653a GIT binary patch literal 1972 zcmV;l2TS;gP)P|5NA&mlJKR z&5-rUiU3anqGn@N%V=cMnVd%X^-719(I87e8jm4u9|9^6O=}qzGU=Q~`SnUiTFZdE zsVdSpPXZtt1raL(28T|CZD(jOBOrqG$y3nn1SkOe_ue0-ZOe@%Q@WaeV0vZ-P18fO zXgQ#&QC6r0t~s~OKOh7chUeU&;vsyP??8Zf4-ini{mujk*NIbAARqz%9|9PLdjuT+ zb1nb4m_2Rt8jUZ1bk3iJAtU$@@Q47r^H#mmBBl%k_|pKV_7DBK!B-!C@$_9O*WZ?1 z1Dx95z9HOU;bmi}8nCf7&A&!#a_Ua)Kk>oY;;kqFUO^go2--i!a0CeRY0yt6PEU`UHf}8X z{8Hfd#Og*syrTzzF^ zO$}Ibe-Ox5$T_eypc+af5?xSZt(}Jkjx?&V*3Ng*H}2^D`tC1^Tmn2va3P>NN+c4| z0Sp1(?H_1TYvbF`&)smx?ymdpHXV1}8e^d>S>S3xShrfh^gKM77-;%t&z>ExUr^J) zV}Oo-0=z|V_>Ezb$>d@^9*@(}-Vwl!;4pyE85s%RNena{*t_SBrklPrKk@gUJiOGo zzBRz^JU}v;ylNUCZ<0`iAs0bLry+F(4n95599>cO>E#<(INDe0ohJ@?>0AB<+>?ioZ`s-cWzkKTFrx#Xj+o{Ilw-$Yo zJX}N(^_#Z=0z>4!oVheDhr`29^~WFI*R!joy-SR}akLk?WiE#V-x|=|)WZ10SWew3 z0@hu(-qrY6tZrdOZSDN__Xir6^t1>}Tq%4zH8LfS9XOO|ec~HE_q4Qk;Y5P(A+YJg zHx(T7`zA?IvgzyJmwcV-3nNof8+yN&XzknYOhSPOf+Qi#g2}#agkLUB!T4p^|74<7 ziPYTF(%#hvtiaqr>6`^ZE;3$(q6^TB@XLiM7{3Dh`XmX+jp0#1anu0eGYj~I5Hj)% zV|m^zU%MJsUjt9|?in{Sv%f+TkOs6u0ua6y6oSBtfH2Xv8dlfB;csIvlU{zf$qYE+uHe4ILzlm8Xc=>3nhx%Ih;eqayJ~#Htl`WrXy+7+#-zpES_cON`j^UvIsI@Z)fQym<6 zq&ua}jh#uI8#)3^0u#U#Fb8PXF7a!CRX`)q0MsGJiDQ;15gL%Oeb(5@3jC36*&S`! zy8D^0K60eNPJre7vyUx0|05uUBw!ktMQVVdB11t37J$oUAM+&`wG?!u^HvC|<~3@_ z{%@gJM9gznVy=Q)V!E~g8e)#lr+^vcozIvy2#Wv>NdV@LDn;zI_X+zD5N7}O;^$gc z)YgF_2@qN5k3H__y#3DS%ml54gS72)Q6+=>+Z2!_XdowV z&-ZmtYI9>}y_}DC^oS*w#g`Kgb?+_pR4ZCFePR04^L=~n)aNH&N}U@zhD>pe&O3Ef zt^W|UG{8#TF{Cf^NXKXJ2zxuCVOts!>;eihbIl>s_+|6^lFq|b9s&VirS5s;P>7nx zGDycW6yZEn?+~y@K-B!SCUo|>eo-A&E9@y$A?vWOZ1z76&6OwtiJT<>0000Action: {message_action}
arg1: {arg1}
arg2: {arg2}' + ui.messageBox(msg) + + # Return value. + now = datetime.now() + currentTime = now.strftime('%H:%M:%S') + html_args.returnData = f'OK - {currentTime}' + + +# This event handler is called when the command terminates. +def command_destroy(args: adsk.core.CommandEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME}: Command destroy event.') + + global local_handlers + local_handlers = [] diff --git a/commands/paletteShow/resources/16x16.png b/commands/paletteShow/resources/16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..f1b8e5705f6c5544667a8ea92580d82360687e34 GIT binary patch literal 340 zcmV-a0jvIrP);xMdD{U>TEPR43wo$aQ@EP(1mX;P~ zGMS&mVj&Yl2)nu6z&(fijR%Z?9^LX80KK9p&PP|bbJNd{_XGe@6m=ZHR1n8;C)o46 zsleJCo9A=E3%>6bXl>BOa5-Kn3r|DQDp(8xC&;uxN(DH9NEO#F8;$V8*$-ie6DXw- zsr*+GspPKz-6#+#_`X+JkZBSjx!L_HOG*0$wG)))H<7JCJ literal 0 HcmV?d00001 diff --git a/commands/paletteShow/resources/32x32.png b/commands/paletteShow/resources/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..a61a47e4710bdc2aedde29ee1f9f4f0b25d8c82a GIT binary patch literal 587 zcmV-R0<`^!P)do&Mr37>Z`J(s>*sC z{uEW5lPmy5Q8X}Nppp-uDs>t_R9Y#xw=v&xdUjzq8Nfa*I{-wZJ0Rum5b275DAx9; zAbh+3?!X|?C+C0s3qY{8f0g0ez)sSn$Cz4c8X!cV55O1b z+Uqxigl}EH)&TTv!$|~S;sI`4xjYCU_}2ivMd(mZaP`u~)&!{v)ELuKgsMzj128Ky z#^5w^66OGio`eD5?T7a*$JOzB)jy9n!+EC8H1eyTM=s&=hE1=F2 z78efB9>8nu1rT6+>FAODdx2(b7T5;F0Q0~h;2`xoF^ByEz5-ti^&4%@ix)AkgeD&1 Z=Wl;YQq&g|YVQC5002ovPDHLkV1iU;1q1*9 literal 0 HcmV?d00001 diff --git a/commands/paletteShow/resources/64x64.png b/commands/paletteShow/resources/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd5dfcb2d51b5651ff4fcc8a4f757af32a172a5 GIT binary patch literal 1165 zcmV;81akX{P)% z&x;&I6vscc+pd`ng1BxNLQoO|akDB}6N3aJNOV0Y2oeY$)=NSlC&i2Zh2SY5@nQrC zFIn=)32v$-uuq`Rozv} z7{k*H4?roU00o=@PDG!Z2m@RLE&#?DW4!2w0(Jl&UcB_{r{mJD3ap&^$OC=^9syef zc)(7e73hG@>N+1Uzq2*y%GEzASF{YYcg+E`2yOL&UQEC=FbnMP`#$|%e_-J7^`ye@ z`w)8iEbtJb8~{uKGl19g`#kRV1_lmaPbzwTA3~dF2yNaXfJmTX54d>gO7$9+jK~8N zk?}A{003Wqd8R^@#*YL68ygStgJ2xGln!rt8qb>^lnsB$7=zKxY>v!S(yk-{fNp4a zC|Q*t0Apxudo0)`gaGKKhJav}m;LtktA zoISR=O%=<9eF&KB5EwQ-mgS8bH|#>d80RD?jr;1C=bh$q!8f1$n|a@o0QV{&jaxoY zU0CGlU3wLe#=XD4>ok|!?`Pj1SqLob>T!2&Pq~DLBke_s=EQXB8Rvsh| zII?u8_(YD#O5RG@hJZHcKxY!rYPHzh+)T)HP~RM&l)TJOO@dhjteiS2%Ilm2?L8ps z1e^9yC?p^$7d;@z@qkH00S_<@_JBeJq%-jQ^&8xO@L)i;XZLR2d-wPVR4oTesbZaA z76HS?$Fh1SL2ClY^?)=;b@->tmkZD1iqB5U)d>nDK^-lJT?mkt(TfKc3eV$;tYy4H zA@#RTIUo&^2P_W=kptT8Hl0oO(520#YCF@=JRQ z&*O>_b%N1m$qWMKUU-r9fBsI$>Rvgl-wo#FfW^hvMR}b!SA{vistS->1ttkmiq+V%V3>hT?T2e2S{0PD)Aj;SDADeBfsz!li;DnSIXCD9LPz68o2%D8~dLF zo<;DE35NzA1Gl5kz!L&A&;x!3{sQKJXDU{(Z1jP3;2xo^8lDgk09{};x?U$dOV}g? zzy_g>-RK%+j6o?iMR=faC*h&Ot#fq^LhR*T;6LDzF-FG(#D}Ln!hgPL#>dcv|GZCh fcv>~F + + + + Title + + + +
+ +

Fusion Palette Sample

+ +

+ +

Send Data to HTML Event Handler

+
+ +

+ +
+ +

HTML Event Response Value:

+
Response
+ +

Message from "Send to Palette" Command

+
+

Message from Fusion

+

+
+ +
+ + diff --git a/commands/paletteShow/resources/html/static/palette.js b/commands/paletteShow/resources/html/static/palette.js new file mode 100644 index 0000000..dacdafc --- /dev/null +++ b/commands/paletteShow/resources/html/static/palette.js @@ -0,0 +1,48 @@ +function getDateString() { + const today = new Date(); + const date = `${today.getDate()}/${today.getMonth() + 1}/${today.getFullYear()}`; + const time = `${today.getHours()}:${today.getMinutes()}:${today.getSeconds()}`; + return `Date: ${date}, Time: ${time}`; +} + +function sendInfoToFusion() { + const args = { + arg1: document.getElementById("sampleData").value, + arg2: getDateString() + }; + + // Send the data to Fusion as a JSON string. The return value is a Promise. + adsk.fusionSendData("messageFromPalette", JSON.stringify(args)).then((result) => + document.getElementById("returnValue").innerHTML = `${result}` + ); + +} + +function updateMessage(messageString) { + // Message is sent from the add-in as a JSON string. + const messageData = JSON.parse(messageString); + + // Update a paragraph with the data passed in. + document.getElementById("fusionMessage").innerHTML = + `Your text: ${messageData.myText}
` + + `Your expression: ${messageData.myExpression}
` + + `Your value: ${messageData.myValue}`; +} + +window.fusionJavaScriptHandler = { + handle: function (action, data) { + try { + if (action === "updateMessage") { + updateMessage(data); + } else if (action === "debugger") { + debugger; + } else { + return `Unexpected command type: ${action}`; + } + } catch (e) { + console.log(e); + console.log(`Exception caught with command: ${action}, data: ${data}`); + } + return "OK"; + }, +}; diff --git a/config.py b/config.py new file mode 100644 index 0000000..774f687 --- /dev/null +++ b/config.py @@ -0,0 +1,21 @@ +# Application Global Variables +# This module serves as a way to share variables across different +# modules (global variables). + +import os + +# Flag that indicates to run in Debug mode or not. When running in Debug mode +# more information is written to the Text Command window. Generally, it's useful +# to set this to True while developing an add-in and set it to False when you +# are ready to distribute it. +DEBUG = True + +# Gets the name of the add-in from the name of the folder the py file is in. +# This is used when defining unique internal names for various UI elements +# that need a unique name. It's also recommended to use a company name as +# part of the ID to better ensure the ID is unique. +ADDIN_NAME = os.path.basename(os.path.dirname(__file__)) +COMPANY_NAME = 'ACME' + +# Palettes +sample_palette_id = f'{COMPANY_NAME}_{ADDIN_NAME}_palette_id' \ No newline at end of file diff --git a/resources/32x32-panel.png b/resources/32x32-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..84946af40a706bc7911824bb84c018b8fff60467 GIT binary patch literal 397 zcmV;80doF{P)Px$M@d9MR9Hvt*FR3fFcin}Z#UQ&Ve86PCK%xcJwm%N(vy^}Bi%Z31vc2)k&z9K z&<*md66Ct^Kgq@t>XRZ>ij()rtNr2(s2qdJ`3V7rz*z+H3(%1qKEQY4-7bOm#A}=t zcm~F)Hv^Ipt_rMy5wHZN0mu_DF~%ezN&sYuMw9|XBT53I5TyY@#9sh}xN!A}9DsY^ z+Obl))p6A<>rLO7901=c?V8yUP^|)L>55s?EkA0^f#B<;^dzhfIIe;!6;SunK0;vw z9-1xQqd75hPeLWv24xGJT7X+%Zk*dWas$qR&-Mw0IB9>YBHer81-R>~-~zk?H|-|# zEx4BUw{0fEzH~3SuL4RgL~Pg((uX)m?gQwY5Q|8Gdc7ScqBnU65SiOMOo)~FpWCBD rUq(3)-h2EzKFBl literal 0 HcmV?d00001 diff --git a/resources/32x32-saveas.png b/resources/32x32-saveas.png new file mode 100644 index 0000000000000000000000000000000000000000..b2eeb1eada81b07f438c715b0e559aa9ae7aa910 GIT binary patch literal 467 zcmV;^0WAKBP)Px$jY&j7R9HvtmLWd|K@f+Zx6x=c8cn3B8jVKt5qtx_fk>kfHIYW6(P$!#8^4|V z&8^+Ly}fg9xrw`T^M7_`c5Ww3*(OugX9NgkU=H}`sWi*LYhA1mz;{pL8m1Pa4xnp) z*n>nN{u4kl&Y?v>8W$UNf-n>T{n^r(@s{ zn76(u*Dw@&7r5{?c7W>ykcvw#Kx{hB0$&k`nb2Miz^O}RNaqsR^ES?kU-G_NlV7v^ zm`ck5xCJ&Nr@R5S%$^n3gEDS5V-|%8^mJbCq?5Od7$UOfv$A{2vxzRU7_UuD?~-Sk_rX6B`W{`002ov JPDHLkV1hZA$PEAh literal 0 HcmV?d00001