Lisp to calculate total length By Layer fix

Lisp to calculate total length By Layer fix

kylei7449
Enthusiast Enthusiast
10,769 Views
28 Replies
Message 1 of 29

Lisp to calculate total length By Layer fix

kylei7449
Enthusiast
Enthusiast

I'd like to get this code modified so it will 1) return length by default (doesn't ask for user input), and 2) returns the total length in feet & inches. Here is the code I found that will work for what I need...essentially the same as TLEN, but this selects By Layer, which for my application is much more useful (as you only need to select one entity).

 

Thanks

 

; Length/Area of Polyline by Layer
; David Bethel May 2004 from an original idea by David Watson
; This command will give a total area or length for all polylines on a specified layer.
;
(defun c:zone ( / ss la rv i tv op en)

(while (not ss)
(princ "\nPick any object on the required layer")
(setq ss (ssget)))

(initget "Length Area")
(setq rv (getkword "\nWould you like to measure Length/<Area> : "))
(and (not rv)
(setq rv "Area"))

(setq la (cdr (assoc 8 (entget (ssname ss 0))))
ss (ssget "X" (list (cons 0 "*POLYLINE")
(cons 8 la)))
i (sslength ss)
tv 0
op 0)
(while (not (minusp (setq i (1- i))))
(setq en (ssname ss i))
(command "_.AREA" "_E" en)
(cond ((= rv "Length")
(setq tv (+ tv (getvar "PERIMETER"))))
(T
(setq tv (+ tv (getvar "AREA")))
(if (/= (logand (cdr (assoc 70 (entget en))) 1) 1)
(setq op (1+ op))))))

(princ (strcat "\nTotal " rv
" for layer " la
" = " (rtos tv 2 2)
" in " (itoa (sslength ss)) " polylines\n"
(if (/= rv "Length")
(strcat (itoa op) " with open polylines") "")))
(prin1))

0 Likes
Accepted solutions (2)
10,770 Views
28 Replies
Replies (28)
Message 2 of 29

Ranjit_Singh
Advisor
Advisor

Something like this may be. Minimal testing.

(defun c:zone  (/ ss la rv i tv op en)
 (while (not ss) (princ "\nPick any object on the required layer") (setq ss (ssget))) ;(initget "Length Area")
 ;(setq rv (getkword "\nWould you like to measure Length/<Area> : "))
 ;(and (not rv) (setq rv "Area"))
 (setq la (cdr (assoc 8 (entget (ssname ss 0))))
       ss (ssget "X" (list (cons 0 "*POLYLINE") (cons 8 la)))
       i  (sslength ss)
       tv 0
       op 0
       rv "length")
 (while (not (minusp (setq i (1- i))))
  (setq en (ssname ss i)) ;(command "_.AREA" "_E" en)
  (cond ((= rv "Length") (setq tv (+ tv (getvar "PERIMETER"))))
        (t
         (setq tv (+ tv (getvar "AREA")))
         (if (/= (logand (cdr (assoc 70 (entget en))) 1) 1)
          (setq op (1+ op))))))
 (princ (strcat "\nTotal "
                rv
                " for layer "
                la
                " = "
                (rtos tv 4 2)
                " in "
                (itoa (sslength ss))
                " polylines\n"
                (if (/= rv "Length")
                 (strcat (itoa op) " with open polylines")
                 "")))
 (prin1))
0 Likes
Message 3 of 29

kylei7449
Enthusiast
Enthusiast

Thanks for the quick turn around, it does the first part but it returns 0". It loads and runs without any errors but it's not returning the total length.

0 Likes
Message 4 of 29

Kent1Cooper
Consultant
Consultant

@kylei7449 wrote:

Thanks for the quick turn around, it does the first part but it returns 0". It loads and runs without any errors but it's not returning the total length.


That would be because the AREA command is commented out:

 

  (setq en (ssname ss i)) ;(command "_.AREA" "_E" en)

 

It needs to run that command on an object in order to set the area and perimeter into the AREA and PERIMETER System Variables, so it will have numbers to add up.  Try removing the semicolon.

 

[And by the way, though E (presumably for Entity) is still accepted as an option in the AREA command, at least as far as Acad2016 that I have here, the option as listed in the prompt has been O for Object for a long time now.  It might be worth changing that, in case they drop E as an accepted option in the future.]

