Minimum (single axis) distance between two 2D polylines

Dalephilip93
Explorer
Explorer

Minimum (single axis) distance between two 2D polylines

Dalephilip93
Explorer
Explorer

Hey everyone, I'm looking for a function to get the minimum distance between two polylines on a single axis (Y-axis in this case).

 

Does anyone have a suggestion on how to create a LISP for this?

The function would produce a line that indicates the location where the two lines are closest on the Y-Axis. It's not difficult to do this manually but need to automate it as I need to do it frequently.

 

Example:

The function produces the white line automatically between the red & blue line below

Dalephilip93_0-1680474972584.png

 

Thanks for any help.

 

0 Likes
Reply
2,102 Views
24 Replies
Replies (24)

Sea-Haven
Mentor
Mentor

Try this.

 

(defun c:wow ( / obj1 obj2 x step ch x pt1 pt2 pt3 pt4)
(setq obj1 (vlax-ename->vla-object (car (entsel "\nPick the 1st pline "))))
(setq obj2 (vlax-ename->vla-object (car (entsel "\nPick the 2nd pline "))))
(setq x 50)
(setq step (/ (vla-get-length obj1) x))
(setq ch 0.0 dist 1000000.0)
(repeat x
(setq pt1 (vlax-curve-getpointatdist obj1 ch))
(setq pt2 (vlax-curve-getclosestpointto obj2 pt1))
(setq dist2 (distance pt1 pt2))
(if (< dist2 dist)
  (progn
  (setq dist dist2)
  (setq pt3 pt1 pt4 pt2)
  )
)
(setq ch (+ ch step))
)
(command "line" pt3 pt4 "")
(alert (strcat "Minimum dist is " (rtos dist 2 2)))
(princ)
)
0 Likes

leeminardi
Mentor
Mentor

@Sea-Haven First, congrats on posting a solution in less than an hour! Impressive.

It's not clear to me if the OP wants the solution that is the true shortest distance or the shortest vertical distance (i.e.,  Y-direction).  If the latter, then the vertical distance from pt1 should be used not  vlax-curve-getclosestpointto. For example, in the following, your code finds the red line but is the yellow line the desired result?

leeminardi_0-1680481837095.png

 

In the following example, the red line is the result of your code, the green distance is shorter, and the yellow is the shortest vertical distance.

leeminardi_1-1680482060626.png

Is the problem that vlax-curve-getpointatdist may skip over a vertex?

 

 

lee.minardi
0 Likes

Dalephilip93
Explorer
Explorer

Hi @Sea-Haven, thanks for the reply & code to try out.

 

It is calculating the minimum distance but isn't doing so on the Y-axis only, see snip below (green lines are line generated by the function)

Dalephilip93_0-1680482587184.png

 

Is there a way to modify it so it produces the lines on the vert. axis only?

 

Dalephilip93
Explorer
Explorer

Yes the under an hour reply is very impressive!
Props to Sea-Haven for the turn around

 

I can confirm the yellow line in your example is the desired solution

0 Likes

Sea-Haven
Mentor
Mentor

Sorry should have read a bit closer (Y-axis in this case). 

 

The step size can be de-creased to very small and a double check would be to do vertices also, really need both. As the variation in the 2 plines is to great. Possibly also check from the vertices of the second pline.

 

Will add back onto the "to do" list.

0 Likes

Kent1Cooper
Consultant
Consultant

A brute-force way would be to make a copy of one of the Polylines, and Move it vertically toward the other one by a very small distance, use (vla-intersectwith) methods to check whether it intersects the other, and if not, Move it another increment of distance, etc., until they touch.  But questions arise.  At the first position in which they intersect, depending on the configuration, it's likely there will be two intersections.  Often one Polyline or the other would have a vertex between those two, which is probably where the closest distance would hit, but if it's two arc segments that first hit, that won't be the case, and I imagine there could be other kinds of ambiguities.  And you could have parallel segments, in which the closest distance applies over an extended length of the two, such as the red areas here:

Kent1Cooper_0-1680522513706.png

Where should the vertical Line be drawn in that case?

Kent Cooper, AIA
0 Likes

leeminardi
Mentor
Mentor

@Kent1Cooper , I like your suggestion but instead of moving the lower polyline up a little bit each iteration the numerical bisection method could be used. I.e., the lower polyline could be moved up half the distance "d" on the first try. Here d = the vertical disatnce from the highest point on the upper polyline to the lowest point of the lower polyline. If there is an intersection then the polyline is move down by d/2. If there isn't an intersection then it's moved up by d/2.  For each iteration the lower polyline is moved up or down by half the preceding move.  This approach may converge to a solution faster than walking along one of the polylines with very small steps.   There is still the issue you point out about parallel segments.

lee.minardi
0 Likes

Kent1Cooper
Consultant
Consultant

@leeminardi wrote:

....  For each iteration the lower polyline is moved up or down by half the preceding move.  ....


That makes sense.  Presumably, most of the time when there are intersections there will be at least two, though there could sometimes be a vertex that lands exactly on the other Polyline, for only one intersection.  The iteration could continue until either there is only one intersection or there are two and the distance between them is less than some tolerance.

 

But the parallel-segments situation still presents a problem.  In my example, if/when one of them is Moved until they meet exactly:

Kent1Cooper_0-1680526048231.png

the (intersectwith) test returns the two ends of the red overlap, and no amount of iteration fine-tuning the position will get them closer -- there's a potential for an endless loop.

 

Of course, another possibility is that, even without parallel closest segments, there are more than one location with equal minimal distance between:

Kent1Cooper_2-1680526446750.png

Kent Cooper, AIA
0 Likes

Dalephilip93
Explorer
Explorer

Great to see the discussion Lee & Kent, I can confirm for this particular use case one polyline will be an arc and highly unlikely for a parallel section to align for an extended distance. For clarity, I'm trying to find the point of minimum clearance between a catenary cable and a ground line profile. The catenary cable polyline is a polyline made up of a large number of straight segments to create the curve; the ground line polyline is similar but at a lower resolution. Therefore the likelihood of an extended parralel section is low.

