Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mac support #7

Closed
tapnair opened this issue Jul 24, 2019 · 92 comments
Closed

Mac support #7

tapnair opened this issue Jul 24, 2019 · 92 comments

Comments

@tapnair
Copy link

tapnair commented Jul 24, 2019

I am wondering if the answer to Mac support is as simple as removing the ".exe" filter from the input box in settings? Probably not. But thought id try. I couldn't figure out how to get my forked copy of your plugin to run to test. I'd be happy to do so.

In: FusionConfigurable.java, Line 85

@JesusFreke
Copy link
Owner

There are a few things off the top of my head that would need to be tweaked or taken a look at.

First, the logic at

File startPath = new File(homeDir.getCanonicalPath(), "AppData/Local/Autodesk/webdeploy/production");
would need to be made mac-aware. There may be a few other similar path-related issues here and there.

Also, There is a python script that is used to inject a small python snippet that configures the remote debugger in the fusion process. I had based the script off of an existing script from the IDEA source, and that script had 2 different branches, depending on whether it was running on windows, or linux/mac. I tried to maintain the mac branch, based on the style from the original script, but I have no way of verifying if it actually works or not. See:

@JesusFreke
Copy link
Owner

I don't remember offhand exactly how I had debugged issues with the python script when I was working on it, but I do recall it being difficult to debug, due to the nature of how it is run. e.g. it's hard to even know when an exception happens, and what the exception is. One hacky workaround might be to wrap that section in a try/except: clause, and then write out the exception details to a file somewhere.

I'm happy to help you get your development environment set up so you can work on the plugin and run it, but I need a bit more detail about where you're getting stuck :)

@rbackman
Copy link

I am interested in helping with the mac port. Spyder has been driving me nuts :) Can you explain the build process workflow I would need to do test it is working?

@JesusFreke
Copy link
Owner

For anyone looking to port this to mac, I think the first thing to do would be to see if IDEA's native "attach to process" functionality can attach to fusion 360. It won't show fusion's process by default, but you can change the process filter in settings. In IDEA, the setting is at "Build, Execution, Deployment"->"Python Debugger"->"For Attach To Process show processes with names including".

If that doesn't work, then it may be somewhat difficult to get this plugin to work. But if it does work, then hopefully it's just a matter of tweaking the launch script used by this plugin, or something like that.

@tapnair
Copy link
Author

tapnair commented Oct 21, 2019

I have spent quite a bit of time on this but have been unsuccessful. That said I don't really have any clue what I'm doing haha. If anybody has any ideas on how to do this i think i could get the rest of the port done. When I do it, my problem is that it is launching a second instance of Fusion 360 rather than connecting to the one that is already running. Also then it tries to simply "open" the python script, which maybe is supported on windows, but doesn't look to be supported on Mac?

@Jerakin
Copy link

Jerakin commented Oct 26, 2019

As a first thing I would be super happy if I could get auto completion :)

@JesusFreke
Copy link
Owner

You don't even need the plugin for that :). Just create a python module and add the python stub apis as a dependency

@Jerakin
Copy link

Jerakin commented Oct 27, 2019

I tried too but I couldn't find the files in "Api/Python/packages/adsk/defs" (is that where they should live?)
On mac the file lives here ~/Library/Application Support/Autodesk/webdeploy/production/<<version>>/Autodesk Fusion 360.app/Contents/Api/Python/packages/adsk/defs
I made a setup.py file to build the stub as a wheel if anyone wants a lazy way to get auto complete. adsk-stub

@jlirochon
Copy link

I have spent quite a bit of time on this but have been unsuccessful. That said I don't really have any clue what I'm doing haha. If anybody has any ideas on how to do this i think i could get the rest of the port done. When I do it, my problem is that it is launching a second instance of Fusion 360 rather than connecting to the one that is already running. Also then it tries to simply "open" the python script, which maybe is supported on windows, but doesn't look to be supported on Mac?

More precisely here is what appears in the log:

Attaching to a process with PID=89902
"/Users/julien/Library/Application Support/Autodesk/webdeploy/production/078c0152f608cb87272eeb7be2226a5f77176092/Autodesk Fusion 360.app/Contents/MacOS/Autodesk Fusion 360" "/Users/julien/Library/Application Support/JetBrains/Toolbox/apps/PyCharm-P/ch-0/192.7142.42/PyCharm.app/Contents/helpers/pydev/pydevd_attach_to_process/attach_pydevd.py" --port 60927 --pid 89902