Kent Cooper, AIA
Message 5 of 29

Kent1Cooper
Consultant
Consultant

But there's no need to go through the AREA command if you're restricting it to Polylines and want only their lengths.  They have a Length property that's directly available, avoiding the AREA command and the use of the PERIMETER System Variable.

 

Also, it looks like the reporting of how many open Polylines there were was part of what happened when you chose the Area option, but if you're looking only for Length, do you need to know that?

 

I've assumed that by "returns the total length in feet & inches" you mean rounded to the nearest whole inch.  If not, adjust the 0 in the (rtos) function below for the precision you need.

 

Another little thing....  Your code pulls the Layer name out of its association with an 8 in the entity data list, and then later puts it back together with another 8 to use in the (ssget) filter list.  You can just take the whole 8-code entry directly.  [I would have omitted the 'lay' variable entirely, and just used (assoc 8 edata) right in the (ssget) filter list, if not for the inclusion of the Layer name in the report of the total length at the end.]

 

And I've built the selection differently, to avoid the "Select objects: " [always in the plural]  prompt that (ssget) always uses, which is annoying when you only want a single object selected, and is always on a new line so the prompt you spelled out to precede it has moved up, and to avoid potential problems if a User selects more than one thing [though that can be prevented, with more code].

 

Here's my take on it, lightly tested:

 

;|
Length of Polylines by Layer
This command will give a total length for all Polylines on the Layer
  of a selected one.
|;
(defun c:zone (/ esel edata lay ss total)
  (while
    (not
      (and
        (setq esel (entsel "\nPick any Polyline on the required layer"))
        (setq edata (entget (car esel)))
        (wcmatch (cdr (assoc 0 edata)) "*POLYLINE")
      ); and
    ); not
    (prompt "\nNothing selected, or not a Polyline.")
  ); while
  (setq
    lay (assoc 8 edata)
    ss (ssget "_X" (list (cons 0 "*POLYLINE") lay))
    total 0
  ); setq
  (repeat (setq n (sslength ss))
    (setq total (+ total (vla-get-Length (vlax-ename->vla-object (ssname ss (setq n (1- n)))))))
  ); repeat
  (prompt (strcat "\nTotal Length for Layer " (cdr lay) " = " (rtos total 4 0) "."))
  (princ)
); defun
(vl-load-com)
Kent Cooper, AIA
0 Likes
Message 6 of 29

kylei7449
Enthusiast
Enthusiast

Thanks, yes the second version worked fine. The first code kind of worked, but was quirky when run & wouldn't calculate the feet & inches correctly. Thanks for your input....next will be altering it to work via the selected polyline's linetype instead of by layer. 

0 Likes
Message 7 of 29

Kent1Cooper
Consultant
Consultant

@kylei7449 wrote:

.....next will be altering it to work via the selected polyline's linetype instead of by layer. 


In that case, be careful about the difference between a linetype of BYLAYER, with the linetype of the Layer determining that of the Polyline, and an override linetype assignment, making the linetype of the Polyline independent of that of its Layer.  The former has no linetype entry in entity data, so the selection becomes more complicated [but not impossible] -- not a simple (ssget) function with a filter list, unless every one on that Layer would be BYLAYER and no others would have that linetype as an override.  But in that case you'd select them by Layer rather than linetype, anyway.  If you might have some of each variety resulting in the same linetype, and you want to find all of them, it's more complicated still [but also doable].

Kent Cooper, AIA
0 Likes
Message 8 of 29

kylei7449
Enthusiast
Enthusiast

Attached is the TLEN code I'm trying to modify. What I need it to do is return the total lengths of pwpolylines by linetype (w/ no user selection). All the linetypes are on the same layer so it would be linetype overrides. The second half of the program I don't have yet - it's to get areas (see below).

 

 

(defun C:TLEN2 (/ ss tl n ent itm obj l)
(setq ss (ssget "x"
  foreach lt '("get_linetype1" "get_linetype2" "get_linetype3" "get_linetype4" "get_linetype5" "get_linetype6")))  ;;etc. however many linetypes I need to evaluate

;;so this would need to loop to check drawing for each linetype, return total length 

 

tl 0
n (1- (sslength ss)))
(while (>= n 0)
(setq ent (entget (setq itm (ssname ss n)))
obj (cdr (assoc 0 ent))
l (cond
((= obj "LINE")
(distance (cdr (assoc 10 ent))(cdr (assoc 11 ent))))
((= obj "ARC")
(* (cdr (assoc 40 ent))
(if (minusp (setq l (- (cdr (assoc 51 ent))
(cdr (assoc 50 ent)))))
(+ pi pi l) l)))
((or (= obj "CIRCLE")(= obj "SPLINE")(= obj "POLYLINE")
(= obj "LWPOLYLINE")(= obj "ELLIPSE"))
(command "_.area" "_o" itm)
(getvar "perimeter"))
(T 0))
tl (+ tl l)
n (1- n)))
(alert (strcat "Total length of selected objects is " (rtos tl)))
(princ)
)

 

 

The other half of this program needs to query and return square footages of hatches based on layer. Problem is self intersecting boundaries. Is there a means to extract the area (without any further user input) from a hatch if it returns a "0" area?? The Area command requires user input to select the boundary, but it ignores islands. Hatches are easy and perfect, but for this one thing. The only work-around so far is to recreate the hatch boundary by simply offsetting the self-intersecting boundary (say by 1/1000th of an inch).

 

"What if" you use the 'last' object in the code?? Say you do this: the program loops through looking for hatches By Layer, when it comes across a hatch it tries to return the area: if it can't, it's nil ("0"), it then offsets the existing hatch boundary (last object) by 1/1000 inch, creates a new hatch from the offset boundary (using a hatch pattern & scale specified in the code; say ANSI 37 100), then gets the area of the new hatch? Then the loop continues on to the next layer and so on.

 

The whole purpose of this 'simple' program is to query the drawing database for object properties without any further user input. (I don't want to have to redraw or recreate items--too many, too time consuming) much like how the LIST function works. But, I code in what it's looking for (the linetypes & layers for the hatches)

 

Anybody have any ideas???

0 Likes
Message 9 of 29

kylei7449
Enthusiast
Enthusiast

sorry, "lwpolylines"

0 Likes
Message 10 of 29

kylei7449
Enthusiast
Enthusiast

Updated code to close foreach loop...(still returns an error though ("bad point argument")

 

 

;|

TLEN.LSP - Total LENgth of selected objects
(c) 1998 Tee Square Graphics

|;

(defun C:TLEN2 (/ ss tl n ent itm obj l)
(setq ss (ssget "x"
foreach lt '("get_linetype1" "get_linetype2" "get_linetype3" "get_linetype4" "get_linetype5" "get_linetype6"))

tl 0
n (1- (sslength ss)))
(while (>= n 0)
(setq ent (entget (setq itm (ssname ss n)))
obj (cdr (assoc 0 ent))
l (cond
((= obj "LINE")
(distance (cdr (assoc 10 ent))(cdr (assoc 11 ent))))
((= obj "ARC")
(* (cdr (assoc 40 ent))
(if (minusp (setq l (- (cdr (assoc 51 ent))
(cdr (assoc 50 ent)))))
(+ pi pi l) l)))
((or (= obj "CIRCLE")(= obj "SPLINE")(= obj "POLYLINE")
(= obj "LWPOLYLINE")(= obj "ELLIPSE"))
(command "_.area" "_o" itm)
(getvar "perimeter"))
(T 0))
tl (+ tl l)
n (1- n)))

(alert (strcat "Total length of selected objects is " (rtos tl))
) ;close foreach loop

(princ)
)

0 Likes
Message 11 of 29

Kent1Cooper
Consultant
Consultant

@kylei7449 wrote:

 

....

(defun C:TLEN2 (/ ss tl n ent itm obj l)
(setq ss (ssget "x"
foreach lt '("get_linetype1" "get_linetype2" "get_linetype3" "get_linetype4" "get_linetype5" "get_linetype6"))

tl 0....

(alert (strcat "Total length of selected objects is " (rtos tl))
) ;close foreach loop

....


You can't embed a (foreach) function inside an (ssget) function like that [even if you do include the opening left parenthesis before the word foreach].  That would have to be structured differently, something like:

....

(defun C:TLEN2 (/ ss tl n ent itm obj l)
  (foreach lt '("get_linetype1" "get_linetype2" "get_linetype3" "get_linetype4" "get_linetype5" "get_linetype6")
    (setq

      ss (ssget "x" (list (cons 6 lt)))
      tl 0

  ....

    (prompt (strcat "Total length of selected objects of " lt " linetype is " (rtos tl)))
  ) ;close foreach loop

....

 

See whether you can get that to work [I didn't dig deeply into the rest of it].  I changed the (alert) to (prompt) so there would be a remaining "record" in the command-line history of the results for every linetype, and it includes the linetype names in the prompts.

 

Consider also an (if) function around the (ssget), so that it doesn't try to find a total length if there are no objects of a given linetype, which will cause an error otherwise.

 

Another question:  Your alert says "of selected objects", but your (ssget) has the "X" in it for searching the entire drawing without User selection.  What's the intent?

Kent Cooper, AIA
0 Likes
Message 12 of 29

kylei7449
Enthusiast
Enthusiast

;|

TLEN.LSP - Total LENgth of selected objects
(c) 1998 Tee Square Graphics

|;

(defun C:TLEN2 (/ ss tl n ent itm obj l lt)

 

 (foreach lt '("get_linetype1" "get_linetype2" "get_linetype3" "get_linetype4" "get_linetype5" "get_linetype6")

 

      (If (setq ss (ssget "x" (list (cons 6 lt)))

          tl 0

            n (1- (sslength ss)))
              (while (>= n 0)
                (setq ent (entget (setq itm (ssname ss n)))
                  obj (cdr (assoc 0 ent))
                  l (cond
                      ((= obj "LINE")
                      (distance (cdr (assoc 10 ent))(cdr (assoc 11 ent))))
                      ((= obj "ARC")
                      (* (cdr (assoc 40 ent))
               (if (minusp (setq l (- (cdr (assoc 51 ent))
                  (cdr (assoc 50 ent)))))
                  (+ pi pi l) l)))
                  ((or (= obj "CIRCLE")(= obj "SPLINE")(= obj "POLYLINE")
                  (= obj "LWPOLYLINE")(= obj "ELLIPSE"))
                  (command "_.area" "_o" itm)
                  (getvar "perimeter"))
                  (T 0))
                  tl (+ tl l)
                  n (1- n)) ; eliminate one bracket here

            (prompt (strcat "Total length of objects is " (rtos tl)))) ;;;note, add bracket to move this into while loop

       ) ;close if statement 
  ) ;close foreach loop


(princ)
)

 

Thank you for the direction, I did get this to run however it still has issues. 

 

Testing on a clean drawing experimenting with just two linetypes (4 polylines total) (meaning 2 plines of each linetype) it gives you the total length of each entity (4 returns), but also gives you the aggregate total length of the two linetypes. Which is good, that's exactly what I need. However, it needs to return the linetype name so it can be identified with the length (otherwise you have no idea what the lengths are associated with). I also need to tweak it to get rid of the area call, I just need the length. But this is where it's at now. (it also gives a bad argument type as program terminates) 

0 Likes
Message 13 of 29

Ranjit_Singh
Advisor
Advisor

Here is one more example. Adjust the linetype names and output format as needed.

;;Ranjit Singh
;;7/25/17
(defun c:somefunc  (/ lst lst2)
  (setq lst (vl-sort (mapcar '(lambda (x) (cons (getpropertyvalue x "Length") (cdr (assoc 6 (entget x)))))
                             (mapcar 'cadr (ssnamex (ssget "_x" '((0 . "lwpolyline") (6 . "NP8,G3,SD12"))))))
                     '(lambda (x y) (< (cdr x) (cdr y)))))
  (mapcar '(lambda (x)
             (if (null (member x lst2))
               (setq lst2 (cons x lst2))))
          (mapcar '(lambda (x)
                     (cons (cdr x)
                           (apply '+
                                  (vl-remove nil
                                             (mapcar '(lambda (y)
                                                        (if (= (cdr y) (cdr x))
                                                          (car x)))
                                                     lst)))))
                  lst))
  (mapcar '(lambda (x) (princ (strcat (car x) " - " (rtos (cdr x) 2 2) "\n"))) lst2)
  (princ (strcat "Total length of selected objects is " (rtos (apply '+ (mapcar 'cdr lst2)) 2 2)))
  (princ))

Length_by_lt.gif

 

0 Likes
Message 14 of 29

Kent1Cooper
Consultant
Consultant

@kylei7449 wrote:

.... 

      (If (setq ss (ssget "x" (list (cons 6 lt)))

          tl 0

            n (1- (sslength ss)))
....

 

.... it needs to return the linetype name so it can be identified with the length (otherwise you have no idea what the lengths are associated with). I also need to tweak it to get rid of the area call, I just need the length. ....


You adopted my change of function from (alert) to (prompt), but you didn't adopt my adjustment of the prompt itself to include the linetype name with its corresponding total length.

 

The AREA command is a way of getting at the length of certain objects, not to get the area but in the process to set the PERIMETER System Variable value, which it then reads.  It's a way of getting at the length of more complicated objects where you can't use the kinds of calculations it does with Lines or Arcs.  BUT there's one universal  way of getting the length from any of those things, eliminating a heck of a lot of code from the routine, like this:

 

(vlax-curve-getDistAtParam ent (vlax-curve-getEndParam ent))

 

No need for all that handling of different object types in different ways, no AREA command needed for some of them, no calculations from extracted (assoc) values.

 

There's also a problem with the code portion quoted above.  You should have whether there are any of a given linetype be the one test in the (if) function.  As you have it:

 

      (If

        (setq

          ss (ssget "x" (list (cons 6 lt)))

          tl 0

          n (1- (sslength ss))

        ); setq

 

that (setq function will have a problem if there are none when it get to setting 'n' -- ss will be nil [not a selection set with 0  items in it, but just nothingness], and you'll get an error message from trying to get the length of nil.  That's probably where your bad-argument error is coming from, when it looks that way at one of the other linetypes in the list.  Do this instead:

 

(if

  (setq ss (ssget "x" (list (cons 6 lt)))); close the (setq) for a test of the one question first, and if it finds any, then go on to:

  (progn ; 'then' expression

    (setq

      tl 0

      n (1- (sslength ss))

....

 

You can simplify:

 

(foreach

....

  (if

    (setq ss (ssget "x" (list (cons 6 lt))))

    (progn ; 'then' expression

      (repeat (setq n (sslength ss)); instead of (while)

        (setq

          ent (entget (setq itm (ssname ss (setq n (1- n))))); step 'n' down here instead of later

;;; [no need for 'obj' -- just set 'l' to that (vlax-curve...) function above]

....

      ); repeat

      (prompt ....

    ); progn

  ); if

); foreach

Kent Cooper, AIA
0 Likes
Message 15 of 29

kylei7449
Enthusiast
Enthusiast

Ok, got the linetype names to print...just missing the var holding the linetype names in the strcat. One down.

0 Likes
Message 16 of 29

kylei7449
Enthusiast
Enthusiast

Thanks Ranjit, I will give this one a try.

0 Likes
Message 17 of 29

kylei7449
Enthusiast
Enthusiast

Yes, this is closest to the output I'm looking for. My next task is to get it to give me the aggregate total including multiples. So, if I have multiples of the same linetype it would give me the total lengths of that linetype instead of listing them separately. 

 

Example, get_linetype1 = 5'-0", and if I have another segment of get_linetype1 at = 7'-0", it returns them as separate values instead of the aggregate of 12'-0". So what I need it to do is give me the aggregate total for each linetype. I will work on it...thanks, this is the what I was looking for.

0 Likes
Message 18 of 29

Kent1Cooper
Consultant
Consultant
Accepted solution

@kylei7449 wrote:

.... if I have multiples of the same linetype it would give me the total lengths of that linetype instead of listing them separately. 

 

Example, get_linetype1 = 5'-0", and if I have another segment of get_linetype1 at = 7'-0", it returns them as separate values instead of the aggregate of 12'-0". So what I need it to do is give me the aggregate total for each linetype. I will work on it...thanks, this is the what I was looking for.


It looks like the code in Post 13 is for only Polylines, which is clearly not in line with your original intent.  And if expanded to handle more entity types, unless also otherwise significantly enhanced it would be limited to those that have a Length property, which is also not true of all the types in your original.

 

I'm tellin' ya, it can be a lot simpler and  more universal:

(defun C:TLxLT ; = Total Length [by] LineType
  (/ ss tl n ent)
  (foreach lt '("get_linetype1" "get_linetype2" "get_linetype3" "get_linetype4" "get_linetype5" "get_linetype6")
    (if (setq ss (ssget "_X" (list (cons 6 lt) '(0 . "*LINE,CIRCLE,ARC,ELLIPSE"))))
      (progn ; then
        (setq tl 0)
        (repeat (setq n (sslength ss))
          (setq
            ent (ssname ss (setq n (1- n)))
            tl (+ tl (vlax-curve-getDistAtParam ent (vlax-curve-getEndParam ent))); FOR ALL TYPES
          ); setq
        ); repeat
        (prompt (strcat "\nTotal length of selected objects of " lt " linetype is " (rtos tl)))
      ); progn
    ); if
  ); foreach
  (princ)
); defun
(prompt "\nType TLxLT for Total Lengths by LineType.")
(vl-load-com)

Put your real linetypes into the list near the top, of course.

 

The *LINE part of the filter list covers Lines, Polylines of all types, and Splines, but If  you ever use XLINEs or MLINEs with any of the linetypes as overrides, it will find those but won't be able to handle them [XLINEs because they have no end parameter (and of course infinite length, so you don't want them anyway), and MLINEs because they are not "vlax-curve"-class objects], so a check should be included to omit those from the calculations.  Or if you do want MLINEs, additional code would have to be created to figure out their length.

Kent Cooper, AIA
0 Likes
Message 19 of 29

Ranjit_Singh
Advisor
Advisor
Accepted solution

@kylei7449 wrote:

Yes, this is closest to the output I'm looking for. My next task is to get it to give me the aggregate total including multiples. So, if I have multiples of the same linetype it would give me the total lengths of that linetype instead of listing them separately. 

 

Example, get_linetype1 = 5'-0", and if I have another segment of get_linetype1 at = 7'-0", it returns them as separate values instead of the aggregate of 12'-0". So what I need it to do is give me the aggregate total for each linetype. I will work on it...thanks, this is the what I was looking for.


OK. Try below for example

;;Ranjit Singh
;;7/25/17
(defun c:somefunc  (/ lst lst2)
 (setq lst (mapcar '(lambda (x) (cons (getpropertyvalue x "Length") (cdr (assoc 6 (entget x)))))
                   (vl-remove-if 'listp
                                 (mapcar 'cadr (ssnamex (ssget "_x" '((0 . "lwpolyline") (6 . "PHANTOMx2,TRACKS,ZIGZAG,HIDDEN2"))))))))
 (vl-remove nil
            (mapcar '(lambda (x)
                      (setq lst2 (cons (if (null (member x lst2))
                                        x)
                                       lst2)))
                    (mapcar 'cdr lst)))
 (mapcar '(lambda (x)
           (princ (strcat "\n=======\n" (car x)))
           (mapcar 'print (cdr x))
           (princ (strcat "\nTotal: " (rtos (apply '+ (cdr x)) 2 2) "\n=======\n")))
         (mapcar '(lambda (x) (cons (cdar x) (mapcar 'car x)))
                 (mapcar '(lambda (y) (vl-remove-if-not '(lambda (x) (= y (cdr x))) lst)) (vl-remove nil lst2))))
 (princ))

Total_Length_and_Sub_Length.gif

0 Likes
Message 20 of 29

kylei7449
Enthusiast
Enthusiast

Yes, that's the ticket; that works perfectly.  And the  (vlax-curve-getDistAtParam ent (vlax-curve-getEndParam ent) big difference, so much cleaner.

 

The line:  tl (+ tl (vlax-curve-getDistAtParam....) that's adding each of the multiples correct??

 

So far, no need for multilines. 

 

Thank you for bracketing it out and adding the comments, much helpful. Can you suggest a good resource for learning Lisp? Textbooks or websites?? But trying to find for example formatting of numbers can be a challenge (ex. decimal, engineering, architectural, etc.) if you don't know how to word it in a search. I think once I have a better understanding the terms & how they are used (naturally) it will be much easier to follow along with the code. 

 

 

0 Likes