Skip to content

Commit 35e8ca3

Browse files
committed
Base
0 parents  commit 35e8ca3

File tree

3 files changed

+270
-0
lines changed

3 files changed

+270
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vscode/
2+
__pycache__

README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Blender Debugger for VS Code
2+
3+
Inspired by [Blender-VScode-Debugger](/Barbarbarbarian/Blender-VScode-Debugger) which was itself inspired by this [remote_debugger](/sybrenstuvel/random-blender-addons/blob/master/remote_debugger.py) for pycharm as explained in this [Blender Developer's Blog post](https://code.blender.org/2015/10/debugging-python-code-with-pycharm/).
4+
5+
6+
Since the VS Code one wasn't really well documented and it looked kind of dead, once I figured it out, I was just going to add the documentation, but then I ended up rewriting the whole thing.
7+
8+
Now it can:
9+
10+
- Auto-detect where python is and auto set the path to ptvsd if installed.
11+
- Tell you when the debugger has actually attached.
12+
13+
# How to Use
14+
15+
I'm in the process of writing a more detailed blog post for those who just started messing with python in Blender or programming in general, but if you're semi-familiar with Python, VS Code, and the command line the following should make sense.
16+
17+
## Installing Python and Getting PTVSD
18+
19+
Install Python 3 with pip and check add to PATH.
20+
- If you already have python installed and you can run it from the command line (aka PATH is set), the addon should find it. It uses `where python` or `whereis python` or `which python` depending on the OS to determine where python is. I only have windows at the moment, so only that is tested, but it should work.
21+
22+
`pip install ptvsd==3.0.0"
23+
- 3.0 because that was what was recommended in the VS Code documentation.
24+
25+
## Setting up your Addon
26+
27+
This is the most important part. Otherwise it won't work. I thought it was my VS Code config but no, it was this.
28+
29+
In Blender go to: `User Preferences > File` and set the path to `Scripts` to the folder you're developing your addon in (e.g: "C:\Code\Blender Stuff") BUT the folder must look like this:
30+
31+
```
32+
Blender Stuff
33+
└── addons
34+
├── your-addon-folder
35+
├── __init__.py
36+
├── ...etc
37+
├── another-addon
38+
├── ...
39+
```
40+
41+
Now remove your addon from Blender if it was installed, save settings, and when you restart your addon should be installed automatically.
42+
43+
## Setting up this Addon
44+
45+
Install the addon.
46+
47+
If it did not find the path it'll say "PTVSD not Found", you'll have to set it manually. It's wherever python is + "\lib\site-packages". NO trailing backslash.
48+
49+
If you want, increase the timeout for the confirmation. It'll print "Waiting..." in the console ~ every second until it prints it's timedout. This does not mean the server has timedout *just* the confirmation listener.
50+
51+
Open up Blender's search (default shortcut: space), type "Debug".
52+
53+
Click `Debug: Start Debug Server for VS Code`. Note: you can only start the server once. You cannot stop it, at least from what I understand. If you run it again it'll just tell you it's already running and start the timer again to check for a confirmation.
54+
55+
## Connecting VS Code
56+
57+
Open your addon folder (e.g. "C:\Code\Blender Stuff\addons\myaddon").
58+
59+
Install the Python extension for VS Code if you haven't already.
60+
61+
Go to the Debugging tab and add a configuration. Pick Python. You'll want the configuration that looks like this, you can delete the rest. There's no need to change the defaults.
62+
63+
```JSON
64+
{
65+
"name": "Python: Attach",
66+
"type": "python",
67+
"request": "attach",
68+
"localRoot": "${workspaceFolder}",
69+
"remoteRoot": "${workspaceFolder}",
70+
"port": 3000,
71+
"secret": "my_secret",
72+
"host": "localhost"
73+
},
74+
```
75+
76+
Now when you run the debugger with this config in Blender the console should print "Debugger is Attached" if it was still waiting (it should still attach even if it wasn't, it just won't tell you).
77+
78+
## How to Use
79+
80+
At this point you should be able to add a breakpoint and when you trigger it in Blender, Blender should freeze and VS Code should pause on the breakpoint.
81+
82+
Note though that if you make changes to the file, Blender will not detect them. Have open `User Preferences > Addons` so you can toggle your addon on and off when you make changes. If anyone knows any way to improve this I'd love to know.
83+
84+
# Notes
85+
86+
The addon also detects python if PYTHONPATH is set (because Blender will add it to sys.path) or if you used the Python bundled with Blender to install ptvsd (but that's a bit of a pain because it doesn't have pip installed unless you want to install it manually)
87+
88+
89+
90+
91+

__init__.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
bl_info = {
2+
'name': 'Debugger for VS Code',
3+
'author': 'Alan North',
4+
'version': (0, 0, 1),
5+
'blender': (2, 79, 0),
6+
"description": "Starts debugging server for VS Code.",
7+
'location': 'In search (default shortcut:space) type "Debug"',
8+
"warning": "",
9+
"wiki_url": "https://github.com/AlansCodeLog/blender-debugger-for-vscode",
10+
"tracker_url": "https://github.com/AlansCodeLog/blender-debugger-for-vscode/issues",
11+
'category': 'Development',
12+
}
13+
14+
import bpy
15+
import sys
16+
import os
17+
import subprocess
18+
import re
19+
20+
# check path for ptvsd
21+
def check_for_ptvsd():
22+
#check for python with where/whereis
23+
python_exe = None
24+
try:
25+
python_exe = subprocess.Popen(
26+
["whereis", "python"],
27+
shell=False,
28+
stdout=subprocess.PIPE,
29+
stderr=subprocess.PIPE
30+
)
31+
except Exception:
32+
pass
33+
if python_exe is None:
34+
try:
35+
python_exe = subprocess.Popen(
36+
["where", "python"],
37+
shell=False,
38+
stdout=subprocess.PIPE,
39+
stderr=subprocess.PIPE
40+
)
41+
except Exception:
42+
pass
43+
if python_exe is None:
44+
try:
45+
python_exe = subprocess.Popen(
46+
["which", "python"],
47+
shell=False,
48+
stdout=subprocess.PIPE,
49+
stderr=subprocess.PIPE
50+
)
51+
except Exception:
52+
pass
53+
if python_exe is not None:
54+
python_exe = str(python_exe.communicate()[0], "utf-8")
55+
match = re.search(".*(\\\\|/)", python_exe).group()
56+
if os.path.exists(match+"\lib\site-packages\ptvsd"):
57+
return match+"\lib\site-packages"
58+
59+
#check in our path
60+
for path in sys.path:
61+
if os.path.exists(path+"\ptvsd"):
62+
return path
63+
if os.path.exists(path+"\site-packages\ptvsd"):
64+
return path+"\site-packages"
65+
if os.path.exists(path+"\lib\site-packages\ptvsd"):
66+
return path+"lib\site-packages"
67+
return "PTVSD not Found"
68+
69+
# Preferences
70+
class DebuggerPreferences(bpy.types.AddonPreferences):
71+
bl_idname = __name__
72+
73+
path = bpy.props.StringProperty(
74+
name="Location of PTVSD",
75+
subtype="DIR_PATH",
76+
default=check_for_ptvsd()
77+
)
78+
79+
timeout = bpy.props.IntProperty(
80+
name="Timeout",
81+
default=20
82+
)
83+
def draw(self, context):
84+
layout = self.layout
85+
layout.prop(self, "path")
86+
layout.label(text="Pluging will try to auto-find it, if no path found, or you would like to use a different path, set it here.")
87+
row = layout.split()
88+
row.label(text="Timeout in seconds for attach confirmation listener.")
89+
row.prop(self, "timeout")
90+
91+
# check if debugger has attached
92+
def check_done(i, modal_limit):
93+
if i % 100 == 0:
94+
print("Waiting")
95+
if i > modal_limit:
96+
print("Attach Confirmation Listener Timed Out")
97+
return {"CANCELLED"}
98+
if not ptvsd.is_attached():
99+
return {"PASS_THROUGH"}
100+
print('Debugger is Attached')
101+
return {"FINISHED"}
102+
103+
class DebuggerCheck(bpy.types.Operator):
104+
bl_idname = "debug.check_for_debugger"
105+
bl_label = "Debug: Check if VS Code is Attached"
106+
bl_description = "Starts modal timer that checks if debugger attached until attached or until timeout."
107+
108+
_timer = None
109+
count = 0
110+
modal_limit = 20000
111+
112+
# call check_done
113+
def modal(self, context, event):
114+
self.count = self.count + 1
115+
if event.type == "TIMER":
116+
return check_done(self.count, self.modal_limit)
117+
return {"PASS_THROUGH"}
118+
119+
def execute(self, context):
120+
# set initial variables
121+
self.count = 0
122+
prefs = bpy.context.user_preferences.addons[__name__].preferences
123+
self.modal_limit = prefs.timeout*100
124+
125+
wm = context.window_manager
126+
self._timer = wm.event_timer_add(0.1, context.window)
127+
wm.modal_handler_add(self)
128+
return {"RUNNING_MODAL"}
129+
130+
def cancel(self, context):
131+
print("Debugger Confirmation Cancelled")
132+
wm = context.window_manager
133+
wm.event_timer_remove(self._timer)
134+
135+
class DebugServerStart(bpy.types.Operator):
136+
bl_idname = "debug.connect_debugger_vscode"
137+
bl_label = "Debug: Start Debug Server for VS Code"
138+
bl_description = "Starts ptvsd server for debugger to attach to."
139+
140+
def execute(self, context):
141+
#get ptvsd and import if exists
142+
prefs = bpy.context.user_preferences.addons[__name__].preferences
143+
ptvsd_path = os.path.abspath(prefs.path)
144+
145+
#actually check ptvsd is still available
146+
if ptvsd_path == "PTVSD not Found":
147+
self.report({"ERROR"}, "Couldn't detect ptvsd, please specify the path manually in the addon preferences or reload the addon if you installed ptvsd after enabling it.")
148+
return {"CANCELLED"}
149+
150+
if not os.path.exists(ptvsd_path+"/ptvsd"):
151+
self.report({"ERROR"}, "Can't find ptvsd at: %r/ptvsd." % ptvsd_path)
152+
return {"CANCELLED"}
153+
154+
if not any(ptvsd_path in p for p in sys.path):
155+
sys.path.append(ptvsd_path)
156+
157+
global ptvsd #so we can do check later
158+
import ptvsd
159+
160+
# can only be attached once, no way to detach (at least not that I understand?)
161+
try:
162+
ptvsd.enable_attach("my_secret", address = ("0.0.0.0", 3000))
163+
except:
164+
print("Server already running.")
165+
166+
# call our confirmation listener
167+
bpy.ops.debug.check_for_debugger()
168+
return {"FINISHED"}
169+
170+
def register():
171+
bpy.utils.register_module(__name__)
172+
173+
def unregister():
174+
bpy.utils.unregister_module(__name__)
175+
176+
if __name__ == "__main__":
177+
register()

0 commit comments

Comments
 (0)