Then a new instance of Fusion 360 is spawned.

@jlirochon
Copy link

jlirochon commented Nov 25, 2019

As a workaround, it's possible to use the debugger on Mac without this plugin. You can create a Python Remote Debug configuration in the IDE, then add the given code snippet to your script. It looks like this (please update to reflect the path and port on your system):

sys.path.append("/Users/julien/Library/Application Support/JetBrains/Toolbox/apps/PyCharm-P/ch-0/192.7142.42/PyCharm.app/Contents/debug-eggs/pydevd-pycharm.egg")
import pydevd_pycharm
pydevd_pycharm.settrace("localhost", port=12345, stdoutToServer=True, stderrToServer=True)

Then you have to launch the debugger from the IDE, it will display "Waiting for process connection...".
Finally, you have to run the script from Fusion 360.

Tested with Fusion 360 2.0.6670 and PyCharm Pro 2019.2.4.

In my experience you can relaunch the script multiple times, but if you close/restart the debugging session in the IDE, for some reason it won't work anymore. Restarting Fusion 360 solves the problem, but just don't kill the debugging session and it should be ok.

@jeremyherbert
Copy link

I've been having a play with getting this to work, after fixing up the paths I can get the extension configured and working in the editor with autocomplete. However, I'm now stuck at an odd point. When I try to run the script, the extension fails to get the PID of fusion 360 due to some sort of synchronisation issue:

