Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianPommerening committed Apr 19, 2020
0 parents commit 19c720f
Show file tree
Hide file tree
Showing 64 changed files with 850 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.idea
/.vscode
/__pycache__
/defaults.json
13 changes: 13 additions & 0 deletions FingerJoints.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"autodeskProduct": "Fusion360",
"type": "addin",
"id": "51e13efe-8453-49c7-becd-bfed6571c1cf",
"author": "Florian Pommerening",
"description": {
"": "An add-in for making finger joints."
},
"version": "1.0.0",
"runOnStartup": true,
"supportedOS": "windows|mac",
"editEnabled": true
}
91 changes: 91 additions & 0 deletions FingerJoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#Author-Florian Pommerening
#Description-An Add-In for making finger joints.

# Select two overlapping bodies and a direction. The overlap is cut along the
# direction multiple times resulting in the individual fingers/notches. We
# then remove every second finger from the first body and the other fingers
# from the second body. The remaining bodies then do not overlap anymore.

# Some inspiration was taken from the dogbone add-in developed by Peter
# Ludikar, Gary Singer, Patrick Rainsberry, David Liu, and Casey Rogers.

import adsk.core
import adsk.fusion

from . import commands
from . import options
from . import ui
from . import geometry


# Global variable to hold the add-in (created in run(), destroyed in stop())
addin = None


class FingerJointCommand(commands.RunningCommandBase):
def __init__(self, args: adsk.core.CommandCreatedEventArgs):
super(FingerJointCommand, self).__init__(args)
self.options = options.FingerJointOptions()
self.ui = ui.FingerJointUI(args.command.commandInputs, self.options)

def onInputChanged(self, args: adsk.core.InputChangedEventArgs):
self.ui.updateVisibility()
self.ui.focusNextSelectionInput()

def onValidate(self, args: adsk.core.ValidateInputsEventArgs):
args.areInputsValid = self.ui.areInputsValid()

def onExecutePreview(self, args: adsk.core.CommandEventArgs):
if self.ui.isPreviewEnabled():
if self.doExecute():
args.isValidResult = True
self.ui.setInputErrorMessage('')
else:
self.ui.setInputErrorMessage('Finger joints could not be completed. Try selecting overlapping bodies and double-check the dimensions.')

def onExecute(self, args: adsk.core.CommandEventArgs):
if not self.doExecute():
args.executeFailed = True
args.executeFailedMessage = 'Finger joints could not be completed. Try selecting overlapping bodies and double-check the dimensions.'

def doExecute(self):
self.ui.setRelevantOptions(self.options)
body0 = self.ui.getBody0()
body1 = self.ui.getBody1()
direction = self.ui.getDirection()
return geometry.createFingerJoint(body0, body1, direction, self.options)

def onDestroy(self, args: adsk.core.CommandEventArgs):
super(FingerJointCommand, self).onDestroy(args)
if args.terminationReason == adsk.core.CommandTerminationReason.CompletedTerminationReason:
self.options.writeDefaults()


class FingerJointAddIn(object):
def __init__(self):
self.button = commands.CommandButton('FingerJointBtn', 'SolidModifyPanel', FingerJointCommand)

def start(self):
self.button.addToUI('Finger Joint',
'Creates a finger joint from the overlap of two bodies',
'resources/ui/command_button')

def stop(self):
self.button.removeFromUI()


def run(context):
global addin
try:
if addin is not None:
stop()
addin = FingerJointAddIn()
addin.start()
except:
ui.reportError('Uncaught exception', True)


def stop(context):
global addin
addin.stop()
addin = None
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Finger Joints

This add-in for Autodesk Fusion 360 can create a finger joint (box joint) from the overlap of two objects. Although, not specifically designed for this, the add-in also works for lap joints and can cut pieces into slices.

![Finger joint](resources/doc/demo.png)


## Installation

