Need help testing and improving my lisp

Need help testing and improving my lisp

roland.r71
Collaborator Collaborator
5,377 Views
44 Replies
Message 1 of 45

Need help testing and improving my lisp

roland.r71
Collaborator
Collaborator

Hi guys,

 

Attached you will find my MFT.lsp, which stands for "Multiple Filehandling Tool"

It allows to select files (*.dwg / *.dxf), in pretty much any way thinkable.

& to suply a lisp (*.lsp) to be loaded and/or commands to be executed, on each file.

MFT Main dialogMFT Main dialog

The idea is to share this with anybody who needs to process multiple DWG's (or DXF's).

& Its supposed to work on any version of ACAD, in combination with DOSlib.

...but I can only test it on 2014 (64bit) & 2015 (64bit)

...and the code can surely be improved in many ways. (which comes with a ton of comments)

 

& thats what I need you guys for.

Any version you can test it on, any bug you find, any improvement you can make, any suggestion you have: Bring it on! (& make this a tool everybody wants Smiley Wink)

 

Note: the command part is an addition I stuck in recently and still need to work out a bit.

The idea was to create a script & load that with each drawing, but that doesn't work.

So I changed it to an (eval ...), which works with the example you see inside the screenshot above, but I didn't yet test it with multiple lines and/or script like commands (e.g: "Zoom e" instead of "(command "zoom" "e")")

 

I might just reduce the option to a single line of code, if it turns out to difficult to allow for more. (& if you put 'garbage' in, you will get garbage out during processing, so be carefull with that option)

 

It comes with a ini file & dcl for the main dialog.

I've used "C:/Lisp/MFT/" to put the files in & it uses a subdirectory "DATA" for saving a list of files and a .scr file to hold the commands. It doesn't check or create it, so if not there, nothing will be saved/loaded for those two files/options.

You should be able to put it anywhere, just edit the path inside the .lsp INIT section

   (setq MFT_files "c:/Lisp/MFT/")

 

Where you might also set a path to find DOSlib. (It will try to find it within support path first)

   (setq DLpath "C:/Lisp/DOSLIB/")

5,378 Views
44 Replies
Replies (44)
Message 21 of 45

roland.r71
Collaborator
Collaborator

It would seem that inbetween the fixes, I managed to make the open function fail.

Spend half the night trying to figure it out, but I didn't get it to work again. Just can't put my finger on it, yet. So I didn't get to testing with John suggestions yet.

 

The command/script part works if using the (command ...) structure. Not sure about other ways, like a true script, or even macro yet. But as I realy need to make it function again firts, that's gonna have to wait a little.

 

I did notice the macro looks a lot like a string which would be parsed by John's function. So it might even be possible to work with that, eventually.

 

& I noticed I'm still getting a FILETABCLOSE, at least when hitting ESC (when it fails to open). Though it should be restored, or not happen at all. (The tabs remain visible right to the end, and then they dissapear.) When done, I hit the arrow-up key, and there it says FILETABCLOSE apparently AFTER the function ended (?) I am clueless as to where thats coming from and why.

0 Likes
Message 22 of 45

Anonymous
Not applicable

After I posted that I found the DIR button.. Works great!  My bad!

0 Likes
Message 23 of 45

roland.r71
Collaborator
Collaborator

Ok. Got it functioning again (at least on 2015)

 

Took me a totall of six hours & I still don't realy know what was the cause, but at least I found a few more 'bugs'. (Note to self: Don't change var names inside HUGE functions (without Search&Replace ...))

 

& here (on ACAD2015) my FILETAB command comes AFTER the FILETABCLOSE.

So it gets restored.

 

Still eludes me why it pops up.

The filetabs are there the whole time, right until you click ok, in the finall "we're done" dialog. All it does after that is resetting SDI & LISPINIT values.

 

Anyway, here's the corrected version:

0 Likes
Message 24 of 45

john.uhden
Mentor
Mentor
I meant to tell you...
"Don't change var names inside HUGE functions (without Search&Replace
...)"😂

John F. Uhden

0 Likes
Message 25 of 45

roland.r71
Collaborator
Collaborator

