Visual LISP, AutoLISP and General Customization
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Help modifiing a replace block script

20 REPLIES 20
SOLVED
Reply
Message 1 of 21
dglenn9000
1093 Views, 20 Replies

Help modifiing a replace block script

 I came across a great script that asks you to select a block and then replaces it with another block you select. Is there a way to modify this script so you dont have to manually choose the blocks. Instead have it find all block names starting with "Supply Diffuser 24x24" and replace them with a block in the drawing named V01.

 

(defun C:CHANGEBLOCKS(/ printDebug ssAll ssAllTotal ss ssTotal ssCount ssCount2 ssTotal ssMainBlock ssMainBlock entListMainBlock ssBlocks entListBlock entListBlock2 entElementNewBlock entElementOldBlock entElementOldBlock2 oldBlockName)
  ;PROGRAM CHANGES MULTIPLE BLOCKS
  ;WRITTEN BY --- RAUL BENITEZ

  ;Command reference
  ;subst - returns a list with a new item substituted for every occurrence of an old item
  ;quote (or ') - easy way to make the string into a list.
  ;cdr - returns a list containing all but the first element of the specified list
  ;assoc - searches an association list for an element and returns that association list entry
  ;entmod - modifies the definition data of an object (entity)
  ;cons - adds an element to the beginning of a list, or constructs a dotted list
  
  (vl-load-com)
  (setvar "CMDECHO" 0)
  (setq printDebug 0)

  (setq ss nil) ;set ss variable to nil
  (princ "\n(1). Pick the blocks you want to replace: ")
  (setq ss (ssget))               ;Gets selection set
  (setq ssTotal (sslength ss))    ;Gets the total number of entities within the selection
  
  (command "PICKADD" "0")         ;Changes the pickadd variable so the user can only select one object at a time
  
  ;Obtain the block you want to replace the others with
  (princ "\n(2). Pick the block you want to replace the others with: ") 
  (setq ssMainBlock (ssget))      ;Create selection set for the main block you want to replace the others with  
  (setq entListMainBlock (entget (ssname ssMainBlock 0)))    ;Obtain entity list (group codes) containing definition data of the block
  (setq entElementNewBlock (assoc 2 entListMainBlock))       ;Searches the block entity list and returns the element associate with 2 (the block name)
  
  (if (= printDebug 1)
  	  (progn
	  (princ "\nEntity list of the block you want to replace others with")
	  (princ "\n-----------------------------------------------------------\n")
	  (princ entListMainBlock)
	  )
  )
  
  ;Cycle thru all the blocks replacing each block one at a time
  (setq ssCount 0) 
  (while (< ssCount ssTotal)
              
	  (setq entListBlock (entget (ssname ss ssCount)))   ;Obtain entity list (group codes) from each of the entities (blocks) in the selection set
	  (setq entElementOldBlock (assoc 2 entListBlock))   ;Searches the block entity list and returns the element associate with 2 (the block name)
          (setq oldBlockName (cdr entElementOldBlock))       ;Returns a list containing all but the first element of the specified list, therefore only the block name

          (setq ssAll nil)				                          ;Set the variable to nil
	  (setq ssAll (ssget "X" (list '(0 . "INSERT") (cons 2 oldBlockName))))   ;Obtain the entire selection of all the blocks throughout the drawing
    	  (setq ssAllTotal (sslength ssAll))                                      ;Gets the total number of entities within the selection
	  (if (= printDebug 1)
		  (progn
	    	  (princ "\n\n\nCount of all the old blocks in the drawing: ")
		  (princ ssAllTotal)
	          )
 	  )
    
          (setq ssCount2 0) 
  	  (while (< ssCount2 ssAllTotal)
		  (setq entListBlock2 (entget (ssname ssAll ssCount2)))   ;Obtain entity list (group codes) from each of the entities (blocks) in the selection set
		  (setq entElementOldBlock2 (assoc 2 entListBlock2))      ;Searches the block entity list and returns the element associate with 2 (the block name)
	          
	          (entmod (subst entElementNewBlock entElementOldBlock2 entListBlock2))   ;Substitute the new block entity name for the old block entity name within the old block entity list
	          (command "MATCHPROP" (ssname ssMainBlock 0) (ssname ssAll ssCount2) "" )  ;Match the properties of the old block(s) to the new block
	          (setq ssCount2 (+ ssCount2 1))  ;Loop counter
	  )
        
          ;Print out information
          (if (= printDebug 1)
		  (progn
		  (princ "\n\n\nssCount: ")
		  (princ ssCount)
		  (princ "\nEntity list of the block that is going to be replaced")
	  	  (princ "\n-----------------------------------------------------------\n")
	          (princ entListBlock)
		  (princ "\n\nName entity of the block that is going to replaced: ")
	          (princ entElementOldBlock)
		  (princ "\nName entity of the new block: ")
	          (princ entElementNewBlock)
		  (princ "\n\n")
		  )
	  )
    
    	  (setq ssCount (+ ssCount 1))  ;Loop counter
  )  

  (command "PICKADD" "2")         ;Changes the pickadd variable so the user select multiple objects at a time
  
  (setvar "CMDECHO" 1)

  (print "Changed multiple blocks.") 
  (princ)  

)

 

20 REPLIES 20
Message 2 of 21
Kent1Cooper
in reply to: dglenn9000


@dglenn9000 wrote:

 I came across a great script that asks you to select a block and then replaces it with another block you select. Is there a way to modify this script so you dont have to manually choose the blocks. Instead have it find all block names starting with "Supply Diffuser 24x24" and replace them with a block in the drawing named V01.

.... 



That's so much easier a task that it's not worth modifying -- here's a from-scratch way to do it [minimally tested]:

 

(defun C:TEST ()

  (vl-load-com)
  (foreach
    x
    (mapcar 'cadr (ssnamex (ssget "_X" '((2 . "Supply Diffuser 24x24*")))))
    (vla-put-name (vlax-ename->vla-object x) "V01")
  ); foreach
); defun

 

EDIT:

You can make a generic one as a function that takes the two Block names as arguments, rather than as a Command, so you don't need a dedicated routine for every possible replacement:

 

(defun BlockReplace (old new)

  (vl-load-com)
  (foreach
    x
    (mapcar 'cadr (ssnamex (ssget "_X" (list (cons 2 old)))))
    (vla-put-name (vlax-ename->vla-object x) new)

  ); foreach
); defun

 

Usage for your example: (blockreplace "Supply Diffuser 24x24*" "V01")

 

[Further EDIT:  I hadn't really noticed the "starting with" part until lgabriel's post brought it to my attention -- I've added the asterisk to the Block name.]

Kent Cooper, AIA
Message 3 of 21
lgabriel
in reply to: dglenn9000

Replace:

 

(princ "\n(1). Pick the blocks you want to replace: ")
  (setq ss (ssget))               ;Gets selection set

 

With:

 

(setq ss (ssget "X" '((0 . "INSERT")(2. "Supply Diffuser 24x24*"))))

 

SSGET with an "X" argument searches the entire drawing file. The filters are all blocks (0 . "INSERT") whose names begin with "Supply Diffuser 24x24" (2 . "Supply Diffuser 24x24*"). Note the asterisk at the end of the block name. This is interpreted as a wild card.

 

 

Message 4 of 21
dglenn9000
in reply to: dglenn9000

Thanks lgabriel, your solution works and answer my question exactly. 

 

Thanks Kent1Cooper, your script is able to accomplish the task in 1/10th the code I had before.  However, I do not understand how I would use the generic version exactly. How would I redefine old & new if I had multiple blocks to replace? 

 

 

 

(defun BlockReplace (old new)

  (vl-load-com)
  (foreach
    x

(mapcar 'cadr (ssnamex (ssget "_X" (list (cons 2 old)))))
    (vla-put-name (vlax-ename->vla-object x) new)

  ); foreach
); defun

Message 5 of 21
Kent1Cooper
in reply to: dglenn9000


@dglenn9000 wrote:

.... 

Thanks Kent1Cooper, your script is able to accomplish the task in 1/10th the code I had before.  However, I do not understand how I would use the generic version exactly. How would I redefine old & new if I had multiple blocks to replace? 

....


The 'old' and 'new' entries are "arguments" to the function, which is what the list in parentheses after the function name at the top is.  For a function defined without a C: prefix before the name, if arguments are listed there [before a slash if there is one, but there's no need for one here], then you must supply values for those arguments when you use the function, just as with any standard AutoLISP function.  You do that by entering, in parentheses [also just as with any AutoLISP function], the function name followed by values for all the arguments.  Everywhere the argument name appears in the code, the argument value supplied will be used for it.  See the Usage sample following that code, for the situation in your original example.

 

If by "multiple blocks to replace" you mean multiple Block names, for each one of which you want to replace all insertions of it with some other Block name, you would need to run the function once per combination.  So if you want to replace all Blocks named "Bob" with "Carol," and all Blocks named "Ted" with "Alice," you would do:

(BlockReplace "Bob" "Carol")

(BlockReplace "Ted" "Alice")

 

If having multiple replacements to do like that is something you need regularly, it wouldn't be difficult to make a variation in which you could supply two lists, one of the old names and another of the new, for instance one that would be used this way to do the above and a few more:

(BlockReplaceL '("Bob" "Ted" "Mickey" "Fred") '("Carol" "Alice" "Minnie" "Ginger"))

The same could be done in a way that might make it easier to keep track, using one argument that would be a list of two-item lists, each containing an old name with its corresponding new name:

(BlockReplaceL '(("Bob" "Carol") ("Ted" "Alice") ("Mickey" "Minnie") ("Fred" "Ginger")))

Either way would be able to handle any number of Block replacements you want, with only one function definition.  [The L at the end of the presumed function name would be for "List," to distinguish it from the one-name-at-a-time routine.]  Is that something you would have a use for?

 

[If by "multiple blocks to replace" you mean there are multiple insertions of a single Block name and you want to replace all of them with the same different Block name, it already does that.]

 

[And by the way, it's not a Script, nor is the code in you first post.  The word "Script" has a specific meaning in AutoCAD, and these don't qualify.  Call them "routines" or "functions" or (with the C: prefix to the name) "command definitions" or something.]

Kent Cooper, AIA
Message 6 of 21
dglenn9000
in reply to: Kent1Cooper

GREAT! Thanks for the explanation. For now I will use multiple insertions like so, seems easier for me to read that the others.

 

(BlockReplace "Bob" "Carol")

(BlockReplace "Ted" "Alice")

(BlockReplace "Jon" "Tom")

 

I will read up on error handling and try and incorporate it with this script routine.  I should be able to find enough info to tackle that one, something like:


"if exist then...

if not then..."

 

Thanks again.

Message 7 of 21
Kent1Cooper
in reply to: dglenn9000


@dglenn9000 wrote:

.... 

I will read up on error handling and try and incorporate it with this script routine.  ....


This may not be something that requires error handling in the usual sense.  That's generally intended to do things like set back System Variable settings that have been changed, etc., in case the routine itself doesn't get around to doing so because of some error that interrupts it.  But this routine doesn't change anything that would need to be reset.

 

There are two errors I can picture you could get: if there are no insertions of Blocks with the old name, in which case the (ssnamex) function will have a problem, and if the new name is not a defined Block in the drawing, in which case the substitution won't work.  If you don't mind seeing an error message in either case, you can just leave it alone, because the errors won't mean there's anything that needs to be "handled."

 

You could use an error handler simply to suppress display of error messages, but there's perhaps a better way to avoid those.  The routine can test whether there are any Blocks of the old name, and whether the new name is a defined Block, and only proceed to replace them if both are true.

 

(defun BlockReplace (old new / ss)

  (vl-load-com)

  (if

    (and

      (setq ss (ssget "_X" (list (cons 2 old)))); there are some

      (tblsearch "block" new); the Block is defined

    ); and
    (foreach ; then
      x
      (mapcar 'cadr (ssnamex ss))
      (vla-put-name (vlax-ename->vla-object x) new)

    ); foreach

  ); if

  (princ)

); defun

 

Come to think of it, I guess having such a Block on a locked Layer would be a third error possibility.  Is that something you would need to account for?  If so, it would be better to have the routine check for each Block it finds, because that way, it can skip the locked ones but still replace all those that are not on locked Layers, whereas if you don't check for each one, it will stop when it reaches the first one on a locked Layer, and later ones it has found won't be replaced even if they're not on locked Layers.

Kent Cooper, AIA
Message 8 of 21
m_rogoff
in reply to: Kent1Cooper

Hi Kent, thank you for your routine. I would like to use it but am having trouble following.

 

Where does the (BlockReplace "NEW" "OLD") go in relation to the code? Are you saying if the (new or old) block definition does not exist in the drawing it will crap out?

 

Thanks

Message 9 of 21
Kent1Cooper
in reply to: m_rogoff


@m_rogoff wrote:

.... 

Where does the (BlockReplace "NEW" "OLD") go in relation to the code? Are you saying if the (new or old) block definition does not exist in the drawing it will crap out?

....


After you have loaded the routine, then this [arguments the other way around, in the order they're listed in the parentheses after the function name]:

 

(BlockReplace "OLD" "NEW")

 

is what you would type at the Command: line [or include in other code] to use it.  See the beginning of Post 5.

 

If either there are no insertions of the "OLD" Block [even if it is defined in the drawing], or there is no Block definition with the "NEW" name in the drawing, or both, it won't "crap out," but will simply not do anything, because there will be nothing to do.  The (ssget) function is looking for those insertions, and the (tblsearch) function is looking for that definition.  The (and) function ensures that only if both of those find something will it proceed to replace the old one(s) with the new one.

 

If you like, it could be made to notify you if it doesn't have anything to do, and why if you want to know that.

Kent Cooper, AIA
Message 10 of 21
m_rogoff
in reply to: Kent1Cooper

Bear with me please, Kent. If I understand you correctly...(see my changes below)

Thank you!

 

(defun BlockReplaceL '(("Bob" "Carol") ("Ted" "Alice") ("Mickey" "Minnie") ("Fred" "Ginger"))) / ss)

  (vl-load-com)

  (if

    (and

      (setq ss (ssget "_X" (list (cons 2 "Carol" "Alice" "Minnie" "Ginger"))));

      (tblsearch "block"'("Bob" "Ted" "Mickey" "Fred"));

    ); and
    (foreach ; then
      x
      (mapcar 'cadr (ssnamex ss))
      (vla-put-name (vlax-ename->vla-object x) new)

    ); foreach

  ); if

  (princ)

); defun

Message 11 of 21
Kent1Cooper
in reply to: m_rogoff


@m_rogoff wrote:

Bear with me please, Kent. If I understand you correctly...(see my changes below)

.... 

(defun BlockReplaceL '(("Bob" "Carol") ("Ted" "Alice") ("Mickey" "Minnie") ("Fred" "Ginger"))) / ss)

....


It would require that we "make a variation" [Post 5] of the routine, but unfortunately that needs to be more varied than simply replacing the Block names with lists.  It would need to step through the list-of-lists and do the operation on each thing in it.  It could be done a few different ways, maybe most simply by having the same BlockReplace function in Post 7, and applying that to each sub-list, something like this [untested]:

 

[include the BlockReplace routine definition in the same file, then:]

 

(defun BlockReplaceL (blklist)

  (foreach sublist blklist (BlockReplace (car sublist) (cadr sublist)))

)

 

Usage would then be as described in Post 5:

 

(BlockReplaceL '(("Bob" "Carol") ("Ted" "Alice") ("Mickey" "Minnie") ("Fred" "Ginger")))

Kent Cooper, AIA
Message 12 of 21
MRogoff
in reply to: Kent1Cooper

Like this? Do I need to replace "new" with the names of blocks (shown in red below). If I add "new" and "old" into the routine, it'll more or less just be ignored, right? (shown blue). If I added for example ("Bob" "Bob") would it redefine the old version of the block Bob?

 

 

(defun BlockReplaceL '(("new" "old") ("Bob" "Carol") ("Ted" "Alice") ("Mickey" "Minnie") ("Fred" "Ginger"))) / ss)

  (vl-load-com)

  (if

    (and

      (setq ss (ssget "_X" (list (cons 2 "old" "Carol" "Alice" "Minnie" "Ginger"))));

      (tblsearch "block"'("new" "Bob" "Ted" "Mickey" "Fred"));

    ); and
    (foreach ; then
      x
      (mapcar 'cadr (ssnamex ss))
      (vla-put-name (vlax-ename->vla-object x) ("new" "Bob" "Ted" "Mickey" "Fred"))

    ); foreach

  ); if

  (princ)

); defun

 

(defun BlockReplaceL (blklist)

  (foreach sublist blklist (BlockReplace (car sublist) (cadr sublist)))

)

Message 13 of 21
Kent1Cooper
in reply to: MRogoff


@MRogoff wrote:

Like this? .... 

 

(defun BlockReplaceL '(("new" "old") ("Bob" "Carol") ("Ted" "Alice") ("Mickey" "Minnie") ("Fred" "Ginger"))) / ss)

....


No, you don't want to build specific Block names into the definition of the routine like that.  You want to define it with placeholders, and then supply the specific Block names as arguments to be fed in for the placeholders when you use the routine.

 

Using the way I suggested in Post 11 [it's not the only way to do this], put into one .lsp file or separately, as you prefer, as long as both get loaded, these two function definitions:

 

(defun BlockReplace (old new / ss) ; from Post 7

  (vl-load-com)

  (if

    (and

      (setq ss (ssget "_X" (list (cons 2 old)))); there are some old ones

      (tblsearch "block" new); the new Block is defined

    ); and
    (foreach ; then
      x
      (mapcar 'cadr (ssnamex ss)); extracts the Block references
      (vla-put-name (vlax-ename->vla-object x) new); changes their Block names

    ); foreach

  ); if

  (princ)

); defun

 

(defun BlockReplaceL (blklist) ; from Post 11

  (foreach sublist blklist (BlockReplace (car sublist) (cadr sublist))); calling the other routine

)

 

Then to use it, see the end of Post 11, with your list of two-name sublists as the argument that blklist is a placeholder for in the lower routine  Within that, the first and second item in each sublist are the arguments that old and new are placeholders for in the upper routine.

 

This way of doing it means you can still do a single such replacement by using the upper routine as a stand-alone, not called from within the lower routine, with just the two names, and without the need to put them into a sublist:

 

(BlockReplace "Laurel" "Hardy")

 

That would be equivalent [but simpler] to using the lower routine with a single two-name sublist in its blklist argument:

(BlockReplaceL '(("Laurel" "Hardy")))

Kent Cooper, AIA
Message 14 of 21
m_rogoff
in reply to: Kent1Cooper

Thanks Kent. I got this working by calling it with a script. 

 

I have a couple questions/comments:

1. If the block does not already exist in the dwg, this will not work. Is there a way to specify a block from a folder location (ie: M:\SYM\blockname.dwg)? Or possibly insert the new block version and immediately delete it?

2. If a dynamic block has been modified, ie: visibilty state changed from default or parameters have been stretched, the block will not be replaced.

3. If I need to REDEFINE a block from a newer version from the folder location, is this possible?

 

Thank you!

Message 15 of 21
Kent1Cooper
in reply to: m_rogoff


@m_rogoff wrote:

....

1. If the block does not already exist in the dwg, this will not work. Is there a way to specify a block from a folder location (ie: M:\SYM\blockname.dwg)? Or possibly insert the new block version and immediately delete it?

2. If a dynamic block has been modified, ie: visibilty state changed from default or parameters have been stretched, the block will not be replaced.

3. If I need to REDEFINE a block from a newer version from the folder location, is this possible?

....


1. The (tblsearch) function that looks for it can't look for an external file, nor can the (vla-put-name) function use one, so in that case you would need to bring it in.  Precede the rest of the code with something like:

(if (not (tblsearch "block" "blockname")) (command "_.insert" "M:\\SYM\\blockname" nil))

 

That should bring in the definition, and when it's asking for an insertion point, cancel the command, so it's not necessary to finishing Inserting and then Erase it.  [Note the double backslashes, which could be replaced by single forward-slashes, but single backslashes can't be used because they are control characters and trigger other stuff depending on what follows them.]

 

2. Unfortunately, (ssget) searching for code-2 entries can't find Blocks by their effective name.  So I think it would be necessary to find all Block insertions, and in stepping through them, get their effective names, and for those that match, do the replacement.  Search this Forum for code using the effectivename VLA property.

 

3. If the folder location is in the Support File Search Path list [OPTIONS, Files tab], and the Block name is the same as the drawing name, then you can replace a Block's definition with the Insert command using the blockname= form, again cancelling the command after the new definition has been brought in, something like:

 

(command "_.insert" "blockname=" "_yes" nil)

 

[You don't need to include the .dwg filetype ending -- that's the only filetype it will look for.]  Or if that location isn't one that it will know to look in, you can spell it out, with things on both sides of the = sign:

 

(command "_.insert" "blockname=M:\\SYM\\blockname" "_yes" nil)

 

That "_yes" answer assumes that the Block exists in the drawing already -- it's answering the question of whether to replace it.  To make the whole thing cover more possibilities, you might include a check for whether it does, before running that part.

Kent Cooper, AIA
Message 16 of 21
m_rogoff
in reply to: Kent1Cooper

Maybe something like this? I found this here (link below, post 9) and you, Kent, commented in this thread.

 

https://forums.autodesk.com/t5/visual-lisp-autolisp-and-general/block-name-to-vla-object/td-p/378688...

 

 

(defun edit_att	(blkname att_tag str)
  (vl-load-com)
  (ssget "x"
	 (list '(0 . "INSERT") (cons 2 (strcat blkname ",`*U*")) '(66 . 1))
  )
  (vlax-for item (vla-get-activeselectionset
		   (vla-get-activedocument (vlax-get-acad-object))
		 )
    (if (eq (strcase blkname)
               (strcase (vla-get-effectivename item)))
    (foreach att (vlax-safearray->list
		   (vlax-variant-value (vla-getattributes item))
		 )
      (if (= (strcase att_tag) (vla-get-tagstring att))
	(vla-put-textstring att str)
      )
    )
      )
  )
)

 

 

 

Message 17 of 21
m_rogoff
in reply to: m_rogoff

Why couldn't I just _rename / block and then redefine using your suggestion with blockname=? Wouldn't that just be a simple 2 line code?

 

 

(command "_rename" "oldblockname" "newblockname")

(command "_.insert" "newblockname=" "_yes" nil)

Message 18 of 21
m_rogoff
in reply to: m_rogoff

Or...?  (from hmsilva)

 

(setq obj (vlax-ename->vla-object (car (entsel "\nSelect a DynBlock : "))))
(setq name (vlax-get-property
      obj
      (if (vlax-property-available-p obj 'effectivename)
        'effectivename
        'name
      )
    )
)

 

 

 

Message 19 of 21
Kent1Cooper
in reply to: m_rogoff


@m_rogoff wrote:

Why couldn't I just _rename / block and then redefine using your suggestion with blockname=? Wouldn't that just be a simple 2 line code?

 

 

(command "_rename" "oldblockname" "newblockname")

(command "_.insert" "newblockname=" "_yes" nil)


That ought to work, but only if "oldblockname" is already in the drawing [Post 14 item 1 suggests it won't always be].  But if it is, and newblockname is not a Block currently in the drawing but is a drawing in a Support File Search Path location, and if you don't particularly need to have the different name, but only want the definition changed, you should be able to just do:

 

(command "_.insert" "oldblockname=newblockname" "_yes" nil)

Kent Cooper, AIA
Message 20 of 21
MRogoff
in reply to: Kent1Cooper

Kent, thank you so much for your help. I think the short simple solution will work for me. 

 

When I have a dynamic block with an assocative hatch and the block is stretched or modified, when I redefine the block, the hatch goes back to the original shape. As long as I select the grip, it fixes itself. This is not just when using the lisp, it seems like a bug in ACad. Any thoughts on this?

 

I have not tested this yet with Annotative blocks, I am hoping it keeps the scales in tact, as defined in the dwg.

 

P.S. there was a typo in my last post    (command "_rename" "block" "oldblockname" "newblockname")

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Design & Make Report

”Boost