java.lang.Throwable: Synchronous execution on EDT: /bin/ps -a -x -o pid,state,user,comm
	at com.intellij.openapi.diagnostic.Logger.error(Logger.java:145)
	at com.intellij.execution.process.OSProcessHandler.checkEdtAndReadAction(OSProcessHandler.java:117)
	at com.intellij.execution.process.OSProcessHandler.waitFor(OSProcessHandler.java:55)
	at com.intellij.execution.process.CapturingProcessRunner.runProcess(CapturingProcessRunner.java:31)
	at com.intellij.execution.process.CapturingProcessHandler.runProcess(CapturingProcessHandler.java:50)
	at com.intellij.execution.util.ExecUtil.execAndGetOutput(ExecUtil.kt:80)
	at com.intellij.execution.process.impl.ProcessListUtil.parseCommandOutput(ProcessListUtil.java:78)
	at com.intellij.execution.process.impl.ProcessListUtil.getProcessListOnMac(ProcessListUtil.java:151)
	at com.intellij.execution.process.impl.ProcessListUtil.doGetProcessList(ProcessListUtil.java:58)
	at com.intellij.execution.process.impl.ProcessListUtil.getProcessList(ProcessListUtil.java:35)
	at com.intellij.execution.process.OSProcessUtil.getProcessList(OSProcessUtil.java:24)
	at com.intellij.xdebugger.attach.LocalAttachHost.getProcessList(LocalAttachHost.java:27)
	at org.jf.fusionIdea.facet.FusionFacet.findTargetProcesses(FusionFacet.java:212)
	at org.jf.fusionIdea.run.FusionExecutionTargetProvider.getTargets(FusionExecutionTargetProvider.java:59)
	at com.intellij.execution.ExecutionTargetProvider.getTargets(ExecutionTargetProvider.java:30)
	at com.intellij.execution.ExecutionTargetManagerImpl.lambda$getTargetsFor$1(ExecutionTargetManagerImpl.java:239)
	at com.intellij.execution.ExecutionTargetManagerImpl.doWithEachNonCompoundWithSpecifiedTarget(ExecutionTargetManagerImpl.java:279)
	at com.intellij.execution.ExecutionTargetManagerImpl.getTargetsFor(ExecutionTargetManagerImpl.java:237)
	at com.intellij.execution.ExecutionTargetManagerImpl.updateActiveTarget(ExecutionTargetManagerImpl.java:145)
	at com.intellij.execution.ExecutionTargetManagerImpl.updateActiveTarget(ExecutionTargetManagerImpl.java:140)
	at com.intellij.execution.ExecutionTargetManagerImpl.access$100(ExecutionTargetManagerImpl.java:28)
	at com.intellij.execution.ExecutionTargetManagerImpl$2.runConfigurationSelected(ExecutionTargetManagerImpl.java:72)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at com.intellij.util.messages.impl.MessageBusConnectionImpl.deliverMessage(MessageBusConnectionImpl.java:102)
	at com.intellij.util.messages.impl.MessageBusImpl.doPumpMessages(MessageBusImpl.java:446)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpWaitingBuses(MessageBusImpl.java:406)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpMessages(MessageBusImpl.java:395)
	at com.intellij.util.messages.impl.MessageBusImpl.pumpMessages(MessageBusImpl.java:379)
	at com.intellij.util.messages.impl.MessageBusImpl.sendMessage(MessageBusImpl.java:372)
	at com.intellij.util.messages.impl.MessageBusImpl.lambda$createTopicHandler$1(MessageBusImpl.java:242)
	at com.sun.proxy.$Proxy67.runConfigurationSelected(Unknown Source)
	at com.intellij.execution.impl.RunManagerImpl.setSelectedConfiguration(RunManagerImpl.kt:449)
	at com.intellij.execution.impl.RunConfigurable.updateActiveConfigurationFromSelected(RunConfigurable.kt:578)
	at com.intellij.execution.impl.RunConfigurable.apply(RunConfigurable.kt:570)
	at com.intellij.openapi.options.ex.SingleConfigurableEditor.doOKAction(SingleConfigurableEditor.java:163)
	at com.intellij.execution.impl.EditConfigurationsDialog.doOKAction(EditConfigurationsDialog.java:52)
	at com.intellij.openapi.ui.DialogWrapper$OkAction.doAction(DialogWrapper.java:1861)
	at com.intellij.openapi.ui.DialogWrapper$DialogWrapperAction.actionPerformed(DialogWrapper.java:1821)
	at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1967)
	at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2308)
	at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
	at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
	at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:270)
	at java.desktop/java.awt.Component.processMouseEvent(Component.java:6651)
	at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3342)
	at java.desktop/java.awt.Component.processEvent(Component.java:6416)
	at java.desktop/java.awt.Container.processEvent(Container.java:2263)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5026)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4858)
	at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4918)
	at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4547)
	at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4488)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2307)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2773)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4858)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:778)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:751)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:749)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:748)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:824)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:769)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:412)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:693)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:411)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:117)
	at java.desktop/java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:190)
	at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:235)
	at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:233)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.desktop/java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:233)
	at java.desktop/java.awt.Dialog.show(Dialog.java:1063)
	at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl$MyDialog.show(DialogWrapperPeerImpl.java:707)
	at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl.show(DialogWrapperPeerImpl.java:432)
	at com.intellij.openapi.ui.DialogWrapper.doShow(DialogWrapper.java:1685)
	at com.intellij.openapi.ui.DialogWrapper.show(DialogWrapper.java:1644)
	at com.intellij.execution.actions.EditRunConfigurationsAction.actionPerformed(EditRunConfigurationsAction.java:28)
	at com.intellij.openapi.actionSystem.ex.ActionUtil$1.run(ActionUtil.java:265)
	at com.intellij.openapi.actionSystem.ex.ActionUtil.performActionDumbAware(ActionUtil.java:282)
	at com.intellij.openapi.actionSystem.impl.ActionManagerImpl.lambda$null$4(ActionManagerImpl.java:1403)
	at com.intellij.openapi.application.TransactionGuardImpl.performUserActivity(TransactionGuardImpl.java:192)
	at com.intellij.openapi.actionSystem.impl.ActionManagerImpl.lambda$tryToExecuteNow$5(ActionManagerImpl.java:1363)
	at com.intellij.openapi.wm.impl.FocusManagerImpl.lambda$doWhenFocusSettlesDown$3(FocusManagerImpl.java:170)
	at com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(UIUtil.java:2464)
	at com.intellij.ide.IdeEventQueue.ifFocusEventsInTheQueue(IdeEventQueue.java:168)
	at com.intellij.ide.IdeEventQueue.executeWhenAllFocusEventsLeftTheQueue(IdeEventQueue.java:120)
	at com.intellij.openapi.wm.impl.FocusManagerImpl.doWhenFocusSettlesDown(FocusManagerImpl.java:161)
	at com.intellij.openapi.wm.impl.FocusManagerImpl.doWhenFocusSettlesDown(FocusManagerImpl.java:167)
	at com.intellij.openapi.wm.impl.IdeFocusManagerImpl.doWhenFocusSettlesDown(IdeFocusManagerImpl.java:63)
	at com.intellij.openapi.actionSystem.impl.ActionManagerImpl.tryToExecuteNow(ActionManagerImpl.java:1362)
	at com.intellij.openapi.actionSystem.impl.ActionManagerImpl.lambda$tryToExecute$2(ActionManagerImpl.java:1346)
	at com.intellij.openapi.actionSystem.impl.ActionManagerImpl.tryToExecute(ActionManagerImpl.java:1349)
	at com.intellij.execution.actions.RunConfigurationsComboBoxAction.performWhenButton(RunConfigurationsComboBoxAction.java:165)
	at com.intellij.execution.actions.RunConfigurationsComboBoxAction.access$000(RunConfigurationsComboBoxAction.java:35)
	at com.intellij.execution.actions.RunConfigurationsComboBoxAction$1.fireActionPerformed(RunConfigurationsComboBoxAction.java:142)
	at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2308)
	at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
	at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
	at java.desktop/javax.swing.AbstractButton.doClick(AbstractButton.java:369)
	at java.desktop/javax.swing.AbstractButton.doClick(AbstractButton.java:349)
	at com.intellij.openapi.actionSystem.ex.ComboBoxAction$ComboBoxButton$1.mousePressed(ComboBoxAction.java:172)
	at java.desktop/java.awt.AWTEventMulticaster.mousePressed(AWTEventMulticaster.java:288)
	at java.desktop/java.awt.AWTEventMulticaster.mousePressed(AWTEventMulticaster.java:287)
	at java.desktop/java.awt.AWTEventMulticaster.mousePressed(AWTEventMulticaster.java:287)
	at java.desktop/java.awt.Component.processMouseEvent(Component.java:6648)
	at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3342)
	at java.desktop/java.awt.Component.processEvent(Component.java:6416)
	at java.desktop/java.awt.Container.processEvent(Container.java:2263)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5026)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4858)
	at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4918)
	at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4544)
	at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4488)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2307)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2773)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4858)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:778)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:751)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:749)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:748)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:824)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:769)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:412)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:704)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:411)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

