-
Notifications
You must be signed in to change notification settings - Fork 1
Ruby talks to AutoLISP
davidbl edited this page Sep 13, 2010
·
1 revision
Often when coding against AutoCAD in some language other than AutoLISP, it is nice to be able to read or write LISP variables. As it turns out, it is now pretty easy to do just that using Ruby and AcadHelper.
I was looking through a dumpbin listing for acad.exe and found this little jewel, ?acedEvaluateLisp@@YAHPB_WAAPAUresbuf@@@Z. I played with a bit without any success so I googled it and found some code by Alexander Rivilis on the autodesk discussion board
So I used Alexander’s code to create a little C# class
public class RubyLisp { //This code is taken from a post here http://discussion.autodesk.com/forums/thread.jspa?threadID=522825 //by Alexander Rivilis - Thanks Alexander [SuppressUnmanagedCodeSecurity] [DllImport(@"C:\Program Files (x86)\AutoCAD Civil 3D 2009\acad.exe", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, EntryPoint = "?acedEvaluateLisp@@YAHPB_WAAPAUresbuf@@@Z")] extern private static int acedEvaluateLisp(string lispString, out IntPtr result);
static public Object evalLisp(string lispString) { IntPtr out_data = IntPtr.Zero; acedEvaluateLisp(lispString, out out_data); if (out_data != IntPtr.Zero) { try { ResultBuffer ret_data = DisposableWrapper.Create(typeof(ResultBuffer), out_data, true) as ResultBuffer; return ret_data; } catch { return null; } } return null; } }
I compiled that code and then, using IronRuby, I hacked together this.
#wrappers for interfacing with the AutoLISP memory space and AutoLISP Functions
require 'C:/LISP/RubyTest/RubyTest/bin/Release/RubyLisp.dll'
# extensions to the ResultBuffer class for easier access class Autodesk::AutoCAD::DatabaseServices::ResultBuffer def to_a ret_val = [] self.AsArray.each do |typed_val| ret_val << typed_val.Value end ret_val end
def to_full_array ret_val = [] self.AsArray.each do |typed_val| ret_val << [typed_val.type_code, typed_val.value] end ret_val end
#just return the value def val self.AsArray[0].value end
#just return the code def code self.AsArray[0].type_code end
def size self.AsArray.size end
#return the element whose code matches our argument def assoc(code) arr = self.to_full_array arr.assoc(code) end
#return just the value of the element whose code matches our argument def cdr_assoc(code) arr = self.to_full_array arr.assoc(code)[1] end
end
#some wrapper methods for the C# EvaluateLisp function
#evaluate a string of LISP code #examples of valid LISP strings include (setq x 55.5), (setq my_name "David Blackmon"), (entget(entlist)), (load "my_lisp_file") #but, because the argument must be passed as a string, we have to escape the quotes when the need them #actual argument strings would be "(setq x 55.5)", "(setq my_name \"David Blackmon\")" def lisp_eval(lisp_string) ret_val = RubyLisp::RubyLisp.evalLisp(lisp_string) return nil if ret_val.code == 5019 ret_val end
#load a LISP file. Raising ArgumentError is file is not in the AutoCAD search path def lisp_load(file_name) find_file_res = lisp_eval "(findfile \"#{file_name}\")"
if find_file_res lisp_eval "(load \"#{file_name}\")" else raise ArgumentError, "file #{file_name} not found" end end
#a wrapper for LISP setq - now only works for simple assigns such as (setq x 5), (setq y "abc") #and lists (setq my_list (list 1 2 3 "test" "foo" "bar" 3.1419)) #the sym argument can be a Ruby symbol or a string #valid calls are setq("x", 55.5), setq(:x, 55.5), setq("y", "this is a string variable") #setq(:my_list, (list 22, 33, 44)) def setq(sym, expr) if expr.is_a?(String) if expr.match(/\(list/) str = "(setq #{sym.to_s} #{expr.to_s})" else str = "(setq #{sym.to_s} \"#{expr.to_s}\")" end puts str else str = "(setq #{sym.to_s} #{expr.to_s})" puts str
end lisp_eval str end
#get the value of a LISP symbol, passed a Ruby symbol,eg getq(:x), getq(:my_list) def getq(sym) ret_val = lisp_eval "(vl-symbol-value \'#{sym.to_s} )" end
Using these new wrappers, we can write cool stuff such as
def lisp_load_test begin lisp_load "rubytest" x = lisp_load "rubytestasdfasdf" puts x.inspect rescue Exception => e puts_ex e end end
def lisp_test begin #create some variables in LISP memory space setq(:x, 25) #an integer setq('y', "abc asdf 12") #a string setq('z', '(list 1 2.1 "asdf" 3.0 4.46464)') # a list
#try to load an LSP file named 'rubytest.lsp'. it must be in the search path lisp_load "rubytest"
#try to read an existing LISP variable lsp_string = "(eval ruby_test)" lisp_val = lisp_eval lsp_string puts lisp_val.inspect
#send some lisp code and look at the return value lsp_string = "(entget (entlast))" ret_val = lisp_eval lsp_string puts ret_val.to_full_array.inspect
#we can iterate over the entire entity list, searching for our match ret_val.each do |typed_val| puts "Layer of last entity in drawing is #{typed_val.Value}" if typed_val.TypeCode == 8 end
#or we can reach into the list like we would in LISP using ResultBuffer#cdr_assoc layer_name = ret_val.cdr_assoc(8) puts "Layer of last entity in drawing is #{layer_name}"
#we can also get the full assoc element puts ret_val.assoc(40).inspect
#get the values of the LISP variables we set above #we can get the value as an array of values qq = getq(:z).to_a puts qq.inspect qq = getq(:x).to_a puts qq.inspect
#we can get the value as the pure ResultBuffer qq = getq(:Pi) puts qq.inspect
#we can get just the value without the type data qq = getq(:Pi).val puts qq
#we can get just the type data puts getq(:Pi).code
#we can get the size of the result buffer puts getq(:z).size
#we can get a nest array of [type_codes, values] for the entire ResultBuffer puts getq(:z).to_full_array.inspect
rescue Exception => e puts_ex e end
end
This code will be added to the master repo soon
Have fun!ps. Next up, Block insertion made fun and easy