Download the [latest version of the plugin](https://github.com/FlorianPommerening/FingerJoints/archive/master.zip), unpack it to your add-ins directory (see below) and remove the "-master" from the name. To start the add-in inside Fusion use the "ADD-INS" button in the "TOOLS" ribbon, then find the add-in in the "Add-Ins" tab, select it and click "Run".

Add-ins directory

* Windows: `%AppData%\Autodesk\Autodesk Fusion 360\API\AddIns`
* Mac OS X: `~/Library/Application Support/Autodesk/Autodesk Fusion 360/API/AddIns`
or ` ~/Library/Containers/com.autodesk.mas.fusion360/Data/Library/Application Support/Autodesk/Autodesk Fusion 360/API/AddIns`

There are also [more detailed installation instructions](https://tapnair.github.io/installation.html) by Patrick Rainsberry.


## Usage

Select two overlapping bodies and a direction. The direction is required, so the add-in knows in which plane to cut. In most cases, you probably want an edge that runs along the joint as shown in the picture.

![Selection of body and edges](resources/doc/usage1.png)

The next setting controls whether the first body has fingers on both ends, notches on both ends, or a finger on one end and a notch on the other. In the next picture, we place three fingers on the first body with different settings, the number of fingers on the second body adjusts accordingly.

![Finger placement](resources/doc/usage2.png)

The next settings determine how the size of the overlap is distributed between fingers of the first body and fingers of the second body (as we view everything from the perspective of the first body, we call fingers of the second body "notches").

When the number of fingers is fixed, we can specify their size in three ways. In the example above, we chose to have fingers and notches of equal size. Instead, we can chose to fix the size of either fingers or notches (the other size is determined automatically). The next picture shows the effect of fixing the size of fingers or notches to 5mm.

![Fixed finger size with fixed number of fingers](resources/doc/usage3.png)

Finally, instead of fixing the number of fingers, we might want to have both fingers and notches of a certain size and place as many fingers as will fit along the joint. Fixing both sizes could lead to cases where one body ends in half a finger, so instead, we only fix either the size of fingers or notches and specify a minimal size for the other. For example, the following picture fixes the size of a finger to 9mm and uses a minimal notch size of 5mm. The height of the joint is 50mm, so using four 9mm fingers and three 5mm notches would exceed the size by 1 mm. Instead, the add-in uses three 9mm fingers and increases the size of the two notches to 11.5mm. Likewise, we could fix the size of notches and make the size of fingers dynamic, or we could require that fingers and notches have the same size.

![Dynamic finger number with fixed finger size](resources/doc/usage4.png)


Of course, the objects you join do not have to be rectangular and can overlap in multiple places.

![General objects can be joined](resources/doc/usage5.png)
![Resulting objects](resources/doc/usage6.png)


## Other Uses

In addition to creating finger joints, the add-in can be used to create lap joints and cut pieces into slices.

### Lap Joints

To create a lap joint, start with two overlapping pieces and create a finger joint with one finger placed asymmertically on the start or end of the first body.

![Lap joint settings](resources/doc/lapjointssettings.png)
![Lap joint result](resources/doc/lapjointsresults.png)

### Slicing

This is not really by design but you can duplicate a body so it perfectly overlaps with itself, then create a finger joint between the
copy and the original. This will slice the body along your chosen axis.

![Slicing](resources/doc/slicing1.png)
![Slicing](resources/doc/slicing2.png)


## Issues

If you find any issues with the add-in, please report them on [Github][issuetracker]

## TODO / Help wanted

If you have ideas how to improve the add-in, you can create an issue on [Github][issuetracker]. Since I am working on this in my free time I cannot promise I will get to it, though. Pull requests for fixes and new features are very welcome.

The add-in is currently not parametric, so updating the underlying geometry will not update the placement or size of the fingers. I'm not sure this is possible with the current API (there is a [related request on the Fusion 360 IdeaStation][ideastation]). If you have an idea of how to implement this, please get in touch.

I am not an artist, so the icons are still [Programmer art](https://en.wikipedia.org/wiki/Programmer_art). I'd be happy to replace them with something closer to the style of the built-in commands.

## License

[![Creative Commons License][by-nc-sa-logo]][by-nc-sa-link]

This add-in is licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][by-nc-sa-link]. If you are interested in using the add-in under different terms, contact florian.pommerening@unibas.ch.


[by-nc-sa-logo]: https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png
[by-nc-sa-link]: http://creativecommons.org/licenses/by-nc-sa/4.0/
[issuetracker]: https://github.com/FlorianPommerening/FingerJoints/issues
[ideastation]: https://forums.autodesk.com/t5/fusion-360-ideastation/allow-add-ins-to-be-fully-parametric-and-represented-in-the/idi-p/8660436
131 changes: 131 additions & 0 deletions commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import adsk.core
import adsk.fusion

from . import ui

# Keep track of all currently running commands in a global set, so their
# callback handlers are not GC'd.
running_commands = set()

class RunningCommandBase(object):
"""
Base class to keep persistent data during the lifetime of a command from
creation to destruction. The constructor of this class automatically adds
the instance to running_commands and the onDestroy event removes it again.
To use this class, inherit from it an override the events.
"""

def __init__(self, args):
running_commands.add(self)

cmd = adsk.core.Command.cast(args.command)

self._inputChangedHandler = makeForwardingHandler(
adsk.core.InputChangedEventHandler, self.onInputChanged)
cmd.inputChanged.add(self._inputChangedHandler)

self._selectionHandler = makeForwardingHandler(
adsk.core.SelectionEventHandler, self.onSelectionEvent)
cmd.selectionEvent.add(self._selectionHandler)

self._validateHandler = makeForwardingHandler(
adsk.core.ValidateInputsEventHandler, self.onValidate)
cmd.validateInputs.add(self._validateHandler)

self._executeHandler = makeForwardingHandler(
adsk.core.CommandEventHandler, self.onExecute)
cmd.execute.add(self._executeHandler)

self._executePreviewHandler = makeForwardingHandler(
adsk.core.CommandEventHandler, self.onExecutePreview)
cmd.executePreview.add(self._executePreviewHandler)

self._destroyHandler = makeForwardingHandler(
adsk.core.CommandEventHandler, self.onDestroy)
cmd.destroy.add(self._destroyHandler)

def onCreated(self, args):
pass

def onInputChanged(self, args):
pass

def onSelectionEvent(self, args):
pass

def onValidate(self, args):
pass

def onExecute(self, args):
pass

def onExecutePreview(self, args):
pass

def onDestroy(self, args):
running_commands.remove(self)


class CommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self, runningCommandClass):
super().__init__()
self.runningCommandClass = runningCommandClass

def notify(self, args):
try:
running_command = self.runningCommandClass(args)
running_command.onCreated(args)
except:
ui.reportError('Command creation callback method failed', True)


def makeForwardingHandler(handler_cls, callback):
class ForwardingHandler(handler_cls):
def __init__(self, callback):
super().__init__()
self.callback = callback

def notify(self, args):
try:
self.callback(args)
except:
ui.reportError('Callback method failed', True)
return ForwardingHandler(callback)


class CommandButton(object):
def __init__(self, commandID, panelName, commandDataClass):
self.commandID = commandID
self.panelName = panelName

fusion = adsk.core.Application.get()
self.fusionUI = fusion.userInterface
self.creationHandler = CommandCreatedHandler(commandDataClass)

def addToUI(self, name, tooltip='', resourceFolder=''):
# If there are existing instances of the button, clean them up first.
try:
self.removeFromUI()
except:
pass

# Create a command definition and button for it.
commandDefinition = self.fusionUI.commandDefinitions.addButtonDefinition(
self.commandID, name, tooltip, resourceFolder)
commandDefinition.commandCreated.add(self.creationHandler)

panel = self.fusionUI.allToolbarPanels.itemById(self.panelName)
buttonControl = panel.controls.addCommand(commandDefinition)

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

def removeFromUI(self):
commandDefinition = self.fusionUI.commandDefinitions.itemById(self.commandID)
if commandDefinition:
commandDefinition.deleteMe()
panel = self.fusionUI.allToolbarPanels.itemById(self.panelName)
buttonControl = panel.controls.itemById(self.commandID)
if buttonControl:
buttonControl.deleteMe()
Loading

0 comments on commit 19c720f

Please sign in to comment.