Author Topic: Clojure in Cad - a taste of things to come  (Read 7289 times)

0 Members and 1 Guest are viewing this topic.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Clojure in Cad - a taste of things to come
« on: November 02, 2014, 10:18:42 PM »
I've been tinkering with a few ideas today and got a little Clojure interpreter happening within Bricscad, very rough at this point and proof of concept. Pretty simple though.
You will need to download clojureclr from: https://github.com/clojure/clojure-clr/wiki/Getting-binaries
then ref the clojure.dll into the project.

To use it create a C# class library project and add the following, change your lib's as required:
Code - C#: [Select]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.IO;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8.  
  9. using Teigha.Runtime;
  10. using Teigha.DatabaseServices;
  11. using Bricscad.ApplicationServices;
  12. using Bricscad.EditorInput;
  13.  
  14. using _AcRx = Teigha.Runtime;
  15. using _AcAp = Bricscad.ApplicationServices;
  16. using _AcDb = Teigha.DatabaseServices;
  17. using _AcEd = Bricscad.EditorInput;
  18.  
  19. using clojure;
  20.  
  21. [assembly: CommandClass(typeof(ClojureCad.Commands))]
  22. namespace ClojureCad
  23. {
  24.     class Commands
  25.     {
  26.         [CommandMethod("CLJCAD")]
  27.         public static void CljCad()
  28.         {
  29.             Editor ed = _AcAp.Application.DocumentManager.MdiActiveDocument.Editor;
  30.             clojure.lang.Compiler.loadFile("C:\\testcad.clj");
  31.             PromptResult res = ed.GetString("Enter a clojure expression within \"\": ");
  32.             char[] trimchars = {'"'};
  33.             string cljstring = res.StringResult.Trim(trimchars);
  34.             object o = clojure.lang.RT.readString(cljstring);
  35.             object result = clojure.lang.Compiler.eval(o);
  36.             ed.WriteMessage("Result: " + result.ToString());
  37.         }
  38.  
  39.         [CommandMethod("CLJCAD1")]
  40.         public static void CljCad1()
  41.         {
  42.             Editor ed = _AcAp.Application.DocumentManager.MdiActiveDocument.Editor;
  43.             clojure.lang.Compiler.loadFile("C:\\testcad.clj");
  44.             PromptResult res = ed.GetString("Enter a clojure command: ");
  45.             var foo = clojure.lang.RT.var("cljcad", res.StringResult);
  46.             object o = foo.invoke();
  47.             ed.WriteMessage("Result: " + o.ToString());
  48.         }
  49.     }
  50. }
  51.  

And here's a very simple Clojure script file:
Code - Auto/Visual Lisp: [Select]
  1. (ns cljcad) ;; namespace for this file
  2.  
  3. (defn command1 []
  4.   (str "Simple test to see clojure is connected!"))
  5.  
  6. (defn command2 []
  7.   (str "Another command, getting boring now :)"))
  8.  

here's how it works:
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #1 on: November 02, 2014, 10:23:48 PM »
Ok, nothing very suprising there...yet.

The real benefit is that I can edit my script and run it again without having to Netload all the time, I can make a quick edit and run it again.
The other bonus is I can use any .net framework as well as the ARX .net wrappers, if you are happy with your script you can then compile it to a dll proper and netload it like any other class lib.

Very early stages yet, I will be trying to add a few things to the db next and try to hook up a proper repl so you can code on the fly and test things out.

There's very little help out there for clojure-clr so it will be uphill for a while if you're interested.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #2 on: November 02, 2014, 11:34:45 PM »
a bit more testing the limits of using the command line:

Quote
: CLJCAD
Enter a clojure expression within "": "(defn tester [] (str "tester
: CLJCAD
Enter a clojure expression within "": "(defn tester [] (+ 1 2))"
Result: #'clojure.core/tester
: CLJCAD
Enter a clojure expression within "": "(tester)"
Result: 3
: CLJCAD
Enter a clojure expression within "": "(defn add2 [n1 n2] (+ n1 n2))"
Result: #'clojure.core/add2
: CLJCAD
Enter a clojure expression within "": "(add2 4 5)"
Result: 9

