Jump to content

Lisp to convert partial ellipse to polyline


Fermis

Recommended Posts

I am pretty new to lisp programming and have only been working in it for a few months and I am pretty stuck on how to get this lisp program I've hacked together into what I need. Any help would be greatly appreciated.

 

I would like help optimizing the lisp to be more like autocad's save as an old dxf way of converting an ellipse to a polyline. The current way I am converting an ellipse to poly line is by moving a static degree of distance between each point, the way I cannot figure out and the ideal way for me to do this is by specifying the maximum gap between the true ellipse and the polyline version. I have looked at other lisp programs out there and they are not high resolution enough for what I need, I need the data to be extremely close to the actual ellipse. I would appreciate help in pointing me towards how to do this mathematically, help implement that into a lisp, and any tips on best practice lisp programming that I might be really messing up here.  

 

Below is the function I have written to handle this so far, it takes in el which is the vla ellipse object and  angleIncrement which is the degree difference between two points of the polyline. In a perfect world the second argument would be changed to be the maximum distance between the true ellipse and the polyline version. This is a function used in another script that then chains the data and does a few other things as well.

 

(defun EllipseToLine (el angleIncrement / majorradius minorradius startangle endangle startpoint endpoint centerpoint tempVar)
  (vl-load-com)
  (setvar "cmdecho" 0)
  (setq majorradius (vla-get-MajorRadius el))
  (setq minorradius (vla-get-MinorRadius el))
  (setq startangle (* (vla-get-StartAngle el) (/ 180 pi)))
  (setq endangle (* (vla-get-EndAngle el)(/ 180 pi)))
  (setq startpoint (vlax-safearray->list (vlax-variant-value (vla-get-startpoint el))))
  (setq endpoint (vlax-safearray->list (vlax-variant-value (vla-get-endpoint el))))
  (setq centerpoint (vlax-safearray->list (vlax-variant-value (vla-get-center el))))
  (setq centerx (nth 0 centerpoint))
  (setq centery (nth 1 centerpoint))

  (princ "End angle: ")(princ endangle)(princ "\n")
  (princ "Start angle: ")(princ startangle)(princ "\n")
  
  (setq startx (nth 0 startpoint))
  (setq starty (nth 1 startpoint))
  (setq endx (nth 0 endpoint))
  (setq endy (nth 1 endpoint))
  
  ; make sure we convert the ellipse on the correct layer and then later return to the layer we were on
  (setq ellipseLayer (vla-get-layer el))
  (setq currentLayer (getvar "clayer"))
  (setvar "clayer" ellipseLayer)
  
  ; calculate angle between start angle and start point because the start point 
  ; is not at the start angle so we have to adjust so numbers match up
  (setq angleOffset (- (* (angle centerpoint startpoint) (/ 180 pi)) startangle))
  (setq radianOffset (* angleOffset (/ pi 180)))
     
  ; a = major radius
  ; b = minor radius
  (setq ecc (sqrt (- 1 (/ (expt minorradius 2) (expt majorradius 2)))))
  (setq angleInverted 0)
  (setq done 0)
  (setq i 0)
  
  (setq currentAngle startangle)
  
  (setq nintyDegrees 90.0)
  (setq twoHundredSeventyDegrees 270.0)
  
  (while (= done 0)
    (if (< currentAngle 0)
      (progn
        (setq currentAngle (+ currentAngle 360))                    
        (setq angleInverted 1)
      )
    )
    (if (> currentAngle 360)
      (progn
        (setq currentAngle (- currentAngle 360))
        (setq angleInverted 1)
      )
    )
    
    (cond
      ((or (and (> startangle endangle) (= angleInverted 0)) (and (> endangle startangle) (= angleInverted 1)))
        (if (< currentAngle endangle)
          (setq currentAngle endangle)
        )
      )
      ((or (and (> endangle startangle) (= angleInverted 0)) (and (> startangle endangle) (= angleInverted 1)))
        (if (> currentAngle endangle)
          (setq currentAngle endangle)
        )
      )
    )
 
    (setq newAngle currentAngle)
    (if (or (> newAngle 0) (< newAngle 0))
      (setq newAngle (* newAngle (/ pi 180)))  
    )
    
    (setq factor -1)
    (if  (or (<= currentAngle nintyDegrees) (>= currentAngle twoHundredSeventyDegrees))
      (setq factor 1)
    )
    
    ; using the origional un rotated angle get the next point on the ellipse
    (setq x2 (/ (* majorradius minorradius) (* factor (sqrt (+ (expt minorradius 2) (* (expt majorradius 2) (expt (/ (sin newAngle) (cos newAngle)) 2)))))))
    (setq y2 (* x2 (/ (sin newAngle) (cos newAngle))))
    
    (setq
      x2 (+ x2 centerx)
      y2 (+ y2 centery)
    )
    
    ; rotate point on the ellipse by radian/angle offset so it matches the cad data
    (setq rotatedx (+ (- (* (cos radianOffset) (- x2 centerx)) (* (sin radianOffset) (- y2 centery))) centerx))
    (setq rotatedy (+ (+ (* (sin radianOffset) (- x2 centerx)) (* (cos radianOffset) (- y2 centery))) centery))
    
    (setq
      x2 rotatedx
      y2 rotatedy
    )

    (if (= currentAngle endangle)
      (setq done 1)
    )

    (if (> i 0)
      (if (= i 1)
        (command "_.line" (strcat (rtos startx) "," (rtos starty)) (strcat (rtos x2) "," (rtos y2)) "")
        (if (= done 1)
          (command "_.line" (strcat (rtos x1) "," (rtos y1)) (strcat (rtos endx) "," (rtos endy)) "")
          (command "_.line" (strcat (rtos x1) "," (rtos y1)) (strcat (rtos x2) "," (rtos y2)) "")
        )
      )
    )
 
    (setq
      x1 x2
      y1 y2
    )

    (setq
      currentAngle (+ currentAngle angleIncrement)
      i (+ 1 i)
    )
  )
  
  ; delete the origional ellipse
  (vla-delete el)

  (setvar "clayer" currentLayer)
  (princ)
)

 