Well, I finally got to implement some of your code, and it works like a charm on both a (simple) script and the macro style of code Smiley Happy

 

Mainly because it negates the difference. Being enters (\n) .vs. ;

As the \n is converted to ; and the string converted to a list by the same ;

 

entering a macro like:

-LAYER;S;0;;-PURGE;A;;N;_ZOOM;E

works the same as if it where written like a *.scr

 

The only thing missing is:

Command:(command "zoom" "extent") LISP command is not available.
0 Likes
Message 26 of 45

john.uhden
Mentor
Mentor

@roland.r71 wrote,

"The only thing missing is:

Command:(command "zoom" "extent") LISP command is not available."

What do you mean by not available?

"ZOOM;E" would work, right?

Meaning (eval (apply 'command '("ZOOM" "E")))

 

But if you mean that the string was actually "(command "zoom" "extent")"

it has been my plan to convert that, if necessary.  I think there may have to be a method of dealing with quotes inside quotes.  I dunno, but I'll figure it out, regardless.

 

Oh wait, I see...

Command: (setq str (getstring t "\nEnter string: "))

Enter string: (command "zoom" :"e")
"(command \"zoom\" :\"e\")"

Command: (eval (read str))
zoom
Specify corner of window, enter a scale factor (nX or nXP), or
[All/Center/Dynamic/Extents/Previous/Scale/Window] <real time>:
Command: e Unknown command "E".  Press F1 for help.

Command: nil

The ZOOM needs to be in quotes, but the "E" does not (I think).  Hmm, that could be a tough one for programming  code to interpret and convert.  I'll have to play with it.

John F. Uhden

0 Likes
Message 27 of 45

roland.r71
Collaborator
Collaborator

@john.uhden wrote:

@roland.r71wrote,

"The only thing missing is:

Command:(command "zoom" "extent") LISP command is not available."

What do you mean by not available?

"ZOOM;E" would work, right?

Meaning (eval (apply 'command '("ZOOM" "E")))

 

But if you mean that the string was actually "(command "zoom" "extent")"

it has been my plan to convert that, if necessary.  I think there may have to be a method of dealing with quotes inside quotes.  I dunno, but I'll figure it out, regardless.

 

Oh wait, I see...

Command: (setq str (getstring t "\nEnter string: "))

Enter string: (command "zoom" :"e")
"(command \"zoom\" :\"e\")"

Command: (eval (read str))
zoom
Specify corner of window, enter a scale factor (nX or nXP), or
[All/Center/Dynamic/Extents/Previous/Scale/Window] <real time>:
Command: e Unknown command "E".  Press F1 for help.

Command: nil

The ZOOM needs to be in quotes, but the "E" does not (I think).  Hmm, that could be a tough one for programming  code to interpret and convert.  I'll have to play with it.


Yes, "zoom" e or "zoom;e" works.

But ACAD says LISP is not available if you use (command "zoom" "e")

 

However, as I just discoverred, it does work to call up a custom lisp function like:

(C:ZA)

& I suspect/hope it will take any kind of argument to go with that.

 

So, currently it already does all I was hoping for Smiley Happy

but if it could interpret the (command ...) style of coding that would be a great addition.

