commit 5a41c6454af2dccc91fc641a6138bfbdd616aaea Author: bartool Date: Wed Sep 21 21:50:43 2022 +0200 save plugins Fusion 360 diff --git a/ControllByPs4/.env b/ControllByPs4/.env new file mode 100644 index 0000000..6aedf1e --- /dev/null +++ b/ControllByPs4/.env @@ -0,0 +1 @@ +PYTHONPATH=C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Api/Python/packages diff --git a/ControllByPs4/.vscode/launch.json b/ControllByPs4/.vscode/launch.json new file mode 100644 index 0000000..1c69300 --- /dev/null +++ b/ControllByPs4/.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/ControllByPs4/.vscode/settings.json b/ControllByPs4/.vscode/settings.json new file mode 100644 index 0000000..1c5eab8 --- /dev/null +++ b/ControllByPs4/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.autoComplete.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.analysis.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.pythonPath": "C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Python/python.exe" +} \ No newline at end of file diff --git a/ControllByPs4/ControllByPs4 copy.py b/ControllByPs4/ControllByPs4 copy.py new file mode 100644 index 0000000..b1fe956 --- /dev/null +++ b/ControllByPs4/ControllByPs4 copy.py @@ -0,0 +1,145 @@ +# Assuming you have not changed the general structure of the template no modification is needed in this file. +# from . import commands +from tkinter import E +from .lib import fusion360utils as futil +import threading +import adsk.core +import adsk.fusion +import adsk.cam +import traceback +import json + + +app = None +ui = adsk.core.UserInterface.cast(None) +handlers = [] +stopFlag = None +myCustomEvent = 'MyCustomEventId' +customEvent = None + + +# The event handler that responds to the custom event being fired. +class ThreadEventHandler(adsk.core.CustomEventHandler): + def __init__(self): + super().__init__() + self.viewport = app.activeViewport + + def notify(self, args: adsk.core.CustomEventArgs): + global app + try: + # Make sure a command isn't running before changes are made. + activeCmd = ui.activeCommand + if activeCmd != 'SelectCommand': + ui.commandDefinitions.itemById('SelectCommand').execute() + + camera = self.viewport.camera + eye = camera.eye + target = camera.target + up = camera.upVector + zoom = camera.viewExtents + + front = eye.vectorTo(target) + right = up.crossProduct(front) + rotate_matrix = adsk.core.Matrix3D.create() + rotate_matrix.setWithCoordinateSystem(target, right, front, up) + mat = rotate_matrix.asArray() + + # Get the value from the JSON data passed through the event. + eventArgs = json.loads(args.additionalInfo) + + X_radians = float(eventArgs['X_rotation']) + if X_radians: + rotate_matrix.setToRotation(X_radians, front, target) + eye.transformBy(rotate_matrix) + up.transformBy(rotate_matrix) + + Y_radians = float(eventArgs['Y_rotation']) + if Y_radians: + rotate_matrix.setToRotation(Y_radians, right, target) + eye.transformBy(rotate_matrix) + up.transformBy(rotate_matrix) + + Z_radians = float(eventArgs['Z_rotation']) + if Z_radians: + rotate_matrix.setToRotation(Z_radians, up, target) + eye.transformBy(rotate_matrix) + up.transformBy(rotate_matrix) + + X_pan = float(eventArgs['X_pan']) + Y_pan = float(eventArgs['Y_pan']) + Z_pan = float(eventArgs['Z_pan']) + if any([X_pan, Z_pan]): + move_vector = adsk.core.Vector3D.create(X_pan, 0, Z_pan) + move_vector.transformBy(rotate_matrix) + eye.translateBy(move_vector) + target.translateBy(move_vector) + + if Y_pan: + zoom = zoom + Y_pan * 10 + + camera.isSmoothTransition = True + camera.eye = eye + camera.upVector = up + camera.target = target + camera.viewExtents = zoom + app.activeViewport.camera = camera + app.activeViewport.refresh() + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + + def rotate(self, X_radians: float, Y_radians: float, Z_radians: float): + pass + + +# The class for the new thread. +class MyThread(threading.Thread): + def __init__(self, event): + threading.Thread.__init__(self) + self.stopped = event + + def run(self): + # Every five seconds fire a custom event, passing a random number. + while not self.stopped.wait(2): + args = {'X_rotation': 0.0, 'Y_rotation': 0.0, 'Z_rotation': 0.0, + 'X_pan': 0.0, 'Y_pan': 1.0, 'Z_pan': 0.0} + app.fireCustomEvent(myCustomEvent, json.dumps(args)) + + +def run(context): + global ui + global app + try: + app = adsk.core.Application.get() + ui = app.userInterface + + # Register the custom event and connect the handler. + global customEvent + customEvent = app.registerCustomEvent(myCustomEvent) + onThreadEvent = ThreadEventHandler() + customEvent.add(onThreadEvent) + handlers.append(onThreadEvent) + + # Create a new thread for the other processing. + global stopFlag + stopFlag = threading.Event() + myThread = MyThread(stopFlag) + myThread.start() + except: + futil.handle_error('run') + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + + +def stop(context): + try: + if handlers.count: + customEvent.remove(handlers[0]) + stopFlag.set() + app.unregisterCustomEvent(myCustomEvent) + ui.messageBox('Stop addin') + except: + futil.handle_error('stop') + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) diff --git a/ControllByPs4/ControllByPs4.manifest b/ControllByPs4/ControllByPs4.manifest new file mode 100644 index 0000000..39d8a75 --- /dev/null +++ b/ControllByPs4/ControllByPs4.manifest @@ -0,0 +1,13 @@ +{ + "autodeskProduct": "Fusion360", + "type": "addin", + "id": "1a7fc1eb-71fd-42da-86f0-a7e640d647d0", + "author": "", + "description": { + "": "" + }, + "version": "", + "runOnStartup": false, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/ControllByPs4/ControllByPs4.py b/ControllByPs4/ControllByPs4.py new file mode 100644 index 0000000..938ad75 --- /dev/null +++ b/ControllByPs4/ControllByPs4.py @@ -0,0 +1,24 @@ +# Assuming you have not changed the general structure of the template no modification is needed in this file. +from . import commands +from .lib import fusion360utils as futil + + +def run(context): + try: + # This will run the start function in each of your commands as defined in commands/__init__.py + commands.start() + + except: + futil.handle_error('run') + + +def stop(context): + try: + # Remove all of the event handlers your app has created + futil.clear_handlers() + + # This will run the start function in each of your commands as defined in commands/__init__.py + commands.stop() + + except: + futil.handle_error('stop') diff --git a/ControllByPs4/__pycache__/config.cpython-39.pyc b/ControllByPs4/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000..ad1b1d4 Binary files /dev/null and b/ControllByPs4/__pycache__/config.cpython-39.pyc differ diff --git a/ControllByPs4/commands/__init__.py b/ControllByPs4/commands/__init__.py new file mode 100644 index 0000000..195567b --- /dev/null +++ b/ControllByPs4/commands/__init__.py @@ -0,0 +1,29 @@ +# 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". +import imp +from .commandDialog import entry as commandDialog +from .backend import backend + +# TODO add your imported modules to this list. +# Fusion will automatically call the start() and stop() functions. +commands = [ + commandDialog, + backend +] + + +# 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() diff --git a/ControllByPs4/commands/__pycache__/__init__.cpython-39.pyc b/ControllByPs4/commands/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..b0d2de9 Binary files /dev/null and b/ControllByPs4/commands/__pycache__/__init__.cpython-39.pyc differ diff --git a/ControllByPs4/commands/backend/__init__.py b/ControllByPs4/commands/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ControllByPs4/commands/backend/__pycache__/__init__.cpython-39.pyc b/ControllByPs4/commands/backend/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..be20c21 Binary files /dev/null and b/ControllByPs4/commands/backend/__pycache__/__init__.cpython-39.pyc differ diff --git a/ControllByPs4/commands/backend/__pycache__/backend.cpython-39.pyc b/ControllByPs4/commands/backend/__pycache__/backend.cpython-39.pyc new file mode 100644 index 0000000..9099fe9 Binary files /dev/null and b/ControllByPs4/commands/backend/__pycache__/backend.cpython-39.pyc differ diff --git a/ControllByPs4/commands/backend/backend.py b/ControllByPs4/commands/backend/backend.py new file mode 100644 index 0000000..32916a7 --- /dev/null +++ b/ControllByPs4/commands/backend/backend.py @@ -0,0 +1,178 @@ +# Assuming you have not changed the general structure of the template no modification is needed in this file. +# from . import commands +from ast import arg +from glob import glob +from ...lib import fusion360utils as futil +import threading +import adsk.core +import adsk.fusion +import adsk.cam +import traceback +import json + +CMD_NAME = 'View Controller backend' + +app = adsk.core.Application.get() +ui = app.userInterface +stopFlag = None +cameraViewEvent = 'CameraViewEventId' +updateConstantsEvent = "UpdateConstantsEventId" + +customEvents = [] +local_handlers = [] + +X_ROTATION = 0 +Y_ROTATION = 0 +Z_ROTATION = 0 +X_PAN = 0 +Y_PAN = 0 +Z_PAN = 0 + + +class UpdateConstantsEventHandler(adsk.core.CustomEventHandler): + def __init__(self): + super().__init__() + + def notify(self, args: adsk.core.CustomEventArgs) -> None: + futil.log(f'{CMD_NAME} UpdateConstants notity...') + eventArgs = json.loads(args.additionalInfo) + global X_ROTATION, Y_ROTATION, Z_ROTATION + global X_PAN, Y_PAN, Z_PAN + X_ROTATION = float(eventArgs["X_rotation"]) + Y_ROTATION = float(eventArgs["Y_rotation"]) + Z_ROTATION = float(eventArgs["Z_rotation"]) + X_PAN = float(eventArgs["X_pan"]) + Y_PAN = float(eventArgs["Y_pan"]) + Z_PAN = float(eventArgs["Z_pan"]) + + +# The event handler that responds to the custom event being fired. +class CameraViewEventHandler(adsk.core.CustomEventHandler): + def __init__(self): + super().__init__() + self.viewport = app.activeViewport + + def notify(self, args: adsk.core.CustomEventArgs): + global app + try: + # Make sure a command isn't running before changes are made. + activeCmd = ui.activeCommand + if activeCmd != 'SelectCommand': + ui.commandDefinitions.itemById('SelectCommand').execute() + + camera = self.viewport.camera + eye = camera.eye + target = camera.target + up = camera.upVector + zoom = camera.viewExtents + + front = eye.vectorTo(target) + right = up.crossProduct(front) + rotate_matrix = adsk.core.Matrix3D.create() + rotate_matrix.setWithCoordinateSystem(target, right, front, up) + mat = rotate_matrix.asArray() + + # Get the value from the JSON data passed through the event. + eventArgs: dict = json.loads(args.additionalInfo) + + X_radians = float(eventArgs.get('X_rotation', 0)) * X_ROTATION + if X_radians: + rotate_matrix.setToRotation(X_radians, front, target) + eye.transformBy(rotate_matrix) + up.transformBy(rotate_matrix) + + Y_radians = float(eventArgs.get('Y_rotation', 0)) * Y_ROTATION + if Y_radians: + rotate_matrix.setToRotation(Y_radians, right, target) + eye.transformBy(rotate_matrix) + up.transformBy(rotate_matrix) + + Z_radians = float(eventArgs.get('Z_rotation', 0)) * Z_ROTATION + if Z_radians: + rotate_matrix.setToRotation(Z_radians, up, target) + eye.transformBy(rotate_matrix) + up.transformBy(rotate_matrix) + + X_pan = float(eventArgs.get('X_pan', 0)) * X_pan + Y_pan = float(eventArgs.get('Y_pan', 0)) * Y_pan + Z_pan = float(eventArgs.get('Z_pan', 0)) * Z_pan + if any([X_pan, Z_pan]): + move_vector = adsk.core.Vector3D.create(X_pan, 0, Z_pan) + move_vector.transformBy(rotate_matrix) + eye.translateBy(move_vector) + target.translateBy(move_vector) + + if Y_pan: + zoom = zoom + Y_pan * 10 + + camera.isSmoothTransition = True + camera.eye = eye + camera.upVector = up + camera.target = target + camera.viewExtents = zoom + app.activeViewport.camera = camera + app.activeViewport.refresh() + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + + def rotate(self, X_radians: float, Y_radians: float, Z_radians: float): + pass + + +# The class for the new thread. +class ControllerThread(threading.Thread): + def __init__(self, event): + threading.Thread.__init__(self) + self.stopped = event + + def run(self): + # Every five seconds fire a custom event, passing a random number. + while not self.stopped.wait(2): + futil.log(f'{CMD_NAME} Thread start sending data') + args = {'X_rotation': 0.0, 'Y_rotation': 0.0, 'Z_rotation': 0.0, + 'X_pan': 0.0, 'Y_pan': 0.0, 'Z_pan': 0.0} + app.fireCustomEvent(cameraViewEvent, json.dumps(args)) + + +def start(): + + futil.log(f'{CMD_NAME} Start...') + # Register the custom event and connect the handler. + global customEvents + customEvents.append(app.registerCustomEvent(cameraViewEvent)) + onCameraViewEvent = CameraViewEventHandler() + customEvents[0].add(onCameraViewEvent) + local_handlers.append(onCameraViewEvent) + futil.log(f'{CMD_NAME} CameraViewEvent register') + + customEvents.append(app.registerCustomEvent(updateConstantsEvent)) + onUpadateConstantEvent = UpdateConstantsEventHandler() + customEvents[1].add(onUpadateConstantEvent) + local_handlers.append(onUpadateConstantEvent) + futil.log(f'{CMD_NAME} UpdateConstantsEvent register') + + # Create a new thread for the other processing. + global stopFlag + stopFlag = threading.Event() + myThread = ControllerThread(stopFlag) + # myThread.start() + futil.log(f'{CMD_NAME} ControllerThread started') + + +def stop(): + try: + for event, handler in zip(customEvents, local_handlers): + event.remove(handler) + # if local_handlers.count: + # customEvent.remove(local_handlers[0]) + + stopFlag.set() + app.unregisterCustomEvent(cameraViewEvent) + app.unregisterCustomEvent(updateConstantsEvent) + ui.messageBox('Stop addin') + except: + futil.handle_error('stop') + # if ui: + # ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) diff --git a/ControllByPs4/commands/commandDialog/__init__.py b/ControllByPs4/commands/commandDialog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ControllByPs4/commands/commandDialog/__pycache__/__init__.cpython-39.pyc b/ControllByPs4/commands/commandDialog/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..8eaa6a1 Binary files /dev/null and b/ControllByPs4/commands/commandDialog/__pycache__/__init__.cpython-39.pyc differ diff --git a/ControllByPs4/commands/commandDialog/__pycache__/entry.cpython-39.pyc b/ControllByPs4/commands/commandDialog/__pycache__/entry.cpython-39.pyc new file mode 100644 index 0000000..127ae96 Binary files /dev/null and b/ControllByPs4/commands/commandDialog/__pycache__/entry.cpython-39.pyc differ diff --git a/ControllByPs4/commands/commandDialog/entry.py b/ControllByPs4/commands/commandDialog/entry.py new file mode 100644 index 0000000..aed17f8 --- /dev/null +++ b/ControllByPs4/commands/commandDialog/entry.py @@ -0,0 +1,190 @@ +import adsk.core +import os +from ...lib import fusion360utils as futil +from ... import config +import json +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 = 'View Controller' +CMD_Description = 'A Fusion 360 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) + + default_angle = adsk.core.ValueInput.createByString('5') + inputs.addAngleValueCommandInput("x_rotation", "X Rotation", default_angle) + inputs.addAngleValueCommandInput("y_rotation", "Y Rotation", default_angle) + inputs.addAngleValueCommandInput("z_rotation", "Z Rotation", default_angle) + + # # Create a value input field and set the default using 1 unit of the default length unit. + defaultLengthUnits = app.activeProduct.unitsManager.defaultLengthUnits + default_value_x = adsk.core.ValueInput.createByString("1") + default_value_y = adsk.core.ValueInput.createByString("2") + default_value_z = adsk.core.ValueInput.createByString("3") + inputs.addValueInput('x_pan', 'X Disytance', + defaultLengthUnits, default_value_x) + inputs.addValueInput('y_pan', 'Y Disytance', + defaultLengthUnits, default_value_y) + inputs.addValueInput('z_pan', 'Z Disytance', + defaultLengthUnits, default_value_z) + + # 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') + triad_input: adsk.core.TriadCommandInput = inputs.itemById('matrix_input') + args = {} + # value_input: adsk.core.ValueCommandInput = inputs.itemById["x_rotation"] + args["X_rotation"] = inputs.itemById("x_rotation").value + args["Y_rotation"] = inputs.itemById("y_rotation").value + args["Z_rotation"] = inputs.itemById("z_rotation").value + + # angle_input: adsk.core.AngleValueCommandInput = inputs.itemById["x_pan"] + args["X_pan"] = inputs.itemById("x_pan").value + args["Y_pan"] = inputs.itemById("y_pan").value + args["Z_pan"] = inputs.itemById("z_pan").value + # Do something interesting + # text = text_box.text + # expression = value_input.expression + # msg = f'Your text: {text}
Your value: {expression}' + # ui.messageBox(msg) + + app.fireCustomEvent('UpdateConstantsEventId', json.dumps(args)) + +# 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/ControllByPs4/commands/commandDialog/resources/16x16.png b/ControllByPs4/commands/commandDialog/resources/16x16.png new file mode 100644 index 0000000..03babdc Binary files /dev/null and b/ControllByPs4/commands/commandDialog/resources/16x16.png differ diff --git a/ControllByPs4/commands/commandDialog/resources/32x32.png b/ControllByPs4/commands/commandDialog/resources/32x32.png new file mode 100644 index 0000000..863b2e3 Binary files /dev/null and b/ControllByPs4/commands/commandDialog/resources/32x32.png differ diff --git a/ControllByPs4/commands/commandDialog/resources/64x64.png b/ControllByPs4/commands/commandDialog/resources/64x64.png new file mode 100644 index 0000000..dd285fd Binary files /dev/null and b/ControllByPs4/commands/commandDialog/resources/64x64.png differ diff --git a/ControllByPs4/config.py b/ControllByPs4/config.py new file mode 100644 index 0000000..d4eb231 --- /dev/null +++ b/ControllByPs4/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 = 'BTL' + +# Palettes +sample_palette_id = f'{COMPANY_NAME}_{ADDIN_NAME}_palette_id' diff --git a/ControllByPs4/lib/fusion360utils/__init__.py b/ControllByPs4/lib/fusion360utils/__init__.py new file mode 100644 index 0000000..56e95d5 --- /dev/null +++ b/ControllByPs4/lib/fusion360utils/__init__.py @@ -0,0 +1,2 @@ +from .general_utils import * +from .event_utils import * diff --git a/ControllByPs4/lib/fusion360utils/__pycache__/__init__.cpython-39.pyc b/ControllByPs4/lib/fusion360utils/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..bf5cd12 Binary files /dev/null and b/ControllByPs4/lib/fusion360utils/__pycache__/__init__.cpython-39.pyc differ diff --git a/ControllByPs4/lib/fusion360utils/__pycache__/event_utils.cpython-39.pyc b/ControllByPs4/lib/fusion360utils/__pycache__/event_utils.cpython-39.pyc new file mode 100644 index 0000000..d442d6e Binary files /dev/null and b/ControllByPs4/lib/fusion360utils/__pycache__/event_utils.cpython-39.pyc differ diff --git a/ControllByPs4/lib/fusion360utils/__pycache__/general_utils.cpython-39.pyc b/ControllByPs4/lib/fusion360utils/__pycache__/general_utils.cpython-39.pyc new file mode 100644 index 0000000..6815523 Binary files /dev/null and b/ControllByPs4/lib/fusion360utils/__pycache__/general_utils.cpython-39.pyc differ diff --git a/ControllByPs4/lib/fusion360utils/event_utils.py b/ControllByPs4/lib/fusion360utils/event_utils.py new file mode 100644 index 0000000..97a09b0 --- /dev/null +++ b/ControllByPs4/lib/fusion360utils/event_utils.py @@ -0,0 +1,88 @@ +# Copyright 2022 by Autodesk, Inc. +# Permission to use, copy, modify, and distribute this software in object code form +# for any purpose and without fee is hereby granted, provided that the above copyright +# notice appears in all copies and that both that copyright notice and the limited +# warranty and restricted rights notice below appear in all supporting documentation. +# +# AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. AUTODESK SPECIFICALLY +# DISCLAIMS ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. +# AUTODESK, INC. DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +# UNINTERRUPTED OR ERROR FREE. + +import sys +from typing import Callable + +import adsk.core +from .general_utils import handle_error + + +# Global Variable to hold Event Handlers +_handlers = [] + + +def add_handler( + event: adsk.core.Event, + callback: Callable, + *, + name: str = None, + local_handlers: list = None +): + """Adds an event handler to the specified event. + + Arguments: + event -- The event object you want to connect a handler to. + callback -- The function that will handle the event. + name -- A name to use in logging errors associated with this event. + Otherwise the name of the event object is used. This argument + must be specified by its keyword. + local_handlers -- A list of handlers you manage that is used to maintain + a reference to the handlers so they aren't released. + This argument must be specified by its keyword. If not + specified the handler is added to a global list and can + be cleared using the clear_handlers function. You may want + to maintain your own handler list so it can be managed + independently for each command. + + :returns: + The event handler that was created. You don't often need this reference, but it can be useful in some cases. + """ + module = sys.modules[event.__module__] + handler_type = module.__dict__[event.add.__annotations__['handler']] + handler = _create_handler(handler_type, callback, event, name, local_handlers) + event.add(handler) + return handler + + +def clear_handlers(): + """Clears the global list of handlers. + """ + global _handlers + _handlers = [] + + +def _create_handler( + handler_type, + callback: Callable, + event: adsk.core.Event, + name: str = None, + local_handlers: list = None +): + handler = _define_handler(handler_type, callback, name)() + (local_handlers if local_handlers is not None else _handlers).append(handler) + return handler + + +def _define_handler(handler_type, callback, name: str = None): + name = name or handler_type.__name__ + + class Handler(handler_type): + def __init__(self): + super().__init__() + + def notify(self, args): + try: + callback(args) + except: + handle_error(name) + + return Handler diff --git a/ControllByPs4/lib/fusion360utils/general_utils.py b/ControllByPs4/lib/fusion360utils/general_utils.py new file mode 100644 index 0000000..fcf2667 --- /dev/null +++ b/ControllByPs4/lib/fusion360utils/general_utils.py @@ -0,0 +1,64 @@ +# Copyright 2022 by Autodesk, Inc. +# Permission to use, copy, modify, and distribute this software in object code form +# for any purpose and without fee is hereby granted, provided that the above copyright +# notice appears in all copies and that both that copyright notice and the limited +# warranty and restricted rights notice below appear in all supporting documentation. +# +# AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. AUTODESK SPECIFICALLY +# DISCLAIMS ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. +# AUTODESK, INC. DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +# UNINTERRUPTED OR ERROR FREE. + +import os +import traceback +import adsk.core + +app = adsk.core.Application.get() +ui = app.userInterface + +# Attempt to read DEBUG flag from parent config. +try: + from ... import config + DEBUG = config.DEBUG +except: + DEBUG = False + + +def log(message: str, level: adsk.core.LogLevels = adsk.core.LogLevels.InfoLogLevel, force_console: bool = False): + """Utility function to easily handle logging in your app. + + Arguments: + message -- The message to log. + level -- The logging severity level. + force_console -- Forces the message to be written to the Text Command window. + """ + # Always print to console, only seen through IDE. + print(message) + + # Log all errors to Fusion log file. + if level == adsk.core.LogLevels.ErrorLogLevel: + log_type = adsk.core.LogTypes.FileLogType + app.log(message, level, log_type) + + # If config.DEBUG is True write all log messages to the console. + if DEBUG or force_console: + log_type = adsk.core.LogTypes.ConsoleLogType + app.log(message, level, log_type) + + +def handle_error(name: str, show_message_box: bool = False): + """Utility function to simplify error handling. + + Arguments: + name -- A name used to label the error. + show_message_box -- Indicates if the error should be shown in the message box. + If False, it will only be shown in the Text Command window + and logged to the log file. + """ + + log('===== Error =====', adsk.core.LogLevels.ErrorLogLevel) + log(f'{name}\n{traceback.format_exc()}', adsk.core.LogLevels.ErrorLogLevel) + + # If desired you could show an error as a message box. + if show_message_box: + ui.messageBox(f'{name}\n{traceback.format_exc()}') diff --git a/demo/.env b/demo/.env new file mode 100644 index 0000000..6aedf1e --- /dev/null +++ b/demo/.env @@ -0,0 +1 @@ +PYTHONPATH=C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Api/Python/packages diff --git a/demo/.vscode/launch.json b/demo/.vscode/launch.json new file mode 100644 index 0000000..1c69300 --- /dev/null +++ b/demo/.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/demo/.vscode/settings.json b/demo/.vscode/settings.json new file mode 100644 index 0000000..5627768 --- /dev/null +++ b/demo/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.autoComplete.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.analysis.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.defaultInterpreterPath": "C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Python/python.exe", + "python.pythonPath": "C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Python/python.exe" +} \ No newline at end of file diff --git a/demo/demo.manifest b/demo/demo.manifest new file mode 100644 index 0000000..28ab33c --- /dev/null +++ b/demo/demo.manifest @@ -0,0 +1,10 @@ +{ + "autodeskProduct": "Fusion360", + "type": "script", + "author": "", + "description": { + "": "" + }, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/demo/demo.py b/demo/demo.py new file mode 100644 index 0000000..4e2b6af --- /dev/null +++ b/demo/demo.py @@ -0,0 +1,58 @@ +# Fusion360API Python script +import adsk.core, adsk.fusion, adsk.cam, traceback +import math, random + +# Number of divisions during one revolution +COUNT = 60 + +def run(context): + ui = None + try: + app: adsk.core.Application = adsk.core.Application.get() + ui = app.userInterface + + # get state + vi :adsk.core.Viewport = app.activeViewport + camera :adsk.core.Camera = vi.camera #This is a deep copy. + eye :adsk.core.Point3D = camera.eye + tgt :adsk.core.Point3D = camera.target + up :adsk.core.Vector3D = camera.upVector + baseArea = camera.viewExtents + + # get matrix + front :adsk.core.Vector3D = eye.vectorTo(tgt) + right :adsk.core.Vector3D = up.copy() + right = right.crossProduct(front) + mat :adsk.core.Matrix3D = adsk.core.Matrix3D.create() + mat.setWithCoordinateSystem(tgt, right, front, up) + + # get position & ratio + unit = math.radians(360 // COUNT) + rads = [unit] * COUNT + ratios = [random.uniform(0.1, 5.0) for _ in range(COUNT)] + if 360 % COUNT != 0: + rads.append(math.radians(360 % COUNT)) + ratios.append(1) + + # show + camera.isSmoothTransition = False + for rad, ratio in zip(rads, ratios): + mat.setToRotation(rad, up, tgt) + eye.transformBy(mat) + up.transformBy(mat) + mat.setToRotation(rad, right, tgt) + eye.transformBy(mat) + up.transformBy(mat) + # mat.setToRotation(rad, front, tgt) + camera.eye = eye + camera.upVector = up + # zoom = baseArea * ratio + # camera.viewExtents = zoom + + vi.camera = camera + vi.refresh() + adsk.doEvents() + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) \ No newline at end of file diff --git a/eyeCameraTest/.env b/eyeCameraTest/.env new file mode 100644 index 0000000..6aedf1e --- /dev/null +++ b/eyeCameraTest/.env @@ -0,0 +1 @@ +PYTHONPATH=C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Api/Python/packages diff --git a/eyeCameraTest/.vscode/launch.json b/eyeCameraTest/.vscode/launch.json new file mode 100644 index 0000000..1c69300 --- /dev/null +++ b/eyeCameraTest/.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/eyeCameraTest/.vscode/settings.json b/eyeCameraTest/.vscode/settings.json new file mode 100644 index 0000000..1c5eab8 --- /dev/null +++ b/eyeCameraTest/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.autoComplete.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.analysis.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.pythonPath": "C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Python/python.exe" +} \ No newline at end of file diff --git a/eyeCameraTest/eyeCameraTest.manifest b/eyeCameraTest/eyeCameraTest.manifest new file mode 100644 index 0000000..28ab33c --- /dev/null +++ b/eyeCameraTest/eyeCameraTest.manifest @@ -0,0 +1,10 @@ +{ + "autodeskProduct": "Fusion360", + "type": "script", + "author": "", + "description": { + "": "" + }, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/eyeCameraTest/eyeCameraTest.py b/eyeCameraTest/eyeCameraTest.py new file mode 100644 index 0000000..34df516 --- /dev/null +++ b/eyeCameraTest/eyeCameraTest.py @@ -0,0 +1,27 @@ +# Author- +# Description- + +import adsk.core +import adsk.fusion +import adsk.cam +import traceback + + +def run(context): + try: + + app = adsk.core.Application.get() + ui = app.userInterface + camera = app.activeViewport.camera + eye = camera.eye + target = camera.target + upvector = camera.upVector + baseExtent = camera.viewExtents + + ui.messageBox('Eye: {0}, {1}, {2} \ntarget: {3}, {4}, {5} \nupvector: {6}, {7}, {8}, \nviewExtants: {9}'.format( + eye.x, eye.y, eye.z, target.x, target.y, target.z, upvector.x, upvector.y, upvector.z, baseExtent)) + + except: + + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) diff --git a/rotate_Test/.env b/rotate_Test/.env new file mode 100644 index 0000000..6aedf1e --- /dev/null +++ b/rotate_Test/.env @@ -0,0 +1 @@ +PYTHONPATH=C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Api/Python/packages diff --git a/rotate_Test/.vscode/launch.json b/rotate_Test/.vscode/launch.json new file mode 100644 index 0000000..1c69300 --- /dev/null +++ b/rotate_Test/.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/rotate_Test/.vscode/settings.json b/rotate_Test/.vscode/settings.json new file mode 100644 index 0000000..1c5eab8 --- /dev/null +++ b/rotate_Test/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.autoComplete.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.analysis.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.pythonPath": "C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Python/python.exe" +} \ No newline at end of file diff --git a/rotate_Test/rotate_Test.manifest b/rotate_Test/rotate_Test.manifest new file mode 100644 index 0000000..28ab33c --- /dev/null +++ b/rotate_Test/rotate_Test.manifest @@ -0,0 +1,10 @@ +{ + "autodeskProduct": "Fusion360", + "type": "script", + "author": "", + "description": { + "": "" + }, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/rotate_Test/rotate_Test.py b/rotate_Test/rotate_Test.py new file mode 100644 index 0000000..bde569a --- /dev/null +++ b/rotate_Test/rotate_Test.py @@ -0,0 +1,58 @@ +# Author- +# Description- + +import math +import adsk.core +import adsk.fusion +import adsk.cam +import traceback + + +def run(context): + ui = None + try: + print('hello, world') + app = adsk.core.Application.get() + + move_camera(app, app.activeViewport) + + ui = app.userInterface + # ui.messageBox('Hello script') + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + + +def move_camera(app, view): + try: + camera = view.camera + + # target = adsk.core.Point3D.create(0, 0, 0) + up = adsk.core.Vector3D.create(0, 0, 1) + steps = 1000 + + eye = camera.eye + dist = camera.target.distanceTo(eye) + x_eye = eye.x + y_eye = eye.y + + for i in range(0, steps): + x = dist * math.cos((math.pi*2) * (i/steps)) + x_eye + y = dist * math.sin((math.pi*2) * (i/steps)) + y_eye + z = eye.z + + eye = adsk.core.Point3D.create(x, y, z) + + camera.eye = eye + # camera.target = target + # camera.upVector = up + + # camera.isSmoothTransition = False + view.camera = camera + adsk.doEvents() + view.refresh() + except: + ui = app.userInterface + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) diff --git a/tamplate/.env b/tamplate/.env new file mode 100644 index 0000000..6aedf1e --- /dev/null +++ b/tamplate/.env @@ -0,0 +1 @@ +PYTHONPATH=C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Api/Python/packages diff --git a/tamplate/.vscode/launch.json b/tamplate/.vscode/launch.json new file mode 100644 index 0000000..1c69300 --- /dev/null +++ b/tamplate/.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/tamplate/.vscode/settings.json b/tamplate/.vscode/settings.json new file mode 100644 index 0000000..1c5eab8 --- /dev/null +++ b/tamplate/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.autoComplete.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.analysis.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.pythonPath": "C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Python/python.exe" +} \ No newline at end of file diff --git a/tamplate/commands/__init__.py b/tamplate/commands/__init__.py new file mode 100644 index 0000000..0499301 --- /dev/null +++ b/tamplate/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/tamplate/commands/commandDialog/__init__.py b/tamplate/commands/commandDialog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tamplate/commands/commandDialog/entry.py b/tamplate/commands/commandDialog/entry.py new file mode 100644 index 0000000..cafd41c --- /dev/null +++ b/tamplate/commands/commandDialog/entry.py @@ -0,0 +1,158 @@ +import adsk.core +import os +from ...lib import fusion360utils 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 360 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/tamplate/commands/commandDialog/resources/16x16.png b/tamplate/commands/commandDialog/resources/16x16.png new file mode 100644 index 0000000..03babdc Binary files /dev/null and b/tamplate/commands/commandDialog/resources/16x16.png differ diff --git a/tamplate/commands/commandDialog/resources/32x32.png b/tamplate/commands/commandDialog/resources/32x32.png new file mode 100644 index 0000000..863b2e3 Binary files /dev/null and b/tamplate/commands/commandDialog/resources/32x32.png differ diff --git a/tamplate/commands/commandDialog/resources/64x64.png b/tamplate/commands/commandDialog/resources/64x64.png new file mode 100644 index 0000000..dd285fd Binary files /dev/null and b/tamplate/commands/commandDialog/resources/64x64.png differ diff --git a/tamplate/commands/paletteSend/__init__.py b/tamplate/commands/paletteSend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tamplate/commands/paletteSend/entry.py b/tamplate/commands/paletteSend/entry.py new file mode 100644 index 0000000..0878e66 --- /dev/null +++ b/tamplate/commands/paletteSend/entry.py @@ -0,0 +1,149 @@ +import json +import adsk.core +import os +from ...lib import fusion360utils 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/tamplate/commands/paletteSend/resources/16x16.png b/tamplate/commands/paletteSend/resources/16x16.png new file mode 100644 index 0000000..c18250a Binary files /dev/null and b/tamplate/commands/paletteSend/resources/16x16.png differ diff --git a/tamplate/commands/paletteSend/resources/32x32.png b/tamplate/commands/paletteSend/resources/32x32.png new file mode 100644 index 0000000..f6b4c18 Binary files /dev/null and b/tamplate/commands/paletteSend/resources/32x32.png differ diff --git a/tamplate/commands/paletteSend/resources/64x64.png b/tamplate/commands/paletteSend/resources/64x64.png new file mode 100644 index 0000000..ed1ae27 Binary files /dev/null and b/tamplate/commands/paletteSend/resources/64x64.png differ diff --git a/tamplate/commands/paletteShow/__init__.py b/tamplate/commands/paletteShow/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tamplate/commands/paletteShow/entry.py b/tamplate/commands/paletteShow/entry.py new file mode 100644 index 0000000..f48c1c4 --- /dev/null +++ b/tamplate/commands/paletteShow/entry.py @@ -0,0 +1,193 @@ +import json +import adsk.core +import os +from ...lib import fusion360utils as futil +from ... import config +from datetime import datetime + +app = adsk.core.Application.get() +ui = app.userInterface + +# TODO ********************* Change these names ********************* +CMD_ID = f'{config.COMPANY_NAME}_{config.ADDIN_NAME}_PalleteShow' +CMD_NAME = 'Show My Palette' +CMD_Description = 'A Fusion 360 Add-in Palette' +PALETTE_NAME = 'My Palette Sample' +IS_PROMOTED = False + +# Using "global" variables by referencing values from /config.py +PALETTE_ID = config.sample_palette_id + +# Specify the full path to the local html. You can also use a web URL +# such as 'https://www.autodesk.com/' +PALETTE_URL = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources', 'html', 'index.html') + +# The path function builds a valid OS path. This fixes it to be a valid local URL. +PALETTE_URL = PALETTE_URL.replace('\\', '/') + +# Set a default docking behavior for the palette +PALETTE_DOCKING = adsk.core.PaletteDockingStates.PaletteDockStateRight + +# 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) + palette = ui.palettes.itemById(PALETTE_ID) + + # Delete the button command control + if command_control: + command_control.deleteMe() + + # Delete the command definition + if command_definition: + command_definition.deleteMe() + + # Delete the Palette + if palette: + palette.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.') + + # 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.destroy, command_destroy, local_handlers=local_handlers) + + +# Because no command inputs are being added in the command created event, the execute +# event is immediately fired. +def command_execute(args: adsk.core.CommandEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME}: Command execute event.') + + palettes = ui.palettes + palette = palettes.itemById(PALETTE_ID) + if palette is None: + palette = palettes.add( + id=PALETTE_ID, + name=PALETTE_NAME, + htmlFileURL=PALETTE_URL, + isVisible=True, + showCloseButton=True, + isResizable=True, + width=650, + height=600, + useNewWebBrowser=True + ) + futil.add_handler(palette.closed, palette_closed) + futil.add_handler(palette.navigatingURL, palette_navigating) + futil.add_handler(palette.incomingFromHTML, palette_incoming) + futil.log(f'{CMD_NAME}: Created a new palette: ID = {palette.id}, Name = {palette.name}') + + if palette.dockingState == adsk.core.PaletteDockingStates.PaletteDockStateFloating: + palette.dockingState = PALETTE_DOCKING + + palette.isVisible = True + + +# Use this to handle a user closing your palette. +def palette_closed(args: adsk.core.UserInterfaceGeneralEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME}: Palette was closed.') + + +# Use this to handle a user navigating to a new page in your palette. +def palette_navigating(args: adsk.core.NavigationEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME}: Palette navigating event.') + + # Get the URL the user is navigating to: + url = args.navigationURL + + log_msg = f"User is attempting to navigate to {url}\n" + futil.log(log_msg, adsk.core.LogLevels.InfoLogLevel) + + # Check if url is an external site and open in user's default browser. + if url.startswith("http"): + args.launchExternally = True + + +# Use this to handle events sent from javascript in your palette. +def palette_incoming(html_args: adsk.core.HTMLEventArgs): + # General logging for debug. + futil.log(f'{CMD_NAME}: Palette incoming event.') + + message_data: dict = json.loads(html_args.data) + message_action = html_args.action + + log_msg = f"Event received from {html_args.firingEvent.sender.name}\n" + log_msg += f"Action: {message_action}\n" + log_msg += f"Data: {message_data}" + futil.log(log_msg, adsk.core.LogLevels.InfoLogLevel) + + # TODO ******** Your palette reaction code here ******** + + # Read message sent from palette javascript and react appropriately. + if message_action == 'messageFromPalette': + arg1 = message_data.get('arg1', 'arg1 not sent') + arg2 = message_data.get('arg2', 'arg2 not sent') + + msg = 'An event has been fired from the html to Fusion with the following data:
' + msg += f'Action: {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/tamplate/commands/paletteShow/resources/16x16.png b/tamplate/commands/paletteShow/resources/16x16.png new file mode 100644 index 0000000..f1b8e57 Binary files /dev/null and b/tamplate/commands/paletteShow/resources/16x16.png differ diff --git a/tamplate/commands/paletteShow/resources/32x32.png b/tamplate/commands/paletteShow/resources/32x32.png new file mode 100644 index 0000000..a61a47e Binary files /dev/null and b/tamplate/commands/paletteShow/resources/32x32.png differ diff --git a/tamplate/commands/paletteShow/resources/64x64.png b/tamplate/commands/paletteShow/resources/64x64.png new file mode 100644 index 0000000..4dd5dfc Binary files /dev/null and b/tamplate/commands/paletteShow/resources/64x64.png differ diff --git a/tamplate/commands/paletteShow/resources/html/index.html b/tamplate/commands/paletteShow/resources/html/index.html new file mode 100644 index 0000000..2a6176e --- /dev/null +++ b/tamplate/commands/paletteShow/resources/html/index.html @@ -0,0 +1,39 @@ + + + + + Title + + + +
+ +

Fusion 360 Palette Sample

+
+ + Learn more about working with Palettes in Fusion 360 + +
+

+ +

Send Data to HTML Event Handler

+
+ +

+ +
+ +

HTML Event Response Value:

+
Response
+ +

Message from "Send to Palette" Command

+
+

Message from Fusion

+

+
+ +
+ + diff --git a/tamplate/commands/paletteShow/resources/html/static/palette.js b/tamplate/commands/paletteShow/resources/html/static/palette.js new file mode 100644 index 0000000..dacdafc --- /dev/null +++ b/tamplate/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/tamplate/config.py b/tamplate/config.py new file mode 100644 index 0000000..774f687 --- /dev/null +++ b/tamplate/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/tamplate/lib/fusion360utils/__init__.py b/tamplate/lib/fusion360utils/__init__.py new file mode 100644 index 0000000..56e95d5 --- /dev/null +++ b/tamplate/lib/fusion360utils/__init__.py @@ -0,0 +1,2 @@ +from .general_utils import * +from .event_utils import * diff --git a/tamplate/lib/fusion360utils/event_utils.py b/tamplate/lib/fusion360utils/event_utils.py new file mode 100644 index 0000000..97a09b0 --- /dev/null +++ b/tamplate/lib/fusion360utils/event_utils.py @@ -0,0 +1,88 @@ +# Copyright 2022 by Autodesk, Inc. +# Permission to use, copy, modify, and distribute this software in object code form +# for any purpose and without fee is hereby granted, provided that the above copyright +# notice appears in all copies and that both that copyright notice and the limited +# warranty and restricted rights notice below appear in all supporting documentation. +# +# AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. AUTODESK SPECIFICALLY +# DISCLAIMS ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. +# AUTODESK, INC. DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +# UNINTERRUPTED OR ERROR FREE. + +import sys +from typing import Callable + +import adsk.core +from .general_utils import handle_error + + +# Global Variable to hold Event Handlers +_handlers = [] + + +def add_handler( + event: adsk.core.Event, + callback: Callable, + *, + name: str = None, + local_handlers: list = None +): + """Adds an event handler to the specified event. + + Arguments: + event -- The event object you want to connect a handler to. + callback -- The function that will handle the event. + name -- A name to use in logging errors associated with this event. + Otherwise the name of the event object is used. This argument + must be specified by its keyword. + local_handlers -- A list of handlers you manage that is used to maintain + a reference to the handlers so they aren't released. + This argument must be specified by its keyword. If not + specified the handler is added to a global list and can + be cleared using the clear_handlers function. You may want + to maintain your own handler list so it can be managed + independently for each command. + + :returns: + The event handler that was created. You don't often need this reference, but it can be useful in some cases. + """ + module = sys.modules[event.__module__] + handler_type = module.__dict__[event.add.__annotations__['handler']] + handler = _create_handler(handler_type, callback, event, name, local_handlers) + event.add(handler) + return handler + + +def clear_handlers(): + """Clears the global list of handlers. + """ + global _handlers + _handlers = [] + + +def _create_handler( + handler_type, + callback: Callable, + event: adsk.core.Event, + name: str = None, + local_handlers: list = None +): + handler = _define_handler(handler_type, callback, name)() + (local_handlers if local_handlers is not None else _handlers).append(handler) + return handler + + +def _define_handler(handler_type, callback, name: str = None): + name = name or handler_type.__name__ + + class Handler(handler_type): + def __init__(self): + super().__init__() + + def notify(self, args): + try: + callback(args) + except: + handle_error(name) + + return Handler diff --git a/tamplate/lib/fusion360utils/general_utils.py b/tamplate/lib/fusion360utils/general_utils.py new file mode 100644 index 0000000..fcf2667 --- /dev/null +++ b/tamplate/lib/fusion360utils/general_utils.py @@ -0,0 +1,64 @@ +# Copyright 2022 by Autodesk, Inc. +# Permission to use, copy, modify, and distribute this software in object code form +# for any purpose and without fee is hereby granted, provided that the above copyright +# notice appears in all copies and that both that copyright notice and the limited +# warranty and restricted rights notice below appear in all supporting documentation. +# +# AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. AUTODESK SPECIFICALLY +# DISCLAIMS ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. +# AUTODESK, INC. DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +# UNINTERRUPTED OR ERROR FREE. + +import os +import traceback +import adsk.core + +app = adsk.core.Application.get() +ui = app.userInterface + +# Attempt to read DEBUG flag from parent config. +try: + from ... import config + DEBUG = config.DEBUG +except: + DEBUG = False + + +def log(message: str, level: adsk.core.LogLevels = adsk.core.LogLevels.InfoLogLevel, force_console: bool = False): + """Utility function to easily handle logging in your app. + + Arguments: + message -- The message to log. + level -- The logging severity level. + force_console -- Forces the message to be written to the Text Command window. + """ + # Always print to console, only seen through IDE. + print(message) + + # Log all errors to Fusion log file. + if level == adsk.core.LogLevels.ErrorLogLevel: + log_type = adsk.core.LogTypes.FileLogType + app.log(message, level, log_type) + + # If config.DEBUG is True write all log messages to the console. + if DEBUG or force_console: + log_type = adsk.core.LogTypes.ConsoleLogType + app.log(message, level, log_type) + + +def handle_error(name: str, show_message_box: bool = False): + """Utility function to simplify error handling. + + Arguments: + name -- A name used to label the error. + show_message_box -- Indicates if the error should be shown in the message box. + If False, it will only be shown in the Text Command window + and logged to the log file. + """ + + log('===== Error =====', adsk.core.LogLevels.ErrorLogLevel) + log(f'{name}\n{traceback.format_exc()}', adsk.core.LogLevels.ErrorLogLevel) + + # If desired you could show an error as a message box. + if show_message_box: + ui.messageBox(f'{name}\n{traceback.format_exc()}') diff --git a/tamplate/tamplate.manifest b/tamplate/tamplate.manifest new file mode 100644 index 0000000..1ca7de4 --- /dev/null +++ b/tamplate/tamplate.manifest @@ -0,0 +1,13 @@ +{ + "autodeskProduct": "Fusion360", + "type": "addin", + "id": "f1a3be42-3a4c-4ea3-9b2d-86ad07c09291", + "author": "", + "description": { + "": "" + }, + "version": "", + "runOnStartup": false, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/tamplate/tamplate.py b/tamplate/tamplate.py new file mode 100644 index 0000000..3695089 --- /dev/null +++ b/tamplate/tamplate.py @@ -0,0 +1,24 @@ +# Assuming you have not changed the general structure of the template no modification is needed in this file. +from . import commands +from .lib import fusion360utils as futil + + +def run(context): + try: + # This will run the start function in each of your commands as defined in commands/__init__.py + commands.start() + + except: + futil.handle_error('run') + + +def stop(context): + try: + # Remove all of the event handlers your app has created + futil.clear_handlers() + + # This will run the start function in each of your commands as defined in commands/__init__.py + commands.stop() + + except: + futil.handle_error('stop') \ No newline at end of file diff --git a/zoom_test/.env b/zoom_test/.env new file mode 100644 index 0000000..6aedf1e --- /dev/null +++ b/zoom_test/.env @@ -0,0 +1 @@ +PYTHONPATH=C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Api/Python/packages diff --git a/zoom_test/.vscode/launch.json b/zoom_test/.vscode/launch.json new file mode 100644 index 0000000..1c69300 --- /dev/null +++ b/zoom_test/.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/zoom_test/.vscode/settings.json b/zoom_test/.vscode/settings.json new file mode 100644 index 0000000..1c5eab8 --- /dev/null +++ b/zoom_test/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.autoComplete.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.analysis.extraPaths": ["C:/Users/bartool/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.pythonPath": "C:/Users/bartool/AppData/Local/Autodesk/webdeploy/production/9209df45963e1599ff476303834125d21fd43de4/Python/python.exe" +} \ No newline at end of file diff --git a/zoom_test/zoom_test.manifest b/zoom_test/zoom_test.manifest new file mode 100644 index 0000000..28ab33c --- /dev/null +++ b/zoom_test/zoom_test.manifest @@ -0,0 +1,10 @@ +{ + "autodeskProduct": "Fusion360", + "type": "script", + "author": "", + "description": { + "": "" + }, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/zoom_test/zoom_test.py b/zoom_test/zoom_test.py new file mode 100644 index 0000000..817697e --- /dev/null +++ b/zoom_test/zoom_test.py @@ -0,0 +1,65 @@ +# Author- +# Description- + +import adsk.core +import adsk.fusion +import adsk.cam +import traceback + + +def run(context): + ui = None + try: + app = adsk.core.Application.get() + ui = app.userInterface + + zoom_camera(app, app.activeViewport) + # ui.messageBox('Hello script') + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + + +def zoom_camera(app: adsk.core.Application, view: adsk.core.Viewport): + try: + camera = view.camera + eye = camera.eye + target = camera.target + + eye_x = eye.x + eye_y = eye.y + eye_z = eye.z + + tgt_x = target.x + tgt_y = target.y + tgt_z = target.z + + eye_tgt_vector = eye.vectorTo(target) + + eye_tgt_vector.scaleBy(0.5) + + new_x = eye_tgt_vector.asPoint().x + new_y = eye_tgt_vector.asPoint().y + new_z = eye_tgt_vector.asPoint().z + # eye.set(eye_x - new_x, eye_y - new_y, eye_z - new_z) + eye = adsk.core.Point3D.create( + eye_x - new_x, eye_y - new_y, eye_z - new_z) + # eye.translateBy(eye_tgt_vector) + eye = adsk.core.Point3D.create(100, 100, 100) + camera.eye = eye + fit = camera.isFitView + base = camera.viewExtents + base = base*2 + camera.viewExtents = base + view.camera = camera + view.refresh() + + ui = app.userInterface + ui.messageBox('Eye: {0}, {1}, {2} \ntarget: {3}, {4}, {5}'.format( + eye.x, eye.y, eye.z, target.x, target.y, target.z)) + + except: + ui = app.userInterface + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))