LISP Request - Block Count Inside a Closed Polyline

LISP Request - Block Count Inside a Closed Polyline

Anonymous
Not applicable
16,153 Views
81 Replies
Message 1 of 82

LISP Request - Block Count Inside a Closed Polyline

Anonymous
Not applicable

Hi CAD Friends,

 

Wonder if there is a LISP routine (already created by someone) to count all the blocks inside a closed polyline.

 

These are typically plant blocks with attributes in it. I have these sheet layout boundaries in model space, and the City now wants the count of all the plant blocks in a particular sheet, and a totals count in the end. I was using BCOUNT, but then I have to select the blocks individually along the sheet boundaries carefully. Instead of that If I can select the polylines of these sheet boundaries, then the lisp should count and provide a total count of different types of blocks contained (all the blocks with the centre point inside that selected polyline) inside the closed polyline. 

 

If some one can modify the BCOUNT lisp adding an additional feature to count blocks inside the user selected closed polyline ( get input from user selecting the closed polyline), that would be awesome.

 

Regards,

 

VA

0 Likes
16,154 Views
81 Replies
Replies (81)
Message 61 of 82

Anonymous
Not applicable

Hi DannyNL,

 

I was working on another drawing and just found out this.

 

For BCPL routine to provide correct results, all the blocks and the polylines  should be in the same plane. if any of the entities ( blocks / polyline boundaries ) has a different elevation then that is not included in the count and the results will be wrong. Ideally in plan drawings, all the blocks should have a "Position Z = 0" and the polyline boundaries should have an 'Elevation = 0'. Could you please put a Initial Warning in your routine " Please make sure all entities should be in Elevation = 0". I don't know the command: FLATTEN will do this job,  and can be included in the routine.

 

Another thing I noticed is:  If any block does not have an attribute value it will not get listed in the table, instead a blank row will be included in the table.  Please include a warning message to say "Attribute Value Missing !!!" The Block can still be listed under the Block Name in the table with a "None" in the Attribute Value column. That way user can go back and add attributes to the blocks.

 

I think we have to give an Initial Check List after activation of the routine by user:

 

1. All Blocks should be named properly.

2. All Blocks should have their Insertion Point in the exact centre of the block.

3. All Blocks should have Correct Attribute Values.

4. All Blocks and Block Polyline Boundaries should be in the same Elevation.

 

After this message the selection procedure  and rest of the routine can run.

 

Please provide your comments/ thoughts on this.

 

Regards,

 

VA

0 Likes
Message 62 of 82

Kent1Cooper
Consultant
Consultant

@Kent1Cooper wrote:
.... for Polylines or Splines with certain kinds of convolutions in their shapes, Offsetting even outward can result in more than one new Polyline. ...

Here's a thought:  When that happens, if such a Polyline is Offset outboard, there will be one  resulting Polyline that is larger than the selected boundary, even if there may also be some other smaller one(s).  So what if, after Offsetting, the routine finds all objects newer than the last one before starting, no matter how many there are [i.e. all results from the Offset], and compares the area of the largest of those to the boundary?  If the largest  of them is smaller  than the boundary, then the Block must be inside  the boundary.  If that's a sufficient test, it takes a heck of a lot less code than making ray-casting (etc.) routines foolproof:

 

(defun C:BIB ; = Blocks Inside Boundary
  (/ bndry reflen blks n new elast blk)
  (setq
    bndry (car (entsel "\nBoundary object: "))
    reflen (vla-get-area (vlax-ename->vla-object bndry))
    blks (ssget "_X" (list '(0 . "INSERT") (cons 410 (getvar 'ctab))))
    insiders (ssadd); initially empty
  ); setq
  (repeat (setq n (sslength blks))
    (setq new (ssadd) elast (entlast)); reset for each -- initially empty set
    (command "_.offset" "_through" bndry
      "_none" (cdr (assoc 10 (entget (setq blk (ssname blks (setq n (1- n)))))))
      ""
    ); command
    (while (setq elast (entnext elast)) (ssadd elast new)); however many result
    (if ; are ALL of the objects resulting from Offset smaller than the boundary?
      (<
        (apply 'max ; compare only the largest resulting object
          (mapcar
            '(lambda (x) (vla-get-area (vlax-ename->vla-object x)))
            (mapcar 'cadr (ssnamex new)); list of result-object entity names
          ); mapcar
        ); 'max
        reflen
      ); <
      (ssadd blk insiders); then -- put it in selection
    ); if
    (command "_.u"); Undo Offset [however many it made]
  ); repeat
  (prompt (strcat "\n" (itoa (sslength insiders)) " Block(s) found inside & put in 'insiders' selection set."))
  (princ)
); defun

HOWEVER, I find that Splines  of not-even-all-that-convoluted shapes can sometimes result in an outboard path that isn't one  Spline, but is in segments.  So the above wouldn't be completely reliable with Spline boundaries.  But if you use it only on Polylines [Circles and Ellipses work, too], it may be viable for you.

 

 

Again, it only identifies the Blocks with insertion points inside -- process further in ways already offered.  And it would still have a problem if a Block was at a central interior point that would not give a result [see Post 31], if that's a possibility, but that one, at least, could perhaps be accounted for with some (vl-catch-all...) code.

 

[If you use it on an Arc or a partial  Ellipse, I believe it will "see" all Blocks whose insertion points would be inside the equivalent closed extension of the boundary -- Circle or full Ellipse -- whether or not they're inside the shape of the boundary if closed across with a straight line.]

Kent Cooper, AIA
0 Likes
Message 63 of 82

kerry_w_brown
Advisor
Advisor

 

I think we need to do something about the Code Block Display Width :

https://forums.autodesk.com/t5/community-ideas/code-block-display-width/idi-p/7602883

 


// Called Kerry or kdub in my other life.

Everything will work just as you expect it to, unless your expectations are incorrect. ~ kdub
Sometimes the question is more important than the answer. ~ kdub

NZST UTC+12 : class keyThumper<T> : Lazy<T>;      another  Swamper
Message 64 of 82

john.uhden
Mentor
Mentor

A very good observation.

John F. Uhden

0 Likes
Message 65 of 82

DannyNL
Advisor
Advisor
Accepted solution

Hi Vince,

 

Good catch on the additional line in the table if the value has not been filled.

If you change this line

 

(setq BCPL_SortOrder (list "[A-Z]" "[a-z]" "[a-z][a-z]"))

into

 

(setq BCPL_SortOrder (list "[A-Z]" "[a-z]" "[a-z][a-z]" "" "[A-Z][A-Z]*,#*,.*,???*"))

The table will include the blocks with all other values and place those at the bottom of the table.

 

Regarding your other request to check on specified conditions before proceeding, this would be a whole additional (sub)routine. This could be build but what should be checked? All blocks in the drawing? Only the selected blocks by the routine? Only blocks that match a specified name pattern? What does it need to do with objects that don't match? 

 

And regarding checking on elevation, I'm not sure if it would be a check that should necessarily need to be included in this counting routine. Having an elevation would probably be an error for each object in each situation and not only when you want to count the blocks.

So probably a checking routine would need to be a whole separate command instead of embedding it in the counting routine, right?

0 Likes
Message 66 of 82

DannyNL
Advisor
Advisor

Kent,

 

To me it seems you want to sacrifice accuracy to save some additional lines of code.

And offsetting will not always work even with polylines. If you have something like a polyline below, offsetting inside or outside will always create an offset to the inside..

 

Polyline Offset - Incorrect.jpg

 

 

0 Likes
Message 67 of 82

Kent1Cooper
Consultant
Consultant

@DannyNL wrote:

.... If you have something like a polyline below, offsetting inside or outside will always create an offset to the inside.. 


I admit I hadn't thought of the possibility of a self-intersecting Polyline.  They cause difficulties with a lot of approaches to a lot of things.  For instance, in something like a ray-casting approach to this thread's problem, in which the number of intersections of a ray with the boundary object, using (intersectwith) methods, are counted, a Ray [or Line or Xline] that goes through the self-intersection point  will give incorrect results -- if it is cast from outside, it will be counted as having only one  intersection, even though it crosses two  segments, and one that goes through there from inside  [i.e. also crossing the left or right end segment in your image] will be counted as having only two, though it crosses three  segments.

Kent Cooper, AIA
0 Likes
Message 68 of 82

DannyNL
Advisor
Advisor
Accepted solution

Yes, you are right.

 

But that is the reason why my routine doesn't just use one ray but multiple rays in all directions.

- If only some of the rays have intersections, the point is outside the boundary.

- If all rays have intersections with the boundary but all rays intersect an even times, the point is considered outside the boundary.

- If all rays have intersections with the boundary but some intersect an odd times, the point is inside the boundary.

 

True, still not foolproof and I guess I could come up with some weird shaped complex polyline that still gives a false positive or negative due to rays intersecting one or more vertices instead of edges. But I hope to believe this is currently a more accurate detection than using boundary or offset Smiley Happy

 

I've extracted the ray casting routine and modified it, so the rays are not deleted and colored red (no intersection) or green (one or more intersections).

If all rays intersect an even times with the selected boundary, all rays will be colored red. Please try to find some flaws, so it may be further improved.

 

(defun c:RayCaster (/ PI_Point PI_PolylineObject PI_RayAngles PI_ClosestPoint PI_Distance PI_AngleList PI_RaySelection PI_RefAngle PI_AngleList PI_CheckLine PI_IntersectPoints PI_CheckList)   
   (setq PI_OnEdgeTrue nil)
   (if
      (and
         (setq PI_Point (getpoint "\nSelect point "))
         (not (vl-catch-all-error-p (setq PI_PolylineObject (vl-catch-all-apply 'vlax-ename->vla-object (list (car (entsel "\nSelect boundary: ")))))))
         (member (vla-get-ObjectName PI_PolylineObject) '("AcDbPolyline" "AcDb2dPolyline" "AcDbCircle" "AcDbEllipse" "AcDbSpline"))
         (vlax-Curve-isClosed PI_PolylineObject)
         (vlax-Curve-isPlanar PI_PolylineObject)
      )
      (progn         
         (setq PI_ClosestPoint (vlax-curve-GetClosestPointTo PI_PolylineObject PI_Point))
         (setq PI_Distance (distance PI_Point PI_ClosestPoint))
         (if      
            (not (equal PI_Distance 0.0))
            (progn
               (vla-StartUndoMark (vla-get-ActiveDocument (vlax-get-acad-object)))
               (setq PI_RayAngles (_AngleList 12))
               (setq PI_AngleList (mapcar '(lambda (PI_Factor) (* pi PI_Factor)) PI_RayAngles))
               (if
                  (not (equal (rem (setq PI_RefAngle (angle PI_Point PI_ClosestPoint)) (/ pi 2.0)) 0))
                  (setq PI_AngleList (append (mapcar '(lambda (PI_Factor) (+ (* pi PI_Factor) PI_RefAngle )) PI_RayAngles) PI_AngleList))
               )
               (setq PI_RaySelection (ssadd))
               (foreach PI_Angle PI_AngleList
                  (setq PI_CheckLine (entmakex (list '(0 . "RAY") '(100 . "AcDbEntity") '(100 . "AcDbRay") (cons 10 PI_Point) (cons 11 (mapcar '- (polar PI_Point PI_Angle 5.0) PI_Point)))))
                  (if         
                     (not (vl-catch-all-error-p (setq PI_IntersectPoints (vl-catch-all-apply 'vlax-safearray->list (list (vlax-variant-value (vla-IntersectWith (vlax-ename->vla-object PI_CheckLine) PI_PolylineObject acExtendNone)))))))
                     (progn
                        (setq PI_CheckList (cons (length (_GroupByNum PI_IntersectPoints 3)) PI_CheckList))
                        (vla-put-Color (vlax-ename->vla-object PI_CheckLine) 3)
                     )
                     (progn
                        (setq PI_CheckList (cons nil PI_CheckList))
                        (vla-put-Color (vlax-ename->vla-object PI_CheckLine) 1)
                     )
                  )
                  (ssadd PI_CheckLine PI_RaySelection)
                  ;(entdel PI_CheckLine)
               )
               (vla-EndUndoMark (vla-get-ActiveDocument (vlax-get-acad-object)))
            )
         )
         (cond
            (
               (and
                  (equal PI_Distance 0.0)
                  PI_OnEdgeTrue
               )
               T
            )
            (
               (and
                  (not (vl-some 'not PI_CheckList))
                  (vl-some '(lambda (PI_Value) (= (rem PI_Value 2) 1)) PI_CheckList)
               )
               T         
            )
            (
               (and
                  (not (vl-some 'not PI_CheckList))
                  (not (vl-some '(lambda (PI_Value) (= (rem PI_Value 2) 1)) PI_CheckList))
               )
               (foreach PI_Ray (ssnamex PI_RaySelection)
                  (vla-put-Color (vlax-ename->vla-object (cadr PI_Ray)) 1)
               )
             (setq PI_RaySelection nil)
            )            
            (
               T
               nil
            )
         )
      )
   )
)

(defun _AngleList (AL_Integer / AL_Step AL_Factor AL_Return)
   (setq AL_Step (/ 2.0 AL_Integer))
   (setq AL_Factor 0.0)
   (repeat AL_Integer
      (setq AL_Return (append AL_Return (list (+ 0.0 AL_Factor))))
      (setq AL_Factor (+ AL_Factor AL_Step))
   )
   AL_Return
)

; By Lee Mac
(defun _GroupByNum (l n / r)
   (if
      l
      (cons (reverse (repeat n (setq r (cons (car l) r) l (cdr l)) r)) (_GroupByNum l n))
   )
)

 

0 Likes
Message 69 of 82

DannyNL
Advisor
Advisor

In case someone wonder why I cast 12 or 24 rays; if the angle from the (insertion)point to the closest point on the boundary is not 0, 0.5, 1.0, 1.5 pi, 12 additional rays will be cast with the measured angle as reference. This way rays will be cast parallel to the closest edge and may find an opening in the boundary, which in case of only 12 rays would have gone undetected.

 

Outside - Almost enclosed.jpg

 

 

0 Likes
Message 70 of 82

lando7189
Advocate
Advocate

Coincidentally, a similar topic was discussed on how to do a similar function using .NET... so just adding it here.  (I need something similar to recognize fixture blocks and equipment placed on countertops in a planview where the countertop is perimeter is a polyline, but need to interact with some other .NET functions i already have)

 

It uses the MdiActiveDocument.Database.Editor.SelectByPolyline function.  By temporarily drawing (and deleting afterwards) a point on a specific layer and filtering for the new points upon selection, along with using a dictionary of point handles & inserted block handles, you can get the blocks where the points reside in the polyline 'window' and cross reference the dictionary to get the blocks.

 

Link:  https://forums.autodesk.com/t5/net/find-block-inside-a-polyline/td-p/7586489

0 Likes
Message 71 of 82

DannyNL
Advisor
Advisor

@lando7189, thanks for the info and although I'm not really into .NET, looking at the code I figure it looks like what I did with one of the previous versions.

 

The problem (in LISP) with selection by a polygon window/crossing is the limited number of points you can provide for the polygon. This seems to be limited to 256 (in LISP) and if the list of points is any longer the selection fails and returns nothing. And with i.e. splines converted to curved polylines the number of points can literally be thousands. And reducing the number of points sacrifices accuracy in that case.

 

So for rectangular polylines or polylines with limited vertices and edges, this would work perfectly but in the case of the OP from this thread it wouldn't work. Only if .NET doesn't have the same limitation on points for the selection this will be a solution I think.

0 Likes
Message 72 of 82

Anonymous
Not applicable

Hi DannyNL,

 

Regarding your other request to check on specified conditions before proceeding, this would be a whole additional (sub)routine. This could be build but what should be checked? All blocks in the drawing? Only the selected blocks by the routine? Only blocks that match a specified name pattern? What does it need to do with objects that don't match? 

 

And regarding checking on elevation, I'm not sure if it would be a check that should necessarily need to be included in this counting routine. Having an elevation would probably be an error for each object in each situation and not only when you want to count the blocks.

So probably a checking routine would need to be a whole separate command instead of embedding it in the counting routine, right?

 

Danny,

 

As you suggested let the user make sure these conditions are met before using the routine. That way let the routine do not alter the users drawing. If the user finds there is a reason for the drawing is done that way, they he can decide whether to correct it or not.

 

Could you please include a warning at the start of the program, for the users to aware of the limitations of this routine? I think the following are the ones I noticed. Please include any specific technical limitations as you may choose to include  ( I am not technical enough to suggest those).

 

All Blocks should be in Zero Elevation (Position Z= 0).

All Blocks should have Plant Key Attributes.

All Blocks should have the Base Point in the Centre of the Block.

All Polyline Boundaries should be in Elevation Zero.

 

Any comments/ suggestions?

 

Regards,

 

VA

 

0 Likes
Message 73 of 82

john.uhden
Mentor
Mentor
I am still struggling with it, but I found that the reason for my @bulge
function was to avoid using a plethora of points on a bulged segment. When
I get it working correctly (I hope) it may be as close as you can get to
perfection, and with no limitation on the number of polyline vertices. But
as we both know, splines are another matter. But who really uses them?
You can't join them or even explode them.

John F. Uhden

Message 74 of 82

Anonymous
Not applicable

Dear Sir,

 

Thank you Very much for helping me on my exact requirement. But I need a small modification in code.

your code is giving the block quantity in selected polylines. I need a small modification in code, like if we have different polylines with different layers (Layers Like ZONE1, ZONE2 etc..). So then the lisp shall generate the quantities with both block names along polyline layer names.

 

Please look in to my attachments

0 Likes
Message 75 of 82

pbejse
Mentor
Mentor

Really tnvsb?

 

54b.gif

 

Hanging....

 

 

 

 

 

Message 76 of 82

john.uhden
Mentor
Mentor

I still owe you guys and gals.  In my mind I am very close to a 99.99% successful @Anonymous function.  Of course, it originated with the objective of returning whether a point was inside a closed polyline.  I guess it gets trickier to determine (with high certainty) if a complex object is wholly within or partially within a closed polyline.  One of the limitations is that the boundingbox function returns only the lower left and upper right coordinates of any object.  So while an object is definitely within that box and any corner of that box may fall within the closed polyline, it is entirely likely that no particle of the object is within the closed polyline.

John F. Uhden

Message 77 of 82

john.uhden
Mentor
Mentor

Danny:

We've been through this a number of times before.

There is always the chance that a point outside can have a ray cast that just grazes the bulged edge or one vertex of a polyline (= 1 intersection).  The same is possible for a point inside that grazes once and intersects once (= 2 intersections).

I have been working on a different approach for years, but never find the time to conclude my hypothesis.  But as long as the user knows that ray casting is not perfect, then it's a lot better than nothing.

John F. Uhden

Message 78 of 82

jeguizaASRZM
Enthusiast
Enthusiast

Hello,

Great Lisp!

I have no idea how to write routines but I am interested in learning. I had a few modifications I would like to make, Would someone be able to help me? I would try and modify it myself with trial and error but I don't know what lines to start to look at.

 

The first thing I would like to change the properties of the table. For Example text Size, Column Length, Ext. As if I was inserting a new table with the "Insert table" dialog box.

 

The Second request would be for that table to update as I change the Polyline. So it would't just keep making a new table but rather have a set table were the Count Column of the block can change based on the Polyline. So for example if I move a vertex or stretch the Polyline and use the command "REGEN" or "REGENALL" the table Count Column would update

 

My Final Idea for modification is for the BCPL routine to have an option to only pick specific blocks based on Block Name or Layer.

 

Would This be possible?

 

Any Help, Guidance, Ideas are very Appreciated

 

Best, JE

0 Likes
Message 79 of 82

Anonymous
Not applicable

Hi,

The above lisp works for me. The only concern for me, how to do count a block under different enclosed polyline at a time

0 Likes
Message 80 of 82

Anonymous
Not applicable

The above lisp works for me if I select a single polyline. If I have multiple polylines, I will be able to select a polyline of the same layer, and it must give the consolidated counts

0 Likes