Autolisp: "While" loop with .dxf output - dxf

I'm writing a lisp app to create 2D patterns for a CNC cutter. I have an Excel program that outputs 3-10 short lisp subroutines for individual pieces associated with each pattern, and I can get any number of these subs to load inside my "while" loop and draw the piece, but none of the methods I can get working to create .dxf files from the drawings generated by one of the pattern subs will let me resume the loop. If I pull out the dxfout and erase steps and just let them all draw on top of one another, it works like a charm.
(defun c:CreateDXF (/ owd ofd sdate)
(setq owd (acet-file-cwd))
(acet-file-chdir "P:\\")
(setq ofd (getvar "filedia"))
(setvar "filedia" 0)
(setq sdate (getstring "\nEnter the order start date in YYYYMMDD format: "))
(setq fpath (strcat "P:\\LSP\\" sdate))
(setq wopath (acet-ui-pickdir "Select Work Order Folder" fpath))
(setq flist (vl-directory-files wopath "*.lsp" 1))
(while (> (length flist) 0)
(setq fname (car flist))
(setq wfile (strcat wopath "\\" fname))
(vl-file-rename wfile "P:\\LSP\\temp.lsp")
(load "P:\\LSP\\temp.lsp")
(vl-file-rename "P:\\LSP\\temp.lsp" wfile)
(setq savepath (strcat "P:\\DXF\\" sdate))
(setq savename (strcat (vl-filename-base wfile) ".dxf"))
(setq patt (ssget "x"))
(command "_.dxfout" savepath "_Objects" patt "" "_Version" "2013" "16" "" "")
(command "erase" "all" "")
(setq flist (cdr flist))
);while
(acet-file-chdir owd)
(setvar "filedia" ofd)
(alert "Done")
(princ)
);defun
Running this as written hangs up at the first dxfout step. I can reset the program through the lisp editor, and then file appears in the output directory and the dxf comes up in a new tab, but obviously that exits the loop without processing the rest of the pieces.
Am I missing something obvious? Would it be better to draw all of the pattern pieces in the same drawing as named entities and then batch out each entity at the end?

It might be easier to approach this from outside of AutoCAD. Maybe using a PowerShell script to fetch the .dwg files list, open AutoCAD, then loop through the drawing list, export the .dxf, close the .dwg and do the next, then close AutoCAD.

Related

Input/Output json file in lisp

Good morning everyone,
To finish this project i needs your help, again.
So now i'm trying to create two functions to reading/writing files in lisp.
this is the description how the functions must work
(json-load filename) -> JSON
(json-write JSON filename) -> filename
The json-load function opens the file filename returns a JSON object (or generates an error). If
filename does not exist the function generates an error. The suggestion is to read the whole file in one
string and then to call json-parse.
The json-write function writes the JSON object to the filename file in JSON syntax. If
filename does not exist, it is created and if it exists it is overwritten. Of course it is expected that
CL-PROMPT> (json-load (json-write '(json-obj # | stuff | #) "foo.json"))
(json-obj # | stuff | #)
this is my json-load function
(defun json-load (filename)
(with-open-file (file-stream filename)
(let ((file-contents (make-string (file-length file-stream))))
(read-sequence file-contents file-stream)
file-contents)) (json-parse (file-contents)))
but it not working
i need some help to write function too.
thanks guys
edit 1:
(defun json-load (filename)
(with-open-file (in filename
:direction :input
:if-does-not-exist :error)
(file-get-contents filename))
(json-parse filename))
(defun file-get-contents (filename)
(with-open-file (stream filename)
(let ((contents (make-string (file-length stream))))
(read-sequence contents stream)
contents)))
so the function should be not far away to be correct but the problem, i think, is the file-get-contents function.
I think that because if i run this function the output is
"\"{\\\"nome\\\" : \\\"Arthur\\\",\\\"cognome\\\" : \\\"Dent\\\"}\""
and so the json-parse does not recognize json-object anymore.
Any ideas?
edit 2:
i try both functions but with the same result. if i call json-parse with the same json-object in the file it's all right but if i call json-load lisp respond me with my own error message "undefined JSON object (json-parse)".
Why?
Edit 3:
This is json-write function but, for now, it doesn't work.
(defun json-write (json filename)
(with-open-file (out filename
:direction :output
:if-exists :overwrite
:if-does-not-exist :create)
(pprint (json out))))
so the description at the beginning of the post says that the json-write function writes JSON object to the filename file in JSON syntax.
Now, 2 questions
1) it's my function partially correct?
2) how can i write a Json object in Json syntax?
Thanks
I'm working on the same project, hopefully the professors don't mind us sharing info ;)
This is the approach I took:
(defun json-load (filename)
(with-open-file (in filename
:direction :input
:if-does-not-exist :error)
(multiple-value-bind (s) (make-string (file-length in))
(read-sequence s in)
(json-parse s))))
Remember that read-sequence overwrites the given sequence, in this case s. I'm using multiple-value-bind simply so that I don't have to use neither variable declarations nor a lambda function (Although it is just a less idiomatic version of (let ((v form)) ...)), as #tfb pointed out).

