Macro or Lisp that takes selected text, makes a new layer with the same name as selected text and then puts selected text on that new layer

Macro or Lisp that takes selected text, makes a new layer with the same name as selected text and then puts selected text on that new layer

frederick_gillis
Explorer Explorer
712 Views
19 Replies
Message 1 of 20

Macro or Lisp that takes selected text, makes a new layer with the same name as selected text and then puts selected text on that new layer

frederick_gillis
Explorer
Explorer

Completely new to the idea of Lisp in general, just realizing now that it’s a thing. Wondering if it’s possible to do this - create a macro or Lisp that takes selected text, makes a new layer with the same name as selected text and then puts selected text on that new layer. Any help would be appreciated, even pointing me in the right direction to learn how to do it myself, thank you!

0 Likes
Accepted solutions (1)
713 Views
19 Replies
Replies (19)
Message 2 of 20

patrick-emin
Collaborator
Collaborator
Accepted solution

Hi, here you are:

 

(defun c:Text2Layer ( / oldcmdecho ss i ent edata txt layName )
  (vl-load-com)
  
  ;; Store current command echo setting and turn it off for cleaner execution
  (setq oldcmdecho (getvar "CMDECHO"))
  (setvar "CMDECHO" 0)
  
  (prompt "\nSelect TEXT or MTEXT to create layers from: ")
  
  ;; Only allow selection of Text or MText objects
  (if (setq ss (ssget '((0 . "TEXT,MTEXT"))))
    (progn
      (setq i 0)
      
      ;; Loop through all selected text objects
      (while (< i (sslength ss))
        (setq ent (ssname ss i))
        (setq edata (entget ent))
        
        ;; Extract the text string (DXF group code 1)
        (setq txt (cdr (assoc 1 edata)))
        
        ;; Clean up text for valid layer name 
        ;; Replaces illegal AutoCAD characters with an underscore
        (setq layName (vl-string-translate "<>/\\\":;?*|=''," "______________" txt))
        
        ;; Check if the layer already exists
        (if (not (tblsearch "layer" layName))
          ;; If it doesn't exist, make it
          (command "._-layer" "_make" layName "")
        )
        
        ;; Move the entity to the new layer (Modify DXF group code 8)
        (setq edata (subst (cons 8 layName) (assoc 8 edata) edata))
        (entmod edata)
        
        (setq i (1+ i))
      )
      (princ (strcat "\nSuccess! Processed " (itoa (sslength ss)) " text object(s)."))
    )
    (princ "\nNo text selected.")
  )
  
  ;; Restore original command echo setting
  (setvar "CMDECHO" oldcmdecho)
  (princ)
)
Message 3 of 20

Kent1Cooper
Consultant
Consultant

....       
        ;; Check if the layer already exists
        (if (not (tblsearch "layer" layName))
          ;; If it doesn't exist, make it
          (command "._-layer" "_make" layName "")
        )
....

A suggestion:

That approach, using the Make option in the LAYER command, will make each Layer current in the process.  If doing that, I would suggest that you also save the initial current Layer [the CLAYER System Variable] at the beginning, and reset it at the end, as the routine does with CMDECHO.

But instead, I recommend that you use the New option in the LAYER command, rather than Make.  Then the current Layer is not changed, but the New Layer will still be available to put the Text/Mtext on.  No need to save the initial current Layer and restore it later, because the initial current Layer will remain current throughout.

Also, using the Make option that causes the Layer to become current will run into trouble if the Layer already exists but is Frozen, because a Frozen Layer cannot be made current, so you should include a Thaw option on the Layer name first.  With the New option, that is not a problem, unless you don't want to see the Text/Mtext disappear when it's moved to the frozen Layer.

And while we're in that part of the routine, you really don't need to test whether the Layer exists.  You can just do the LAYER command part [whether with the Make or New option], without its being an argument within an (if) function, and it won't matter if the Layer already exists.  It will not cause any error -- the command will just go back to the main prompt again [to be concluded with the final Enter ""].

Kent Cooper, AIA
Message 4 of 20

scot-65
Advisor
Advisor

Message 2 looks like AI generated. Shame on you.

There is a good chance the new layer's color will not be to your liking.

 

You can try to entmake the layer:

 (entmake (list (cons 0 "LAYER")
  (cons 100 "AcDbSymbolTableRecord")
  (cons 100 "AcDbLayerTableRecord")
  (cons 2  Name as "string")
  (cons 70 0)
  (cons 62 Color as "string")
  (cons 6 Linetype as "string")
  (cons 290 Plt)
  (cons 370 LineWeight)
 ))

Some items can be left out except the first 3.

Study up on the DFX codes. They will be useful down the road.

 


Scot-65
A gift of extraordinary Common Sense does not require an Acronym Suffix to be added to my given name.

Message 5 of 20

patrick-emin
Collaborator
Collaborator

Hello. Yes, thank you for these comments, which help improve the process. As with every AutoLISP programming request, especially when it comes from people who are unfamiliar with the language, we provide a draft version that is then discussed and refined based on feedback from the original requester. These discussions help requesters improve their skills in this area.

Message 6 of 20

ronjonp
Mentor
Mentor

@frederick_gillis Here's a quick VLA variant:

(defun c:foo (/ lyrs o txt s)
  (cond	((setq s (ssget ":L" '((0 . "*TEXT"))))
	 (setq lyrs (vla-get-layers (vla-get-activedocument (vlax-get-acad-object))))
	 (foreach e (vl-remove-if 'listp (mapcar 'cadr (ssnamex s)))
	   (setq txt (vla-get-textstring (setq o (vlax-ename->vla-object e))))
	   (if (eq 'vla-object (type (vl-catch-all-apply 'vla-add (list lyrs txt))))
	     (vla-put-layer o txt)
	   )
	 )
	)
  )
  (princ)
)
Message 7 of 20

paullimapa
Mentor
Mentor

Just a couple of additional comments:

(1) it's possible especially with MText that contain multiple lines of word wrapped content that the text string is now separated into two dxf group codes:

dxf group code 3 and dxf group code of 1

To avoid having to deal with the possibility of this I would instead of using this line of code:

(setq txt (cdr (assoc 1 edata)))

I would use these lines of code to retrieve the contents for MText vs Text:

(if (eq (getpropertyvalue ent "LocalizedName") "MText")
 (setq txt (getpropertyvalue ent "Contents"))
 (setq txt (cdr (assoc 1 edata)))
)

(2) If the total characters from the text string retrieved is greater than 255, then the routine will fail to create the layer.

So after cleaning up text for valid layer name you may want to add these lines of code to check for this:

(if (> (strlen layName) 255)(setq layName (substr layName 1 255)))

 


Paul Li
IT Specialist
@The Office
Apps & Publications | Video Demos
Message 8 of 20

paullimapa
Mentor
Mentor

Nice and short...but I would add check to make sure length of textstring is not greater than 255.

Though AutoCAD will still allow your code to create the layer that is longer, I would avoid having to deal with possible future drawing database problems with layer names that are longer than what is allowed.


Paul Li
IT Specialist
@The Office
Apps & Publications | Video Demos
Message 9 of 20

navya_gelli
Autodesk
Autodesk

Hello @frederick_gillis,
 

Welcome to the community!

 

Have any of the suggestions provided in this thread helped resolve your issue? If so, please mark the helpful response as the solution. If you’re still facing the same problem, reply to this thread with an update so the community can assist you further.

Navya | Community Manager
Message 10 of 20

Kent1Cooper
Consultant
Consultant

@paullimapa wrote:

.... it's possible especially with MText that contain multiple lines of word wrapped content that the text string is now separated into two dxf group codes:

dxf group code 3 and dxf group code of 1

.... check to make sure length of textstring is not greater than 255. ....


The DXF code 1 & 3 thing is independent of word wrapping.  It's just about total characters in the text content -- beyond 250 it brings in a 3 entry for each clump of 250 characters, with the leftovers going in the 1 entry.  However, @frederick_gillis, can you enlighten us on the circumstances under which you need to do this?  I can't imagine a reason to make a Layer whose name is extracted text content of hundreds of characters, even if a Layer name of up to 255 is "allowed."

Kent Cooper, AIA
Message 11 of 20

paullimapa
Mentor
Mentor

I think we need to be especially careful with MText because the special formatting and the new lines are all carried out using additional characters which all add up to the 250 character limit causing the remainder to go towards dxf 3. 


Paul Li
IT Specialist
@The Office
Apps & Publications | Video Demos
Message 12 of 20

komondormrex
Mentor
Mentor

@frederick_gillis 

For unformatted texts only

(defun c:movt (/ text contents)
	(setq text (car (entsel "\nPick text: ")))
	(setq contents (cdr (assoc 1 (entget text))))
	(if (snvalid contents) 
		(entmod (append (entget text) (list (cons 8 (cdr (assoc 1 (entget text)))))))
		(alert (strcat "Incorrect name for layer: \"" contents "\""))
	)
	(princ)
)

 

Message 13 of 20

DGCSCAD
Advisor
Advisor

So, today I learned about snvalid. Nice. Thanks Komondormrex.

 

I think if you run that ^ contents var through Strip MText or LM:Unformat you'll have a solid solution to the OP's query.

AutoCad 2018 (full)
Win 11 Pro
Message 14 of 20

paullimapa
Mentor
Mentor

Or to get contents of MText without any special formatting:

(setq contents (getpropertyvalue text "Text"))

 


Paul Li
IT Specialist
@The Office
Apps & Publications | Video Demos
Message 15 of 20

DGCSCAD
Advisor
Advisor

Testing...

 

Using MText with some formatting:

(1 . "TESTING SOME {\\C5;(JHSDBFJVHDB)}\\PAND THEN {\\C3;SUB1} AND {\\C3;SUB2}")

 

Command: (setq text (car (entsel "\nPick text: ")))

Pick text: <Entity name: 29536b3edb0>

Command: (setq contents (getpropertyvalue text "Text"))
"TESTING SOME (JHSDBFJVHDB)\r\nAND THEN SUB1 AND SUB2"

Command: (snvalid contents)
nil

 

...and then:

Command: (setq text (car (entsel "\nPick text: ")))

Pick text: <Entity name: 29536b3edb0>

Command: (setq contents (cdr (assoc 1 (entget text))))
"TESTING SOME {\\C5;(JHSDBFJVHDB)}\\PAND THEN {\\C3;SUB1} AND {\\C3;SUB2}"

Command: (setq contents (LM:UnFormat contents nil))
"TESTING SOME (JHSDBFJVHDB) AND THEN SUB1 AND SUB2"

Command: (snvalid contents)
T

 

Looks like getpropertyvalue is carrying some formatting with it.

 

AutoCad 2018 (full)
Win 11 Pro
Message 16 of 20

paullimapa
Mentor
Mentor

You're correct. It doesn't take into account the hard returns.

I would use this function to remove them:

; aec_replace function to find & replace multiple instances
; https://www.cadtutor.net/forum/topic/68245-find-and-replace-multyple-letters/
; Arguments:
; new = new character
; old = old character
; string = string value
(defun aec_replace (new old string / pos)
  (while (setq pos (vl-string-search old string pos))
   (setq string (vl-string-subst new old string pos)
         pos    (+ pos (strlen new))
   ) ; setq
  ) ; while
  string
 )
(setq contents (aec_replace "" "\r\n" contents))

 


Paul Li
IT Specialist
@The Office
Apps & Publications | Video Demos
Message 17 of 20

frederick_gillis
Explorer
Explorer

Thanks for your suggestions everyone - I was kind of in a rush so I ended up taking the first response by @patrick-emin and adding some modifications based on further need and some of the posts I saw here afterwards.

Here is the final product that I ended up using, it could definitely be adjusted based on things I don't need - as in I'll never need to select multiple Text objects or Mtext, so that whole loop and check section is unneeded but definitely educational.

It takes the selected object, makes a new layer with the same name as the selection, moves the selection to the new layer, makes it lowercase, sets the current layer back to 0, checks to see if the existing layers are all on the allowed list, tells me otherwise, and finishes all while silencing and then restoring the cmdecho.

Thanks again for all the help everyone! Getting into lisps more and more.

 

(defun c:Text2Layer ( / oldcmdecho ss i ent edata txt layName )
  (vl-load-com)
 
  ;; Store current command echo setting and turn it off for cleaner execution
  (setq oldcmdecho (getvar "CMDECHO"))
  (setvar "CMDECHO" 0)
 
  (defun to-lower (s / lst)
    (setq lst '())
    (foreach c (vl-string->list s)
      (setq lst (cons (cond
                        ((and (>= c 65) (<= c 90)) (+ c 32)) ; convert A–Z → a–z
                        (c)
                      )
                      lst))
    )
    (vl-list->string (reverse lst))
  )
  
  (prompt "\nSelect TEXT or MTEXT to create layers from: ")
  
  ;; Only allow selection of Text or MText objects
  (if (setq ss (ssget '((0 . "TEXT,MTEXT"))))
    (progn
      (setq i 0)
      
      ;; Loop through all selected text objects
      (while (< i (sslength ss))
        (setq ent (ssname ss i))
        (setq edata (entget ent))
        
        ;; Extract the text string (DXF group code 1)
        (setq txt (cdr (assoc 1 edata)))
        
        ;; Clean up text for valid layer name 
        ;; Replaces illegal AutoCAD characters with an underscore
        (setq layName (vl-string-translate "<>/\\\":;?*|=''," "______________" txt))
(setq layName (to-lower layName))
        
        ;; Check if the layer already exists
        (if (not (tblsearch "layer" layName))
          ;; If it doesn't exist, make it
          (command "._-layer" "_make" layName "")
        )
        
        ;; Move the entity to the new layer (Modify DXF group code 8)
        (setq edata (subst (cons 8 layName) (assoc 8 edata) edata))
        (entmod edata)
 
        (setq i (1+ i))
      )
      (setvar "CLAYER" "0")
      (setq allowed (list "0" "COLUMN" layName))
      (setq lst (tblnext "LAYER" T))
      (setq found nil)
 
      (while lst
        (if (not (member (cdr (assoc 2 lst)) allowed))
          (setq found T)
        )
        (setq lst (tblnext "LAYER"))
      )
 
      (if found
        (prompt "\nOther layers exist.")
        (prompt "\nOnly allowed layers found.")
      )
      (princ (strcat "\nSuccess! Processed " (itoa (sslength ss)) " text object(s)."))
    )
    (princ "\nNo text selected.")
  )
  
  ;; Restore original command echo setting
  (setvar "CMDECHO" oldcmdecho)
  (princ)
)

 

0 Likes
Message 18 of 20

paullimapa
Mentor
Mentor

FYI @Kent1Cooper  had mentioned this in another post but when entmod is used, it’ll automatically create a layer that does not exist. So there’s no need for the section of code that checks if the layer exists and makes it if it does not. 


Paul Li
IT Specialist
@The Office
Apps & Publications | Video Demos
Message 19 of 20

Kent1Cooper
Consultant
Consultant

A more direct and much shorter way to make all alphabetic characters in a string lower-case:

  (defun to-lower (s) (strcase s T))

No list to build or string to re-build from the list, no stepping through, no calculation of adjusted ASCII values, no effect on non-alphabetic characters, etc.  Read about the [which] argument in the AutoLisp Reference's entry for the (strcase) function.

Maybe shorter enough that you could just omit that sub-routine entirely, and use (strcase) directly where needed.

Kent Cooper, AIA
Message 20 of 20

frederick_gillis
Explorer
Explorer

Oh my, that'll do for sure - this is what I get for relying on copilot to help me out in a pinch, it told me to use "vl-string-lower" which AutoCAD was saying there was no function definition for, and then it wrote me that manual "to-lower" sub routine which ended up working. If I had just asked google (which I just tested) then (strcase) would have popped up immediately haha

0 Likes