The limit is when using parens or quoted strings in Editor.GetString() Bricscad wants to exit and interpret it as a lisp expression right away, does anyone have a work around for this?
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8697
  • AKA Daniel
Re: Clojure in Cad - a taste of things to come
« Reply #3 on: November 03, 2014, 01:57:46 AM »
Hi Mick,
Cool stuff!  Instead of get string, maybe you could use LispFunction

Code: [Select]
       [LispFunction("Clojure")]
        public static ResultBuffer MyClojureLispFunc(ResultBuffer args)
        {
            ResultBuffer buf = new ResultBuffer();
            foreach (var item in args)
            {
                if ((LispDataType)item.TypeCode == LispDataType.Text)
                {
                    object o = clojure.lang.RT.readString(item.Value.ToString());
                    object result = clojure.lang.Compiler.eval(o).ToString();
                    buf.Add(new TypedValue((int)LispDataType.Text, result));
                }
            }
            return buf;
        }

Code: [Select]
(Clojure "(+ 1 3)" "(+ 3 9)")

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8697
  • AKA Daniel
Re: Clojure in Cad - a taste of things to come
« Reply #4 on: November 03, 2014, 02:36:33 AM »
Code: [Select]
    public static class MyClojureFuncs
    {
        static List<object> loaded = new List<object>();


        //(LoadClojure "C:\\Temp\\testcad.clj")
        [LispFunction("LoadClojure")]
        public static object LoadClojure(ResultBuffer args)
        {
            foreach (var item in args)
            {
                if ((LispDataType)item.TypeCode == LispDataType.Text)
                {
                    loaded.Add(clojure.lang.Compiler.loadFile(item.Value.ToString()));
                }
            }
            return true;
        }

        //(CL "cljcad" "command1")
        [LispFunction("CL")]
        public static object CL(ResultBuffer args)
        {
            String func = String.Empty;
            String space = String.Empty;
            String result = String.Empty;
            List<TypedValue> _args = new List<TypedValue>(args.AsArray());

            if ((LispDataType)_args[0].TypeCode == LispDataType.Text)
            {
                space = _args[0].Value.ToString();
            }
            if ((LispDataType)_args[1].TypeCode == LispDataType.Text)
            {
                func = _args[1].Value.ToString();
            }
            var foo = clojure.lang.RT.var(space, func);
            object o = foo.invoke();
            return o.ToString();
        }
    }

: (LoadClojure "C:\\Temp\\testcad.clj")
T
: (CL "cljcad" "command1")
"Simple test to see clojure is connected!"

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #5 on: November 03, 2014, 02:42:29 AM »
Cool Daniel! Thanks for that, I know little of the LispFunction attribute, works well:

: (clj "(+ 3 4)")
("7")
: (clj "(defn my-add [n1 n2] (+ n1 n2))")
("#'clojure.core/my-add")
: (clj "(my-add 3 4)")
("7")
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #6 on: November 03, 2014, 04:30:58 AM »
Here's an updated script file contents so far, it now has an add-line command, loving the conciseness of this code, the imports are a bit of a drag though :)

Code - Auto/Visual Lisp: [Select]
  1. (ns cljcad
  2.         (:import (Teigha.DatabaseServices Database SymbolUtilityServices Transaction BlockTable BlockTableRecord Line OpenMode))
  3.         (:import (Teigha.Geometry Point3d))
  4.         (:import (Bricscad.ApplicationServices Application)))
  5.  
  6. (defn command1 []
  7.   (str "Simple test to see clojure is connected!"))
  8.  
  9. (defn command2 []
  10.   (str "Another command, getting boring now :)"))
  11.  
  12. (defn- add-to-db ;; private function -> defn-
  13.         "Adds an AcDbEntity to ModelSpace of the current database"
  14.         [entity]
  15.         (let [ db (.. Application DocumentManager MdiActiveDocument Database)
  16.                         tr (.. db TransactionManager StartTransaction)
  17.                         bt (.GetObject tr (. db BlockTableId) OpenMode/ForWrite)
  18.                         btr(.GetObject tr (. SymbolUtilityServices GetBlockModelSpaceId db) OpenMode/ForWrite)
  19.                   ]
  20.         (let [id (.AppendEntity btr entity)]
  21.         (doto tr
  22.                 (.AddNewlyCreatedDBObject entity true)
  23.                 (.Commit)
  24.                 (.Dispose))
  25.                 id)))
  26.        
  27. (defn add-line
  28.         []
  29.         (let [ line (Line. (Point3d. 20.0 20.0 0.0) (Point3d. 200.0 50.0 0.0))]
  30.                  (add-to-db line)))
  31.  