My best guess is that the findTargetProcesses is running in the wrong thread, but I'm a bit mistified as to why this would work fine on Windows and not mac. If you have any ideas about this it would be appreciated.

I'm using IDEA ultimate 2019.3.3.

@jeremyherbert
Copy link

I'm also struggling a bit to understand how the inject.py script works, in particular this line:

import add_code_to_python_process

where is add_code_to_python_process.py in the plugin? Is this some sort of autogenerated file?

@jeremyherbert
Copy link

Ok, worked out the add_code_to_python_process (it's included with the jetbrains tools).

Unfortunately, it looks like in the current state that this can't work on mac. The reason is that lldb cannot attach to the fusion360 process:

sh-3.2# lldb --no-lldbinit -o 'process attach --pid 32040'
(lldb) process attach --pid 32040
error: attach failed: Error 1

This may be because the kernel is preventing the attach for security reasons, or fusion 360 is preventing it for copy protection reasons. The actual attaching to the python interpreter once it is running is fine as per @jlirochon 's workaround, but there doesn't seem to be a way to launch the python debug client inside fusion using pycharm so that step will need to happen manually.

@tapnair it looks like you work for autodesk. If you would like to push this forward, something like the following would be needed.

https://forums.autodesk.com/t5/fusion-360-ideastation/calling-functions-and-api-scripts-from-os-command-line/idi-p/6241441

https://forums.autodesk.com/t5/fusion-360-api-and-scripts/call-fusion-from-command-line-with-python-script-argument/td-p/5481204

Or alternatively some local RPC socket service that can be sent commands (and one of the commands is to run a script). This part might be possible with a fusion addin?

@JesusFreke
Copy link
Owner

JesusFreke commented Mar 15, 2020

Another problem is that I believe the python remote debug is not available in IDEA community edition, only ultimate edition.

But yeah, having an add-in that manages some sort of socket to facilitate the communication between IDEA and Fusion is probably the way to go, at least, until/unless the command line functionality you mentioned is implemented.

The add-in would need to load the pydevd implementation that IDEA expects, and then listen on a socket for a connection from IDEA. That socket would only need a very basic protocol, that accepts a port and script name, and then it essentially runs the same code that inject.py otherwise injects, which sets up another port for the debug connection and then launches the script.

@jeremyherbert
Copy link

At least when I was running it, it seemed to be ok in the community edition. When debugging the plugin from ultimate, it launches inside the community edition and seems to behave as expected. It just hangs on the lldb attach.

@jeremyherbert
Copy link

Ok, I have this working on mac (and it should be cross platform too, but I haven't checked windows). It just uses an addin inside fusion 360 to allow remote running of scripts. You can find the addin here: https://gist.github.com/jeremyherbert/c82e2733f0d8e3a1621684bfd38ddb3d

It's not pretty, more of a rough proof of concept. By default the server listens on localhost at port 8181.

To use it, you simply set the addin to run on startup inside fusion, restart fusion, and then do a HTTP request with the relevant info.

To test, I just set up a remote debug session in pycharm (listening on port 7681), started the remote debug server and set a breakpoint. Then with curl, ran this to do a POST request to the server in the addin:

curl --data '{"script": "/Users/jeremy/Seafile/misc_projects/f360_test/main.py", "detach": false, "debug_port":7681, "jetbrains_pydevd_path":"/Users/jeremy/Library/Application Support/JetBrai
ns/Toolbox/apps/PyCharm-P/ch-0/193.6015.41/PyCharm.app/Contents/plugins/python/helpers/pydev"}' http://localhost:8181

At which point the script runs and the breakpoint is triggered in pycharm.

@JesusFreke I must admit that the java side of things is not exactly my forte, do you want to try to work together to get something like this integrated in your extension? Or if you want to just run with this it's also fine by me ;)

Also one issue is that detach seems to not work properly, so keeping detach as false is better for now.

@tapnair
Copy link
Author

tapnair commented Mar 16, 2020

Hey, this is great. Couple of points:

  • As far as Autodesk "officially" supporting this I think that will be a stretch. We made the commitment with VS Code... and honestly I would rather see something like this project be maintained by the community, even though I think that this is and will be far superior to the VS Code integration.
  • One small thing to investigate (and I apologize as my memory is a bit hazy on this) is in regards to to process attaching. There was something about installing x-code command line tools that was necessary to get that part working. I want to say that at one point in my endless quest on this project i was able to attach, although never could really do anything. But i believe it was something to do with having that x-code command line tools configured properly.
  • Running the add-in to listen for debugging: love this. I had briefly started down this path but quickly hit my ceiling. That said I would be very happy to help help massage the Fusion 360 side of this approach to see what we can do. I'll try to take a look at your current code and see what I can do. I'd love a little more info on getting it all setup, but maybe once I start it will be more self explanatory?
    Anyway I would LOVE to see this project grow, and then be able to at least reference it in our API documentation as more robust development environment for more serious developers. I actually just changed roles at Autodesk and am now a Product Manager for Fusion 360 and the API is one of my projects. So, needless to say, I am very excited to get this going and work more closely with you all.

@jeremyherbert
Copy link

Yep, I do have Xcode command line tools installed (without it there would be no lldb installed anyway I’m pretty sure). I suspect this is more to do with the recent clampdown on this sort of functionality in Catalina.

As for using the code, it’s pretty simple. You just

  1. Create an empty add in
  2. Copy that code in and tick the box to have it run at startup
  3. Restart

The curl call is simply to trigger a HTTP request to the server to run a script. You could use any other tool or language that supports HTTP requests to do this part.

@JesusFreke
Copy link
Owner

Yeah, this is probably a less troublesome approach even for the non-mac case. Attaching seems to be broke in current versions of IDEA. I was able to get it to work on my machine, but it involved some hackery with modifying the pydevd implementation provided by IDEA, so not really a solution that could be used more widely.

@jeremyherbert
Copy link

What did you need to change to get it to work?

The detach problem is due to a old version of pydev in pycharm which was not backported correctly: https://youtrack.jetbrains.com/issue/PY-40359

I have observed this problem in IDEA on mac too.

I'm thinking that bundling a more modern version of pydev from the main repo: https://github.com/fabioz/PyDev.Debugger/blob/master/pydevd.py with the extension may solve the problem

@JesusFreke
Copy link
Owner

JesusFreke commented Mar 17, 2020

Yeah, I reported the same issue, with the same diagnosis re: the bad backport, and it was duplicated to that one :)

https://youtrack.jetbrains.com/issue/PY-40928

PyCharm has a working pydevd, so I think I overwrote IDEA's pydevd with the current version of PyCharm's (2019.2.3), and then made one or two needed fixes.

I'll do a diff to refresh my memory and report back the needed fixes.

@JesusFreke
Copy link
Owner

Actually, looking at it now, I don't think that's what I did... but if not, I don't recall exactly how I got it working. Trying to figure that out now.. haha

@JesusFreke
Copy link
Owner

JesusFreke commented Mar 17, 2020

Ah, there we go, I found it. I did copy it from PyCharm, but it was from a source checkout I had, not from the installed version of 2019.2.3. It's from the pycharm/193.5233.15 tag. I basically copied over the entire <pycharm_source>/python/helpers/pydev to /.IdeaIC2019.3/config/plugins/python-ce/helpers/pydev

The only tweak I had to make was in pydev/_pydevd_bundle/pydevd_comm.py:

diff pydev/_pydevd_bundle/pydevd_comm.py 
/mnt/z/projects/idea/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
737c737
<                 append('file="%s" line="%s">' % (make_valid_xml_value(my_file), lineno))
---
>                 append('file="%s" line="%s">' % (quote(make_valid_xml_value(my_file), '/>_= \t'), lineno))

JesusFreke added a commit that referenced this issue Mar 17, 2020
This updates the version if IDEA that we're building against to 2019.3.3, and
includes some minor fixes needed to be used with this version.

Note that 2019.3.3 has a broken version of pydev/pydevd that prevents attaching
and debugging fusion scripts. It's possible to get it to work, but it's a bit
hacky. See #7 (comment)
for more info.
@JesusFreke
Copy link
Owner

I also just uploaded an idea-2019.3.3 branch of this plugin, with some needed fixes to get it working with that version.

@jeremyherbert
Copy link

I just tidied this up into an addin: https://github.com/jeremyherbert/fusion360_python_debug_addin

I'm not set up to build binary python extensions on windows, but if someone can checkout PyDev.Debugger, run python setup.py bdist_egg and send me the resulting lib.something folder in the build folder, I can add that too.

@JesusFreke
Copy link
Owner

JesusFreke commented Mar 28, 2020

Does the stock PyDev.Debugger work with PyCharm/IDEA? I thought they used a modified version of it.

I was actually looking at this a bit for the plugin today. From the perspective of using something like this from the plugin, it's probably best to have the IDE send the location of the pydevd that is bundled with IDEA/PyCharm to the add-in and let it load it from there, to minimize the chances of version mismatches/incompatibilities between a pydevd bundled with the add-in and that used by IDEA/PyCharm.

I'm also a bit wary of opening up a long-running port in fusion 360 with no protections in place. It definitely seems like something that could be used for cross-user execution, privilege escalation, etc. Although it is listening on localhost at least, so it shouldn't be able to be connected to remotely. But even just local access seems potentially risky.

I was thinking of having the add-in pop up some UI the first time a debugger connects, asking the user to confirm the connection. Maybe have the plugin generate some sort of session key or more permanent key that could be used for further operations, so the user doesn't have to confirm every single connection.

@jeremyherbert
Copy link

Yes, it seems to work without issue for everything I tested.

The problem with the version of pydevd that is shipped with the jetbrains stuff is (as you know) that it is simply broken for this use case, it cannot detach and restart properly (at least on mac); that is not the case with the latest version of pydevd. I figured it could just include the egg until jetbrains fixes the problem, and then remove it and change over to using the included one.

I thought about the security for a bit but I figured given that it is only really supposed to be run in a development environment it didn't bother me too much. But the UI confirmation is probably a good idea anyway.

@JesusFreke
Copy link
Owner

JesusFreke commented Mar 28, 2020

Yeah, I don't think the attaching issue should be a problem with this, since we're not actually attaching to a process -- instead using the HTTP server as a way to bypass the need to attach to the process.

The newest version of the python plugin for IDEA seems to have a fix for the attaching problem, but there's still some problem that prevents breakpoints from working. Not sure what the deal is. But my workaround of copying the pydevd from that one version of PyCharm still works. shrug. I'm not sure if that will still be a problem when using the http server to initiate the connection. I'm hoping to have a quick proof-of-concept working this weekend, although the fleshed out implementation will take longer.

@jeremyherbert
Copy link

The attaching problem is one issue, but the one I am referring to is that at least on mac if you ever detach the debugger (with the stop button inside pycharm/IDEA) then you can never attach the debugger again. The problem is that pydevd is still running inside fusion360 basically as a zombie thread that blocks it from restarting but also from debugging. The culprit is this section in attach_script.py:

        py_db = pydevd.get_global_debugger()
        if py_db is not None:
            py_db.dispose_and_kill_all_pydevd_threads(wait=False)

        # run script here...

In the jetbrains version, they never backported the dispose_and_kill_all_pydevd_threads function, so this causes an exception and then the script will never run. This is why I needed to include the newer version of pydevd, because it does implement this function.

@jeremyherbert
Copy link

no changes on the python side, debugging/running scripts works. It's all working and packet shows up on lo0.

localhost.54717 > localhost.ssdp: UDP, length 97

@JesusFreke
Copy link
Owner

Weird. Doesn't work on windows though. hah!

@jeremyherbert
Copy link

Dang. That would be too easy...

@JesusFreke
Copy link
Owner

Okay, one last try. I just uploaded new versions of both that add support for ipv6 multicasting.

Here's the test command:

 echo "test" | socat - UDP6-DATAGRAM:[ff01:fb68:e6b7:45f9:4acc:2559:6c6e:c014]:1900

crosses fingers

@jeremyherbert
Copy link

jeremyherbert commented Apr 10, 2020

fusion addin can't start anymore unfortunately (also attaching images to github is broken?)

f360

@jeremyherbert
Copy link

f360

@JesusFreke
Copy link
Owner

JesusFreke commented Apr 10, 2020

sigh. thanks.

I suspect the problem may be that the network interface must be specified, instead of using 0 to indicate auto-assignment (see, e.g. sccn/liblsl#36)

Can you try setting the network index here. I think you can find the network index by cat /proc/net/if_net6. The first number after the address is the index.

If that works, then I'll hopefully be able to fix it up and get it working.

Note: at least on windows, using the loopback interface doesn't seem to work. I have to use one of the actual interfaces. But the multicast address itself is an interface-local address, so it will never be routed elsewhere.

If that doesn't work, I'm pretty much out of ideas with respect to using a multicast approach. Maybe we can explore a file-based approach like you suggested.

@jeremyherbert
Copy link

jeremyherbert commented Apr 11, 2020

/proc doesn't exist on mac unfortunately.

I couldn't work out how to get the interface index easily, so I just brute forced it. Index 1, 4, 6, 7, 12, 15 don't cause an exception, but it also don't work. All other indices up to 15 give an exception on fusion startup (exception is "invalid argument" on that line).

For the indices that do start without an exception, error in idea is:

java.net.SocketTimeoutException: Receive timed out
	at java.base/java.net.PlainDatagramSocketImpl.receive0(Native Method)
	at java.base/java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:181)
	at java.base/java.net.DatagramSocket.receive(DatagramSocket.java:814)
	at org.jf.fusionIdea.run.FusionScriptState$SSDPServer.lambda$start$0(FusionScriptState.java:276)
	at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:125)
	at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:69)
	at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:78)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