Link to comment
Share on other sites

Welcome, I know you have only been coding for a month but read up on lisp functions. this can save you alot of time in the long run. also writing out what you want to do and then inserting code to replace notes is a good way to start. like

 

;; redraw lisp as polyline

1. Select ellipse

2. Set system variable to draw ellipse as polyline

3. Calculate points needed to draw ellipse

     3.1 What is needed? what is duplicate information?

4. Draw polyline

5. Set system variable back

 

This is where over thinking a problem can take you down a rabbit hole.  I myself have had this happen on multiple occasions. replacing alot of code with something simple.

 

Pulled a little inspiration from here and updated the code.

;;----------------------------------------------------------------------------;;
;; Convert Spline Ellips to Polyline
(defun C:E2P (/ old cen ent d P1 P2 P3)
  (while (setq old (car (entsel "\nSelect Ellipse To Change: "))) ;Select nothing to exit
    (if (= (cdr (assoc 0 (setq ent (entget old)))) "ELLIPSE")
      (progn
        (setvar 'pellipse 1)
        (setq cen (cdr (assoc 10 ent))
              P1 (vlax-curve-getStartPoint old)
              d (distance p1 cen)
              P2 (polar cen (angle P1 cen) (distance P1 cen))
              P3 (vlax-curve-getclosestpointto old cen)
        )
        (command "ELLIPSE" P1 P2 P3)
        (setvar 'pellipse 0)
      )
      (alert "This is not a spline ellipse!")
    )
  )
  (princ)
)
Edited by mhupp
Link to comment
Share on other sites

Try this a Google would have found something similar.

 

; convert ellipse to pline
; By AlanH Jan 2024

(defun c:ellpl ( / lwpoly obj oldsnap num inc dist plst pt)
(defun LWPoly (lst cls)
 (entmakex (append (list (cons 0 "LWPOLYLINE")
                         (cons 100 "AcDbEntity")
                         (cons 100 "AcDbPolyline")
                         (cons 90 (length lst))
                         (cons 70 cls))
                   (mapcar (function (lambda (p) (cons 10 p))) lst))))
(setq obj (vlax-ename->vla-object (car (entsel "\nPick ellipse "))))
(setq oldsnap (getvar 'osmode))
(setvar 'osmode 0)
(setq num 100)
(setq  inc (/ (vlax-curve-getdistatparam obj (vlax-curve-getendparam obj)) num)
       dist 0.0
	   plst '()
)

(repeat (+ num 1)
       (setq pt (vlax-curve-getpointatdist obj dist))
	   (setq plst (cons pt plst))
       (setq dist (+ dist Inc))
)

(LWPoly plst 1)

(setvar 'oldsnap)
(princ 
)

 

Link to comment
Share on other sites

I tried this code and it works very well for a full ellipse but with a partial ellipse it's not working at all. I didn't see an option when drawing an ellipse to set the start and end angle of the ellipse.

 

13 hours ago, mhupp said:

Welcome, I know you have only been coding for a month but read up on lisp functions. this can save you alot of time in the long run. also writing out what you want to do and then inserting code to replace notes is a good way to start. like

 

;; redraw lisp as polyline

1. Select ellipse

2. Set system variable to draw ellipse as polyline

3. Calculate points needed to draw ellipse

     3.1 What is needed? what is duplicate information?

4. Draw polyline

5. Set system variable back

 

This is where over thinking a problem can take you down a rabbit hole.  I myself have had this happen on multiple occasions. replacing alot of code with something simple.

 

Pulled a little inspiration from here and updated the code.

;;----------------------------------------------------------------------------;;
;; Convert Spline Ellips to Polyline
(defun C:E2P (/ old cen ent d P1 P2 P3)
  (while (setq old (car (entsel "\nSelect Ellipse To Change: "))) ;Select nothing to exit
    (if (= (cdr (assoc 0 (setq ent (entget old)))) "ELLIPSE")
      (progn
        (setvar 'pellipse 1)
        (setq cen (cdr (assoc 10 ent))
              P1 (vlax-curve-getStartPoint old)
              d (distance p1 cen)
              P2 (polar cen (angle P1 cen) (distance P1 cen))
              P3 (vlax-curve-getclosestpointto old cen)
        )
        (command "ELLIPSE" P1 P2 P3)
        (setvar 'pellipse 0)
      )
      (alert "This is not a spline ellipse!")
    )
  )
  (princ)
)

 

Link to comment
Share on other sites

This works well with a full ellipse as well however on a partial ellipse it just fills in the empty part with a line. This was the main issue I had when trying out ellipse to polyline lisps I found, they work well on a full ellipse but have issues with a partial. 

 

11 hours ago, BIGAL said:

Try this a Google would have found something similar.

 

; convert ellipse to pline
; By AlanH Jan 2024

(defun c:ellpl ( / lwpoly obj oldsnap num inc dist plst pt)
(defun LWPoly (lst cls)
 (entmakex (append (list (cons 0 "LWPOLYLINE")
                         (cons 100 "AcDbEntity")
                         (cons 100 "AcDbPolyline")
                         (cons 90 (length lst))
                         (cons 70 cls))
                   (mapcar (function (lambda (p) (cons 10 p))) lst))))
(setq obj (vlax-ename->vla-object (car (entsel "\nPick ellipse "))))
(setq oldsnap (getvar 'osmode))
(setvar 'osmode 0)
(setq num 100)
(setq  inc (/ (vlax-curve-getdistatparam obj (vlax-curve-getendparam obj)) num)
       dist 0.0
	   plst '()
)

(repeat (+ num 1)
       (setq pt (vlax-curve-getpointatdist obj dist))
	   (setq plst (cons pt plst))
       (setq dist (+ dist Inc))
)

(LWPoly plst 1)

(setvar 'oldsnap)
(princ 
)

 

 

Link to comment
Share on other sites

Here's a modification to @BIGAL's code that let's you specify the maximum error between the true ellipse and the polyline approximation.  It uses the fact that the maximum deviation will occur on the ellipse at the peak of the major axis where the angle subtended by a chord is:

angle = acos( (semimajor_axis - error) / (semimajor_axis))

This angle is used to determine the number of points arond the ellipse.

(defun c:ellpl (/ lwpoly obj oldsnap num inc dist plst pt)
  (vl-load-com)
  (setq err (getreal "\nEnter maximum error"))
  (defun LWPoly	(lst cls)
    (entmakex
      (append (list (cons 0 "LWPOLYLINE")
		    (cons 100 "AcDbEntity")
		    (cons 100 "AcDbPolyline")
		    (cons 90 (length lst))
		    (cons 70 cls)
	      )
	      (mapcar (function (lambda (p) (cons 10 p))) lst)
      )
    )
  )
  (setq obj (vlax-ename->vla-object (car (entsel "\nPick ellipse "))))
  (setq oldsnap (getvar 'osmode))
  (setvar 'osmode 0)
  (setq a (vla-get-MajorRadius obj))
  (setq thetr (acos (/ (- a err) a)))
  (setq num (/ (* 2 pi) thetr))
  (setq num (atoi (rtos (- num 0.5) 2 0))) ; round up to integer
  ;;  (setq num 100)
  (setq	inc  (/	(vlax-curve-getdistatparam obj (vlax-curve-getendparam obj))
		num
	     )
	dist 0.0
	plst '()
  )
  (repeat (+ num 1)
    (setq pt (vlax-curve-getpointatdist obj dist))
    (setq plst (cons pt plst))
    (setq dist (+ dist Inc))
  )

  (LWPoly plst 1)

  (setvar 'oldsnap)
  (princ)
)
;; ArcCosine  -  Lee Mac
;; Args: -1 <= x <= 1

(defun acos ( x )
    (if (<= -1.0 x 1.0)
        (atan (sqrt (- 1.0 (* x x))) x)
    )
)

 

  • Like 1
Link to comment
Share on other sites

@lrm This works wonderfully other than the issue with the partial ellipse where it fills in the gap in the ellipse with a line, is there a way to have it take into account the start and end angle of the ellipse that you're aware of?

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...