Open .htm .html files automatically with shr.el in emacs

I've just discovered the shr package in emacs 24.5.1
i.e.
C-x C-f anyfile.html
M-x shr-render-buffer
Looks really good - just what I was after
Can I automate emacs to call shr-render-buffer when I open any .htm or .html file?
UPDATE
I've tried adding the following to my .emacs:
(add-to-list 'auto-mode-alist '("[.]htm$" . shr-render-buffer))
(add-to-list 'auto-mode-alist '("[.]html$" . shr-render-buffer))
but I get the error:
File mode specification error: (void-function shr-render-buffer)
The html file then gets opened in Fundamental mode and it looks even worse than HTML mode
It seems you want to run the function shr-render-buffer automatically once a html file is opened. As you said, the mode for .htm/.html is html-mode by default, you can add the function invocation to the html-mode-hook, such as:
(add-hook 'html-mode-hook '(lambda() (shr-render-buffer (current-buffer))))
As #lawlist pointed, put it after (require 'shr).
As this is emacs, the hardest part of doing what you want is deciding on what is the best approach. This largely depends on personal taste/workflows. I would highly recommend looking at the browse-url package in more detail. One thing I use is a function which allows me to switch between using eww or my default system browser - this means I can easily render web content either in emacs or in chrome/safari/whatever.
Some years ago, I wrote a utility which would allow me to view a number of different file formats, including rendered html, in emacs. I rarely use this now as doc-view has pretty much replaced most of this functionality and is much better. However, it does show how you can use defadvice to modify the view-file function so that id does different things depending on the file type. Note that as this is old emacs code and emacs has improved, there are probably better ways of doing this now. I also know that the 'advice' stuff has been re-worked, but this legacy stuff still works OK. Should get you started. Note that the functionality for MS doc, docx, pdf etc relies on external executables.
My preferred workflow would be to write a function which allows me to reset the browse-url-browser-function to either eww-browse-url or browse-url-default-browser and bind that to a key. I can then choose to display the html in emacs or the external browser and leverage of all the work already done in browse-url.
(require 'custom)
(require 'browse-url)
;; make-temp-file is part of apel prior to emacs 22
;;(static-when (= emacs-major-version 21)
;; (require 'poe))
(defgroup txutils nil
"Customize group for txutils."
:prefix "txutils-"
:group 'External)
(defcustom txutils-convert-alist
'( ;; MS Word
("\\.\\(?:DOC\\|doc\\)$" doc "/usr/bin/wvText" nil nil nil nil nil)
;; PDF
("\\.\\(?:PDF\\|pdf\\)$" pdf "/usr/bin/pdftotext" nil nil nil nil nil)
;; PostScript
("\\.\\(?:PS\\|ps\\)$" ps "/usr/bin/pstotext" "-output" t nil nil nil)
;; MS PowerPoint
("\\.\\(?:PPT\\|ppt\\)$" ppt "/usr/bin/ppthtml" nil nil nil t t))
"*Association for program convertion.
Each element has the following form:
(REGEXP SYMBOL CONVERTER SWITCHES INVERT REDIRECT-INPUT REDIRECT-OUTPUT HTML-OUTPUT)
Where:
REGEXP is a regexp to match file type to convert.
SYMBOL is a symbol to designate the fyle type.
CONVERTER is a program to convert the fyle type to text or HTML.
SWITCHES is a string which gives command line switches for the conversion
program. Nil means there are no switches needed.
INVERT indicates if input and output program option is to be
inverted or not. Non-nil means to invert, that is, output
option first then input option. Nil means do not invert,
that is, input option first then output option.
REDIRECT-INPUT indicates to use < to direct input from the input
file. This is useful for utilities which accept input
from stdin rather than a file.
REDIRECT-OUTPUT indicates to use > to direct output to the output
file. This is useful for utilities that only send output to
stdout.
HTML-OUTPUT Indicates the conversion program creates HTML output
rather than plain text."
:type '(repeat
(list :tag "Convertion"
(regexp :tag "File Type Regexp")
(symbol :tag "File Type Symbol")
(string :tag "Converter")
(choice :menu-tag "Output Option"
:tag "Output Option"
(const :tag "None" nil)
string)
(boolean :tag "Invert I/O Option")
(boolean :tag "Redirect Standard Input")
(boolean :tag "Redirect Standard Output")
(boolean :tag "HTML Output")))
:group 'txutils)
(defun txutils-run-command (cmd &optional output-buffer)
"Execute shell command with arguments, putting output in buffer."
(= 0 (shell-command cmd (if output-buffer
output-buffer
"*txutils-output*")
(if output-buffer
"*txutils-output*"))))
(defun txutils-quote-expand-file-name (file-name)
"Expand file name and quote special chars if required."
(shell-quote-argument (expand-file-name file-name)))
(defun txutils-file-alist (file-name)
"Return alist associated with file of this type."
(let ((al txutils-convert-alist))
(while (and al
(not (string-match (caar al) file-name)))
(setq al (cdr al)))
(if al
(cdar al)
nil)))
(defun txutils-make-temp-name (orig-name type-alist)
"Create a temp file name from original file name"
(make-temp-file (file-name-sans-extension
(file-name-nondirectory orig-name)) nil
(if (nth 7 type-alist)
".html"
".txt")))
(defun txutils-build-cmd (input-file output-file type-alist)
"Create the command string from conversion alist."
(let ((f1 (if (nth 3 type-alist)
output-file
input-file))
(f2 (if (nth 3 type-alist)
input-file
output-file)))
(concat
(nth 1 type-alist)
(if (nth 2 type-alist) ; Add cmd line switches
(concat " " (nth 2 type-alist)))
(if (nth 4 type-alist) ; redirect input (which may be output
(concat " < " f1) ; if arguments are inverted!)
(concat " " f1))
(if (nth 5 type-alist) ; redirect output (see above comment)
(concat " > " f2)
(concat " " f2)))))
(defun txutils-do-file-conversion (file-name)
"Based on file extension, convert file to text. Return name of text file"
(interactive "fFile to convert: ")
(let ((f-alist (txutils-file-alist file-name))
output-file)
(when f-alist
(message "Performing file conversion for %s." file-name)
(setq output-file (txutils-make-temp-name file-name f-alist))
(message "Command: %s" (txutils-build-cmd file-name output-file f-alist))
(if (txutils-run-command
(txutils-build-cmd (txutils-quote-expand-file-name file-name)
(txutils-quote-expand-file-name
output-file) f-alist))
output-file
file-name))))
(defadvice view-file (around txutils pre act comp)
"Perform file conversion or call web browser to view contents of file."
(let ((file-arg (ad-get-arg 0)))
(if (txutils-file-alist file-arg)
(ad-set-arg 0 (txutils-do-file-conversion file-arg)))
(if (string-match "\\.\\(?:HTML?\\|html?\\)$" (ad-get-arg 0))
(browse-url-of-file (ad-get-arg 0))
ad-do-it)))
(provide 'init-text-convert)

Fast CSV reading in Common Lisp

What is the fastest way to read a csv file in CL in a way such that:
1) all fields in the first line go into one array called column-names
2) the first field of each of all following lines goes into another
array called row-names
3) all other fields go into another array called values
?
My file has the following form, just with a lot more columns and rows:
"";"ES1 Index";"VG1 Index";"TY1 Comdty";"RX1 Comdty";"GC1 Comdty"
"1999-01-04";1391.12;3034.53;66.515625;86.2;441.39
"1999-01-05";1404.86;3072.41;66.3125;86.17;440.63
"1999-01-06";1435.12;3156.59;66.4375;86.32;441.7
"1999-01-07";1432.32;3106.08;66.25;86.22;447.67
And the result I would like is:
#("1999-01-04" "1999-01-05" "1999-01-06" "1999-01-07" )
#("" "ES1 Index" "VG1 Index" "TY1 Comdty" "RX1 Comdty" "GC1 Comdty")
#(1391.12 3034.53 66.515625 86.2 441.39 1404.86 3072.41 66.3125 86.17 440.63
1435.12 3156.59 66.4375 86.32 441.7 1432.32 3106.08 66.25 86.22 447.67)
Are you aware of some CL library that does so already?
Are there any general issues regarding I/O performance, maybe compiler-specific, that I should be aware of?
Here is the way I am doing it now:
(with-open-file (stream "my-file.csv" :direction :input)
(let* ((header (read-line stream nil))
(columns-list (mapcar #'read-from-string
(cl-ppcre:split ";" header)))
(number-of-columns (length columns-list))
(column-names (make-array number-of-columns
:initial-contents columns-list))
(rownames (make-array 1 :adjustable t :fill-pointer 0))
(values (make-array 1 :adjustable t :fill-pointer 0)))
(set-syntax-from-char #\; #\ )
(loop
:for reader = (read stream nil stream)
:until (eq reader stream)
:do (progn (vector-push-extend reader row-names)
(loop
:for count :from 2 :upto number-of-columns
:do (vector-push-extend (read stream nil)
values)))
:finally (return (values row-names
column-names
values)))))
Note: I wouldn't use set-syntax-from-char in real code, I am using it just for the sake of this example.
I suspect that the I/O is the slowest part here. You can probably get faster I/O if you use READ-SEQUENCE rather than calling READ-LINE repeatedly. So your code might look something like this:
(with-open-file (s "my-file.csv")
(let* ((len (file-length s))
(data (make-array len)))
(read-sequence data s)
data))
Then split data by newlines and add your logic.
Whether that helps or not, it'd helpful for you to profile your code, e.g. with :sb-sprof, to see where most of the time is being spent.
To read csv files, I find very useful and fast the cl-csv package (https://github.com/AccelerationNet/cl-csv). For instance, to solve your problem, the following code could be used:
(let ((data (cl-csv:read-csv #P"my-file.csv" :separator #\;)))
(values (apply #'vector (first data))
(apply #'vector (rest (mapcar #'first data)))
(apply #'vector
(mapcar #'read-from-string (loop :for row :in (rest data)
:append (rest row))))))
cl-csv:read-csv returns a list contaning, for each row, a list of strings that are the contents of the cells.

AutoLISP program giving inconsistent results

Software: AutoCAD 2012 (Japanese language)
System: MS Windows 7 (Japanese language)
The Situation
I have made a .LSP file which defines a new function "C:MAKEATABLE".
It asks the user to select a .CSV file and then it imports its data to the drawing file opened in AutoCAD. The .CSV file has two columns:
Serial number (integer)
Data (real number)
The last line of the .CSV file is "EOF".
The data imported should be such that each text entity is independent of the other, except for the fact that they are arranged in a tabular manner from the insertion point specified by the user.
Now, my problem is that whenever I load that .LSP file and call my function, the result is not always the same.
Sometimes the values appear perfectly in the desired manner; but many times the values appear jumbled up. There are three types of ways in which the values appear jumbled up.
I tried restarting the application as well as the PC, but to no
avail.
I have declared all the variables to be local, so that they are not
interfered with.
Here is the complete code: Pastebin link also available
(defun c:makeatable ( / filename csvfile startpt data rdline digits lineno currentpt datapt datarelpt baserelpt indexbasept temp2 temp3 temp4 temp5 wide1 wide2 rowheight txtheight tempvar tmps)
(setq filename (getfiled "Data File" "C:/Temp/drawings/" "csv" 128)) ;prompt user to open the file containing the data
(setq csvfile (open filename "r")) ;open the file to read its contents
(setq startpt (getpoint "\n Table insertion point: ")) ;prompt user to choose the insertion point
;------------;prompt user to input the parameters; if nil, default value is set;--------------------------;
;
;** This code is useful when default values are needed, so that the user doesn't have to enter them. **;
;** If the values appear jumbled, kindly run the program again with appropriate values. **;
;** **;
(initget (+ 2 4))
(if (not(setq txtheight (getreal "\n Enter Text height: ")))
(setq txtheight 4.0)
)
(princ)
(initget (+ 2 4))
(if (not(setq wide1 (getreal "\n Enter first column width: ")))
(setq wide1 15.0)
)
(princ)
(initget (+ 2 4))
(if (not(setq wide2 (getreal "\n Enter second column width: ")))
(setq wide2 30.0)
)
(princ)
(initget (+ 2 4))
(if (not(setq rowheight (getreal "\n Enter Row height: ")))
(setq rowheight 7.0)
)
(princ)
;----------------------------------------------------------------------------------------------------------;
(setq lineno 1) ;this var stores the line at which the program is currently at
(setq digits 0) ;this var stores the (number of digits - 1) of the index
(setq currentpt startpt) ;initialize currentpt
;-------*------temporary variables for the arrangement of the data------*-------;
(setq temp2 (/ (+ rowheight txtheight) 2))
(setq temp3 (+ wide1 (* txtheight 2)))
(setq temp4 (+ wide1 (/ wide2 5)))
(setq temp5 (- wide1 (/ wide1 20)))
(setq tempvar (list 0 (* -1 rowheight) 0))
;-------------------------------------------------------------------------------;
(setq datarelpt (list temp4 temp2 0)) ;these are relative points;
(setq baserelpt (list temp5 temp2 0))
;------------------------------;while loop;-------------------------------------;
(while (/= rdline "EOF")
(cond ((> lineno 9) ;check the number of ;
(setq digits 1) ;digits in the index ;
)
((> lineno 99)
(setq digits 2)
)
);end cond
(setq datapt (mapcar '+ ;these lines ;
currentpt ;set the coordinates ;
datarelpt ;for the data ;
) ; ;
)
(setq indexbasept (mapcar '+ ;these lines ;
currentpt ;set the coordinates ;
baserelpt ;for the index ;
) ; ;
)
(setq rdline (read-line csvfile)) ;read a line from the CSV file
(setq data (substr rdline (+ 3 digits)));extract the data from the read line
(setq tmp (command "STYLE" "MONO" "MONOTXT" "" "" "" "" "" "")) ;makes the text monospace
;-----------------------------printing the values-----------------------;
(command "text" datapt txtheight 0 data) ;write the data
(command "text" "_j" "_r" indexbasept txtheight 0 lineno) ;write the index number
;-------------------------------------------------------------------;
(setq lineno (1+ lineno)) ;increment line number
(setq currentpt (mapcar '+ ;increment the ;
currentpt ;current point ;
tempvar ;coordinates ;
) ; ;
)
)
;------------------------------;while loop ends;------------------------------------;
(entdel (entlast)) ;to remove the extra index number printed at the end
(close csvfile) ;close the opened file
(princ) ;clean exit
)
I even checked the points at which the text are being inserted [using (princ datapt) and (princ indexbasept)], and found them to be alright. However, when AutoCAD creates these text objects on the screen, they occupy the same position and get jumbled up.
Kindly tell me where i might be going wrong and what should I do now.
My first thought is that you need to turn off osnaps before running your command.
(setq orig-osm (getvar "osmode"))
(command "osmode" 0)
... the rest of your command ...
(command "osmode" orig-osm)
Depending on your constraints I think you could also generate the table columns as mtext (multi-line text) and explode them each after creation to create separate text objects. You can use (ssget "L") to operate on the last element added to the database.

ELISP: Function to prompt user for number and ask user for that number of strings and insert then into list

I'd like to create an ELISP function that will prompt a user for a number, n, then continually prompt the user n times for strings. Ideally, I'd like all these string put into a list. Here's what I have so far. Obviously, what I have doesn't work, but it might help clarify the type of thing I want to do.
(defun prompt-user-n-times (n)
"Prompt user n time for strings and append strings to list"
(interactive "nHow many strings: ")
(while (> n 0)
(append newlist (interactive "sGive me input: "))
(setq n (- n 1))
))
Thanks.
just define a binding for your new list:
(defun prompt-user-n-times (n)
"Prompt user n time for strings and append strings to list"
(interactive "nHow many strings: ")
(let ((newlist ()))
(while (> n 0)
(setq newlist (append newlist (list (read-string "Give me input: "))))
(setq n (- n 1)))
newlist))
Several remark: interactive is only at beginning of a defun, in the
function, one use other prompt function, like the simple
read-string. append ask for two list, so the string returned
by read-string should be put into a list by the list function