socat returns

2020/04/11 09:50:07 socat[45578] E sendto(5, 0x7ff88b008200, 5, 0, LEN=28 AF=30 [ff01:fb68:e6b7:45f9:4acc:2559:6c6e:c014]:1900, 28): No route to host

At this point, we can either go for the previous approach and just basically do

if (windows) {
// create socket
} else {
InetAddress localhost = InetAddress.getByName("127.0.0.1");
InetSocketAddress multicastAddress = new InetSocketAddress(localhost, 1900);
MulticastSocket socket = new MulticastSocket(new InetSocketAddress(localhost, 0));
}

or do the file based approach, though I think the file based approach is probably simpler as it doesn't have to deal with firewalls either.

@JesusFreke
Copy link
Owner

I finally just got a VM running so I could investigate directly.

Got it all working, and pushed a new version of both things. Give it a shot and let me know if you find anything off :)

The plugin should now fully support the various mac-specific paths, and debugging/etc. should all work.

@jeremyherbert
Copy link

Unfortunately no :(

java.lang.RuntimeException: Couldn't send SSDP request via ipv6 or ipv4
	at org.jf.fusionIdea.run.FusionScriptState$SSDPServer.lambda$start$0(FusionScriptState.java:286)
	at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:125)
	at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:69)
	at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:78)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

