Skip to content

Commit

Permalink
Merge pull request #2 from ikappaki/feat/panel
Browse files Browse the repository at this point in the history
Feat/panel
  • Loading branch information
ikappaki authored Oct 31, 2024
2 parents 03829f3 + 21e3131 commit 3ff0e6b
Show file tree
Hide file tree
Showing 27 changed files with 2,555 additions and 675 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/tests-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.version }}

Expand All @@ -34,7 +34,7 @@ jobs:
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- uses: actions/cache@v3
- uses: actions/cache@v4
name: Define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
Expand All @@ -48,7 +48,7 @@ jobs:

- name: Restore Blender from cache
id: cache-blender
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: ~/blender
key: ${{ runner.os }}-blender-${{ matrix.blender }}
Expand All @@ -60,7 +60,7 @@ jobs:
- name: "Save Blender in cache"
if: steps.cache-blender.outputs.cache-hit != 'true'
uses: actions/cache/save@v3
uses: actions/cache/save@v4
with:
path: ~/blender
key: ${{ steps.cache-blender.outputs.cache-primary-key }}
Expand All @@ -75,4 +75,4 @@ jobs:
run: |
poetry build
poetry run python scripts/bb_package_install.py
poetry run pytest -m integration -v
poetry run pytest --integration -v
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ dist/
*~

.nrepl-port
/.calva
/.clj-kondo
/.lsp
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

- Added async server interface support in `start-server!` with a client work abstraction.
- Implemented the async Blender nREPL server directly in Basilisp.
- Enhanced error handling to return errors at the interface layer.
- Introduced the nREPL server control panel UI component.
- Upgraded Basilisp to 0.2.4.

## 0.1.0

- Added support for evaluating Basilisp code from Blender's Python console, file, and Text Editor.
Expand Down
43 changes: 39 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,41 @@ import pip
pip.main(['install', 'basilisp-blender'])
```

## Setup

### nREPL server control panel

The library includes an nREPL server control panel accessible in Blender’s properties editor, under the Output panel (icon resembling a printer). From here, users can:
- Start and stop the server.
- Configure the local interface address and port.
- Set the location of the `.nrepl-port` file for editor connections.


![nrepl cntrl pnael output - ready](examples/nrepl-ctrl-panel-output-ready.png)

![nrepl cntrl pnael output - ready](examples/nrepl-ctrl-panel-output-serving.png)

Note: The control panel does not appear automatically and must be activated manually via Blender's Python console within the `Scripting` workspace. To activate, run:

```python
import basilisp_blender
basilisp_blender.control_panel_create()
```

To autoload the panel automatically at Blender’s startup, create a startup file in Blender's `<blender-version>/scripts/startup/` directory. For example, save the code below, say as `bb.py`, in that directory:

```python
import basilisp_blender
basilisp_blender.control_panel_create()

def register():
pass
def unregister():
pass
if __name__ == "__main__":
register()
```

## Usage
### Evaluating Basilisp Code

Expand Down Expand Up @@ -49,7 +84,7 @@ eval_editor("<text-block-name>")
```

#### Starting an nREPL Server
To start an nREPL server within Blender:
To start an nREPL server manually within Blender:

```python
from basilisp_blender.nrepl import server_start
Expand Down Expand Up @@ -189,7 +224,7 @@ $ export BB_BLENDER_TEST_HOME="~/blender420"
Then run the integration tests with

```bash
$ poetry run pytest -m integration
$ poetry run pytest --integration -v
```

### Installing Blender and the Development Package
Expand All @@ -203,7 +238,7 @@ $ poetry run python scripts/blender_install.py 4.2.0
To install the development version of the package at the same location, use:

```bash
$ poetry build # build the package
$ poetry run python scripts/bb_install.py # install it in Blender
$ poetry build # build the package
$ poetry run python scripts/bb_package_install.py # install it in Blender
```

Empty file added basilisp.edn
Empty file.
Binary file added examples/nrepl-ctrl-panel-output-ready.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/nrepl-ctrl-panel-output-serving.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
487 changes: 249 additions & 238 deletions poetry.lock

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "basilisp-blender"
version = "0.1.0"
version = "0.2.0b1"
description = ""
authors = ["ikappaki"]
readme = "README.md"
Expand All @@ -10,11 +10,12 @@ packages = [

[tool.poetry.dependencies]
python = "^3.8"
basilisp = "^0.1.0b2"
basilisp = "^0.2.4"

[tool.poetry.group.test.dependencies]
pytest = "^8.3.1"
nrepl-python-client = "^0.0.3"
basilisp-pprint = "^0.1.0"


[tool.poetry.group.dev.dependencies]
Expand All @@ -26,7 +27,3 @@ isort = "^5.13.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
markers = ["integration: integration tests"]
addopts = '-m "not integration"'
2 changes: 1 addition & 1 deletion scripts/bb_package_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
)

result = subprocess.run(
[blender_path, "--background", "--python", temp_file.name],
[blender_path, "--background", "--factory-startup", "--python", temp_file.name],
capture_output=True,
text=True,
)
Expand Down
7 changes: 7 additions & 0 deletions src/basilisp_blender/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Initialize the Basilisp runtime environment."""

import importlib
import logging

from basilisp import main as basilisp
from basilisp.lang import compiler
from basilisp.lang.util import munge

COMPILER_OPTS = compiler.compiler_opts()
basilisp.init(COMPILER_OPTS)
Expand All @@ -27,3 +29,8 @@ def log_level_set(level, filepath=None):


# log_level_set(logging.DEBUG, "basilisp-blender.log")

def control_panel_create():
"""Initialises and displays the nREPL server UI control panel."""
ctrl_panel_mod = importlib.import_module(munge("basilisp-blender.control-panel"))
ctrl_panel_mod.nrepl_control_panel_create__BANG__()
80 changes: 80 additions & 0 deletions src/basilisp_blender/bpy_utils.lpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
(ns basilisp-blender.bpy-utils
(:require [basilisp.string :as str]
[basilisp-blender.nrepl-server :as ns]
[basilisp-blender.utils :as u])
(:import atexit
bpy
os.path
sys))

(defn nrepl-server-start
"Starts the nrepl-server in async mode according to `opts`, using a
bpy timer to schedule any pending client work every 200ms.
`opts` is a map that can have the following keys
:host The interface address the server should be bound to. It
defaults to 127.0.0.1 if not given or empty.
:port The port number the server should listen to. It defaults to 0,
which indicates a random available port number.
:nrepl-port-dir The directory where the `.nrepl-port` file should be
created at. It defaults to the current working directory if not
given or empty.
It returns a map with the following keys
:error An error message in case the server could not be started
:host The address the server is bound to.
:nrepl-port-file The path to the `.nrepl-port` file with the port
number the server is listening to.
:port The port the server is listening to.
:shutdown! A function to shutdown the server and stop the bpy timer."
[{:keys [host port nrepl-port-dir interval-sec] :as opts
:or {port 0
interval-sec 0.2}}]
(binding [*out* sys/stdout]
(println :server-start :opts opts :host host :port port :nrepl-port-dir nrepl-port-dir :interval-sec interval-sec)

(let [host (if (or (nil? host) (empty? (str/trim host)))
"127.0.0.1"
host)
nrepl-port-dir (if (or (nil? nrepl-port-dir) (empty? (str/trim nrepl-port-dir)))
"."
nrepl-port-dir)]
(if (not (os.path/isdir nrepl-port-dir))
{:error (u/error-make [:nrepl-server-start :nrepl-port-dir-not-a-dir nrepl-port-dir])}

(let [{:keys [error work-fn shutdown-fn] :as ret}
(ns/start-server! {:async? true
:host host
:port port
:nrepl-port-file (os.path/join nrepl-port-dir ".nrepl-port")})]
(if error
(binding [*out* sys/stderr]
(println :server-start-error (u/error->str error))
{:error error})

(let [shutdown?* (volatile! false)
shutdown! #(do (vreset! shutdown?* true)
(shutdown-fn))]
(atexit/register #(let [{:keys [error]} (shutdown-fn)]
(when error
(binding [*out* sys/stderr]
(println (u/error->str error))))))
(-> bpy/app .-timers (.register #(let [{:keys [error]} (work-fn)]
(when error
(binding [*out* sys/stderr]
(println (u/error->str error))))
(if @shutdown?*
(println ::timer-shutdown host port)
interval-sec))))

(-> (select-keys ret [:host :port :nrepl-port-file])
(assoc :shutdown! shutdown!)))))))))

Loading

0 comments on commit 3ff0e6b

Please sign in to comment.