Jump to content

Lisp to sum attributes in blocks and write in another


Philipp

Recommended Posts

Hello!

 

Im new to programming tools for autocad so I wanted to ask here if anyone got a solution for the following problem to learn how its done:

 

I want to run a program that allows me to sum attributes of selected objects and then put in a block with the sum directly written into an attribute.

All of my blocks have the same attributes such as power consumtion

 

So the program should do this:

1. Select blocks in drawing (for example Block A, Block B...)

2. Select attribute (for example "power consumtion" [Value=140, 95, 50])

3. Sum the Values [285]

4. Chose Block to be written in (f.E. Block XY)

5. Place Block with the value insertet

 

Id really appriciate if someone could give me a link to a tutorial to get this done easily, or a program!

 

 

Link to comment
Share on other sites

It would be best to post a dwg with a before and after, copy your blocks in dwg , comments etc are good. Its not a hard question to answer lots of code examples out there. 

 

Should steps 4 5 be other way around ?

Edited by BIGAL
Link to comment
Share on other sites

Attributes can be accessed through functions that's already existing: Attribute Functions.

 

Otherwise I suggest this. I left some comments in there for you to learn:

 

(defun c:foo ( / att blk ent ex i rtn ss zin)

    (setq att nil)	; For this program, in the event that you don't want the command to prompt the user for the tag name, set this to the tag name
    
    (and	; AND can be used to stop the command as soon as the first expression hits nil.
	(setq ss (ssget '((0 . "INSERT") (66 . 1))))	; First, prompt user to select the blocks containing the attributes to sum.
	(if att
	    (setq att (strcase att))
	    (progn
		(while
		    (progn
			(setvar 'errno 0)
			(initget "Name")	; Initialise a prompt input for user
			(setq att (nentsel "\nSelect an attribute whose tag will be summed or [Name] <exit>: "))	; Ask the user to either select an attribute, or choose "Name" to enter the tag name manually.
															; In this command, I prompt user to select an attribute, and take the name of that tag to sum.
			(cond
			    ((= (getvar 'errno) 7) (princ "\nNothing selected"))	; If the variable "ERRNO" equals 7 after prompting for selection (like ENTSEL, NENTSEL, NENTSELP), this indicates that the user misses a selection.
			    ((null att) nil)	; The only other way NENTSEL, ENTSEL, and NENTSELP returns nil is if the user presses Enter.
			    ((eq att "Name") (not (setq att (strcase (getstring "\nEnter name of tag to sum: ")))))	; If the user clicks "Name", prompts the user to enter the name.
			    												; The NOT is so that the whole COND function would return NIL, thus exitting the WHILE loop.
			    ((not (eq (cdr (assoc 0 (setq att (entget (car att))))) "ATTRIB"))
			     (princ "\nNo attributes detected")
			     )
			    ((not (setq att (strcase (cdr (assoc 2 att))))))
			    )
			)
		    )
		att		; This is so that the whole PROGN function returns the value of 'att'. In the event that the user presses Enter to exit, 'att' will be nil, stopping the whole command.
		)
	    )
	(while
	    (progn
		(setvar 'errno 0)
		(setq blk (entsel "\nSelect the block containing the attribute to update <exit>: "))
		(cond
		    ((= (getvar 'errno) 7) (princ "\nNothing selected"))
		    ((not blk) nil)
		    ((not (eq (cdr (assoc 0 (setq blk (car blk) ex (entget blk)))) "INSERT"))
		     (princ "\nObject is not a block reference")
		     )
		    ((/= (cdr (assoc 66 ex)) 1)
		     (princ "\nBlock does not contain any attributes")
		     )
		    ((setq rtn 0)	; Here's where the user has successfully selected everything.
		     (repeat (setq i (sslength ss))	; Loop through every single entity/block that was selected by the user initially for summing
			 (setq i (1- i) ent (ssname ss i))
			 (while
			     (and ent (/= (cdr (assoc 0 (setq ent (entnext ent) ex (entget ent)))) "SEQEND"))	; Use ENTNEXT on the block to access its attributes. Once the list of attributes are over, the entity will be called "SEQEND"
			     (and
				 (eq (cdr (assoc 0 ex)) "ATTRIB")	; Check if the object is really an attribute
				 (eq (strcase (cdr (assoc 2 ex))) att)	; Check if the tag of the attribute is the same as what the user provided
				 (setq ent nil rtn (+ rtn (atof (cdr (assoc 1 ex)))))	; Take its value, and add up.
				 )
			     )
			 )
		     ;; Here is where the single block to update the sum value will be updated.
		     
		     (while
			 (and blk (/= (cdr (assoc 0 (setq blk (entnext blk) ex (entget blk)))) "SEQEND"))
			 (and
			     (eq (cdr (assoc 0 ex)) "ATTRIB")
			     (eq (strcase (cdr (assoc 2 ex))) att)
			     (progn
				 (setq zin (getvar 'dimzin))	; 'DIMZIN is a system variable that controls how zeros will be surpressed when using RTOS
				 (setvar 'dimzin 8)		; Setting DIMZIN to 8 will surpress trailing zeros on decimals. (Example: (rtos 2.0000 2) -> "2")
				 				; If DIMZIN was set to 0, then (rtos 2.0000 2) -> "2.0000"
				 (entmod (subst (cons 1 (rtos rtn 2)) (assoc 1 ex) ex))
				 (setq blk (not (setvar 'dimzin zin)))
				 )
			     )
			 )
		     )
		    )
		)
	    )
	)
    (princ)
    )

 

Link to comment
Share on other sites

Thanks for the fast reply!

Unfortunately Im on my first steps understanding how lisp programs work and I cannot follow the steps in the code, so I made a little dwg file to show what I want to do:

 

I have built up a decent blocklibrary recently and now I want to optimize my calculations. Normally Id make attexport to cvs or so and calculate afterwards.

My goal is now the following:

I have too areas (Room-1 and Room 2) with blocks that are built up similar. Every Block has a attribute named "LEISTUNG".

I want to have a tool wich let me select an area (for Example: polyline of Room-2), next to check the Layer (here it would be "VERBRAUCHER-2"), then sum up the attribute-values "LEISTUNG" from the Blocks ENP105 and ENP106 automatically and insert a new Block "ENP200" with the value for sum of the power-consumption ("LEISTUNG") of the Entitys.

 

I found tutorials that can sum up values, but they all write the result in a table, wich Id have to manipulate again.

 

 

NONAME_2.dwg

Link to comment
Share on other sites

Also, the Program works quite nice, thanks very much!

 

The only thing I can complain is that my cad software hase no cross-polygon command but thats not your fault.

Could you maybe put in a selection query in front of it for area of polygon and layer?

Link to comment
Share on other sites

There is one thing that confuses me why are the blocks 3 individual names 100 105 106 I expect more exist, when you have the same number of attributes and what looks like similar attribute values can see the  "LEISTUNG" in the block ? You can change the attribute value EPN105 etc in each block.

 

Its making it over complicated to find answers, when you insert the block you do so on a layer so you do have that ability to make some choices. Is there something in the ENP100 as to why it would not be added to 105 106 see 3 in room ? It would be much easier to do a get blocks of 1 name in a room and do a range 100+ or 101-107 etc the code would add the EPN so EPN105 + EPN106.

 

I try to write more global answers so don't get next request, "I need for 5 different block names".

 

How do you know which layer to use if you forget which layer a block is on. The task is very easy if the dwg is easy to search.

 

What software are you using ?

 

A sample just pick the 3 blocks in the room. What software are you using should just be able to pick point inside room rest just happens. 

 


(defun c:whatuwant ( / ss att x tot)
; put range test here
(setq ss (ssget (list (cons 0 "INSERT"))))
(setq tot 0.0)
(if (= ss nil)
(alert "No blocks picked try again ")
  (progn
    (repeat (setq x (sslength ss))
      (foreach att (vlax-invoke (vlax-ename->vla-object (ssname SS (setq x (- x 1)))) 'getattributes)
        (if (=  "LEISTUNG"(strcase (vla-get-tagstring att))) 
		; add a range here to test for 105-108
        (setq tot (+ tot (atof (vla-get-textstring att)) ))
        )
      )
    )
  )
)
(command "-insert" "ENP200" (getpoint "\nPick point for block")  1 1 0 "" "" "" "" (rtos tot 2 0) "" "" "" "" "")

(princ)
)

 

 Do you need a better way of inserting the blocks into the room ? Or is it to do with extracting block counts etc, that can be improved also, its often make a new block for every insert which is not a good way to go.

 

Ps when inserting EPN200 what about all the other attribute values do they need to be changed so correct before insert, I have a way of doing that. 

 

 

image.png.7d54d62f8e35451122ed4b4a2b2833ef.png

Link to comment
Share on other sites

Im using the newest version of ares commander. I get unrecognized command here after placing the distributor.

Maybe the insertion of ENP200 is not necessary because I can easily manipulate it after placing.

 

The program from Jonathan was exactly what I need for this, only the selection of all elements inside or crossing a polygon, and filter them by layer would be nice.

 

Ill have a lisp-program to read out the length of a drawn distribur cable in an attribute and area reading tool (for the m²) too, so when it works, after attexport a predifined excel will show me necessary cables, information about distribution areas, benchmarks to calculate the cost efficience and so on.

 

 

 

Link to comment
Share on other sites

For layers, it's easy. This can be appended into the ssget filter. So in my code, it's currently set to (ssget '((0 . "INSERT") (66 . 1))). The DXF code for layers is number 8. So by appending the dotted pair (8 . "VERBRAUCHER-2") into the filter like so, you can select the whole room, and only blocks containing attributes residing in the layer VERBRAUCHER-2 will be highlighted:

 

(ssget '((0 . "INSERT") (8 . "VERBRAUCHER-2") (66 . 1)))

 

Link to comment
Share on other sites

(setq ssvariable "LAYERNAME")

(setq ss(ssget ((0 . "INSERT") (list (cons 8 ssvariable) (66 . 1)))

Can I tweak the program to check the layer I want to select after block selection like this?

 

How do I write the list of layers to chose from like:

"VERBRAUCHER-1"

"VERBRAUCHER-2"

"VERBRAUCHER-3"?

 

Edited by Philipp
Link to comment
Share on other sites

Here are a couple of ways:

(ssget ((0 . "INSERT") (8 . "VERBRAUCHER-[1-3]") (66 . 1)))
(ssget ((0 . "INSERT") (8 . "VERBRAUCHER-1,VERBRAUCHER-2,VERBRAUCHER-3]") (66 . 1)))

 

Link to comment
Share on other sites

(defun c:foo ( / att blk ent ex i rtn ss zin)

    (setq att nil)	; For this program, in the event that you don't want the command to prompt the user for the tag name, set this to the tag name
    
    (and	; AND can be used to stop the command as soon as the first expression hits nil.
	(setq ss (ssget '((0 . "INSERT")  (8 . "VERBRAUCHER-2")  (66 . 1))))	; First, prompt user to select the blocks containing the attributes to sum.
	(if att
	    (setq att (strcase att))
	    (progn
		(while
		    (progn
			(setvar 'errno 0)
			(initget "Name")	; Initialise a prompt input for user
			(setq att (nentsel "\nSelect an attribute whose tag will be summed or [Name] <exit>: "))	; Ask the user to either select an attribute, or choose "Name" to enter the tag name manually.
															; In this command, I prompt user to select an attribute, and take the name of that tag to sum.
			(cond
			    ((= (getvar 'errno) 7) (princ "\nNothing selected"))	; If the variable "ERRNO" equals 7 after prompting for selection (like ENTSEL, NENTSEL, NENTSELP), this indicates that the user misses a selection.
			    ((null att) nil)	; The only other way NENTSEL, ENTSEL, and NENTSELP returns nil is if the user presses Enter.
			    ((eq att "Name") (not (setq att (strcase (getstring "\nEnter name of tag to sum: ")))))	; If the user clicks "Name", prompts the user to enter the name.
			    												; The NOT is so that the whole COND function would return NIL, thus exitting the WHILE loop.
			    ((not (eq (cdr (assoc 0 (setq att (entget (car att))))) "ATTRIB"))
			     (princ "\nNo attributes detected")
			     )
			    ((not (setq att (strcase (cdr (assoc 2 att))))))
			    )
			)
		    )
		att		; This is so that the whole PROGN function returns the value of 'att'. In the event that the user presses Enter to exit, 'att' will be nil, stopping the whole command.
		)
	    )
	(while
	    (progn
		(setvar 'errno 0)
		(setq blk (entsel "\nSelect the block containing the attribute to update <exit>: "))
		(cond
		    ((= (getvar 'errno) 7) (princ "\nNothing selected"))
		    ((not blk) nil)
		    ((not (eq (cdr (assoc 0 (setq blk (car blk) ex (entget blk)))) "INSERT"))
		     (princ "\nObject is not a block reference")
		     )
		    ((/= (cdr (assoc 66 ex)) 1)
		     (princ "\nBlock does not contain any attributes")
		     )
		    ((setq rtn 0)	; Here's where the user has successfully selected everything.
		     (repeat (setq i (sslength ss))	; Loop through every single entity/block that was selected by the user initially for summing
			 (setq i (1- i) ent (ssname ss i))
			 (while
			     (and ent (/= (cdr (assoc 0 (setq ent (entnext ent) ex (entget ent)))) "SEQEND"))	; Use ENTNEXT on the block to access its attributes. Once the list of attributes are over, the entity will be called "SEQEND"
			     (and
				 (eq (cdr (assoc 0 ex)) "ATTRIB")	; Check if the object is really an attribute
				 (eq (strcase (cdr (assoc 2 ex))) att)	; Check if the tag of the attribute is the same as what the user provided
				 (setq ent nil rtn (+ rtn (atof (cdr (assoc 1 ex)))))	; Take its value, and add up.
				 )
			     )
			 )
		     ;; Here is where the single block to update the sum value will be updated.
		     
		     (while
			 (and blk (/= (cdr (assoc 0 (setq blk (entnext blk) ex (entget blk)))) "SEQEND"))
			 (and
			     (eq (cdr (assoc 0 ex)) "ATTRIB")
			     (eq (strcase (cdr (assoc 2 ex))) att)
			     (progn
				 (setq zin (getvar 'dimzin))	; 'DIMZIN is a system variable that controls how zeros will be surpressed when using RTOS
				 (setvar 'dimzin 8)		; Setting DIMZIN to 8 will surpress trailing zeros on decimals. (Example: (rtos 2.0000 2) -> "2")
				 				; If DIMZIN was set to 0, then (rtos 2.0000 2) -> "2.0000"
				 (entmod (subst (cons 1 (rtos rtn 2)) (assoc 1 ex) ex))
				 (setq blk (not (setvar 'dimzin zin)))
				 )
			     )
			 )
		     )
		    )
		)
	    )
	)
    (princ)
    )

The program Jonathan wrote, looks now like this. I can now replace the line ( 8 . "VERBRAUCHER-2") into the layer I want to chose, which would be paracticable.

Even better would be if the program gives me a list of layers to chose.

 

If I write (8 . "VERBRAUCHER-1,VERBRAUCHER-2,VERBRAUCHER-3]") it does not check for the layer anymore

 

edit: ok I see now I extend the filter with more arguments, but can I chose from a drop down menu?

 

I think Ill have to buy a book to understand that lisp-routines, any recommandations?

Edited by Philipp
Link to comment
Share on other sites

Ah, I see what you're after. Here in this case, you'd want to look at the getkword function with the help of initget. So first you use initget to initialise into AutoCAD the choices the user would like to make:

 

(initget "VERBRAUCHER-1 VERBRAUCHER-2 VERBRAUCHER-3")

 

After that you prompt the user for the choice like:

 

(setq ly (getkword "\nSpecify layer to check [VERBRAUCHER-1/VERBRAUCHER-2/VERBRAUCHER-3] <exit>: "))

 

So in total, it will look like:

 

    (and	; AND can be used to stop the command as soon as the first expression hits nil.

	(progn
	    (initget "VERBRAUCHER-1 VERBRAUCHER-2 VERBRAUCHER-3")
	    (setq ly (getkword "\nSpecify layer to check [VERBRAUCHER-1/VERBRAUCHER-2/VERBRAUCHER-3] <exit>: "))
	    )
	(setq ss (ssget (list '(0 . "INSERT") (cons 8 ly) '(66 . 1))))	; First, prompt user to select the blocks containing the attributes to sum.
	(if att
          
.... The rest of the code

 

Once you've gotten the hang of how the language works, I suggest that, as opposed to the above approach, you open your own list of the layers to check (like how I set 'att' to either nil, or your own attribute tag string), and then use some List To String conversion functions to put them with the initget and the getkword message.

 

(P.S. As opposed to buying a book, I self-taught myself through google-searching every single function I know up to date one-by-one, so there are still heaps of methods and functions that I'm not aware of. The rest is just experience. :P)

  • Like 1
Link to comment
Share on other sites

Very Nice!

So here is the tool if anyone wants to use it:

 

Its possible to check all blocks in a distribution area via cp [cross-polygon] in autocad, then use the routine and summon up the maximal power consumtion of distributors, batteries, aggregats or other systems directly, no export is needed.

In Revit or other specific Electrical-Programs this is done automatically, but my goal is to work with ifc-files in cad programs and this a really nice feature for it.

example.dwg foo.lsp

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