@jeremyherbert
Copy link

This code is the problem

for (NetworkInterface netint : Collections.list(nets)) {
        if (netint.supportsMulticast()) {
            boolean hasIpV6 = false;
            for (InetAddress address : Collections.list(netint.getInetAddresses())) {
                if (address instanceof Inet6Address) {
                    hasIpV6 = true;
                    break;
                }
            }
            if (hasIpV6) {
                socket.setNetworkInterface(netint);
                socket.send(new DatagramPacket(SEARCH_MESSAGE, SEARCH_MESSAGE.length, multicastAddress));
            }
        }
    }

    return socket;
} catch (IOException ex) {
    FusionIdeaPlugin.log.debug("ipv6 ssdp failed");
    return null;
}

as it works on all interfaces, but some interfaces generate a no route to host exception

@JesusFreke
Copy link
Owner

Yeah, probably just need to wrap the inner part of the loop in an exception handler

@JesusFreke
Copy link
Owner

Pushed a4adba2 to fix it, hopefully

@jeremyherbert
Copy link

back to this one unfortunately

java.net.SocketTimeoutException: Receive timed out
	at java.base/java.net.PlainDatagramSocketImpl.receive0(Native Method)
	at java.base/java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:181)
	at java.base/java.net.DatagramSocket.receive(DatagramSocket.java:814)
	at org.jf.fusionIdea.run.FusionScriptState$SSDPServer.lambda$start$0(FusionScriptState.java:307)
	at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:125)
	at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:69)
	at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:78)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