« Last Edit: November 03, 2014, 04:45:54 AM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Clojure in Cad - a taste of things to come
« Reply #7 on: November 03, 2014, 04:35:29 AM »

Skipping along the bleeding edge again I see, Mick :-)

Looks interesting.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #8 on: November 03, 2014, 04:54:48 AM »

Skipping along the bleeding edge again I see, Mick :-)

Looks interesting.

It is Kerry!

I haven't netloaded for hours, just save the script and try again.
It's taken me hours to get my head around the interop though but it's coming together. :)
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #9 on: November 04, 2014, 01:05:41 AM »
Here is the latest 'script' file contents that clean up a few things and I've commented the code to explain some of the macros and special form functions in Clojure to explain how they work.

Code - Auto/Visual Lisp: [Select]
  1. ;; Here we declare a namespace 'ns cljcad' and we 'import' the lib's we want to use
  2. ;; in our script. As we are using lib's from the host application we don't need to 'require' them.
  3. (ns cljcad
  4.         (:import (Teigha.DatabaseServices Database SymbolUtilityServices
  5.                                 Transaction BlockTable BlockTableRecord Line OpenMode))
  6.         (:import (Teigha.Geometry Point3d Vector3d))
  7.         (:import (Bricscad.ApplicationServices Application)))
  8.  
  9. (defn command1 []
  10.   (str "Simple test to see clojure is connected!"))
  11.  
  12. (defn add-to-db
  13.         ;; This is a doc string, can be read from a repl with (doc fn-name)
  14.         "Adds an AcDbEntity to ModelSpace of the current database
  15.         Returns the ObjectId of the Entity added to the db."
  16.         [entity]
  17.         ;; get the database:
  18.         (let [ db (.. Application DocumentManager MdiActiveDocument Database)]
  19.                 ;; with-open is the same as 'using' in C#.
  20.                 ;; The '..' macro allows you to dig deep into multiple levels of class hierarchy.
  21.                 (with-open [tr (.. db TransactionManager StartTransaction)]
  22.                         ;; set up the block table and block table record objects:
  23.                         ;; The '.method-name' macro lets you call a method on the object (.method instanceObj)
  24.                         ;; The '. ' macro is like the '..' but only a single level deep
  25.                         ;; The '/' is for getting enums or static methods from an object - obj/enum or Class/staticMethod
  26.                         (let [  bt (.GetObject tr (.BlockTableId db) OpenMode/ForWrite)
  27.                                         btr(.GetObject tr (. SymbolUtilityServices GetBlockModelSpaceId db) OpenMode/ForWrite)]
  28.                                 ;; here we bind the returned object id from AppendEntity to return later:
  29.                                 (let [id (.AppendEntity btr entity)]
  30.                                         ;; 'doto' is like chaining actions to the object we are using, a transaction in this case.
  31.                                         (doto tr
  32.                                                 (.AddNewlyCreatedDBObject entity true)
  33.                                                 (.Commit))
  34.                                                 id))))) ;; returns the object id
  35.                                                
  36. ;; Typical function definition:
  37. ;; 'defn' is a special form to denote function creation,
  38. ;; params passed to the function are within '[]' and you can have more than one 'arity'
  39. ;; of params (think overloading). You may then have a doc string and then the body of the function.
  40. ;; An example for the function below with multiple arity might be -
  41. ;; (defn add-line
  42. ;;              []              ;; no args
  43. ;;              [p1 p2]         ;; 2 args passed in
  44. ;;              ;;body that uses the args passed or uses defaults
  45. ;;              ( ... ))
  46. (defn add-line
  47.         []
  48.         ;; To create new instances of objects we can use (Obj. args) or (new Obj args)
  49.         (let [ line (Line. (Point3d. 20.0 20.0 0.0) (Point3d. 200.0 50.0 0.0))]
  50.                  ;; add it to the db:
  51.                  (add-to-db line)))
  52.  
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8697
  • AKA Daniel
