diff --git a/.gitignore b/.gitignore index e43b0f9..c120690 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +.vscode/* \ No newline at end of file diff --git a/f360-script/External controls/.env b/f360-script/External controls/.env new file mode 100644 index 0000000..e28bd05 --- /dev/null +++ b/f360-script/External controls/.env @@ -0,0 +1 @@ +PYTHONPATH=C:/Users/gena0/AppData/Local/Autodesk/webdeploy/production/525fe9b2ece26c4d179982b11ecdfb713cb5058f/Api/Python/packages diff --git a/f360-script/External controls/.vscode/launch.json b/f360-script/External controls/.vscode/launch.json new file mode 100644 index 0000000..1c69300 --- /dev/null +++ b/f360-script/External controls/.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/f360-script/External controls/.vscode/settings.json b/f360-script/External controls/.vscode/settings.json new file mode 100644 index 0000000..bb38fc9 --- /dev/null +++ b/f360-script/External controls/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "python.autoComplete.extraPaths": [ + "C:\\Users\\gena0\\AppData\\Roaming\\Autodesk\\Autodesk Fusion 360\\API\\Python\\defs" + ], + "python.analysis.extraPaths": ["C:/Users/gena0/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Python/defs"], + "python.pythonPath": "C:/Users/gena0/AppData/Local/Autodesk/webdeploy/production/525fe9b2ece26c4d179982b11ecdfb713cb5058f/Python/python.exe", + "fusion-360-helper.enabled": true, + "fusion-360-helper.python.pythonPath": "C:\\Users\\gena0\\AppData\\Local\\Autodesk\\webdeploy\\production\\eb2a69395707b2ba8a2af9d915b9a52ace64d385\\Python\\python.exe", + "fusion-360-helper.python.extraLibs": [ + "C:\\Users\\gena0\\AppData\\Roaming\\Autodesk\\Autodesk Fusion 360\\API\\Python\\defs" + ] +} \ No newline at end of file diff --git a/f360-script/External controls/External controls.manifest b/f360-script/External controls/External controls.manifest new file mode 100644 index 0000000..377ab05 --- /dev/null +++ b/f360-script/External controls/External controls.manifest @@ -0,0 +1,13 @@ +{ + "autodeskProduct": "Fusion360", + "type": "addin", + "id": "0afdf698-8eed-4eaa-ac5c-d6ae17db8147", + "author": "InTostor", + "description": { + "": "" + }, + "version": "0.1", + "runOnStartup": true, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/f360-script/External controls/External controls.py b/f360-script/External controls/External controls.py new file mode 100644 index 0000000..3a8e22c --- /dev/null +++ b/f360-script/External controls/External controls.py @@ -0,0 +1,161 @@ +# started experiments with f360 driver-script + + +# There is a lot of code copied from stackoverflow, autodesk forums, etc. +# windows only yet + +import adsk.fusion, adsk.core, traceback +import math + +from .lib import joystickapi +from .lib.mathExt import * +from .lib.Joystick import * +from .lib.logger import * + +from .lib import fusion360utils as futil +from .config import * +from . import commands + + +import msvcrt +import time +import inspect, os, sys + +logger=Logger() + + +def greeting(): + logger.print("------Starting------") + logger.print("f360 joystick driver") + logger.print(f"version, {VERSION}. By {AUTHOR}.") + + + +def transformCameraByMatrix(camera: adsk.core.Camera, matrix: adsk.core.Matrix3D): + eye: adsk.core.Point3D = camera.eye + eye.transformBy(matrix) + camera.eye = eye + +def transformCameraByVector(camera: adsk.core.Camera, vector: adsk.core.Vector3D): + eye: adsk.core.Point3D = camera.eye + eye.translateBy(vector) + camera.eye = eye + + + +def changeCameraZoom(camera: adsk.core.Camera,zoom): + zoomOld = camera.viewExtents + if zoom<0: + zoom=0.001 + camera.viewExtents = zoom + +numOfJoysticks = joystickapi.joyGetNumDevs() + + +joy = None + +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') + +# This section executes once on script start + ui = None + app: adsk.core.Application = adsk.core.Application.get() + ui = app.userInterface + greeting() + + if numOfJoysticks==0: + raise Exception("No joystick") + else: + joy = Joystick() + + # this found there https://github.com/Rabbid76/python_windows_joystickapi + + + ret, caps, startinfo = False, None, None + for id in range(numOfJoysticks): + ret, caps = joystickapi.joyGetDevCaps(id) + if ret: + ui.messageBox(str("Using gamepad: " + caps.szPname)) + ret, startinfo = joystickapi.joyGetPosEx(id) + break + else: + ui.messageBox(str("no gamepad detected")) + + first=True +# This section executes once on script start + + +# Loop starts + + while True: + # viewport + vp: adsk.core.Viewport = app.activeViewport + cam: adsk.core.Camera = vp.camera + vecUp: adsk.core.Vector3D = cam.upVector + target: adsk.core.Point3D = cam.target + eye: adsk.core.Point3D = cam.eye + + + + eTvector=eye.vectorTo(target) + angle1 = eTvector.angleTo(vecUp) + dx,dy,dz = getPerpendicularVector(vecUp.x, vecUp.y, vecUp.z) + perpendicular = adsk.core.Point3D.create(dx,dy,dz) + perpendicular=perpendicular.asVector() + + angle2=vecUp.angleTo(perpendicular) + + + if numOfJoysticks==0: + raise Exception("No joystick") + + + deg = (joy.getAxes()[6]/262144) + deg=deg*deg*deg + + zoom = cam.viewExtents + if cam.cameraType==0: + zoom = zoom-joy.getAxes()[1]/2621*zoom/5000 + else: + zoom = zoom-joy.getAxes()[1]/26214400 + + + changeCameraZoom(cam,zoom) + + # futil.log( str(str(joy.getAxes()) +"|"+ str(cam.viewExtents))) # all axes output + logger.print( + "vecUp - target angle: "+str(math.degrees(angle1))+ "\n"+ + "vecUp - calcul angle: "+str(math.degrees(angle2))+ "\n"+ + str(perpendicular.x)+"|"+str(perpendicular.y)+"|"+str(perpendicular.z) + ) + + + # matrix3d + mat: adsk.core.Matrix3D = adsk.core.Matrix3D.create() + + mat.setToRotation(math.radians(deg), vecUp, target) + # update camera + cam.isSmoothTransition = False + + transformCameraByMatrix(cam,mat) + + transformCameraByVector(cam,perpendicular) + + + + + + + + #futil.log(str(axisXYZRUV[0])+"|||||"+str(cam.viewExtents)) + # cam.viewExtents = zoom + vp.camera = cam + vp.refresh() + + adsk.doEvents() diff --git a/f360-script/External controls/__pycache__/config.cpython-39.pyc b/f360-script/External controls/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000..a4c6765 Binary files /dev/null and b/f360-script/External controls/__pycache__/config.cpython-39.pyc differ diff --git a/f360-script/External controls/commands/__init__.py b/f360-script/External controls/commands/__init__.py new file mode 100644 index 0000000..0499301 --- /dev/null +++ b/f360-script/External controls/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/f360-script/External controls/commands/__pycache__/__init__.cpython-39.pyc b/f360-script/External controls/commands/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..923bc4d Binary files /dev/null and b/f360-script/External controls/commands/__pycache__/__init__.cpython-39.pyc differ diff --git a/f360-script/External controls/commands/commandDialog/__init__.py b/f360-script/External controls/commands/commandDialog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/f360-script/External controls/commands/commandDialog/__pycache__/__init__.cpython-39.pyc b/f360-script/External controls/commands/commandDialog/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..c69504c Binary files /dev/null and b/f360-script/External controls/commands/commandDialog/__pycache__/__init__.cpython-39.pyc differ diff --git a/f360-script/External controls/commands/commandDialog/__pycache__/entry.cpython-39.pyc b/f360-script/External controls/commands/commandDialog/__pycache__/entry.cpython-39.pyc new file mode 100644 index 0000000..cd81118 Binary files /dev/null and b/f360-script/External controls/commands/commandDialog/__pycache__/entry.cpython-39.pyc differ diff --git a/f360-script/External controls/commands/commandDialog/entry.py b/f360-script/External controls/commands/commandDialog/entry.py new file mode 100644 index 0000000..e00314c --- /dev/null +++ b/f360-script/External controls/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: + inputs.areInputsValid = True + else: + inputs.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/f360-script/External controls/commands/commandDialog/resources/16x16.png b/f360-script/External controls/commands/commandDialog/resources/16x16.png new file mode 100644 index 0000000..03babdc Binary files /dev/null and b/f360-script/External controls/commands/commandDialog/resources/16x16.png differ diff --git a/f360-script/External controls/commands/commandDialog/resources/32x32.png b/f360-script/External controls/commands/commandDialog/resources/32x32.png new file mode 100644 index 0000000..863b2e3 Binary files /dev/null and b/f360-script/External controls/commands/commandDialog/resources/32x32.png differ diff --git a/f360-script/External controls/commands/commandDialog/resources/64x64.png b/f360-script/External controls/commands/commandDialog/resources/64x64.png new file mode 100644 index 0000000..dd285fd Binary files /dev/null and b/f360-script/External controls/commands/commandDialog/resources/64x64.png differ diff --git a/f360-script/External controls/commands/paletteSend/__init__.py b/f360-script/External controls/commands/paletteSend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/f360-script/External controls/commands/paletteSend/__pycache__/__init__.cpython-39.pyc b/f360-script/External controls/commands/paletteSend/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..01a3254 Binary files /dev/null and b/f360-script/External controls/commands/paletteSend/__pycache__/__init__.cpython-39.pyc differ diff --git a/f360-script/External controls/commands/paletteSend/__pycache__/entry.cpython-39.pyc b/f360-script/External controls/commands/paletteSend/__pycache__/entry.cpython-39.pyc new file mode 100644 index 0000000..f5ab94a Binary files /dev/null and b/f360-script/External controls/commands/paletteSend/__pycache__/entry.cpython-39.pyc differ diff --git a/f360-script/External controls/commands/paletteSend/entry.py b/f360-script/External controls/commands/paletteSend/entry.py new file mode 100644 index 0000000..0878e66 --- /dev/null +++ b/f360-script/External controls/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/f360-script/External controls/commands/paletteSend/resources/16x16.png b/f360-script/External controls/commands/paletteSend/resources/16x16.png new file mode 100644 index 0000000..c18250a Binary files /dev/null and b/f360-script/External controls/commands/paletteSend/resources/16x16.png differ diff --git a/f360-script/External controls/commands/paletteSend/resources/32x32.png b/f360-script/External controls/commands/paletteSend/resources/32x32.png new file mode 100644 index 0000000..f6b4c18 Binary files /dev/null and b/f360-script/External controls/commands/paletteSend/resources/32x32.png differ diff --git a/f360-script/External controls/commands/paletteSend/resources/64x64.png b/f360-script/External controls/commands/paletteSend/resources/64x64.png new file mode 100644 index 0000000..ed1ae27 Binary files /dev/null and b/f360-script/External controls/commands/paletteSend/resources/64x64.png differ diff --git a/f360-script/External controls/commands/paletteShow/__init__.py b/f360-script/External controls/commands/paletteShow/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/f360-script/External controls/commands/paletteShow/__pycache__/__init__.cpython-39.pyc b/f360-script/External controls/commands/paletteShow/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..1b93fad Binary files /dev/null and b/f360-script/External controls/commands/paletteShow/__pycache__/__init__.cpython-39.pyc differ diff --git a/f360-script/External controls/commands/paletteShow/__pycache__/entry.cpython-39.pyc b/f360-script/External controls/commands/paletteShow/__pycache__/entry.cpython-39.pyc new file mode 100644 index 0000000..c6e754a Binary files /dev/null and b/f360-script/External controls/commands/paletteShow/__pycache__/entry.cpython-39.pyc differ diff --git a/f360-script/External controls/commands/paletteShow/entry.py b/f360-script/External controls/commands/paletteShow/entry.py new file mode 100644 index 0000000..f48c1c4 --- /dev/null +++ b/f360-script/External controls/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/f360-script/External controls/commands/paletteShow/resources/16x16.png b/f360-script/External controls/commands/paletteShow/resources/16x16.png new file mode 100644 index 0000000..f1b8e57 Binary files /dev/null and b/f360-script/External controls/commands/paletteShow/resources/16x16.png differ diff --git a/f360-script/External controls/commands/paletteShow/resources/32x32.png b/f360-script/External controls/commands/paletteShow/resources/32x32.png new file mode 100644 index 0000000..a61a47e Binary files /dev/null and b/f360-script/External controls/commands/paletteShow/resources/32x32.png differ diff --git a/f360-script/External controls/commands/paletteShow/resources/64x64.png b/f360-script/External controls/commands/paletteShow/resources/64x64.png new file mode 100644 index 0000000..4dd5dfc Binary files /dev/null and b/f360-script/External controls/commands/paletteShow/resources/64x64.png differ diff --git a/f360-script/External controls/commands/paletteShow/resources/html/index.html b/f360-script/External controls/commands/paletteShow/resources/html/index.html new file mode 100644 index 0000000..2a6176e --- /dev/null +++ b/f360-script/External controls/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/f360-script/External controls/commands/paletteShow/resources/html/static/palette.js b/f360-script/External controls/commands/paletteShow/resources/html/static/palette.js new file mode 100644 index 0000000..dacdafc --- /dev/null +++ b/f360-script/External controls/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/f360-script/External controls/config.py b/f360-script/External controls/config.py new file mode 100644 index 0000000..ca31d6b --- /dev/null +++ b/f360-script/External controls/config.py @@ -0,0 +1,25 @@ +# 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 +VERSION = "11.06.22 12hr" +AUTHOR = "InTostor" + +# 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' +tools_tab_id = "ToolsTab" +my_tab_name = "test" # Only used if creating a custom Tab \ No newline at end of file diff --git a/f360-script/External controls/lib/Joystick.py b/f360-script/External controls/lib/Joystick.py new file mode 100644 index 0000000..b7b6dfd --- /dev/null +++ b/f360-script/External controls/lib/Joystick.py @@ -0,0 +1,137 @@ +from . import joystickapi +import os +from .mathExt import * + +class Joystick: + """This is wrapper class for joystick + + Attributes + ---------- + supportedOS : list + list with supported os + + + Constructor + ----------- + requires only id of joystick in system + todo: maybe in other systems name or uuid required + + Methods + ------- + getAxis(str: axis)->int + returns integer value of axis + """ + osn=None + supportedOS=['nt'] + + + axIndex=["X","Y","Z","R","U","V"] + + def __init__(self,id=0): + """ + initializing joystick. Throws "Unsopported os" if os not supported + """ + self.osn=os.name + + if self.osn not in self.supportedOS: + raise Exception("Unsupported os") + + self.id=id + ret, caps=joystickapi.joyGetDevCaps(id) + + self.ManufacturerId = caps.wMid + self.ProductId = caps.wPid + self.ProductName = caps.szPname + self.NumButtons = caps.wNumButtons + self.PeriodMin = caps.wPeriodMin + self.PeriodMax = caps.wPeriodMax + self.Caps = caps.wCaps + self.MaxAxes = caps.wMaxAxes + self.NumAxes = caps.wNumAxes + self.MaxButtons = caps.wMaxButtons + + self.rangeX=[caps.wXmin,caps.wXmax] + self.rangeY=[caps.wYmin,caps.wYmax] + self.rangeZ=[caps.wZmin,caps.wZmax] + + self.rangeR=[caps.wRmin,caps.wRmax] + self.rangeU=[caps.wUmin,caps.wUmax] + self.rangeV=[caps.wVmin,caps.wVmax] + + self.ranges=[ self.rangeX, self.rangeY, self.rangeZ, self.rangeR, self.rangeU, self.rangeV] + + + + def getAxesRaw(self) -> list: + out=[] + ret, io = joystickapi.joyGetPosEx(self.id) + if ret: + # X Y Z R U V POV + axes = [ + io.dwXpos, + io.dwYpos, + io.dwZpos, + io.dwRpos, + io.dwUpos, + io.dwVpos, + io.dwPOV + ] + + return axes + #else: + # raise Exception("Joystick read error") + + + + + + + def getAxisRaw(self,axis: str) -> int: + """Returns value of defined axis + + axes: + + dwXpos + Current X-coordinate. + + dwYpos + Current Y-coordinate. + + dwZpos + Current Z-coordinate. + + dwRpos + Current position of the rudder or fourth joystick axis. + + dwUpos + Current fifth axis position. + + dwVpos + Current sixth axis position. + + dwPOV + Current position of the point-of-view control. Values for this member are in the range 0 through 35,900. These values represent the angle, in degrees, of each view multiplied by 100. + + source: https://docs.microsoft.com/en-us/previous-versions/dd757112(v=vs.85) + """ + out=0 + availableAxes = ["dwXpos","dwYpos","dwZpos","dwRpos","dwUpos","dwVpos","dwPOV"] + if axis in availableAxes or axis=="list": + axes = self.getAxesRaw() + return axes[availableAxes.index(axis)] + else: + raise Exception("Axis doesnt exists") + + + + def getAxes(self) -> list: + axesRaw = self.getAxesRaw() + out=[] + + if str(type(axesRaw))=="": + return [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + + for i in range(0,len(axesRaw)): + out.append(int(correctAxis(axesRaw[i-1],self.ranges[i-1],0.05))) + + return out \ No newline at end of file diff --git a/f360-script/External controls/lib/__pycache__/Joystick.cpython-39.pyc b/f360-script/External controls/lib/__pycache__/Joystick.cpython-39.pyc new file mode 100644 index 0000000..3065787 Binary files /dev/null and b/f360-script/External controls/lib/__pycache__/Joystick.cpython-39.pyc differ diff --git a/f360-script/External controls/lib/__pycache__/conversions.cpython-39.pyc b/f360-script/External controls/lib/__pycache__/conversions.cpython-39.pyc new file mode 100644 index 0000000..7c5f7f3 Binary files /dev/null and b/f360-script/External controls/lib/__pycache__/conversions.cpython-39.pyc differ diff --git a/f360-script/External controls/lib/__pycache__/joystickapi.cpython-39.pyc b/f360-script/External controls/lib/__pycache__/joystickapi.cpython-39.pyc new file mode 100644 index 0000000..440ae37 Binary files /dev/null and b/f360-script/External controls/lib/__pycache__/joystickapi.cpython-39.pyc differ diff --git a/f360-script/External controls/lib/__pycache__/logger.cpython-39.pyc b/f360-script/External controls/lib/__pycache__/logger.cpython-39.pyc new file mode 100644 index 0000000..6a0a253 Binary files /dev/null and b/f360-script/External controls/lib/__pycache__/logger.cpython-39.pyc differ diff --git a/f360-script/External controls/lib/__pycache__/mathExt.cpython-39.pyc b/f360-script/External controls/lib/__pycache__/mathExt.cpython-39.pyc new file mode 100644 index 0000000..a4b2f8e Binary files /dev/null and b/f360-script/External controls/lib/__pycache__/mathExt.cpython-39.pyc differ diff --git a/f360-script/External controls/lib/conversions.py b/f360-script/External controls/lib/conversions.py new file mode 100644 index 0000000..299837f --- /dev/null +++ b/f360-script/External controls/lib/conversions.py @@ -0,0 +1,33 @@ +def scale(val,minIn,maxIn,minOut,maxOut): + """same as map() in c++ + """ + return (val - minIn) * (maxOut - minOut) / (maxIn - minIn) + minOut + + + +def correctAxis(value: int,rang: list,deadzone: float,)-> int: + """Corrects axis value (its hard to explain what it does) returns 20bit int (-2^19 ; 2^19) + Parameters + ---------- + value: int + value to correct + + range: list + range of axis ( [from,to] ) + + deadzone: float + zone (from center) where value will be always zero + + + """ + out=0 + + + + out=scale(value,rang[0],rang[1],-524287,524287) + if out<-524287*deadzone or out>+524287*deadzone: + out=out + else: + out=0 + + return out \ No newline at end of file diff --git a/f360-script/External controls/lib/fusion360utils/__init__.py b/f360-script/External controls/lib/fusion360utils/__init__.py new file mode 100644 index 0000000..56e95d5 --- /dev/null +++ b/f360-script/External controls/lib/fusion360utils/__init__.py @@ -0,0 +1,2 @@ +from .general_utils import * +from .event_utils import * diff --git a/f360-script/External controls/lib/fusion360utils/__pycache__/__init__.cpython-39.pyc b/f360-script/External controls/lib/fusion360utils/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..fcdf4aa Binary files /dev/null and b/f360-script/External controls/lib/fusion360utils/__pycache__/__init__.cpython-39.pyc differ diff --git a/f360-script/External controls/lib/fusion360utils/__pycache__/event_utils.cpython-39.pyc b/f360-script/External controls/lib/fusion360utils/__pycache__/event_utils.cpython-39.pyc new file mode 100644 index 0000000..be562d2 Binary files /dev/null and b/f360-script/External controls/lib/fusion360utils/__pycache__/event_utils.cpython-39.pyc differ diff --git a/f360-script/External controls/lib/fusion360utils/__pycache__/general_utils.cpython-39.pyc b/f360-script/External controls/lib/fusion360utils/__pycache__/general_utils.cpython-39.pyc new file mode 100644 index 0000000..751df17 Binary files /dev/null and b/f360-script/External controls/lib/fusion360utils/__pycache__/general_utils.cpython-39.pyc differ diff --git a/f360-script/External controls/lib/fusion360utils/event_utils.py b/f360-script/External controls/lib/fusion360utils/event_utils.py new file mode 100644 index 0000000..97a09b0 --- /dev/null +++ b/f360-script/External controls/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/f360-script/External controls/lib/fusion360utils/general_utils.py b/f360-script/External controls/lib/fusion360utils/general_utils.py new file mode 100644 index 0000000..fcf2667 --- /dev/null +++ b/f360-script/External controls/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/f360-script/External controls/lib/joystickapi.py b/f360-script/External controls/lib/joystickapi.py new file mode 100644 index 0000000..0974129 --- /dev/null +++ b/f360-script/External controls/lib/joystickapi.py @@ -0,0 +1,114 @@ +import ctypes + + +try: + winmmdll = ctypes.WinDLL('winmm.dll') + + # [joyGetNumDevs](https://docs.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetnumdevs) + """ + UINT joyGetNumDevs(); + """ + joyGetNumDevs_proto = ctypes.WINFUNCTYPE(ctypes.c_uint) + joyGetNumDevs_func = joyGetNumDevs_proto(("joyGetNumDevs", winmmdll)) + + # [joyGetDevCaps](https://docs.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetdevcaps) + """ + MMRESULT joyGetDevCaps(UINT uJoyID, LPJOYCAPS pjc, UINT cbjc); + + 32 bit: joyGetDevCapsA + 64 bit: joyGetDevCapsW + + sizeof(JOYCAPS): 728 + """ + joyGetDevCaps_proto = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint) + joyGetDevCaps_param = (1, "uJoyID", 0), (1, "pjc", None), (1, "cbjc", 0) + joyGetDevCaps_func = joyGetDevCaps_proto(("joyGetDevCapsW", winmmdll), joyGetDevCaps_param) + + # [joyGetPosEx](https://docs.microsoft.com/en-us/windows/win32/api/joystickapi/nf-joystickapi-joygetposex) + """ + MMRESULT joyGetPosEx(UINT uJoyID, LPJOYINFOEX pji); + sizeof(JOYINFOEX): 52 + """ + joyGetPosEx_proto = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p) + joyGetPosEx_param = (1, "uJoyID", 0), (1, "pji", None) + joyGetPosEx_func = joyGetPosEx_proto(("joyGetPosEx", winmmdll), joyGetPosEx_param) +except: + winmmdll = None + +# joystickapi - joyGetNumDevs +def joyGetNumDevs(): + try: + num = joyGetNumDevs_func() + except: + num = 0 + return num + +# joystickapi - joyGetDevCaps +def joyGetDevCaps(uJoyID): + try: + buffer = (ctypes.c_ubyte * JOYCAPS.SIZE_W)() + p1 = ctypes.c_uint(uJoyID) + p2 = ctypes.cast(buffer, ctypes.c_void_p) + p3 = ctypes.c_uint(JOYCAPS.SIZE_W) + ret_val = joyGetDevCaps_func(p1, p2, p3) + ret = (False, None) if ret_val != JOYERR_NOERROR else (True, JOYCAPS(buffer)) + except: + ret = False, None + return ret + +# joystickapi - joyGetPosEx +def joyGetPosEx(uJoyID): + try: + buffer = (ctypes.c_uint32 * (JOYINFOEX.SIZE // 4))() + buffer[0] = JOYINFOEX.SIZE + buffer[1] = JOY_RETURNALL + p1 = ctypes.c_uint(uJoyID) + p2 = ctypes.cast(buffer, ctypes.c_void_p) + ret_val = joyGetPosEx_func(p1, p2) + ret = (False, None) if ret_val != JOYERR_NOERROR else (True, JOYINFOEX(buffer)) + except: + ret = False, None + return ret + +JOYERR_NOERROR = 0 +JOY_RETURNX = 0x00000001 +JOY_RETURNY = 0x00000002 +JOY_RETURNZ = 0x00000004 +JOY_RETURNR = 0x00000008 +JOY_RETURNU = 0x00000010 +JOY_RETURNV = 0x00000020 +JOY_RETURNPOV = 0x00000040 +JOY_RETURNBUTTONS = 0x00000080 +JOY_RETURNRAWDATA = 0x00000100 +JOY_RETURNPOVCTS = 0x00000200 +JOY_RETURNCENTERED = 0x00000400 +JOY_USEDEADZONE = 0x00000800 +JOY_RETURNALL = (JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | \ + JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | \ + JOY_RETURNPOV | JOY_RETURNBUTTONS) + +# joystickapi - JOYCAPS +class JOYCAPS: + SIZE_W = 728 + OFFSET_V = 4 + 32*2 + def __init__(self, buffer): + ushort_array = (ctypes.c_uint16 * 2).from_buffer(buffer) + self.wMid, self.wPid = ushort_array + + wchar_array = (ctypes.c_wchar * 32).from_buffer(buffer, 4) + self.szPname = ctypes.cast(wchar_array, ctypes.c_wchar_p).value + + uint_array = (ctypes.c_uint32 * 19).from_buffer(buffer, JOYCAPS.OFFSET_V) + self.wXmin, self.wXmax, self.wYmin, self.wYmax, self.wZmin, self.wZmax, \ + self.wNumButtons, self.wPeriodMin, self.wPeriodMax, \ + self.wRmin, self.wRmax, self.wUmin, self.wUmax, self.wVmin, self.wVmax, \ + self.wCaps, self.wMaxAxes, self.wNumAxes, self.wMaxButtons = uint_array + +# joystickapi - JOYINFOEX +class JOYINFOEX: + SIZE = 52 + def __init__(self, buffer): + uint_array = (ctypes.c_uint32 * (JOYINFOEX.SIZE // 4)).from_buffer(buffer) + self.dwSize, self.dwFlags, \ + self.dwXpos, self.dwYpos, self.dwZpos, self.dwRpos, self.dwUpos, self.dwVpos, \ + self.dwButtons, self.dwButtonNumber, self.dwPOV, self.dwReserved1, self.dwReserved2 = uint_array diff --git a/f360-script/External controls/lib/logger.py b/f360-script/External controls/lib/logger.py new file mode 100644 index 0000000..6d73248 --- /dev/null +++ b/f360-script/External controls/lib/logger.py @@ -0,0 +1,12 @@ + +from . import fusion360utils as futil + + + +class Logger: + def __init__(self,mode:str="f360cmd"): + self.mode="f360cmd" + + def print(self,message:any=""): + message=str(message) + futil.log(message) \ No newline at end of file diff --git a/f360-script/External controls/lib/mathExt.py b/f360-script/External controls/lib/mathExt.py new file mode 100644 index 0000000..5f4596e --- /dev/null +++ b/f360-script/External controls/lib/mathExt.py @@ -0,0 +1,69 @@ +import math +from msilib.schema import RadioButton + + +def scale(val,minIn,maxIn,minOut,maxOut): + """same as map() in c++ + """ + return (val - minIn) * (maxOut - minOut) / (maxIn - minIn) + minOut + + + +def correctAxis(value: int,rang: list,deadzone: float,)-> int: + """Corrects axis value (its hard to explain what it does) returns 20bit int (-2^19 ; 2^19) + Parameters + ---------- + value: int + value to correct + + range: list + range of axis ( [from,to] ) + + deadzone: float + zone (from center) where value will be always zero + + + """ + out=0 + + + + out=scale(value,rang[0],rang[1],-524287,524287) + if out<-524287*deadzone or out>+524287*deadzone: + out=out + else: + out=0 + + return out + +def precissionEqual(val1,val2,epsilon): + vmin=vmax=val1-epsilon,val1+epsilon + if val2>=vmin and val2<=vmax: + return True + else: + return False + +def getVectorAngles(x,y,z): + """returns angles in radians + """ + try: + angleX = math.atan(z/y) + except ZeroDivisionError: + angleX=0 + try: + angleY = math.atan(z/x) + except ZeroDivisionError: + angleY=0 + try: + angleZ = math.atan(y/x) + except ZeroDivisionError: + angleZ=0 + return angleX,angleY,angleZ + +def getPerpendicularVector(x,y,z): + length=(x*x+y*y+z*z)**0.5 + oAngleX,oAngleY,oAngleZ = getVectorAngles(x,y,z) + angleX, angleY, angleZ = math.pi/2-oAngleX, math.pi/2-oAngleY, math.pi/2-oAngleZ + dotX,dotY,dotZ = math.cos(angleX)*length,math.cos(angleY)*length,math.cos(angleZ)*length + + return dotX,dotY,dotZ diff --git a/img/operating-graph.png b/img/operating-graph.png new file mode 100644 index 0000000..4a53c8e Binary files /dev/null and b/img/operating-graph.png differ