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

Add VS code setting for custom extension repository #180

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add setting for custom extension repository
Implement sending settings from VS code to blender addon
  • Loading branch information
Mateusz-Grzelinski committed Aug 4, 2024
commit 3e047d5d704b89f01be04805187f81d6da7a6406
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@
"my_addon_name"
],
"description": "Name or the symlink that is created in Blenders addon folder."
},
"blender.addon.extensionsRepository": {
"type": "string",
"scope": "resource",
"default": "vscode_development",
"examples": [
"vscode_development",
"user_default",
"blender_org"
],
"description": "Blender extensions only: repository to use when developing addon. \nBlender -> Preferences -> Get Extensions -> Repositories (dropdown, top right)"
},
"blender.core.buildDebugCommand": {
"type": "string",
Expand Down
19 changes: 16 additions & 3 deletions pythonFiles/include/blender_vscode/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys
import time
from dataclasses import dataclass
from pathlib import Path
from typing import List
Expand All @@ -20,13 +21,25 @@ def startup(editor_address, addons_to_load: List[AddonInfo], allow_modify_extern

installation.ensure_packages_are_installed(["debugpy", "flask", "requests"], allow_modify_external_python)

from . import communication

communication.setupFlaskServer(editor_address)

from .vs_code_settings import handle_setting_change, EXTENSIONS_REPOSITORY

communication.register_post_handler("setting", handle_setting_change)
_extensions_repository = communication.send_get_setting("addon.extensionsRepository")

while EXTENSIONS_REPOSITORY is None:
from .vs_code_settings import EXTENSIONS_REPOSITORY
time.sleep(0.05)
print("Waiting for settings...")

from . import load_addons

path_mappings = load_addons.setup_addon_links(addons_to_load)

from . import communication

communication.setup(editor_address, path_mappings)
communication.setupDebugpyServer(path_mappings)

from . import operators, ui

Expand Down
54 changes: 39 additions & 15 deletions pythonFiles/include/blender_vscode/communication.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import time
from typing import Callable

import flask
import debugpy
import random
Expand All @@ -12,16 +14,23 @@
OWN_SERVER_PORT = None
DEBUGPY_PORT = None

server = flask.Flask("Blender Server")
post_handlers = {}


def setup(address, path_mappings):
global EDITOR_ADDRESS, OWN_SERVER_PORT, DEBUGPY_PORT
def setupFlaskServer(address: str):
global EDITOR_ADDRESS, OWN_SERVER_PORT
EDITOR_ADDRESS = address

OWN_SERVER_PORT = start_own_server()
DEBUGPY_PORT = start_debug_server()
send_flask_connection_information()

send_connection_information(path_mappings)

def setupDebugpyServer(path_mappings):
global DEBUGPY_PORT

DEBUGPY_PORT = start_debug_server()

send_debugpy_connection_information(path_mappings)
print("Waiting for debug client.")
debugpy.wait_for_client()
print("Debug client attached.")
Expand Down Expand Up @@ -65,38 +74,38 @@ def start_debug_server():
# Server
#########################################

server = flask.Flask("Blender Server")
post_handlers = {}


@server.route("/", methods=["POST"])
def handle_post():
data = flask.request.get_json()
print("Got POST:", data)

if data["type"] in post_handlers:
print("Calling handler: ", post_handlers[data["type"]], "with", data)
return post_handlers[data["type"]](data)

return "OK"


@server.route("/", methods=["GET"])
def handle_get():
flask.request
data = flask.request.get_json()
print("Got GET:", data)

if data["type"] == "ping":
pass
else:
raise Exception(data)
return "OK"


def register_post_handler(type, handler):
assert type not in post_handlers
def register_post_handler(type: str, handler: Callable):
assert type not in post_handlers, post_handlers
post_handlers[type] = handler
print("Added POST handler:", type)


def register_post_action(type, handler):
def register_post_action(type: str, handler: Callable):
def request_handler_wrapper(data):
run_in_main_thread(partial(handler, data))
return "OK"
Expand All @@ -108,10 +117,19 @@ def request_handler_wrapper(data):
###############################


def send_connection_information(path_mappings):
def send_flask_connection_information():
send_dict_as_json(
{
"type": "setupFlask",
"blenderPort": OWN_SERVER_PORT,
}
)


def send_debugpy_connection_information(path_mappings):
send_dict_as_json(
{
"type": "setup",
"type": "setupDebugpy",
"blenderPort": OWN_SERVER_PORT,
"debugpyPort": DEBUGPY_PORT,
"blenderPath": str(blender_path),
Expand All @@ -122,10 +140,16 @@ def send_connection_information(path_mappings):


def send_dict_as_json(data):
print("Sending:", data)
print("Sending POST:", data)
requests.post(EDITOR_ADDRESS, json=data)


def send_get_setting(name: str):
data = {"type": "setting", "name": name}
print("Sending GET:", data)
requests.get(EDITOR_ADDRESS, json=data)


# Utils
###############################

Expand Down
14 changes: 12 additions & 2 deletions pythonFiles/include/blender_vscode/load_addons.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .communication import send_dict_as_json
from .environment import addon_directories
from .utils import is_addon_legacy
from .vs_code_settings import EXTENSIONS_REPOSITORY


def setup_addon_links(addons_to_load: List[AddonInfo]):
Expand Down Expand Up @@ -42,7 +43,16 @@ def get_user_addon_directory(source_path: Path):
if is_addon_legacy(source_path):
return Path(bpy.utils.user_resource("SCRIPTS", path="addons"))
else:
return Path(bpy.utils.user_resource("EXTENSIONS", path="user_default"))
ensure_extension_repo_exists(EXTENSIONS_REPOSITORY)
return Path(bpy.utils.user_resource("EXTENSIONS", path=EXTENSIONS_REPOSITORY))


def ensure_extension_repo_exists(extensions_repository: str):
for repo in bpy.context.preferences.extensions.repos:
repo: bpy.types.UserExtensionRepo
if repo.module == extensions_repository and repo.name == extensions_repository:
return repo
return bpy.context.preferences.extensions.repos.new(name=extensions_repository, module=extensions_repository)


def load(addons_to_load: List[AddonInfo]):
Expand All @@ -52,7 +62,7 @@ def load(addons_to_load: List[AddonInfo]):
addon_name = addon_info.module_name
else:
bpy.ops.extensions.repo_refresh_all()
addon_name = "bl_ext.user_default." + addon_info.module_name
addon_name = "bl_ext." + EXTENSIONS_REPOSITORY + "." + addon_info.module_name

try:
bpy.ops.preferences.addon_enable(module=addon_name)
Expand Down
11 changes: 6 additions & 5 deletions pythonFiles/include/blender_vscode/operators/addon_update.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import traceback
from pathlib import Path

import bpy
import sys
import traceback
from bpy.props import *
from ..utils import is_addon_legacy, redraw_all

from ..communication import send_dict_as_json, register_post_action
from ..utils import is_addon_legacy, redraw_all


class UpdateAddonOperator(bpy.types.Operator):
Expand Down Expand Up @@ -38,13 +39,13 @@ def execute(self, context):
return {"FINISHED"}


def reload_addon_action(data):
def reload_addon_action(data, extensions_repository: str):
module_names = []
for name, dir in zip(data["names"], data["dirs"]):
if is_addon_legacy(Path(dir)):
module_names.append(name)
else:
module_names.append("bl_ext.user_default." + name)
module_names.append("bl_ext." + extensions_repository + "." + name)

for name in module_names:
bpy.ops.dev.update_addon(module_name=name)
Expand Down
18 changes: 18 additions & 0 deletions pythonFiles/include/blender_vscode/vs_code_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Optional, Dict

EXTENSIONS_REPOSITORY: Optional[str] = None


def handle_setting_change(data: Dict):
global EXTENSIONS_REPOSITORY
name: str = data["name"]
value = data["value"]

if name == "addon.extensionsRepository":
# can be updated only once to avoid weird corner cases
if EXTENSIONS_REPOSITORY is None:
EXTENSIONS_REPOSITORY = value
print("Setting: EXTENSIONS_REPOSITORY to", EXTENSIONS_REPOSITORY)
else:
print("ERROR: unknown setting: ", name)
return "OK"
68 changes: 47 additions & 21 deletions src/communication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,19 @@ export type AddonPathMapping = { src: string, load: string };

export class BlenderInstance {
blenderPort: number;
debugpyPort: number;
justMyCode: boolean;
path: string;
scriptsFolder: string;
addonPathMappings: AddonPathMapping[];
// debugpyPort: number;
// justMyCode: boolean;
// path: string;
// scriptsFolder: string;
// addonPathMappings: AddonPathMapping[];
connectionErrors: Error[];

constructor(blenderPort: number, debugpyPort: number, justMyCode: boolean, path: string,
scriptsFolder: string, addonPathMappings: AddonPathMapping[]) {
constructor(blenderPort: number) {
this.blenderPort = blenderPort;
this.debugpyPort = debugpyPort;
this.justMyCode = justMyCode;
this.path = path;
this.scriptsFolder = scriptsFolder;
this.addonPathMappings = addonPathMappings;
this.connectionErrors = [];
}


post(data: object): void {
request.post(this.address, { json: data });
}
Expand All @@ -51,8 +46,8 @@ export class BlenderInstance {
});
}

attachDebugger() {
attachPythonDebuggerToBlender(this.debugpyPort, this.path, this.justMyCode, this.scriptsFolder, this.addonPathMappings);
attachDebugger(debugpyPort: number, justMyCode: boolean, path: string, scriptsFolder: string, addonPathMappings: AddonPathMapping[]) {
attachPythonDebuggerToBlender(debugpyPort, path, justMyCode, scriptsFolder, addonPathMappings);
}

get address() {
Expand All @@ -67,6 +62,14 @@ export class BlenderInstances {
this.instances = [];
}

getBlenderInstance(blenderPort: number): BlenderInstance | undefined {
for (const blenderInstance of this.instances) {
if (blenderInstance.blenderPort == blenderPort)
return blenderInstance;
}
return undefined;
}

register(instance: BlenderInstance) {
this.instances.push(instance);
}
Expand All @@ -86,7 +89,7 @@ export class BlenderInstances {
}

for (let instance of this.instances) {
instance.ping().then(() => addInstance(instance)).catch(() => {});
instance.ping().then(() => addInstance(instance)).catch(() => { });
}
setTimeout(() => resolve(responsiveInstances.slice()), timeout);
});
Expand Down Expand Up @@ -124,23 +127,46 @@ export function getServerPort(): number {
return server.address().port;
}

function SERVER_handleRequest(request: any, response: any) {
if (request.method === 'POST') {
function SERVER_handleRequest(request: any, response: http.ServerResponse) {
if (request.method === 'GET') {
let body = '';
request.on('data', (chunk: any) => body += chunk.toString());
request.on('end', () => {
let req = JSON.parse(body);

switch (req.type) {
case 'setup': {
case 'setting': {
let config = getConfig();
let justMyCode: boolean = <boolean>config.get('addon.justMyCode')
let instance = new BlenderInstance(req.blenderPort, req.debugpyPort, justMyCode, req.blenderPath, req.scriptsFolder, req.addonPathMappings);
instance.attachDebugger();
let settingValue: any = config.get(req.name)
response.end('OK');
RunningBlenders.sendToResponsive({ type: "setting", name: req.name, value: settingValue.toString() })
}
}
})
} else if (request.method === 'POST') {
let body = '';
request.on('data', (chunk: any) => body += chunk.toString());
request.on('end', () => {
let req = JSON.parse(body);

switch (req.type) {
case 'setupFlask': {
let instance = new BlenderInstance(req.blenderPort)
RunningBlenders.register(instance);
response.end('OK');
break;
}
case 'setupDebugpy': {
let config = getConfig();
let instance = RunningBlenders.getBlenderInstance(req.blenderPort)
if (instance == undefined) {
console.error("Can not find blender instance!")
}
let justMyCode: boolean = <boolean>config.get('addon.justMyCode')
instance?.attachDebugger(req.debugpyPort, justMyCode, req.blenderPath, req.scriptsFolder, req.addonPathMappings)
response.end('OK');
break;
}
case 'enableFailure': {
vscode.window.showWarningMessage('Enabling the addon failed. See console.');
response.end('OK');
Expand Down