Re: Clojure in Cad - a taste of things to come
« Reply #10 on: November 04, 2014, 05:45:11 AM »
How to namespaces work? Is it per loaded script?
Can you add functions to an existing namespace?
Maybe one add a global namespace that imports all of the references…

clojure.lang.Namespace cSpace = clojure.lang.Compiler.CurrentNamespace;
cSpace.importClass(/* import all the cad stuff to */);
clojure.lang.Compiler.loadFile(item.Value.ToString());

or

clojure.lang.Compiler.loadFile(item.Value.ToString());
clojure.lang.Namespace cSpace = clojure.lang.Compiler.CurrentNamespace;
cSpace.importClass(/* import all the cad stuff to */);

just a thought

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #11 on: November 04, 2014, 02:26:52 PM »
How to namespaces work? Is it per loaded script?
Can you add functions to an existing namespace?
I'm not 100% sure but they are similar to JavaScript namespaces in that they are just a name to avoid name clashes in the symbol table.
I think they also work like Java namespaces in that the names can refer to folder and file structure for when 'require' -ing them, I'll have to experiment.
e.g. (require  '(foo/bar/baz)) would grab the baz.clj file from the bar sub folder of foo.

Quote
Maybe one add a global namespace that imports all of the references…
clojure.lang.Namespace cSpace = clojure.lang.Compiler.CurrentNamespace;
cSpace.importClass(/* import all the cad stuff to */);
clojure.lang.Compiler.loadFile(item.Value.ToString());

or

clojure.lang.Compiler.loadFile(item.Value.ToString());
clojure.lang.Namespace cSpace = clojure.lang.Compiler.CurrentNamespace;
cSpace.importClass(/* import all the cad stuff to */);

just a thought

I'll take a look at that, I think I would set it up so you just have a 'scripts' or 'plugins' folder that Clojure would search each sub folder and load all the files on start up.

My next task is to hook up a REPL so you can enter code and test it live, that will be pretty cool to edit and test arx code snippets on the fly. I would like to hook it up with a simple text editor like Notepadd++ as well but it will most likely end up with emacs at first as emacs is one of the most advanced in this area with Clojure.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Clojure in Cad - a taste of things to come
« Reply #12 on: August 25, 2016, 07:36:50 AM »
Back at it again, this time with a bit more effort :)

Got a pretty sweet setup going now with a simple text editor with syntax colouring and bracket matching with the help of the ScintillaNET code editor control which is pretty cool.
I've hand built the repl and so far it gets the job done but needs a lot more work to make it less error prone.

The workflow so far is to write some code in the editor then you can select it and send it to the repl for evaluation which loads it into the system. From there on you can call your functions in the repl and see the results in the CAD editor. You can just type the same in the repl as well but I mainly use it to call functions I've loaded.
It handles exceptions and syntax errors pretty well for what it is and is quite usable.

What makes this really special though is you can develop your app using the repl for testing and refactoring, no need to restart the CAD system and netload, just select and load your revised function and test again, pretty sweet!
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Clojure in Cad - a taste of things to come
« Reply #13 on: August 26, 2016, 08:49:08 AM »
It is interesting. Can you use the breakpoints and conditional breakpoints in this code editor?

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Clojure in Cad - a taste of things to come
« Reply #14 on: August 26, 2016, 09:02:00 AM »
Sory, it is not about Clojure, but it can be interesting for C# programmers: http://avalonedit.net/