Jump to content

Lisp to change words in an Autocad drawing


davidson_cesar

Recommended Posts

Hi guys! I need your help. I need a lisp to find and change words at once in a Autocad drawing. For example: I need to change the words "milk to coffe, water to wine and juice to beer". Is it possible? I'm waiting for an answer. Thanks!:D

Link to comment
Share on other sites

Very possible, try the FIND command; no need for a LISP.

Actually, if he's using 2000, FIND was nothing more than a simple LISP Express Tool.

Link to comment
Share on other sites

Here it goes ..... :)

 

(defun c:TesT (/ str NewStr ss)
 ; Tharwat 05. 03. 2011
 (if
   (and (not  (eq "" (setq str (getstring "\n Enter text to Find :"))))
          (not (eq ""  (setq NewStr (getstring "\n Enter Text to be replaced :"))))
         (setq ss (ssget "_X"
                        (list '(0 . "MTEXT,TEXT")
                              (cons 1 str)
                        )
                 )
        )
   )
    ((lambda (i / ss1 e)
       (while
         (setq ss1 (ssname ss (setq i (1+ i))))
          (setq e (entget ss1))
          (entupd
            (cdr
              (assoc -1 (entmod (subst (cons 1 NewStr) (assoc 1 e) e)))
            )
          )
       )
     )
      -1
    )
    (princ "\n No matching texts Found ")
 )
 (princ)
)

Tharwat

Link to comment
Share on other sites

Tharwat, again I'd advise against using entmod on text. It causes problems with annotative objects. But the OP is on 2000 so it shouldn't give much hassles.

 

BTW, your code would only modify text which is completely the same as the text to find. What about if the word to change is part of a sentence? You could go and change the (cons 1 str) to (cons 1 (strcat "*" str "*")), but that would simply now select all the texts containing the relevant word.

 

To replace a word inside a text string look into the vl-string-subst function. Otherwise (if you don't want the vl stuff) you'll need to step through the text and check for the word that way. But heck! As Lee's stated, the FIND thingy already does so (even if it's an Express tool in 2000) ... and it can handle capitalization unlike the above methods.

Link to comment
Share on other sites

Thanks guys for replying. But I’m thinking in something different. I’m looking for something like a multfind (serch and replace) command. For example, I would make a data tab at first with the words to find and the words to translate. And after, with just ONE command, I would translate all the words.

Example:

Find word - Translate to

milk ---------- cofee

water --------- wine

juice -----------beer

Link to comment
Share on other sites

Also, what about MText with formatting codes, and all the other objects in which text may be found: Leaders/Dimensions/Block Attributes... This seems like re-inventing the wheel big-time!

Link to comment
Share on other sites

OK then try this:

(vl-load-com)

(setq *QuickFR:Words*
      '(("milk" . "coffee")
        ("water" . "wine")
        ("juice" . "beer"))
     )
(defun str-search (pat str / p)
 (if (setq p (vl-string-search (strcase pat) (strcase str)))
   (substr str (1+ p) (strlen pat))
 )
)
(defun QuickFR (ss / obj item str found changed)
 (vlax-for obj (vla-get-ActiveSelectionSet (vla-get-activedocument (vlax-get-acad-object)))
   (setq str (vla-get-TextString obj) found nil)
   (foreach item *QuickFR:Words*
     (while (setq found (str-search (car item) str))
       (setq str (vl-string-subst (cdr item) found str) changed t)
     )
   )
   (if changed(vla-put-TextString obj str))
 )
)