Overall, having a solution that checks the distance between the polylines in incremental steps of say 0.1 across the length of the polylines and then outputs a line that indicates the point of minimum distance would suffice. The nature of the low resolution ground profile means we don't need a very high accuracy of the actual minimum distance.

0 Likes

Sea-Haven
Mentor
Mentor

Just thinking about electricity transmission cables does the electricity not jump in a radial sense rather than vertical ? 

So offsetting the pline that is the cable and look for a intersect with point, when found draw a vertical line. Do very small increments or like Kent jump 1/2 at a time.

0 Likes

komondormrex
Advisor
Advisor

@Dalephilip93 wrote:

Does anyone have a suggestion on how to create a LISP for this?


hey, 

given both plines have only straight segments, you may consider to check the code below. for most proximity at y axis.

 

(defun c:Y_most_proximity ( / 1st_curve_object 2nd_curve_object y_list min_Y max_Y min_proximity probe_line_object both_curves_x_list
            probe_intersection_1 probe_intersection_2 probe_proximity proximity_point_1 proximity_point_2   
        )
  (setq 1st_curve_object (vlax-ename->vla-object (setq 1st_curve_ename (car (entsel "\nPick 1st curve: "))))
        2nd_curve_object (vlax-ename->vla-object (setq 2nd_curve_ename (car (entsel "\nPick 2nd curve: "))))
  )
    (vla-getboundingbox 1st_curve_object 'llc 'ruc)
    (setq y_list (list (cadr (vlax-safearray->list llc)) (cadr (vlax-safearray->list ruc))))
    (vla-getboundingbox 2nd_curve_object 'llc 'ruc)
    (setq y_list (append y_list (list (cadr (vlax-safearray->list llc)) (cadr (vlax-safearray->list ruc))))
          min_Y (- (apply 'min y_list) 5)
        max_Y (+ (apply 'max y_list) 5)
        min_proximity (- (apply 'max y_list) (apply 'min y_list))
        probe_line_object (vla-addline (vla-get-block (vla-get-activelayout (vla-get-activedocument (vlax-get-acad-object))))
                  (vlax-3d-point (list 0 min_Y))
                  (vlax-3d-point (list 0 max_Y))
         )
        both_curves_x_list (append (mapcar 'cadr (vl-remove-if '(lambda (group) (/= 10 (car group))) (entget 1st_curve_ename)))
                  (mapcar 'cadr (vl-remove-if '(lambda (group) (/= 10 (car group))) (entget 2nd_curve_ename)))
         )
  )
    (foreach x both_curves_x_list
      (vla-move probe_line_object (vla-get-startpoint probe_line_object) (vlax-3d-point (list x min_Y)))
      (if (and (setq probe_intersection_1 (vlax-invoke probe_line_object 'intersectwith 1st_curve_object acextendnone))
       (setq probe_intersection_2 (vlax-invoke probe_line_object 'intersectwith 2nd_curve_object acextendnone))
        )
      (if (> min_proximity (setq probe_proximity (distance probe_intersection_1 probe_intersection_2)))
            (setq min_proximity probe_proximity
              x_proximity x
              proximity_point_1 probe_intersection_1
              proximity_point_2 probe_intersection_2
        )
        )
    )
  )
    (vla-put-startpoint probe_line_object (vlax-3d-point proximity_point_1))
    (vla-put-endpoint probe_line_object (vlax-3d-point proximity_point_2))
    (princ)
)
0 Likes

ivanovsky
Enthusiast
Enthusiast

i read your post and replies, it seems a bit confused.

if you want the minimum distance in y direction only,

the solution is a bit simpler,

you may use "Scan Line Algorithm".

0 Likes

hosneyalaa
Advisor
Advisor

@Dalephilip93 hi

Can you attached example drawing 

0 Likes

yangguoshe
Advocate
Advocate

  If there are no arc segments in the lwpolylines, you can try this code

;;cqnuygs-羊羊羊 2023年4月9日
(defun c:ttt(/ B1 B2 D1 D2 JL1 JL2 LINE1 LINE2 LST1 LST2 MIN_L X)
  (if
   (AND
     (setq line1(car(entsel "SLEECT FIRST LWPOLYLINE")) 
           line2(car(entsel"SLEECT SECONGD LWPOLYLINE")))
     (eq(cdr(assoc 0(entget line1))) "LWPOLYLINE")
     (eq(cdr(assoc 0(entget line2))) "LWPOLYLINE")
    )
  (progn
   (setq lst1(mapcar 'cdr(vl-remove-if-not '(lambda (x) (= (car x) 10)) (entget line1))) 
         lst2(mapcar 'cdr(vl-remove-if-not '(lambda (x) (= (car x) 10)) (entget line2))) 
         b1(mapcar'(lambda(x)(list x(distance x (vlax-curve-getClosestPointToProjection line2 x '(0 1 0)))))lst1)  
         d1(cAar(vl-sort b1'(lambda(x y)(<(cadr x)(cadr y)) ))) 
         jl1(cAdar(vl-sort b1'(lambda(x y)(<(cadr x)(cadr y)) ))) 
         b2(mapcar'(lambda(x)(list x(distance x (vlax-curve-getClosestPointToProjection line1 x '(0 1 0)))))lst2)  
         d2(cAar(vl-sort b2'(lambda(x y)(<(cadr x)(cadr y)) ))) 
         jl2(cAdar(vl-sort b2'(lambda(x y)(<(cadr x)(cadr y)) ))))
   (if(< jl1 jl2)
     (progn(setq min_l jl1) (entmake (list '(0 . "line") (cons 10 d1) (cons 11 (vlax-curve-getClosestPointToProjection line2 d1 '(0 1 0))) )))
     (progn(setq min_l jl2) (entmake (list '(0 . "line") (cons 10 d2) (cons 11 (vlax-curve-getClosestPointToProjection line1 d2 '(0 1 0))) )))         
   )
  )
 )
)

 

0 Likes

yangguoshe
Advocate
Advocate
;;cqnuygs-羊羊羊
;一行表转单点表 
(defun yhb2ddb-3(yhb / ddb) 
   (while yhb
        (setq ddb(cons(list(car yhb) (cadr yhb)(caddr yhb))ddb)
              yhb(cdddr yhb)
        )
  )
  (reverse ddb)
)
;求两图元的交点(改自黄明儒)
(defun inter_pts (en1 en2 / ol)
    (setq ol(mapcar 'vlax-ename->vla-object(list en1 en2)))
    (yhb2ddb-3(vlax-invoke (car ol)'intersectwith (cadr ol) 0))
)
(defun c:ttt(/ B1 B2 D1 D2 JL1 JL2 LINE1 LINE2 LST1 LST2 MIN_L X)
  (if
   (AND
     (setq line1(car(entsel "SLEECT FIRST LWPOLYLINE")) 
           line2(car(entsel"SLEECT SECONGD LWPOLYLINE")))
     (eq(cdr(assoc 0(entget line1))) "LWPOLYLINE")
     (eq(cdr(assoc 0(entget line2))) "LWPOLYLINE")
     )
   (progn
   (setq  lst1 (mapcar 'cdr(vl-remove-if-not '(lambda (x) (= (car x) 10)) (entget line1))) 
          lst2 (mapcar 'cdr(vl-remove-if-not '(lambda (x) (= (car x) 10)) (entget line2))) 
          min-y(fix(1-(apply 'min(mapcar 'cadr(append lst1 lst2))))) 
          max-y(fix(1+(apply 'max(mapcar 'cadr(append lst1 lst2))))))
   (defun lshs(lst line );(setq lst lst1 line line2)
	 (vl-remove nil
	  (mapcar'(lambda(x)
                     (entmake(list'(0 . "line")(cons 10(list(car x)max-y)) (cons 11(list(car x)min-y)) ))
		     (if(setq zb(car(inter_pts line (entlast))));line2
                        (progn
			 (vla-delete (vlax-ename->vla-object(entlast)))
		         (list x (distance x zb))
		        )
		       (vla-delete (vlax-ename->vla-object(entlast)))
		      )
                   )
	      lst
	  ) 
       )
  )
   (setq
	 b1(lshs lst1 line2)
         d1(cAar(vl-sort b1'(lambda(x y)(<(cadr x)(cadr y)) ))) 
         jl1(cAdar(vl-sort b1'(lambda(x y)(<(cadr x)(cadr y)) ))) 
         b2(lshs lst2 line1)
	 d2(cAar(vl-sort b2'(lambda(x y)(<(cadr x)(cadr y)) ))) 
         jl2(cAdar(vl-sort b2'(lambda(x y)(<(cadr x)(cadr y)) ))))
    (if(< jl1 jl2) 
     (progn(setq min_l jl1) (entmake (list '(0 . "line") (cons 10 d1) (cons 11 (setq tyd(vlax-curve-getClosestPointToProjection line2 d1 '(0 1 0)))) )))
     (progn(setq min_l jl2) (entmake (list '(0 . "line") (cons 10 d2)(cons 11  (setq tyd(vlax-curve-getClosestPointToProjection line1 d2 '(0 1 0)))) )))         
    )
  )
  )
)
0 Likes

hak_vz
Advisor
Advisor
(defun c:ymin (/ take take2 pointlist2d pointlist3d pick_poly getintersections f g fo go msg1 msg2 intlst pt qt di min_dist xlo)
	(defun take (amount lst / ret)(repeat amount (setq ret (cons (car lst) (take (1- amount) (cdr lst))))))
	(defun take2 (arg lst)(take 2 lst))
	(defun pointlist2d (lst / ret) (while lst (setq	ret (cons (take 2 lst) ret) lst (cddr lst))) (reverse ret))
	(defun pointlist3d (lst / ret) (while lst (setq	ret (cons (take 3 lst) ret) lst (cdddr lst))) (reverse ret))
	(defun pick_poly (msg)
		(setq e (car(entsel msg)))
		(if (and (not e) (= (getvar 'Errno) 7)) (pick_poly msg) e)
	)
	(defun getintersections	(obj1 obj2 / var)
		(setq var (vlax-variant-value (vla-intersectwith obj1 obj2 0)))
		(if (< 0 (vlax-safearray-get-u-bound var 1))
			(vlax-safearray->list var)
		) 
	)
	(setq msg1 "\nSelect first polyline >" msg2 "\nSelect second polyline >")	
	(mapcar 'set '(f g)(list (pick_poly msg1 )(pick_poly msg2)))
	(mapcar 'set '(fo go)(mapcar 'vlax-ename->vla-object (list f g)))
	(cond 
		((and (setq intlst (getintersections fo go)))
			(foreach pt (pointlist3d intlst)
				(entmakex (list (cons 0 "CIRCLE") (cons 10 pt) (cons 40 (/ (distance p1 p2) 20))))
			)
		)
		(T
			(setq cords_f (pointlist2d (vlax-get fo 'coordinates)))
			(setq cords_g (pointlist2d (vlax-get go 'coordinates)))
			(setq min_dist 1e12)
			(foreach pt cords_f
				(setq xlo 
					(vlax-ename->vla-object	
						(entmakex
							(list
								(cons 0 "XLINE")
								(cons 100 "AcDbEntity")
								(cons 100 "AcDbXline")
								(cons 10 (trans pt 1 0))
								(cons 11 '(0 1 0))
							)
						)
					)
				)
				(cond 
					((and (setq qt(getintersections xlo go)))
						(if (< (setq di (distance pt qt)) min_dist)(setq min_set (list pt qt ) min_dist di))
					)
				)
				(vla-delete xlo)
				
			)
			(foreach pt cords_g
				(setq xlo 
					(vlax-ename->vla-object	
						(entmakex
							(list
								(cons 0 "XLINE")
								(cons 100 "AcDbEntity")
								(cons 100 "AcDbXline")
								(cons 10 (trans pt 1 0))
								(cons 11 '(0 1 0))
							)
						)
					)
				)
				(cond 
					((and (setq qt(getintersections xlo fo)))
						(if (< (setq di (distance pt qt)) min_dist)(setq min_set (list pt qt) min_dist di))
					)
				)
				(vla-delete xlo)
			)
			(command "_.line" "_none"(car min_set) "_none" (cadr min_set) "")
			(princ (strcat "\n Minimal y distance is " (rtos (distance (car min_set)(cadr min_set)) 2 2)))
		)
	)
	(princ)
)

Miljenko Hatlak

EESignature

Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
0 Likes

hosneyalaa
Advisor
Advisor

@hak_vz  @komondormrex  @yangguoshe 

HI ALL

Thank you all your codes
when
There is an arc in the polyline, It doesn't work well
Any idea ... for this case

 

3.jpg

0 Likes

hak_vz
Advisor
Advisor

@hosneyalaa wrote:

@hak_vz  @komondormrex  @yangguoshe 

HI ALL

Thank you all your codes
when there is an arc in the polyline, It doesn't work well
Any idea ... for this case

I have started to write some code for this case, but generally it's not an easy task, and there are many other situations to take in consideration next to one mentioned in posts above. Basically, algorithm is similar to posted code. Cutting object (in my case this is a xline) is moved left to right for some delta value, and distance between intersection points  with two polylines are calculated with holding lowest one. Once the minimal distance is found, loop is rerun with smaller delta value around that initial point, let say from -delta to +delta with step delta/100.

From description it is obvious that code execution will take a lot of time when long polylines are tested.

Miljenko Hatlak

EESignature

Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
0 Likes

JoseMandirigma
Enthusiast
Enthusiast

this isn't mine, I just found this somewhere I forgot and I works for me. 

 

;; http://www.theswamp.org/index.php?topic=23170.60
;; By Joe Burke, Charles Alan Butler and VovKa at theswamp.

;; Bug reports may be sent to me (Joe Burke) directly at 
;; lowercase@hawaii.rr.com

;; Version 1.0 - 5/28/2008.
;;  Find the minimum distance between two vlax-curve objects. 
;;  Supported object types: line, circle, arc, ellipse, polyline and spline.
;;  Shortcut: MD

;; Notes version 1.0:
;;  If two lines are parallel they are reported as such.
;;  If the Z values of the two points found are not equal,
;;  report at command line Z1 = x Z2 = x. When the objects
;;  are not coplanar, the apparent minimum distance will 
;;  usually differ from the actual minimum distance.
;;  There's an option to add a line on the current layer
;;  drawn between the two closest points.
;;  The object types selected are reported at the command line.

;;  Version history:

;;  Version 1.2 beta - 5/31/2008
;;   Added the MinDistLine routine. Shortcut: MDL.
;;   Allows the user to place a line between the last two closest points
;;   calculated by MinDist after it ends. This avoids having to choose
;;   whether a line is placed within MinDist itself. The idea is MinDist
;;   is primarily a measuring tool. As such a minimum distance line is
;;   rarely needed. Note, If the line drawn by MDL is off-screen it is 
;;   selected, otherwise not.

;;  Version 1.3 beta - 6/8/2008
;;   Added support for nested objects in blocks and xrefs.
;;   Added MD:GetXrefs, MD:GetObject, MD:UnlockLayers, MD:RelockLayers 
;;   and MD:XMark sub-functions.
;;   The first object selected is highlighted until the the second
;;   object is selected similar to the fillet tool. If the first object
;;   is contained in an xref it is not highlighted. Rather a temporary 
;;   X mark is placed where the object was selected to indicate the
;;   the object is contained in an xref.

;;  Version 1.4 beta - 6/10/2008
;;   Added error checking for non-uniformly scaled blocks.

;;  Version 1.4a - 6/21/2008
;;   Bug fix for 2D (heavy) and 3D polylines.
;;   Bug fix to avoid error if a dimension is selected.
;;   Revised report when the Z values of the two points are not the same.

;;  Version 1.5 beta - 6/30/2008
;;   Added support for object types point, ray and xline.
;;   If a ray or xline is involved the search for closest point along its 
;;   length is limited by the current view. The search extends beyond the
;;   limits of the current view by a factor of approximately two both ways.

;;  Version 1.5a beta - 7/1/2008
;;   Fixed a bug with rays and xlines.
;;   Both MD and MDL now report when both closest points are off screen.
;;   Revised the MDL routine so it will not draw a very short or zero
;;   length line. Added report for this case.
;;   Added miscellaneous error checking.

;;  Version 1.5b beta - 7/2/2008
;;   Enter at select object prompt ends the routine.
;;   Revised the UniformScale sub-routine to allow operation with objects
;;   nested in dimensions. Thanks to Steve Doman.

;;  Version 1.5c beta - 7/14/2008
;;   Revised the fuzz factor in the MD:UniformScale function.

;;  Version 1.5d - 8/24/2008
;;   Added vla-StartUndoMark and vla-EndUndoMark. An undo after the 
;;   routine would restore a copied object.
;;   Added function MinDistMove (MDM). Moves a selection set from
;;   the first MinDist point to the second. The first object selected
;;   within MinDist is the first point.

;;  Version 1.5e - 9/6/2008
;;   Fixed a minor bug which effected the MinDistMove function when
;;   a ray or xline is involved.

;;  Version 1.5f - 10/1/2008
;;   Added Copy version of move. Shourtcut MDC.

  ;; Both MinDist and MinDistLine use the following two functions.

  ;; Returns the coordinates of the current view, lower left and upper right.
  ;; Works in a rotated view. Returns a list of two 2D UCS points.
  (defun MD:GetScreenCoords ( / ViwCen ViwDim ViwSiz VptMin VptMax)
   (setq ViwSiz (/ (getvar "VIEWSIZE") 2.0)
         ViwCen (getvar "VIEWCTR")
         ViwDim (list
                  (* ViwSiz (apply '/ (getvar "SCREENSIZE")))
                  ViwSiz
                )
         VptMin (mapcar '- ViwCen ViwDim)
         VptMax (mapcar '+ ViwCen ViwDim)
   )
   (list VptMin VptMax)
  ) ;end

  ;; Arguments: 
  ;;  p1 - WCS or UCS point which defines the first corner of area
  ;;  p2 - WCS or UCS point which defines the second corner of area
  ;;  pt - point translated to UCS.
  ;; Returns: T if pt falls within area.
  (defun MD:PointInside (p1 p2 pt / xval yval)
    (and 
      pt
      (setq pt (trans pt 0 1)
            xval (car pt)
            yval (cadr pt)
      )
      (< (min (car p1) (car p2)) xval (max (car p1) (car p2)))
      (< (min (cadr p1) (cadr p2)) yval (max (cadr p1) (cadr p2)))
    )
  ) ;end

(defun c:MinDist ( / *error* doc blocks units obj1 obj2 typ1 typ2 pkpt p2 sc 
                     div fuzz d bd len inc idx resdist dellst res1 res2 pts 
                     locklst interflag z1 z2 diff temp reverseflag 
                     MD:Wait MD:NormalAngle MD:ParallelObjects MD:Pick 
                     MD:GetXrefs MD:UnlockLayers MD:RelockLayers MD:GetObject 
                     MD:XMark MD:UniformScale MD:XlineOrRay)
                     ;; global vars: *mdp1* and *mdpt*

  (vl-load-com)

  (defun *error* (msg)
    (cond
      ((not msg))
      ((wcmatch (strcase msg) "*QUIT*,*CANCEL*"))
      (T (princ (strcat "\nError: " msg)))
    )
    (setvar "lunits" units)
    (if 
      (and 
        obj1
        (not (vlax-erased-p obj1))
      )
      (vla-highlight obj1 acFalse)
    )
    ;; Objects may be switched when a ray or xline
    ;; is involved.
    (if 
      (and 
        obj2
        (not (vlax-erased-p obj2))
      )
      (vla-highlight obj2 acFalse)
    )
    (MD:Wait 0.2)
    (redraw)
    (foreach x dellst (vla-delete x))
    (MD:RelockLayers locklst)
    (vla-EndUndoMark doc)
    (princ)
  ) ;end error

  ;;; START SUB-FUNCTIONS ;;;

  ;; Unlock locked layers.
  ;; Argument: document object.
  ;; Returns a list of layer objects which were locked, 
  ;; or nil if none are locked.
  ;; Typically the function filters out xref layers,
  ;; but not in this case.
  (defun MD:UnlockLayers (doc / laylst)
    (vlax-for x (vla-get-Layers doc)
      (if (eq :vlax-true (vla-get-lock x))
        (progn
          (setq laylst (cons x laylst))
          (vla-put-lock x :vlax-false)
        )
      )
    )
    laylst
  ) ;end

  ;; Argument: a list of layer objects from UnlockLayers above.
  ;; Use vl-catch-all-apply in case a locked
  ;; layer was deleted in the calling function.
  (defun MD:RelockLayers (lst)
    (foreach x lst
      (vl-catch-all-apply 'vla-put-lock (list x :vlax-true))
    )
  ) ;end

  (defun MD:GetXrefs (blklst / lst)
    (if (vl-every '(lambda (x) (= (type x) 'ENAME)) blklst)
      (foreach blk (mapcar 'vlax-ename->vla-object blklst)
        (if (vlax-property-available-p blk 'Path)
          (setq lst (cons blk lst))
        )
      )
    )
    (reverse lst)
  ) ;end

  (defun MD:Wait (seconds / stop)
    (setq stop (+ (getvar "DATE") (/ seconds 86400.0)))
    (while (> stop (getvar "DATE"))
      (princ)
    )
  ) ;end

  ;; Argument: angle in radians, any number including negative.
  ;; Returns: normalized angle in radians between zero and (* pi 2)
  (defun MD:NormalAngle (a)
    (if (numberp a)
      (angtof (angtos a 0 14) 0))
  ) ;end

  ;; Returns T if two lines, rays or xlines are parallel.
  (defun MD:ParallelObjects (obj1 obj2 fuzz / ang1 ang2)
    (if (eq "AcDbLine" (vlax-get obj1 'ObjectName))
      (setq ang1 (MD:NormalAngle (vlax-get obj1 'Angle)))
      (setq ang1 (MD:NormalAngle 
        (angle (vlax-get obj1 'BasePoint) (vlax-get obj1 'SecondPoint)))
      )
    )
    (if (eq "AcDbLine" (vlax-get obj2 'ObjectName))
      (setq ang2 (MD:NormalAngle (vlax-get obj2 'Angle)))
      (setq ang2 (MD:NormalAngle 
        (angle (vlax-get obj2 'BasePoint) (vlax-get obj2 'SecondPoint)))
      )
    )
    (or 
      (equal ang1 ang2 fuzz)
      (equal ang1 (MD:NormalAngle (+ pi ang2)) fuzz)
      (equal ang2 (MD:NormalAngle (+ pi ang1)) fuzz)
      (equal (MD:NormalAngle (+ pi ang1)) (MD:NormalAngle (+ pi ang2)) fuzz)
    )
  ) ;end

  (defun MD:Pick (msg / typlst e obj typ scflag)

    (setq typlst '("AcDbLine" "AcDbArc" "AcDbCircle" "AcDbEllipse" 
                   "AcDbPolyline" "AcDb2dPolyline" "AcDb2dVertex"
                   "AcDb3dPolyline" "AcDb3dPolylineVertex" "AcDbSpline"
                   "AcDbRay" "AcDbXline" "AcDbPoint"))

    (setvar "errno" 0)
    
    (while 
      (or
        (not (setq e (nentselp msg)))
        (not (setq obj (vlax-ename->vla-object (car e))))
        (not (vl-position (setq typ (vlax-get obj 'ObjectName)) typlst))
        (and
          (cadddr e)
          (not (apply 'and (mapcar 'MD:UniformScale (last e))))
          (setq scflag T)
        )
      )
      (cond
        ((= 52 (getvar "errno"))
          (exit)
        )
        ((not e)
          (princ "\nMissed pick. ")
        )
        (scflag
          (princ "\nNon-uniformly scaled block detected, try again. ")
          (setq scflag nil)
        )
        (typ
          (princ (strcat "\n " (substr typ 5) " selected, try again. "))
          (setq typ nil)
        )
      )
    )
    
    (if
      (or
        (eq "AcDb2dVertex" typ)
        (eq "AcDb3dPolylineVertex" typ)
      )
      (setq obj (vlax-ename->vla-object (cdr (assoc 330 (entget (car e)))))
            typ (vlax-get obj 'ObjectName)
      )
    )

    ;; Used to mark xref. Point passed to MD:XMark. 
    ;; The variable is local in the main routine.
    (setq pkpt (cadr e))
    (if (= 2 (length e))
      (list obj typ)
      (list obj typ (caddr e) (cadddr e))
    )
  ) ;end

  ;; Argument: UCS point.
  ;; Returns: nil
  (defun MD:XMark (pt / len p1 p2 p3 p4)
    (setq len (/ (getvar "viewsize") 75.0)
          p1 (polar pt (* pi 0.3) len)
          p2 (polar pt (* pi 0.7) len)
          p3 (polar pt (* pi 1.3) len)
          p4 (polar pt (* pi 1.7) len)
    )
    (grdraw p1 p3 7)
    (grdraw p2 p4 7)
  ) ;end

  ;; Test for uniformly scaled block reference.
  (defun MD:UniformScale (obj / x y z)
    (if (= (type obj) 'ENAME)
      (setq obj (vlax-ename->vla-object obj))
    )
    ;; Added 7/2/2008.
    (if (wcmatch (vlax-get obj 'ObjectName) "*Dimension")
      T
      (progn
        (setq x (vlax-get obj 'XScaleFactor)
              y (vlax-get obj 'YScaleFactor)
              z (vlax-get obj 'ZScaleFactor)
        )
        (and
          (equal (abs x) (abs y) 1e-12)
          (equal (abs y) (abs z) 1e-12)
        )
      )
    )
  ) ;end

  ;; Argument: a list returned by MD:Pick.
  ;; Returns: a vla-object. The first object in list if the object is
  ;; not nested. Otherwise a transformed copy of the object. 
  (defun MD:GetObject (lst / blkref blk obj)
    (cond
      ;; Object is not nested.
      ((= 2 (length lst))
        (setq obj (car lst))
      )
      ;; Object is nested in an xref. Copy it within the xref database.
      ;; The owner is not specified within the CopyObjects function.
      ((setq blkref (car (MD:GetXrefs (last lst))))
        (setq blk (vla-item blocks (vlax-get blkref 'Name)))
        (setq obj
          (car 
            (vlax-invoke
              (vlax-get blk 'XRefDatabase) 'CopyObjects (list (car lst)))))
        (vla-transformby obj (vlax-tmatrix (caddr lst)))
        (setq dellst (cons obj dellst))
        ;; Grdraw X mark on xref where it was selected
        ;; if it is the first object selected.
        (if (not obj1) (MD:XMark pkpt))
      )
      ;; Object is nested in a block reference. 
      ;; Copy it from the block and highlight in the main 
      ;; routine if it is the first object selected.
      (T
        (setq obj 
          (car (vlax-invoke doc 'CopyObjects (list (car lst))
            (vlax-get (vla-get-ActiveLayout doc) 'Block))))
        (vla-transformby obj (vlax-tmatrix (caddr lst)))
        (setq dellst (cons obj dellst))
      )
    )
    obj
  ) ;end

  ;; Argument: ray or xline vla-object.
  ;; Returns: a list of two 3D WCS points beyond where the object
  ;; intersects the edges of the current view.
  ;; The base point of a ray may be returned depending on its
  ;; location relative to the view.
  ;; Revised 6/30/2008.
  (defun MD:XlineOrRay (obj / basept zval secpt lst p pts p2 d typ 
                              expt1 expt2 MD:RectanglePts MD:RectangleList 
                              MD:FarthestPoint)

    ;;;; Sub-functions...

    ;; Pass two points representing a diagonal.
    ;; Returns a list of four UCS points.
    (defun MD:RectanglePts (p1 p2)
      (list
        p1
        (list (car p2) (cadr p1) (caddr p1)) ; revised 6/27/2008 
        p2
        (list (car p1) (cadr p2) (caddr p2)) ; should be OK within context, testing
      )
    ) ;end

    (defun MD:RectangleList ( p1 p2 / rpts)
      (setq rpts (MD:RectanglePts p1 p2))
      (mapcar '(lambda (a b) (list a b)) rpts (append (cdr rpts) (list (car rpts))))
    ) ;end

    (defun MD:FarthestPoint (pt ptlst / x dist res)
      (setq x 0)
      (foreach p ptlst
        (setq dist (distance p pt))
        (if (> dist x)
          (setq x dist res p)
        )
      )
      res
    ) ;end

    ;;;; End Sub-functions

    (setq basept (trans (vlax-get obj 'BasePoint) 0 1)
          zval (caddr basept)
          secpt (trans (vlax-get obj 'SecondPoint) 0 1)
          typ (vlax-get obj 'ObjectName)
    )

    ;; two 2D UCS points
    (if (not sc)
      (setq sc (MD:GetScreenCoords))
    )
    
    (setq d (distance (car sc) (cadr sc))
          sc (mapcar '(lambda (x) (append x (list zval))) sc)
          lst (MD:RectangleList (car sc) (cadr sc))
          sc nil
    )

    (foreach x lst
      (if 
        (and
          (setq p (inters basept secpt (car x) (cadr x) nil))
          (inters basept p (car x) (cadr x))
        )
        (setq pts (cons p pts))
      )
    )

    (cond
      ((eq "AcDbXline" typ)
        (setq expt1 (polar (cadr pts) (angle (cadr pts) (car pts)) (* 2 d))
              expt2 (polar (car pts) (angle (car pts) (cadr pts)) (* 2 d))
              pts (reverse (list expt1 expt2))
        )
      )
      ;; Revised 6/29/2008
      ((eq "AcDbRay" typ)
        (setq expt1 (MD:FarthestPoint basept pts)
              expt1 (polar expt1 (angle basept secpt) (* 2 d))
              pts (list basept expt1)
        )
        ;; If base point is far away attempt to get a closer point 
        ;; by testing for param at point.
        (setq expt2 (polar expt1 (angle secpt basept) (* 5 d)))
        (if (vlax-curve-getParamAtPoint obj (trans expt2 1 0))
          (setq pts (reverse (list expt2 expt1)))
        )
      )         
    )
    ;; Trans UCS points to WCS as needed.
    (mapcar '(lambda (x) (trans x 1 0)) pts)
  ) ;end MD:XlineOrRay

  ;;; END SUB-FUNCTIONS ;;;

  ;;; START MAIN FUNCTION ;;;

  (setq doc (vla-get-ActiveDocument (vlax-get-acad-object))
        blocks (vla-get-Blocks doc)
        locklst (MD:UnlockLayers doc)
        units (getvar "lunits")
  )
  
  (vla-StartUndoMark doc)

  (sssetfirst)

  (princ "\nSelect line, circle, arc, ellipse, polyline, spline, point, ray or xline.")

  (if 
    (and
      (setq res1 (MD:Pick "\nFirst object: "))
      (setq typ1 (cadr res1))
      (princ (substr typ1 5))
      (setq obj1 (MD:GetObject res1))
      (not (vla-highlight obj1 acTrue))
      ;; Get the screen coordinates here in case
      ;; the user pans between select objects.
      (if
        (or
          (eq "AcDbRay" typ1)
          (eq "AcDbXline" typ1)
        )
        (setq sc (MD:GetScreenCoords))
        T
      )
      (setq res2 (MD:Pick "\nSecond object: "))
      (setq typ2 (cadr res2))
      (princ (substr typ2 5))
      (setq obj2 (MD:GetObject res2))
    )
    (progn
      (cond 
        ((equal obj1 obj2)
          (princ "\n Same object selected twice. ")
          (setq resdist 0.0
                interflag T
          )
        )
        ((vlax-invoke obj1 'IntersectWith obj2 acExtendNone)
          (princ "\n Objects intersect. ")
          (setq resdist 0.0
                interflag T
          )
        )
        ((and
           (eq typ1 "AcDbPoint")
           (eq typ2 "AcDbPoint")
          )
          (setq *mdpt* (vlax-get obj1 'Coordinates)
                *mdp1* (vlax-get obj2 'Coordinates)
                d (distance *mdpt* *mdp1*)
          )
        )
        ((or
           (eq typ1 "AcDbPoint")
           (eq typ2 "AcDbPoint")
          )
          (if (eq typ1 "AcDbPoint")
            (setq *mdpt* (vlax-get obj1 'Coordinates)
                  *mdp1* (vlax-curve-getClosestPointTo obj2 *mdpt*)
            )
            (setq *mdpt* (vlax-get obj2 'Coordinates)
                  *mdp1* (vlax-curve-getClosestPointTo obj1 *mdpt*)
            )
          )
          (setq d (distance *mdpt* *mdp1*))
        )
        ;; Core stuff follows.
        (T
          (if 
            (or
             (eq typ2 "AcDbRay")
             (eq typ2 "AcDbXline")
            )
            ;; Reverse the objects and set a flag to reverse 
            ;; the points later.
            (setq temp obj1 obj1 obj2 obj2 temp reverseflag T)
          )
          
          (if (vlax-curve-getEndParam obj1)
            (setq len (vlax-curve-getDistAtParam obj1 (vlax-curve-getEndParam obj1)))
            ;; Obj1 is an xline or ray.
            (progn 
              (setq pts (MD:XlineOrRay obj1)
                    len (distance (car pts) (cadr pts))
                    idx1 (vlax-curve-getParamAtPoint obj1 (car pts))
                    idx2 (vlax-curve-getParamAtPoint obj1 (cadr pts))
              )
              (if (< idx1 idx2)
                (setq idx idx1)
                (setq idx idx2)
              )
            )
          )

          (if (not idx) (setq idx 0))

          ;; Number of divisions seems more than sufficient.
          (setq div 200
                inc (/ len div)
                fuzz 1e-8
          )

          ;; Check first object for the closest point on second object.
          (setq bd 
            (distance 
              (setq *mdp1* (vlax-curve-getPointAtDist obj1 idx))
              (vlax-curve-getClosestPointTo obj2 *mdp1*)
            )
          )
          (repeat (1+ div)
            (if 
              (and
                (setq *mdp1* (vlax-curve-getPointAtDist obj1 idx))
                (setq p2 (vlax-curve-getClosestPointTo obj2 *mdp1*))
              )
              (progn
                (setq d (distance *mdp1* p2))
                (setq idx (+ idx inc))
                (if (<= d bd)
                  (setq bd d *mdpt* *mdp1*)
                )
              )
            )
          )
          ;; Refine the minimum distance as needed. Start with closest
          ;; point on first object. Bounce the closest points back and
          ;; forth between the two objects until delta distance is less
          ;; than the fuzz factor.
          (while 
            (not
              (minusp
                (- (distance *mdpt* 
                   (setq *mdp1* (vlax-curve-GetClosestPointTo obj2 *mdpt*)))
                   (setq d 
                     (distance *mdp1* 
                       (setq *mdpt* (vlax-curve-GetClosestPointTo obj1 *mdp1*))))
                   fuzz
                )
              )
            )
          )
        )
      ) ;cond

      (if (and d *mdpt* *mdp1*)
        (progn
          (setq resdist d)
          ;; Added 9/6/2008.
          ;; If objects were reversed, reverse the points.
          (if reverseflag
            (setq temp *mdpt* *mdpt* *mdp1* *mdp1* temp)
          )          
          (grdraw (trans *mdpt* 0 1) (trans *mdp1* 0 1) -7 1)
          (if
            (and
              (or
                (eq "AcDbLine" typ1)
                (eq "AcDbXline" typ1)
                (eq "AcDbRay" typ1)
              )
              (or
                (eq "AcDbLine" typ2)
                (eq "AcDbXline" typ2)
                (eq "AcDbRay" typ2)
              )
            )
            (if (MD:ParallelObjects obj1 obj2 1e-8)
              (if (and (eq "AcDbLine" typ1) (eq "AcDbLine" typ2))
                (princ "\n Lines are parallel. ")
                (princ "\n Linear objects are parallel. ")
              )
            )
          )
          ;; Check the Z values of the two closest points.
          (setq z1 (caddr *mdpt*) z2 (caddr *mdp1*) diff (abs (- z1 z2)))
          (cond
            ((equal z1 z2 1e-10))
            ;; Units are scientific, decimal or engineering.
            ((< units 4)
              (princ 
                (strcat "\n Z values of the points differ by: "
                  (rtos diff units 10)
                )
              )
            )
            ;; The maximum display accuracy of architectural or
            ;; fractional units is 0.00196. If diff is less, 
            ;; change units to decimal.
            ((and
               (> units 3)
               (< diff 0.00196)
              )
              (princ 
                (strcat "\n Z values of the points differ by: "
                  (rtos diff (setvar "lunits" 2) 10)
                )
              )
              (setvar "lunits" units)
            )
            ;; Otherwise display diff in architectural or fractional units.
            (T
              (princ 
                (strcat "\n Z values of the points differ by: "
                  (rtos diff)
                )
              )
            )
          ) ;cond
        ) ;progn
      ) ;if
    ) ;progn
  ) ;if

  (if (and resdist *mdpt* *mdp1*)
    (progn
      (princ (strcat "\n Distance: " (rtos resdist)))
      (if (not interflag)
        (progn
          (setq sc (MD:GetScreenCoords))
          (if 
            (or
              (MD:PointInside (car sc) (cadr sc) *mdpt*)
              (MD:PointInside (car sc) (cadr sc) *mdp1*)
            )
            (princ "  Enter MDL to place minimum distance line. ")
            (princ "  Off screen points. MDL to place minimum distance line.")
          )
        )
      )
    )
    (princ "\n Could not calculate minimum distance. ")
  )
  
  (*error* nil)
) ;end MinDist

;shortcut
(defun c:MD () (c:MinDist))


;; Added 8/24/2008.
;; Allows a selection set to be moved from the first MinDist point to
;; the second MinDist point. So the order of object selection within 
;; MinDist is important in terms of which way the selection set will move.
;; IOW, if the user anticipates using this function after MD, the first object 
;; selected determines move from point. The second object selected is 
;; the move to point.
(defun c:MinDistMove ( / *error* doc osm ss)

  (defun *error* (msg)
    (cond
      ((not msg))
      ((wcmatch (strcase msg) "*QUIT*,*CANCEL*"))
      (T (princ (strcat "\nError: " msg)))
    )
    (setvar "osmode" osm)
    (vla-EndUndoMark doc)
    (princ)
  ) ;end error

  (setq doc (vla-get-ActiveDocument (vlax-get-acad-object)))
  (vla-StartUndoMark doc)
  (setq osm (getvar "osmode"))
  (if (and *mdpt* *mdp1* (setq ss (ssget)))
    (progn
      (setvar "osmode" 0)
      ;; Added trans 8/27/2008.
      (command "._move" ss "" (trans *mdpt* 0 1) (trans *mdp1* 0  1))
    )
    (princ "\nNothing selected or minimum distance points not set. ")
  )
  (*error* nil)
) ;end
;shortcut
(defun c:MDM () (c:MinDistMove))

(defun c:MinDistCopy ( / *error* doc osm ss)

  (defun *error* (msg)
    (cond
      ((not msg))
      ((wcmatch (strcase msg) "*QUIT*,*CANCEL*"))
      (T (princ (strcat "\nError: " msg)))
    )
    (setvar "osmode" osm)
    (vla-EndUndoMark doc)
    (princ)
  ) ;end error

  (setq doc (vla-get-ActiveDocument (vlax-get-acad-object)))
  (vla-StartUndoMark doc)
  (setq osm (getvar "osmode"))
  (if (and *mdpt* *mdp1* (setq ss (ssget)))
    (progn
      (setvar "osmode" 0)
      ;; Added trans 8/27/2008.
      (command "._copy" ss "" (trans *mdpt* 0 1) (trans *mdp1* 0  1))
    )
    (princ "\nNothing selected or minimum distance points not set. ")
  )
  (*error* nil)
) ;end
;shortcut
(defun c:MDC () (c:MinDistCopy))


;; Revised 6/30/2008.
;; Draw minimum distance line on the current layer.
(defun c:MinDistLine ( / d sc ss)
  (cond
    ((not (and *mdpt* *mdp1*))
      (princ "\n Minimum distance points not found. Run MD and then MDL to draw line.")
    )
    ((and 
       (setq d (distance *mdpt* *mdp1*))
       ;(print d) ;testing
       (< d 1e-5)
      )
      (princ "\n Minimum distance points are too close together. ")
    )
    (T
      (entmake 
        (list 
          '(0 . "LINE")
           (cons 8 (getvar "clayer"))
           (cons 10 *mdpt*)
           (cons 11 *mdp1*)
        )
      )
      (setq sc (MD:GetScreenCoords))
      (if 
        (or
          (MD:PointInside (car sc) (cadr sc) *mdpt*)
          (MD:PointInside (car sc) (cadr sc) *mdp1*)
        )
        (princ "\n Minimum distance line placed. ")
        (progn
          (princ "\n Minimum distance line placed off screen and selected. ")
          (sssetfirst nil (setq ss (ssget "L")))
        )
      )
    )
  )
  (princ)
) ;end MinDistLine

;shortcut
(defun c:MDL () (c:MinDistLine))