diff --git a/README.md b/README.md index 9da7d95..66570bf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Basilisp Blender Integration -[Basilisp](https://github.com/basilisp-lang/basilisp) is a Python-based Lisp implementation that offers broad compatibility with Clojure. +[Basilisp](https://github.com/basilisp-lang/basilisp) is a Python-based Lisp implementation that offers broad compatibility with Clojure. For more details, refer to the [documentation](https://basilisp.readthedocs.io/en/latest/index.html). ## Overview `basilisp-blender` is a Python library designed to facilitate the execution of Basilisp code within Blender and manage an nREPL server for interactive programming. This library provides functions to evaluate Basilisp code from Blender's Python console, file or Text Editor and to start an nREPL server, allowing seamless integration and communication with Basilisp. @@ -37,8 +37,7 @@ To evaluate Basilisp code from a file: ```python from basilisp_blender.eval import eval_file -file_path = "path/to/your/file.lpy" -eval_file(file_path) +eval_file("path/to/your/code.lpy") ``` #### From Blender’s Text Editor @@ -57,7 +56,6 @@ To start an nREPL server within Blender: ```python from basilisp_blender.nrepl import server_start -```python shtudown_fn = server_start(host="127.0.0.1", port=8889) ``` @@ -73,13 +71,13 @@ shtudown_fn = server_start(nrepl_port_filepath="/.nrepl-port" Replace `` with the path to your project's root directory. -#Example +# Examples -Basilisp code to create a torus pattern using the `bpy` Blender library: +Here is an example of Basilisp code to create a torus pattern using the bpy Blender Python library: ```clojure (ns torus-pattern - "Creates a torus pattern with random colored materials." + "Creates a torus pattern with randomly colored materials." (:import bpy math)) @@ -105,13 +103,13 @@ Basilisp code to create a torus pattern using the `bpy` Blender library: mat)) (defn create-torus [radius tube-radius location segments] - (let [_ (.primitive-torus-add mesh ** - :major-radius radius - :minor-radius tube-radius - :location location - :major-segments segments - :minor-segments segments) - obj (-> bpy/context .-object) + (.primitive-torus-add mesh ** + :major-radius radius + :minor-radius tube-radius + :location location + :major-segments segments + :minor-segments segments) + (let [obj (-> bpy/context .-object) material (create-random-material)] (-> obj .-data .-materials (.append material)))) @@ -137,3 +135,42 @@ Basilisp code to create a torus pattern using the `bpy` Blender library: ![torus pattern example img](examples/torus-pattern.png) +# Development + +## Testing + +You can run tests using the following command: + +```bash +$ poetry run pytest +``` +### Integration testing + +To run integration tests, set the `$BB_BLENDER_TEST_HOME` environment variable to the root directory of the Blender installation where the development package is installed. See next section on how to facilitate the installation. + +```bash +$ export BB_BLENDER_TEST_HOME="~/blender420" +# or on MS-Windows +> $env:BB_BLENDER_TEST_HOME="c:\local\blender420" +``` +Then run the integration tests with + +```bash +$ poetry run pytest -m integration +``` + +### Installing Blender and the Development Package + +To download and install Blender in the directory specified by `$BB_BLENDER_TEST_HOME`, use: + +```bash +$ 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 +``` + diff --git a/examples/torus_pattern.lpy b/examples/torus_pattern.lpy index 66e46f5..b9b30db 100644 --- a/examples/torus_pattern.lpy +++ b/examples/torus_pattern.lpy @@ -1,5 +1,5 @@ (ns torus-pattern - "Creates a torus pattern with random colored materials." + "Creates a torus pattern with randomly colored materials." (:import bpy math)) @@ -25,13 +25,13 @@ mat)) (defn create-torus [radius tube-radius location segments] - (let [_ (.primitive-torus-add mesh ** - :major-radius radius - :minor-radius tube-radius - :location location - :major-segments segments - :minor-segments segments) - obj (-> bpy/context .-object) + (.primitive-torus-add mesh ** + :major-radius radius + :minor-radius tube-radius + :location location + :major-segments segments + :minor-segments segments) + (let [obj (-> bpy/context .-object) material (create-random-material)] (-> obj .-data .-materials (.append material)))) diff --git a/pyproject.toml b/pyproject.toml index 1c41a69..68983e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "basilisp-blender" -version = "0.0.0b28" +version = "0.0.0b29" description = "" authors = ["ikappaki"] readme = "README.md" diff --git a/src/basilisp_blender/nrepl_server.lpy b/src/basilisp_blender/nrepl_server.lpy index 4ddf407..93d1029 100644 --- a/src/basilisp_blender/nrepl_server.lpy +++ b/src/basilisp_blender/nrepl_server.lpy @@ -369,10 +369,9 @@ :recv-buffer-size The buffer size to using for incoming nREPL messages. - :work* An optional atom containing a map. If provided, it stores - client entries where each key represents information about the - connected client and each value is a queue of requests to be - processed later, rather than immediately." + :work* An optional atom containing a map. If provided, client + requests are queued in the map under a client information key, + rather than being executed imediately." (let [{:keys [recv-buffer-size work*] :or {recv-buffer-size 1024}} opts socket (.-request tcp-req-handler) @@ -426,9 +425,13 @@ the optional `notify-fn` with the client information and the request to be executed. - `work` is an atom that holds a map. In this map, each key - represents a client information object, and each value is a - `queue.Queue` containing client request functions to be executed." + `work*` is an atom containing a map where each key represents a + client information object, and each value is a `queue.Queue` of + request functions to be executed. + + The optional `notify-fn` argument is a 2-arity function that will be + called with the client information and the request that is about to + be executed." ([work*] (clients-work-do! work* nil)) ([work* notify-fn] @@ -475,14 +478,14 @@ (socketserver/ThreadingTCPServer (python/tuple [host port]) handler))) (defn server-shutdown! - "Shutdown the server contained in the `server*` atom." + "Shutdown the `server`, and optionally close it if `close?` is true." ([server] (server-shutdown! server false)) - ([server wait?] + ([server close?] (debug ":server-shutting-down") (.shutdown server) (debug ":server-close") - (when wait? + (when close? (.server-close server)) (debug ":server-close-done"))) @@ -500,11 +503,26 @@ ``opts`` is a map of options with the following optional keys + + :async? If true, runs the server in asynchronous where requests are + queued rather than executed immediately. This mode requires that + the :server* key is present in the ``opts`` + map. The :server*'s :work-fn key will be set to a function that + processes all pending work (see :atoms* below). + :nrepl-port-file An option filepath to write the port number to. This is typically set to .nrepl-port for the editors to pick up. - :server* An atom to hold the server reference as returned from - :lpy:fn:`server-make`, useful for testing. + :server* A map atom. The map may contains an optional :start-event + key with a `threading.Event` value. This event is set when the + server is about to enter its main loop or if it fails to start. The + map will be populated with the following keys + + :server Set to the server reference. + + :work-fn When in `async?` mode, set to a function that executes all pending work. + + :shutdown-fn set to a function that can be called to shut down the server. also see :lpy:fn:`server-make` for additionally supported ``opts`` keys." @@ -520,14 +538,11 @@ (error "Error: async server requires the server* option.") (try - (when server* (reset! server* {:server server - :start-event start-event - :work-fn (fn - ([] - (clients-work-do! work*)) - ([notify-fn] - (clients-work-do! work* notify-fn))) - :shutdown-fn (partial server-shutdown! server)})) + (when server* (reset! server* (cond-> {:server server + :start-event start-event + :shutdown-fn (partial server-shutdown! server)} + async? + (assoc :work-fn (partial clients-work-do! work*))))) (let [[host port] (py->lisp (.-server-address server))] (binding [*out* sys/stdout] (println (format nrepl-server-signature port host host port))) @@ -549,8 +564,8 @@ server* (atom {:start-event start-event}) opts (assoc opts :async? true :server* server*) start-fn #(start-server! opts) - thread (threading/Thread ** :target start-fn :daemon true) - _ (.start thread)] + thread (threading/Thread ** :target start-fn :daemon true)] + (.start thread) (if (.wait start-event 10) (-> (select-keys @server* [:server :work-fn :shutdown-fn]) (assoc :server-thread thread))