(defun c:QuickFR (/ ss)
 (if (setq ss (ssget "_:L" '((0 . "TEXT,MTEXT"))))
   (QuickFR ss)
 )
 (princ)
)
(defun c:QuickFR_All (/ ss)
 (if (setq ss (ssget "_X" '((0 . "TEXT,MTEXT"))))
   (QuickFR ss)
 )
 (princ)
)

The QuickFR allows you to select the texts you want to search through, the QuickFR_All just searches throughout the entire drawing. Change the list at the top to match the old & new words (add more if you like). No need to worry about case sensitivity, since milk would match all of Milk, mILk, miLK, etc. If you want case sensitivity then change the portion looking like this:

      (while (setq found (str-search (car item) str))
       (setq str (vl-string-subst (cdr item) found str) changed t)
     )

into this:

(while (vl-string-search (car item) str)
 (setq str (vl-string-subst (cdr item) (car item) str) changed t)
)

And you can then omit the str-search defun.

Link to comment
Share on other sites

A few caveats to this (apart from Lee's very valid points). The search would find any word/portion thereof. So you can quite easily change milkrun into coffeerun - the code doesn't cater for whole words at all. Also any fields that may be linked into the text would be lost (unlike what happens with the normal FIND command).

Link to comment
Share on other sites

Irneb,

 

I would be inclined to use something like this to substitute the string:

 

(defun _StringSubst ( new old string / newlen i )
 (setq newlen (strlen new) i 0)
 (while (and (< i (strlen string)) (setq i (vl-string-search old string i)))
   (setq string (vl-string-subst new old string i) i (+ i newlen))
 )
 string
)

to account for cases such as:

 

("is" . "this")

Link to comment
Share on other sites

Thanks Lee! This was a quickie, so I didn't think it through fully. Thankfully there's some truly hawk-eyed guys around here :thumbsup:

Link to comment
Share on other sites

Tharwat, again I'd advise against using entmod on text. It causes problems with annotative objects. But the OP is on 2000 so it shouldn't give much hassles.

 

BTW, your code would only modify text which is completely the same as the text to find. What about if the word to change is part of a sentence? You could go and change the (cons 1 str) to (cons 1 (strcat "*" str "*")), but that would simply now select all the texts containing the relevant word.

 

To replace a word inside a text string look into the vl-string-subst function. Otherwise (if you don't want the vl stuff) you'll need to step through the text and check for the word that way. But heck! As Lee's stated, the FIND thingy already does so (even if it's an Express tool in 2000) ... and it can handle capitalization unlike the above methods.

 

Thank you irneb for your advise .

 

In regard to Annotative texts , I have tried my codes after you have commented it and I found it working with it and replacing the text of it.

 

And I admit that codes would replace only a complete texts that included within the searched texts.

 

Many thanks

Link to comment
Share on other sites

n regard to Annotative texts , I have tried my codes after you have commented it and I found it working with it and replacing the text of it.

Try this: Make sure your current text style is annotative. Change your CAnnoScale to something else than 1:1. Create a text containing only one of the words to be replaced. Now run your code.

 

What will happen is the entget obtains much more than just the text's value. It also gets hold of the currently displayed text height (which is incorrect when it's an Annotative Text). E.g. if your text style was set to be 2mm high and your CAnnoScale was 1:100, then (assoc 40 e) --->> (40 . 200.0). Now you use entmod even with the original. So you effectively set the text's height to 200, but since it is Annotative this gets multiplied by its scale factor and your text becomes 20000 high.

 

Edit: IMO this is a fault from ADesk's side - they didn't think annotative scales through sufficiently. This isn't the only problem with AS, but it's one which caused me to have to rework a lot of my old routines. BTW, you can still use the entmod - you just have to make sure the code 40 is converted back to the Paper Space text height for that text. Or more easily simply omit it from the list you send to entmod (it only needs the -1 . ename and any codes which you want modified - the others are wasted space anyway). But I like using the vla methods for this purpose, it's a slight bit easier (to me at least). Though the VLA methods have other hiccups: e.g. if your text contains some strange characters (notably unicode) this will change to question marks ???? So there's a catch 22 situation here.

Link to comment
Share on other sites

Try this: Make sure your current text style is annotative. Change your CAnnoScale to something else than 1:1. Create a text containing only one of the words to be replaced. Now run your code.

 

What will happen is the entget obtains much more than just the text's value. It also gets hold of the currently displayed text height (which is incorrect when it's an Annotative Text). E.g. if your text style was set to be 2mm high and your CAnnoScale was 1:100, then (assoc 40 e) --->> (40 . 200.0). Now you use entmod even with the original. So you effectively set the text's height to 200, but since it is Annotative this gets multiplied by its scale factor and your text becomes 20000 high.

 

Great information about Annotative texts. I face it as you have described it entirely.

 

Thank you so much.

 

But a question came up to my mind right now with these Anno. texts. :)

 

For example . if we use (entget (car (entsel))) to get the dxf data of the Anno. text , we would not get the correct info about the height of it.

 

So how to get the real height of Anno.texts to be replaced later on with another text style or with a text using it's original height ?

 

many thanks

Link to comment
Share on other sites

So how to get the real height of Anno.texts to be replaced later on with another text style or with a text using it's original height ?
You need to obtain the AnnoScale attached to the entity. And since there could be more than one scale attached (wich may or may not equal the CAnnoScale) this becomes a bit complex. E.g.

 

The annotative text will contain a portion like this:

(102 . "{ACAD_XDICTIONARY") (360 . ) (102 . "}")
That 360 code points to the XDictionary attached to the text, which in turn can look like this:
((-1 . ) (0 . "DICTIONARY") (330 . ) (5 . "14E") (100 . "AcDbDictionary") (280 . 1) (281 . 1) (3 . "AcDbContextDataManager") (360 . ))
Now the thing is that AcDbContaxtDataManager. You can get hold of it through the 360 code directly following it, but the more official way would be to use dictsearch on the ename of the main dictionary:
(setq cm (dictsearch (cdr (assoc 360 e)) "AcDbContextDataManager"))

This should give something like:

((-1 . ) (0 . "DICTIONARY") (5 . "14F") (102 .

"{ACAD_REACTORS") (330 . ) (102 . "}") (330 . ) (100 . "AcDbDictionary") (280 . 0) (281 . 1) (3 . "ACDB_ANNOTATIONSCALES") (350 . ))

So you see a ACDB_ANNOTATIONSCALES entry into the content manager dictionary, followed by a 350 code with an ename. Again the dictsearch would work:
(setq asd (dictsearch (cdr (assoc -1 cm)) "ACDB_ANNOTATIONSCALES"))

Now this gives you the following (I've got 2 scales attached to this text (1:100 and 1:50):

(setq asd (dictsearch (cdr (assoc -1 cm)) "ACDB_ANNOTATIONSCALES"))
((-1 . <Entity name: 7efa4280>) (0 . "DICTIONARY") (5 . "150") (102 . "{ACAD_REACTORS") (330 . <Entity name: 7efa4278>) (102 . "}") (330 . <Entity name: 7efa4278>) (100 . "AcDbDictionary") (280 . 0) (281 . 1) (3 . "*A1") (350 . <Entity name: 7efa4288>) (3 . "*A2") (350 . <Entity name: 7efa4340>))

Can you see them anywhere there? No? That's because they're hidden behind those *A1 and *A2 items. So to get at them you step through this dictionary using dictnext:

(setq sc (dictnext (cdr (assoc -1 asd)) t))
(while sc
 (setq scLst (cons sc scLst) sc (dictnext (cdr (assoc -1 asd))))
)

Which gives you this:

(((-1 . <Entity name: 7efa4340>) (0 . "ACDB_TEXTOBJECTCONTEXTDATA_CLASS") (5 . "168") (102 . "{ACAD_REACTORS") (330 . <Entity name: 7efa4280>) (102 . "}") (330 . <Entity name: 7efa4280>) (100 . "AcDbObjectContextData") (70 . 3) (290 . 0) (100 . "AcDbAnnotScaleObjectContextData") (340 . <Entity name: 7efa40b8>) (70 . 0) (50 . 1.5708) (10 1256.63 992.444 0.0) (11 0.0 0.0 0.0)) 
((-1 . <Entity name: 7efa4288>) (0 . "ACDB_TEXTOBJECTCONTEXTDATA_CLASS") (5 . "151") (102 . "{ACAD_REACTORS") (330 . <Entity name: 7efa4280>) (102 . "}") (330 . <Entity name: 7efa4280>) (100 . "AcDbObjectContextData") (70 . 3) (290 . 1) (100 . "AcDbAnnotScaleObjectContextData") (340 . <Entity name: 7efa40c0>) (70 . 0) (50 . 1.5708) (10 1256.63 992.444 0.0) (11 0.0 0.0 0.0)))

Each of which points to a 340 code:

(foreach sc scLst
 (prin1 (entget (cdr (assoc 340 sc)))) (princ "\n\n")
)

Which in turn gives you the scales themselves:

((-1 . <Entity name: 7efa40b8>) (0 . "SCALE") (5 . "117") (102 . "{ACAD_REACTORS") (330 . <Entity name: 7efa4060>) (102 . "}") (330 . <Entity name: 7efa4060>) (100 . "AcDbScale") (70 . 0) (300 . "1:50") (140 . 1.0) (141 . 50.0) (290 . 0))

((-1 . <Entity name: 7efa40c0>) (0 . "SCALE") (5 . "118") (102 . "{ACAD_REACTORS") (330 . <Entity name: 7efa4060>) (102 . "}") (330 . <Entity name: 7efa4060>) (100 . "AcDbScale") (70 . 0) (300 . "1:100") (140 . 1.0) (141 . 100.0) (290 . 0))

So now you have a list of scale entities attached to that one single text. Each of these has its name in the 300 code, its paper space units in 140 and its model space units in 141.

 

But, say your current CAnnoScale="1:200". Which one of these is actually shown? The answer would be the 1st in the dxf codes - i.e. the last one obtained in the list you saved (since you used cons to generate that list). In this case (300 . "1:100") (140 . 1.0) (141 . 100.0).

 

Now from that you can calculate the scale factor: (#140 / #141) = 0.01. That you then apply to the height you obtained from the original entget (that (40 . 200.0) and you get the value 2.0.

 

See why it's soooo much simpler just using the vla methods? Or even just ignoring the height for entmod?

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...