Jump to content

Recommended Posts

Posted

Hello everyone,

 

I need some assistance because despite searching across the forum and other site, I can't get the code I found here to work. The goal I am trying to achieve is using an outside code source that is able to interact with AutoCAD via the command line and have it automatically select a block named "Titleblock_Properties_22x34" that is on the current sheet only and update two attributes; sheet description (LTSHTDESC) and sheet number (LTSHTNUM). The sample code was setup so that the user has to select the block, but I want this part to be automatic as the script works through each sheet performing various actions. 

 

Sample Code:

(defun _AttFunc	(en lst / vals v)

  (vl-load-com)

  (mapcar (function
	    (lambda (at)
	      (setq vals (list (vla-get-tagstring at) (vla-get-textstring at)))
	      (if (and lst (setq v (assoc (car vals) lst)))
		(vla-put-textstring at (cadr v))
	      )
	      vals
	    )
	  )
	  (vlax-invoke
	    (if	(eq (type en) 'VLA-OBJECT)
	      en
	      (vlax-ename->vla-object en)
	    )
	    'Getattributes
	  )
  )
)

;(_AttFunc (Car (entsel)) nil);----- read values (("tag" "value")("tag1" "value1"))

;(_AttFunc (Car (entsel)) '(("TAGNAME" "New Value")("TAGNAME2" "New Value2")));---- assign values from list [tag and value]

 

When I run the following code, it returns "bad point argument"

 

(_AttFunc (ssget "x" (cons 0 "insert")(cons 2 "Titleblock_Properties_22x34")) '("LTSHTDESC" "TEST"))

 

Any clue on what I have done wrong and suggestions on how to fix it?

 

Bonus points if we can get it to where the variables LTSHTDESC and LTSHTNUM can be defined outside of the by the parent program that has to gather other information from the process that it is running to determine what these will be. This would allow allow something like:

 

Pseudo Code:

Set LTSHTDESC variable to "Detail 1 - Detail 15"

Set LTSHTNUM variable to "D-1"

Run command "_AttFunc" (this would then just run since the variable have already been set and could be used across all sheets"

ChangeAtt4.lsp Detail Sheet Template - 22x34.dwg

Posted (edited)

Okay, let's start here.

 

Command  CTA (for Change Titleblock Attributes) to start the function

This selects the the title block on the current Layout,

then asks  the user to fill in LTSHTDESC and LTSHTNUM.

 

 

;; Load Visual Lisp
(vl-load-com)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Lee Mac, read and set attributes:
;; @see http://www.lee-mac.com/attributefunctions.html

;; Get Attribute Value  -  Lee Mac
;; Returns the value held by the specified tag within the supplied block, if present.
;; blk - [vla] VLA Block Reference Object
;; tag - [str] Attribute TagString
;; Returns: [str] Attribute value, else nil if tag is not found.
(defun LM:vl-getattributevalue ( blk tag )
    (setq tag (strcase tag))
    (vl-some '(lambda ( att ) (if (= tag (strcase (vla-get-tagstring att))) (vla-get-textstring att))) (vlax-invoke blk 'getattributes))
)

;; Get Attribute Values  -  Lee Mac
;; Returns an association list of attributes present in the supplied block.
;; blk - [ent] Block (Insert) Entity Name
;; Returns: [lst] Association list of ((<tag> . <value>) ... )
;; http://www.lee-mac.com/attributefunctions.html#algetattributevaluerc
(defun LM:getattributevalues ( blk / enx )
    (if (and (setq blk (entnext blk)) (= "ATTRIB" (cdr (assoc 0 (setq enx (entget blk))))))
        (cons
            (cons
                (cdr (assoc 2 enx))
                (cdr (assoc 1 (reverse enx)))
            )
            (LM:getattributevalues blk)
        )
    )
)

;; Set Attribute Value  -  Lee Mac
;; Sets the value of the first attribute with the given tag found within the block, if present.
;; blk - [vla] VLA Block Reference Object
;; tag - [str] Attribute TagString
;; val - [str] Attribute Value
;; Returns: [str] Attribute value if successful, else nil.
(defun LM:vl-setattributevalue ( blk tag val )
    (setq tag (strcase tag))
    (vl-some
       '(lambda ( att )
            (if (= tag (strcase (vla-get-tagstring att)))
                (progn (vla-put-textstring att val) val)
            )
        )
        (vlax-invoke blk 'getattributes)
    )
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun cgange_titleblock ( / ss blk)
	;; select the title block on current layout.
	;;  (getvar "ctab")	returns the current layout.	The block keeps this information in: (assoc 410 blk)
	(setq ss (ssget "_X" (list (cons 0 "insert") (cons 2 "Titleblock_Properties_22x34") (cons 410 (getvar "ctab")) )) )
	(setq blk (ssname ss 0)) 
	;;
	
	;;(LM:vl-setattributevalue (vlax-ename->vla-object blk) "LTSHTDESC" "Detail 1 - Detail 15")
	(LM:vl-setattributevalue (vlax-ename->vla-object blk) "LTSHTDESC" (getstring "\nLTSHTDESC: " T))
	;;(LM:vl-setattributevalue (vlax-ename->vla-object blk) "LTSHTNUM" "D-1")
	(LM:vl-setattributevalue (vlax-ename->vla-object blk) "LTSHTNUM" (getstring "\nLTSHTNUM: " T))
	
)

;; Type command CTA (for Change Titleblock Attributes) to start the function
(defun c:cta ( / )
	(cgange_titleblock)
	(princ)
)

 

 

 

 What more would you like the function to do?

More can be automated of course.

 

For example, we could make a list

(list
    (list "Sheet1" "Detail 1 - Detail 15" "D-1")
    (list "Sheet1" "Detail 2 - Detail 15" "D-2")
    (list "Sheet1" "Detail 3 - Detail 15" "D-3")
    ;; ...
)

... then the function fills in everything ...

Edited by Emmanuel Delay
  • Like 1
  • Thanks 1
Posted (edited)

Don't really need functions LM:getattributevalues and LM:vl-setattributevalue. Can be done with one line and you don't need to convert to vla-object. also works with any other values listed in vla-objects. only bad part is it doesn't work with my version of BrisCAD. ☹️

 

getpropertyvalue

setpropertyvalue

 

(defun C:Title_Block_update (/ lst ss Titleblock)
  (setq lst (vl-sort (layoutlist) '<)) ;tabs need to have the same naming convention. "Sheet1" was at the end because its missing a space
  (foreach layout lst
    (if (and (setq ss (ssget "_X" (list (cons 0 "INSERT") (cons 2 "Titleblock_Properties_22x34") (cons 410 layout)))) (= (sslength ss) 1))
      (progn
        (setq Titleblock (ssname ss 0)) 
        (setpropertyvalue Titleblock "AOORDERNUMBER" (getstring "\nAO Order Number: " T)) ;maybe store these values to be used again
        (setpropertyvalue Titleblock "LTSHTDEC" (getstring "\nSheet Descriptioin (Lot): " T)) ;also maybe a dcl menu would be easier to fill out per tab
        (setpropertyvalue Titleblock "LTSHTNUM" (getstring "\nSheet Number (Lot): " T))       ;instead of one line at a time
        ;....
      )
    )
  )
  (princ)
)

 

 

 

 

 

Edited by mhupp
forgot T in getstring to allow spaces
Posted
9 hours ago, Emmanuel Delay said:

Okay, let's start here.

 

Command  CTA (for Change Titleblock Attributes) to start the function

This selects the the title block on the current Layout,

then asks  the user to fill in LTSHTDESC and LTSHTNUM.

 

 

@Emmanuel Delay This worked perfectly and exactly what I was looking for. This will allow my python script to interact with AutoCAD as needed. Thank you so much for putting this together. I have run across the LM functions and seen them used, but I could never get them to work myself. Still a lot of learning to do on my side. 

 

Quote

What more would you like the function to do?

More can be automated of course.

 

For example, we could make a list

(list     (list "Sheet1" "Detail 1 - Detail 15" "D-1")     (list "Sheet1" "Detail 2 - Detail 15" "D-2")     (list "Sheet1" "Detail 3 - Detail 15" "D-3")     ;; ... )

... then the function fills in everything ...

 

I would be interested in how this could work as well. Although my python code is stepping through each sheet as it interacts with other applications gathering information and such, it might prove to be useful to do everything in model space and then run through each sheet in a quick series with a list of attributes that are to be applied to each sheet respectively.

 

 

 

@mhupp

This code stopped after asking for the Sheet Description for some reason which is weird because it is identical to the line above it that asks for the AO Order Number. It is always something like that where I struggle in writing my own code. I like the ability of storing the order number for future use though. Would that be written something like:

 

(setq Titleblock (ssname ss 0))
(setq AOOrderNumber "AO-123456") 
        (setpropertyvalue Titleblock AOOrderNumber))

 

 

 

 

Posted

Its only sample code. Depending on what you want update the code accordingly. This only asks for the first three attributes.

it will be easy to add the rest of the attributes. if you tell me witch ones are static and witch ones change from layout to layout I could give a more complete code.

 

image.png.fc884384923063549302675aed7142f3.png

 

like sheet number could be automatic

(setq i 0)
(setq ts (itoa (lenght lst))) = "5"
(setpropertyvalue Titleblock "LTSHTNUM" (strcat (itoa (1+ i)) " of " TS))

This would set LTSHTNUM on each tab with out asking the user.
1 of 5 
2 of 5
3 of 5
4 of 5 
5 of 5

 

Sample code:

(defun C:Title_Block_update (/ lst layout ss Titleblock)
  (setq lst (vl-sort (layoutlist) '<)) ;tabs need to have the same naming convention. "Sheet1" was at the end because its missing a space
  (setvar 'clayer (setq layout (car lst))) ;switch tab to first in the list
  (setq lst (cdr lst)) ;remove first element of list
  (if (and (setq ss (ssget "_X" (list (cons 0 "INSERT") (cons 2 "Titleblock_Properties_22x34") (cons 410 layout)))) (= (sslength ss) 1))
    (progn
      (setq Titleblock (ssname ss 0)) 
      ;Duplicate these three lines to pull attibute values
      ;Ask for user intput with [last answer shown in brackets] if user right clicks that will be used as intput
      ;Update Titleblock with set value
      ;only covers first three attributes
      (setq *AO (getpropertyvalue Titleblock "AOORDERNUMBER")) 
      (if (= (setq AO (strcase (getstring "\nAO Order Number [" *AO "]: " T))) "") (setq AO *AO)) 
      (setpropertyvalue Titleblock "AOORDERNUMBER" AO) ;updates attibute value
      
      (setq *LTDEC (getpropertyvalue Titleblock "LTSHTDEC"))
      (if (= (setq AO (strcase (getstring "\nSheet Descriptioin (Lot) [" *LTDEC "]: " T))) "") (setq LTDEC *LTDEC))
      (setpropertyvalue Titleblock "LTSHTDEC" LTDEC)
      
      (setq *LTNUM (getpropertyvalue Titleblock "LTSHTNUM"))
      (if (= (setq AO (strcase (getstring "\nSheet Number (Lot) [" *LTNUM "]: " T))) "") (setq LTNUM *LTNUM))
      (setpropertyvalue Titleblock "LTSHTNUM" LTNUM)
    )
  )      
  (foreach layout lst ;once first tab is processed its just ask for input update or just update
    (setvar 'clayer layout) 
    (if (and (setq ss (ssget "_X" (list (cons 0 "INSERT") (cons 2 "Titleblock_Properties_22x34") (cons 410 layout)))) (= (sslength ss) 1))
      (progn
        (setq Titleblock (ssname ss 0)) 
        ;if AO Order Number is the same just update don't need to prompt user again.
        (if (= (setq AO (strcase (getstring "\nAO Order Number [" *AO "]: " T))) "") (setq AO *AO))
        (setpropertyvalue Titleblock "AOORDERNUMBER" AO)
        (if (= (setq AO (strcase (getstring "\nSheet Descriptioin (Lot) [" *LTDEC "]: " T))) "") (setq LTDEC *LTDEC))
        (setpropertyvalue Titleblock "LTSHTDEC" LTDEC)
        (if (= (setq AO (strcase (getstring "\nSheet Number (Lot) [" *LTNUM "]: " T))) "") (setq LTNUM *LTNUM))
        (setpropertyvalue Titleblock "LTSHTNUM" LTNUM)
      )
    )
  )
  (princ)
)

 

Posted

@mhupp Thank you for your continued support on this. Here is some background info to help understand the scope better. This project is for our detail sheets and needs to work with an external python script that is using pyAutoCAD to pass commands to AutoCAD. The python script is reading an excel list that stores a list of details specific to a division that is requesting a set of detail sheets either for a lot specific set or that will be used across a community. Python is logging the first and last detail that is on the sheet and will be setting the Sheet Description (LTSHTDESC) to list these. It then renames the sheet (LTSHTNUM) according to its position; if it is the first sheet, then it will be D-1 and so on. The rest of the fields are intentionally left blank. So the goal of this project is setup something that would allow the python code to send a pyAutoCAD command to start the process and change just the information on that sheet. It would then continue to the next sheet and repeat the process until it gets to the end where it will then delete all of the unused sheets. 

 

I have another project where I have to change all of attributes for lot specifics, but it needs to pull the information from either a xlsx or csv file. What I have so far has some sort of memory leak and fails to gather the data from the xlsx file. 

Posted (edited)

You don't need pyAutocad to read/automate from excel it can all be done with in AutoCAD using lisp. guess i should have read your whole first post also.

 

check this out.

http://lee-mac.com/updatetitleblock.html

 

-edit

update

;update 
(setq utb:ftr nil)  
;to
(setq utb:ftr "Titleblock_Properties_22x34") 

 

Edited by mhupp
Posted (edited)

pyAutoCAD is only being used by python, which is also interacting with BIM 360 via APS API. My lisp is being used more as sub-functions within AutoCAD that handle all of the layout based tasks. 

 

I always defer to Lee Mac and his code before venturing out to other sources. So, I already have the Update Titleblock application, but saw this is more setup for batch processing and was unsure how to cut out the first column requesting the drawings name. In my case, the drawing name will always be the file that is open. That and the attributes that are the same across all sheets are linked to fields for drawing properties. So, this would only update 3 out 15 or so items that need to be updated.  

 

EDIT:

 

Wouldn't that be changing this section to only look at the current drawing and that would allow me to remove the DWG column?

 

    (setq fnb:fun
        (lambda ( s )
            (if (wcmatch (strcase s t) "*.dwg,*.dxf,*.dwt,*.dws")
                (vl-filename-base s) s
            )
        )
    )

 

Edited by BHenry85
Posted (edited)

Sorry should have said its up at the top in the notes section.  (Green part if you looking at it on his website)

the first column is the block name. its saying it will filter the selection set to only find blocks you name there.

The example shows it will find any block that ends with "BOARDER" good for using when you have multiple title blocks. like one for a cover page, index, layout, drawing, notes.

 

;;----------------------------------------------------------------------;;
;; Block Filter (may use wildcards and may be nil)                      ;;
;;----------------------------------------------------------------------;;
(setq utb:ftr nil)  ;; e.g. (setq utb:ftr "*BORDER")

 

17 hours ago, BHenry85 said:

pyAutoCAD is only being used by python, which is also interacting with BIM 360 via APS API. My lisp is being used more as sub-functions within AutoCAD that handle all of the layout based tasks.

 

Ok so what are you or want to pass to Autocad from excel? probably should be in list forum. could be as simple as this then.

 

;lst created from pyAutoCAD befor running command
;'(("attribute1" "attribute2" "attribute3" "tabname") ("attribute1" "attribute2" "attribute3" "tabname"))
(defun C:Title_Block_update (/ lst) 
  (foreach tab lst
    (setvar 'ctab (last tab)) ;might not be needed if setpopertyvalue works on titleblocks on onther tabs
    (if (and (setq ss (ssget "_X" (list (cons 0 "INSERT") (cons 2 "Titleblock_Properties_22x34") (cons 410 (last tab))))) (= (sslength ss) 1))
      (progn
        (setq Titleblock (ssname SS 0))
        (setpropertyvalue Titleblock attribute (car tab))
        (setpropertyvalue Titleblock attribute (cadr tab))
        (setpropertyvalue Titleblock attribute (caddr tab))
      )
    )
  )
  (princ)
)

 

 

Edited by mhupp
Posted
On 1/31/2023 at 2:52 AM, BHenry85 said:

When I run the following code, it returns "bad point argument"

 

(_AttFunc (ssget "x" (cons 0 "insert")(cons 2 "Titleblock_Properties_22x34")) '("LTSHTDESC" "TEST"))


1. This is the correct syntax

(ssget "x" (list (cons 0 "insert")(cons 2 "Titleblock_Properties_22x34")))
2. You need to iterate thru the selection set via index number

3. _AttFunc purpose is to use one function for both GET and SET

 

Here's an example of the functions usage If your purpose is to set the value of the sheet as per layout tab  name

 

(defun c:demo ( / ss totalLayout ent layname)
(if (setq ss (ssget "x" '((0 .  "INSERT")(2 . "Titleblock_Properties_22x34"))))
  (progn
    (setq totalLayout (itoa  (length (layoutlist))))    
	  (repeat (setq i (sslength ss))
	   (setq ent (ssname ss (setq i (1- i)))
		 layname (cdr (assoc 410 (entget ent))))
			(_AttFunc ent (mapcar 'list
					  '("LTSHTDESC" "LTSHTNUM") 
					  	(list (strcat layname " OF " totalLayout) layname)))
	    )
    )
  ); if
  (princ)
  )

 

Let me know what in mind so we can complete the code, perhaps we can use fields value for both TAGS 

 

  • Like 1
Posted

There is no problem using lisp to get and put between Acad/Briscad <-> Excel, you can do many tasks.

Open a new excel or use current open

Get, put a cell

Get a range of cells selected inside excel or get all cells

Make new sheets

To mention a few tasks  

 

If you want examples just ask.

 

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...