As people are sure to try. Or perhaps a warning if lisp code is detected, that they should NOT do that. (as there's SO MUCH, which could go wrong here with the average user ... Smiley LOL

0 Likes
Message 28 of 45

john.uhden
Mentor
Mentor

That's why you build in a catch-all.

You test each expression by setting an undo mark, then embed the expression in a vl-catch-all-apply, check for any error, and undo back.  And of course warn the user of any error.

 

BTW, I think the inclusion of lisp expressions is easier than I thought.  Fret not.  Take care of the rest of it.

John F. Uhden

0 Likes
Message 29 of 45

doaiena
Collaborator
Collaborator

I have to admit, i haven't read the whole through the whole topic, but if your problem is what i think it is try this.

 

(setq str (getstring t "\nEnter string: "))
Enter string: (command "zoom" "e")
Command: !(eval(read str))
0 Likes
Message 30 of 45

john.uhden
Mentor
Mentor
Yes, look closely at my first try and you will see an errant semicolon
right before "E."
It looks like there will be no need for special treatment other than
my @numerify function. So basically my work on this is complete.

John F. Uhden

0 Likes
Message 31 of 45

john.uhden
Mentor
Mentor

Roland:

Could you please send me some test strings to play with?  I want to make sure the conversion to lisp expressions works plus I'll try out my idea of catching errors, which will probably be mostly syntax errors, whatever.

You are from the Netherlands?

John F. Uhden

0 Likes
Message 32 of 45

roland.r71
Collaborator
Collaborator

I'll see if I can dig something up.

I personally hardly use scripts so I don't have anything readily available.

 

Yes I'm from the Netherlands.

0 Likes
Message 33 of 45

roland.r71
Collaborator
Collaborator

For the time being, I have an updated version using @john.uhden's code, this far.

 

notes:

The string returned form the dialogbox (containing \n)

Gets the \n replaced by ; which eventualy translates to an [enter]

There is no check (yet) to see if there are any \n inside quotes "\n" so you can't use that (yet)

Before passing it on to John's functions, all trailing \n's and spaces are removed (in that order)

 

I moved all settings to the top of the file, so you won't have to scroll all the way down to set file path's & default configuration.

 

Just put the MFT.lsp where you want, & add the MFT.dcl from the Opening post, set the correct path inside the MFT.lsp and you should be good to go. (Not forgetting DOSlib)

 

I completely rewrote parts of the MFTOpen function & it performs much better now.

The filelist checking should work properly now too.

0 Likes
Message 34 of 45

john.uhden
Mentor
Mentor

If you make a VLX, then you can include the DCL within it.

Plus, I don't know if Owen Wengerd still makes it, but he had a free VLX_loader.arx (something like that) that enables you to load an ARX that loads your VLX, so you can add the arx name to your acad.rx.  I used it for years for my package of crap I used to try to sell.

John F. Uhden

0 Likes
Message 35 of 45

roland.r71
Collaborator
Collaborator

For now (and possibly ever) I'll leave it like it is.

Might add the code to the lisp for generating, so no loose file is needed for distribution.

Much like I do now with the ini file & my all new 1st import filter (read below)

 

But the dcl should be ok before then.

 

Speaking of which, I just changed it.

Moved stuff around a little & added the progression bar to the options. It could already be turned on/off inside the ini or default settings, but it wasn't on the dialog yet.

& I added an import button/function, which I think is pretty cool Smiley Tongue

 

MFT.jpg

 

Import filters:

From now on, it can truely get the filelist from anywhere!

All that you need to do is write an import filter.

Here's how it works:

Within the subdirectory IMPORT you will find a file called "Import MFT logfile.lsp", this is my 'example'.

MFT will retrieve all the filenames inside this directory and put them in a list.  (without .lsp)

the user can then select one, to run as import filter.

MFT will looad the file & check for a function called "MFT_ImportFilter"

If its there, it will call the function and expect a list in return.

This list will then be added to the filelist.

 

MFTimp.jpg

 

 

Current version will create the directory & my 1st import filter, for loading up files previously skipped as example.

You guys can thus create ANY import filter, getting lists from excel, databases, txt files, anything!

 

On top of that, I further enhanced the script mechanism, to allow for comments, amongst others.

I no longer trim the end, as those spaces & enters can actually have meaning.

It should convert macro's, lines starting with ^, into script lines.

Any line starting with ; will be ignored as comment

and it should detect any comments at the rear of a line

So, while the editor shows:

 

MFT-editscr.jpg

 

What is eventually fed to John's function is:

("grid on" ".ltscale 3.0" "_layer set 0 color red 0" "" "(princ \"\\nTest\")")

 

So: Here are the new files:

0 Likes
Message 36 of 45

roland.r71
Collaborator
Collaborator

For my and possibly your testing convenience, I'm using a seperate file with just the function (exactly how it is now inside the complete thing posted above) So, here's the function, for reading/editing a script file.

It works as it is on its own, just provide a .scr file for import & don't mind the DOS_EDITBOX, its there for reference,

Just call it with a 0 (e.g.: (setCMD 0) to bypass the dialogbox.

 

You'll need to add your own functions, of course.

The @str2list is used for converting macro'

And @numerify comes in at the end

 

   (setq cmdfile  "c:/Lisp/MFT/DATA/MFT.scr")

; --- setCMD -------------------------------------------------------------------

   (defun setCMD ( box / file_w i lnnr line first eolc)

      ; sets cmd_var = complete file, for editing. Holds the original string including macro's & comments
      ; sets cmd_str = string for script preview in main dialog, macro style using ;
      ; sets cmd_lst = list of code used for eval (stripped of comments, macro's converted

      (if (= box 1)
         (progn
            (setq cmd_var (cond ((DOS_EDITBOX "Script editor" "Enter AutoCAD commands and/or lisp code" cmd_var))(cmd_var)))
            (if cmd_var
               (progn
                  (setq file_w (open cmdfile "w"))
                  (write-line cmd_var file_w)
                  (close file_w)
               )
            )
         )
      )
      (setq cmd_str "" cmd_lst nil)
      (if (findfile cmdfile)
         (progn
            (setq file_r  (open cmdfile "r")
                  lnnr    1
                  cmd_var ""
            )
            (while (setq line (read-line file_r))
               (if (= lnnr 1)
                  (setq cmd_var (strcat cmd_var line))
                  (setq cmd_var (strcat cmd_var "\n" line))
               )
               (setq lnnr 2)
               (setq first (substr line 1 1))
               (if (/= first ";")
                  (if (= first "^")
                     (setq cmd_lst (append cmd_lst (@str2list (substr line 5) ";"))) ; Convert macro to script
                  ; else...
                     (progn
                        (if (setq eolc (vl-string-position (ascii ";") line 0 T))
                           (if (< (vl-string-position (ascii "\"") line 0 T) eolc)
                              (setq line (substr line 1 eolc))                       ; cut comment from rear
                           )
                        )
                        (setq cmd_lst (append cmd_lst (list (strcat cmd_str line)))) ; add script line
                     )
                  )
                  ; else skip comment
               )
            )
            (close file_r)
            (setq lnnr 1)
            (if cmd_lst
               (foreach line cmd_lst
                  (if (= lnnr 1)
                     (setq cmd_str (strcat cmd_str line))
                     (setq cmd_str (strcat cmd_str ";" line))
                  )
                  (setq lnnr 2)
               )
            )
         )
      )
      (princ "\n")(print cmd_lst)
;      (set_tile "scrFile" cmd_str)

      ; method by John Uhden
      (setq cmd_lst (mapcar '@numerify cmd_lst))
      (princ)
   )

(setCMD 0)

When it finally comes to run, It uses:

(eval (apply 'command cmd_lst))

& I still need to come up with some good testing material...

0 Likes
Message 37 of 45

roland.r71
Collaborator
Collaborator

Addition to the code for setCMD:

Replaces part with same beginning and end lines. Additionaly transfers lines with script code containing spaces to lists, without breaking up commands inside parenthesis.

               (if (/= first ";")
                  (if (= first "^")
                     (setq cmd_lst (append cmd_lst (@str2list (substr line 5) ";"))) ; Convert macro to script
                  ; else...
                     (progn
                        (if (setq eolc (vl-string-position (ascii ";") line 0 T))
                           (if (< (vl-string-position (ascii "\"") line 0 T) eolc)
                              (setq line (substr line 1 eolc)) ; cut comment from rear
                           )
                        )
                        (if (/= (substr line 1 1) "(") ; if it doesn't start with a parenthesis
                           (progn
                              (while (vl-string-position (ascii " ") line)
                                 (setq line (vl-string-subst ";" " " line)) ; replace each space with ;
                              )
                              (setq cmd_lst (append cmd_lst (@str2list line ";"))) ; Convert script
                           )
                           (setq cmd_lst (append cmd_lst (list (strcat cmd_str line)))) ; add command
                        )
                     )
                  )
                  ; else skip comment
               )

So:

zoom
e
; turn on grid
grid on
.ltscale 3.0
_layer set 0 color red 0

; extra enter to end layer
(c:myFunc "Title;message" 1)
(command "zoom" "e"); zoom extents

becomes:

("zoom" "e" "grid" "on" ".ltscale" "3.0" "_layer" "set" "0" "color" "red" "0" "" "(c:myFunc \"Title;message\" 1)" "(command \"zoom\" \"e\")")
0 Likes
Message 38 of 45

roland.r71
Collaborator
Collaborator

I think i'm getting close:

 

; --- execScr ------------------------------------------------------------------

   (defun execScr ( cmd_lst / part lsp_str scr_lst first)

      (setq lsp_str "")
      (foreach part cmd_lst
         (setq first (substr part 1 1))
         (if (= first "(")
            (progn
               (if scr_lst
                  (progn
                     (eval (apply 'command scr_lst))
                     (setq scr_lst nil)
                  )
               )
               (eval (read part))
            )
         ; else
            (setq scr_lst (append scr_lst (list part)))
         )
      )
      (if scr_lst
         (eval (apply 'command scr_lst))
      )
   )

Add this next to the setCMD function, and remove the line where it call the @numerify function from setCMD.

After calling: (setCMD 0) add: (execScr cmd_lst)

 

It will then at least correctly execute the last example I used, including script, command calls & custom lisp functions. It will not correctly execute lispcode, especially not if spread over multiple lines, but hey thats what the "Load Lisp" function is for Smiley Wink

 

The code will collect all script code parts from the cmd_lst in a list, untill it detects a (

it will then execute the collected list using (eval (apply 'command scr_lst))

and after that the found "command" part using (eval (read part))

It will continue like this for the entire cmd_lst.

...but it needs thorough testing with some serious script & command calls

At least it works perfectly with all of this:

zoom
e
layer
s
0

(c:myFunc "test" 1)
^c^czoom;e;audit;y;-purge;a;;n;(alert "Ready")
(command "zoom" "e")
(princ "\nDone")
0 Likes
Message 39 of 45

roland.r71
Collaborator
Collaborator

I've been looking around for some good script examples to test with, but came up with non. Just basic tutorials doing basic stuff.

 

So I made me a mix of my own, using some of the more common things, in a few different ways. Using text & numbers. Settings vars, getting vars, calling lisp functions. threw in some comments. etc.

(setq fdia 'FILEDIA) ; set a lisp variable
;
; set env.var.
filedia
0
; zoom extend on 1 line
zoom e
(command "_circle" "100,100" "100") ; command version
; set a layer
layer s 0
; empty line to end layer
; call lisp function with arguments. string & int. (c:myFunc "test" 1) ; circle, each arg. a line _circle 60,120 15 ; a macro ^c^czoom;e;audit;y;-purge;a;;n;(alert "Cleaned up") ; another circle, 1 line _circle 140,120 15 ; an arc to finnish it up :) _arc 50,70 100,40 150,70 (command "zoom" "w" "-50,250" "250,-50") ; ; reset env.var (setvar 'filedia fdia) (princ "\nDone")

With my current code it all works. Haven't found a need for the @numerify function yet. tried changing all numbers ones, even that didn't matter. Not even for the layer name "0".

What doesn't work, is fullblown lisp (but it doesn't have to)

& I don't have a clue (yet) how to catch any errors here.

If I forget to load c:myFunc, for example, It stops right there, with an error.

 

Here's the code used sofar: (should run solo as it is here, WITH a .scr file containing the above example:)

(setq cmdfile  "c:/Lisp/MFT/DATA/MFT.scr")

; --- setCMD -------------------------------------------------------------------

   (defun setCMD ( box / file_w i lnnr line first eolc)

      ; sets cmd_var = complete file, for editing. Holds the original string including macro's & comments
      ; sets cmd_str = string for script preview in main dialog, macro style using ;
      ; sets cmd_lst = list of code used for eval (stripped of comments, macro's converted

      (if (= box 1)
         (progn
            (setq cmd_var (cond ((DOS_EDITBOX "Script editor" "Enter AutoCAD commands and/or lisp code" cmd_var))(cmd_var)))
            (if cmd_var
               (progn
                  (setq file_w (open cmdfile "w"))
                  (write-line cmd_var file_w)
                  (close file_w)
               )
            )
         )
      )
      (setq cmd_str "" cmd_lst nil)
      (if (findfile cmdfile)
         (progn
            (setq file_r  (open cmdfile "r")
                  lnnr    1
                  cmd_var ""
            )
            (while (setq line (read-line file_r))
               (if (= lnnr 1)
                  (setq cmd_var (strcat cmd_var line))
                  (setq cmd_var (strcat cmd_var "\n" line))
               )
               (setq line (vl-string-left-trim " " line))
               (setq lnnr 2)
               (setq first (substr line 1 1))
               (if (/= first ";")
                  (if (= first "^")
                     (setq cmd_lst (append cmd_lst (@str2list (substr line 5) ";"))) ; Convert macro to script
                  ; else...
                     (progn
                        (if (setq eolc (vl-string-position (ascii ";") line 0 T))
                           (if (< (vl-string-position (ascii "\"") line 0 T) eolc)
                              (setq line (substr line 1 eolc)) ; cut comment from rear
                           )
                        )
                        (if (and (/= (substr line 1 1) "(")(/= (substr line 1 1) "(")) ; if it doesn't start with a parenthesis
                           (progn
                              (while (vl-string-position (ascii " ") line)
                                 (setq line (vl-string-subst ";" " " line)) ; replace each space with ;
                              )
                              (setq cmd_lst (append cmd_lst (@str2list line ";"))) ; Convert script
                           )
                           (setq cmd_lst (append cmd_lst (list (strcat cmd_str line)))) ; add command
                        )
                     )
                  )
                  ; else skip comment
               )
            )
            (close file_r)
            (setq lnnr 1)
            (if cmd_lst
               (foreach line cmd_lst
                  (if (= lnnr 1)
                     (setq cmd_str (strcat cmd_str line))
                     (setq cmd_str (strcat cmd_str ";" line))
                  )
                  (setq lnnr 2)
               )
            )
         )
      )
   )

; --- @str2list ----------------------------------------------------------------

   (defun @str2list (str pat / i j n lst)
      ; By John Uhden
      ; Function to convert a string with delimiters into a list
      ; pat is the delimiter and can contain multiple characters
      (cond
         ((/= (type str)(type pat) 'STR))
         ((= str pat)'(""))
         (T
            (setq i 0 n (strlen pat))
            (while (setq j (vl-string-search pat str i))
               (setq lst (cons (substr str (1+ i)(- j i)) lst)
                     i (+ j n)
               )
            )
            (reverse (cons (substr str (1+ i)) lst))
         )
      )
   )

; --- @numerify ----------------------------------------------------------------

   (defun @numerify (str)
      ; By John Uhden
      ; example: (mapcar '@numerify '("kjsdlkj" "#123" "#23'4.5" "#245.67" "#pi"))
      ;          ("kjsdlkj" 123 280.5 245.67 PI)
   (cond
      ((wcmatch str "`##,`###,`####,`######")
         (atoi (substr str 2))
      )
      ((wcmatch str "`##'*,`###'*,`####'*,`#####'*")
         (distof (substr str 2) 4)
      )
      ((wcmatch str "`##*")
         (distof (substr str 2))
      )
      ((= (strcase str) "#PI")
         (read (substr str 2))
      )
         (1 str)
      )
   )

; --- execScr ------------------------------------------------------------------

   (defun execScr ( cmd_lst / part lsp_str scr_lst first)

      (setq lsp_str "")
      (foreach part cmd_lst
         (setq first (substr part 1 1))
         (if (= first "(")
            (progn
               (if scr_lst
                  (progn
                     (eval (apply 'command (mapcar '@numerify scr_lst)))
                     (setq scr_lst nil)
                  )
               )
               (eval (read part))
            )
         ; else
            (setq scr_lst (append scr_lst (list part)))
         )
      )
      (if scr_lst
            (eval (apply 'command (mapcar '@numerify scr_lst))) 
)
)
; ------------------------------------------------------------------------------
(setCMD 0)
(execScr cmd_lst)

 

0 Likes
Message 40 of 45

john.uhden
Mentor
Mentor
I am actually upset that I don't have time to look at it for a while. We
are preparing to leave Friday for a Maine vacation for a week. I am
supposed to not even bring my laptop (but I'll hide it in my baggage).

Glad to see your progress.

John F. Uhden

0 Likes