@jeremyherbert
Copy link

hmm seems like the python server isn't listening according to lsof and netstat. I'm also not getting a log file from the addin anymore

@JesusFreke
Copy link
Owner

I can confirm. I'm getting the same. I must have screwed up something in the addin before pushing the changes.

@jeremyherbert
Copy link

https://github.com/JesusFreke/fusion_idea_addin/blob/4f7daf1be6dd95a2e1f68167c8afa0a56e6d94e1/fusion_idea_addin.py#L508 is a syntax error, a single double quote instead of two double quotes (or two single quotes)

@jeremyherbert
Copy link

also it looks like that line does nothing? Or perhaps some code is missing?

@JesusFreke
Copy link
Owner

Yeah, I have no idea what happened there. I specifically remember fixing/changing exactly those things, shortly before pushing everything. Maybe I forgot to push a change between machines at some point or something.

@JesusFreke
Copy link
Owner

I just pushed JesusFreke/fusion_idea_addin@194671c, and verified it's working in my VM.

@jeremyherbert
Copy link

Yep, works fully on this end too now. Great work!

@JesusFreke
Copy link
Owner

Yay, finally. heh.

I'll take another look at everything tomorrow, make sure I'm not missing something stupid, and hopefully release a new version this weekend.

Hmm. I'll need to figure out the best way to communicate the need to install the add-in, since that will be a new requirement for existing users.

@jeremyherbert
Copy link

Great. Maybe if the SSDP fails, print some text in the console like “have you installed the fusion addon from here: https:...”

I guess this ends my yak shaving, can finally get back to writing a script to export DXF files with construction lines on a separate layer...

@JesusFreke
Copy link
Owner

Sounds good. Thanks for all the help and back-and-forth. haha :)

@jeremyherbert
Copy link

No worries. Keep up the good work, and stay safe wherever you are.

JesusFreke added a commit that referenced this issue Apr 12, 2020
This updates the version if IDEA that we're building against to 2019.3.3, and
includes some minor fixes needed to be used with this version.

Note that 2019.3.3 has a broken version of pydev/pydevd that prevents attaching
and debugging fusion scripts. It's possible to get it to work, but it's a bit
hacky. See #7 (comment)
for more info.
@JesusFreke
Copy link
Owner

I've released v0.6.0 that adds support for Mac.

The new version of the plugin (v0.6.0) has been submitted to IDEA's plugin repository, but it may take a few days to be published. In the meantime, you can download the plugin here for manual installation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants