Programmically find all the quadrant points of a polyline

Programmically find all the quadrant points of a polyline

dennis
Advisor Advisor
2,964 Views
22 Replies
Message 1 of 23

Programmically find all the quadrant points of a polyline

dennis
Advisor
Advisor

Scenario: given a spiral as a polyline made up of curves.  I need to find all the quadrant points of that spiral.  Specifically, I need the 'outside' quadrants so I was trying to first build a bounding box, then use vlax-invoke 'intersection.  However, either the bounding box or the 'intersect is not precise enough.  I am finding the lines making up the bounding box are off by e0.000x value so that the intersection isn't found.  Is there a fuzz that could be applied to the 'intersect?

Any other ideas would be appreciated.

0 Likes
Accepted solutions (1)
2,965 Views
22 Replies
Replies (22)
Message 2 of 23

zph
Collaborator
Collaborator

You could compare the X and Y coordinates of the quandrants and boundary box and apply a fuzz distance using the EQUAL function.

 

(equal expr1 expr2 [fuzz])

 

EDIT: or rather compare the boundary box min and max X and Y coordinates to the points on the spiral and match them using the EQUAL function.  And when they do match...you've found the quandrant points.

0 Likes
Message 3 of 23

marko_ribar
Advisor
Advisor

Please check attached DWG - A2010 file format... It has an explanation...

 

HTH, M.R.

Marko Ribar, d.i.a. (graduated engineer of architecture)
Message 4 of 23

Kent1Cooper
Consultant
Consultant

@zph wrote:

You could compare the X and Y coordinates of the quandrants and boundary box and apply a fuzz distance using the EQUAL function.

 .... 

EDIT: or rather compare the boundary box min and max X and Y coordinates to the points on the spiral and match them using the EQUAL function.  And when they do match...you've found the quandrant points.


The first suggestion isn't an option, because [if I understand correctly] they don't have the coordinates of the quadrants to compare -- they're trying to find them.

 

The second suggestion depends on what you mean by "the points on the spiral."  If you mean the vertices of the Polyline, I don't think it can be assumed that the quadrant points are at vertices.  [The OP can certainly clarify that with a screen shot showing the spiral selected with grips showing.]  My assumption is that the quadrants will be at some random distances along arc segments, not necessarily at vertices.  The only way I can imagine to find them from "points on the spiral" would be to step along it at tiny increments and try (equal) at each step [not absolutely precise, but some tolerance would be involved, depending on the step size].

 

But I can imagine a way for a routine to get them precisely.  I first thought of an approach involving temporary Xlines through the Osnap-center locations for each arc segment, and (intersectwith) functions, but this should be simpler:

 

1) Get the spiral's bounding box and extract the extreme X and Y coordinate values.

2) Step through the Polyline, if a spiral like the one in the image is made of relatively few comparatively long arc segments, at a parameter value increment of, say, every .25 [0.0, 0.25, 0.5, 0.75, 1.0, etc.], and apply (osnap ...thatpoint... "_qua") at each one.  That will return a quadrant point for any segment that includes one, including potentially multiple returns of the same point.  With longer segments there's the possibility that one segment might include more than one quadrant point, which is why you should Osnap at multiple locations along each segment.  If it's made of a lot of comparatively short segments, Osnapping at just the midpoint parameter values [0.5, 1.5, etc.] should be enough.

3) Eliminate the duplicates, if any, and compare the results to the bounding box's extreme X and Y coordinate values.  Any that match within a reasonable tolerance will be one of the outer-perimeter quadrants.

 

Kent Cooper, AIA
Message 5 of 23

Kent1Cooper
Consultant
Consultant

@marko_ribar wrote:

Please check attached DWG....


I keep forgetting about the ClosestPointToProjection approach -- that would be a lot simpler to write a routine to do than my idea.

Kent Cooper, AIA
0 Likes
Message 6 of 23

dennis
Advisor
Advisor

zph: I follow what you are saying, but "what if" the vertex points are not at the quadrant?

I kept digging around with google, and have found something that I think will work for me.  I went through various websites, so now, am not sure where I got it....but 'thumbs up' to whoever I snarfed it from.  I am now adapting it for my use, but the gist is along the path I was going using the boundary box, but instead of 'intersection, using 'getclosestpointto.  Then once I find the closest point, I then use (osnap pt '_quad') to get the exact quadrant point.  I tested the code below and I see now I can adapt it for my needs.  But I would like to know if there is a more elegant way to do this.

 

(defun GETCLOSESTPOINTS (DIV OBJ1 OBJ2 / INC LEN LST N PT1 PT2 )
  (setq LEN (vlax-curve-getdistatparam OBJ1 (vlax-curve-getendparam OBJ1))
        N   0.0
        INC (/ LEN DIV)
  )
  (repeat DIV
    (setq PT1 (vlax-curve-getpointatdist OBJ1 N)
          PT2 (vlax-curve-getclosestpointto OBJ2 PT1)
          N   (+ INC N)
          LST (cons (list (distance PT1 PT2) PT1 PT2) LST)
    )
  )
  (setq LST (vl-sort LST (function (lambda (D1 D2) (< (car D1) (car D2)))))
        LST (car LST)
  )
  (setq QUADPT (osnap (cadr LST) "_quad"))
)
(defun C:GCP (/ OBJ1 OBJ2)
  (if (and (setq OBJ1 (car (entsel "\nSelect first curve object: ")))
           (setq OBJ2 (car (entsel "\nSelect second curve object: ")))
      )
    (GETCLOSESTPOINTS 1000 OBJ1 OBJ2)
  )
)

0 Likes
Message 7 of 23

dennis
Advisor
Advisor

Kent and Marco, your additions to the thread were posted while I writing up what I found.  I suspect that my question about a "more elegant" way has been answered since you both were heading down the 'getclosestpointto direction.  Thanks for the addition.

0 Likes
Message 8 of 23

john.uhden
Mentor
Mentor
Accepted solution

I don't think I'd go with a closest method. Rather check each segment for a non-zero bulge, then find the center point of the arc and its radius, and then cast a point (in the correct direction) due east, west, north, or south at the radius distance. Since the point might not be on the arc itself, you can check by using vlax-curve-getparamatpoint to see if the param is found and is between the beginning and ending params of the bulged segment. Since you have a tendancy toward closest, you could also confirm the point by checking if the solved point is equal to the closest point. Yes, I would use a fuzz faactor.

John F. Uhden

Message 9 of 23

dennis
Advisor
Advisor

John, I took your suggestion but in a slightly different direction.  I used the vla-explode, then filtered for arcs.  The remaining arcs after testing the startangle and endangle would leave the arcs that have a quadrant point.  I kinda like this one better though now I have code for whichever direction I want to go.

 

 

(defun C:PLQUAD ( / ent0 quadss)
  (setq ent0 (car (entsel "\nSelect entity to explode: ")))
  (setq quadss (vexplode ent0))
)
(defun vexplode (ent / qss xlst obj startang endang cval delflag)
  (setq xlst (vla-explode (vlax-ename->vla-object ent)))
  (setq qss (ssadd))
  (foreach obj (vlax-safearray->list (vlax-variant-value xlst))
    (if (= (vlax-get-property obj 'ObjectName) "AcDbArc")
      (progn
        (setq delflag 1
              startang (vlax-get-property obj 'StartAngle)
              endang (vlax-get-property obj 'EndAngle)
              cval (* (+ startang endang) 0.5)
        )
        (setq rlst (mapcar '(lambda (x)
                             (if (<= (abs (- x cval))(abs (- startang cval)))
                              (progn
                                (setq delflag nil)
                                (setq qss (ssadd (vlax-vla-object->ename obj) qss))
                              )
                             )
                            )
                           '(0 1.5708 3.14159 4.71239 360)
                   )
        )
        (if delflag
          (vla-delete obj)
        )
      )
      (vla-delete obj)
    )                 
  )
)

0 Likes
Message 10 of 23

john.uhden
Mentor
Mentor
Thanks for the accolade.

I'd rather see you improve your skills with polylines, so here's a function
that I use a lot...
You could reorganize it to suit your need for any given situation.

(defun GetRadius (Object Param / @2D Param1 Param2 P1 P2 P3 P4 Ctr Radius)@Anonymous Param1 Param2 P1 P2 P3 P4 Ctr Radius)
(defun @2D (p)(list (car p)(cadr p)))@Anonymous (p)(list (car p)(cadr p)))
(and
(setq Radius 0.0)
(setq Param1 (+ (fix Param) 0.1))
(setq Param2 (+ (fix Param) 0.9))
(setq P1 (vlax-curve-getpointatparam Object Param1))
(setq P2 (vlax-curve-getpointatparam Object Param2))
(setq P3 (polar P1 (apply 'atan (reverse (@2d
(vlax-curve-getSecondDeriv Object Param1)))) 1))
(setq P4 (polar P2 (apply 'atan (reverse (@2d
(vlax-curve-getSecondDeriv Object Param2)))) 1))
(setq Ctr (inters P1 P3 P2 P4 nil))
(setq Radius (distance Ctr P1))
)
Radius
)

John F. Uhden

Message 11 of 23

Kent1Cooper
Consultant
Consultant

@john.uhden wrote:
....
@(defun GetRadius (Object Param / @Anonymous Param1 Param2 P1 P2 P3 P4 Ctr Radius)@Anonymous Param1 Param2 P1 P2 P3 P4 Ctr Radius)
@(defun @Anonymous (p)(list (car p)(cadr p)))@Anonymous (p)(list (car p)(cadr p)))
....

@Those first two lines need the extraneous repeated stuff [from the second occurrences of @ followed by 2d in each line onward] removed.  EDIT:  The website did the same thing to my post [that combination must be code for "2 times" in webpage language somehow], so I've changed my description.

 

But in any case, I think this will accomplish the same thing:

(defun getrad (Object Param / pt)
  (distance
    (setq pt (vlax-curve-getPointAtParam Object Param))
    (osnap pt "_center")
  ); distance
); defun

 

Kent Cooper, AIA
Message 12 of 23

john.uhden
Mentor
Mentor
For some reason I had given up the osnap function back in the dark ages for something it didn't do correctly. Don't remember anything in particular, but I like your use of it here. I'll have to try using it again.

John F. Uhden

0 Likes
Message 13 of 23

john.uhden
Mentor
Mentor
Oops. Should have mentioned that one needs to verify that the point selected is actually on a segment whose bulge is non-zero.

John F. Uhden

0 Likes
Message 14 of 23

dennis
Advisor
Advisor

I found a bit of 'messed up math' in determining if 0 is between the 'StartAngle and 'EndAngle.  So, I corrected as shown below:

 

                             (if (< endang startang)
                               (setq startang (* (- 6.28319 startang) -1))
                             )
                             (if (and (<= startang x)(>= endang x))
                              (progn

0 Likes
Message 15 of 23

Kent1Cooper
Consultant
Consultant

@john.uhden wrote:
Oops. Should have mentioned that one needs to verify that the point selected is actually on a segment whose bulge is non-zero.

That can be done without digging into entity data to look at bulge factors, by way of that same Osnap to the center, because that will return nil if it's a straight segment:

 

(defun getrad (Object Param / pt ctr)
  (setq pt (vlax-curve-getPointAtParam Object Param))
  (if (setq ctr (osnap pt "_center"))
    (distance pt ctr)
  ); if
); defun

The overall function will also return nil in that case, which in the context of this thread would presumably be to trigger ignoring this segment and moving on to the next segment to look for a quadrant point along it.

 

 

But note that in routines that do that kind of Osnapping, it can be worth temporarily setting the APERTURE System Variable [the Osnap target box] to the [typically much smaller] size of the PICKBOX System Variable or even smaller.  This helps ensure [and pretty much guarantees, when the point comes from a User object selection, rather than a parameter as here] that if you're on a line segment but there happens to be some curved element close by, it won't snap to the center of the latter, and give erroneous results.

Kent Cooper, AIA
0 Likes
Message 16 of 23

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:

....
                           '(0 1.5708 3.14159 4.71239 360)
....


Even given the math adjustment in Post 14, the values in that list would all need to be in radians:

 

'(0 1.5708 3.14159 4.71239 6.28319)

 

Or better yet, with real precision [and less code to boot!]:


(list 0 (/ pi 2) pi (* pi 1.5) (* pi 2))

 

But I assume this is all a step along the way, since that code seems to leave you with the segments that cross quadrant points, but doesn't appear to determine where those points are.

Kent Cooper, AIA
0 Likes
Message 17 of 23

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:

.... in determining if 0 is between the 'StartAngle and 'EndAngle.  So, I corrected as shown below:

 

                             (if (< endang startang)
....


If the end angle is less than the start angle, couldn't you simply skip all of the figuring and immediately put that Arc in the category of those that cross quadrant points?  It will necessarily cross the 0-degree-direction quadrant.  You should need to do the figuring only for those whose end angles are greater than their start angles, to check whether they cross any of the other quadrant points, which would also mean that you could omit 0 and 2-pi from the list of directions to consider.

Kent Cooper, AIA
Message 18 of 23

Kent1Cooper
Consultant
Consultant

Here's a suggested somewhat shorter and fewer-variables version of the (foreach) part of the (vexplode) function [untested]:

....
(foreach obj (vlax-safearray->list (vlax-variant-value xlst)) (if (= (vlax-get-property obj 'ObjectName) "AcDbArc") (progn ; then (setq startang (vla-get-StartAngle obj) endang (vla-get-EndAngle obj) ); setq (if (or (< endang startang); crosses E quadrant (member T ; crosses N/W/S quadrant(s)? (mapcar '(lambda (x) (<= startang x endang)) (list (/ pi 2) pi (* pi 1.5)) ); mapcar ); member ); or (setq qss (ssadd (vlax-vla-object->ename obj) qss)) ; then -- add Arc that crosses a quadrant to set (vla-delete obj); else -- delete Arc not crossing quadrant ); if ); progn -- then (vla-delete obj); else [a Line] ); if [Arc or not] ); foreach
....

 

I also notice that you have some variables that I would think you want to use later -- 'qss' and/or 'quadss' -- that are in the localized-variables lists and therefore will not survive past the end of the command/function.  Those should be removed from the localized lists, along with the names of variables -- 'cval' and 'delflag' and 'rlst' -- that I think are no longer needed [if I interpreted the intent correctly].

Kent Cooper, AIA
Message 19 of 23

dennis
Advisor
Advisor

I am going with the following.  Thanks to all for the assist.  John, I will work on advancing my skills with polylines...I promise.  Kent, noticed I did change a little bit of your last suggestion, I use (* pi 0.5) mainly because my "mind" works better with multiplication over division.

I ended up going with (vlax-invoke 'intersection) which to me, insured the check for the quadrant.  I did find in testing that the generated intersection point and the (osnap '_quad') always came out equal.  If there is a better way on the quadlst, I would be interested.  I first created an empty list, then appended.

 

Thanks again to all,

 

(defun C:PLQ ( / dwgobj dwgdoc mspace ent0 quadlst)
  (vl-load-com)
  (setq
    dwgObj (VLAX-GET-ACAD-OBJECT)
    dwgDoc (vla-get-activedocument dwgObj)
    mspace (vla-get-modelspace dwgDoc)
  )
  (VLA-ZOOMEXTENTS dwgObj)
  (setq ent0 (car (entsel "\nSelect pline to eval: ")))
  (setq quadlst (vexplode mspace ent0))
;;;  visual check lines
  (foreach pt quadlst
    (vla-addline mspace (vlax-3d-point '(0 0 0))(vlax-3d-point pt))
  )
)
(defun vexplode (mspace ent / xlst quadlst obj startang endang x arcrad arccen tpt tmpline)
  (setq
    xlst (vla-explode (vlax-ename->vla-object ent))
    quadlst '()
  )
  (foreach obj (vlax-safearray->list (vlax-variant-value xlst))
    (if (= (vlax-get-property obj 'ObjectName) "AcDbArc")
      (progn
        (setq startang (vlax-get-property obj 'StartAngle)
              endang (vlax-get-property obj 'EndAngle)
        )
        (if (or (< endang startang)
                (member T (mapcar
                            '(lambda (x) (<= startang x endang))
                            (list (* pi 0.5) pi (* pi 1.5))
                          )
               )
            )
          (progn
            (setq
              arcrad (vlax-get-property obj 'Radius)
              arccen (vlax-get-property obj 'Center)
              arccen (vlax-safearray->list (vlax-variant-value arccen))
            )
            (mapcar
              '(lambda (x)
                 (setq tpt (polar arccen x arcrad)
                       tmpline (vla-addline mspace (vlax-3d-point arccen)(vlax-3d-point tpt))
                       tpt (car (GetIntersectionPoint tmpline obj acextendnone))
                 )
                 (if tpt
                   (setq quadlst
                          (append
                            quadlst (list (osnap tpt "_quad"));this osnap '_quad' probably isn't needed
                          )
                   )
                 )
                 (vla-delete tmpline)
              )
              (list 0 (* pi 0.5) pi (* pi 1.5))
            )
          )
        )
      )
    )
    (vla-delete obj)
  )
  quadlst
)

(defun GetIntersectionPoint (ob1 ob2 mod / lst rtn)
  (setq lst (vlax-invoke ob1 'intersectwith ob2 mod))
  (repeat (/ (length lst) 3)
    (setq
      rtn (cons (list (car lst)(cadr lst)(caddr lst)) rtn)
      lst (cdddr lst)
    )
  )
  (reverse rtn)
)

0 Likes
Message 20 of 23

john.uhden
Mentor
Mentor
If endang is < startang, I think you need to just add (* 2 pi) to endang. Don't Use 6.28... as it is only an approximation of (* 2 pi).

John F. Uhden

0 Likes