lispautocadautocad-pluginautolisp

Automatically convert rectangle into concave arcs with a known degree


I am wanting to automatically convert all sides of a rectangle drawn by the user into concave arcs (about 10 degrees all around). I found this wonderful routine written by Marko Ribar (sorry if that's not correct credit) that does exactly what I'm looking for, except that the user has to manually define direction and degree of the arcs using the mouse. Push mouse positive Y direction = convex. Pull mouse negative Y direction = concave (which is what I want). Does anyone know how to add code to automatically pull in the negative Y direction about 10 degrees once rectangle is selected? Is there an easier way to accomplish this task by just making 10 degree arcs instead of lines when drawing a rectangle?

;Code to convert rectangle lines into arcs:

(defun c:lwstraight2arced ( / nthmassocsubst lw enx vs gr enxb p b i pt1 pt2 pt3 pt4 myrec )

;My added code to draw a rectangle by the user picking two opposite corners-----------------
(setq pt1 (getpoint "\nEnter first corner: "))
(setq pt3 (getcorner pt1 "\nEnter cross corner: "))
(setq pt2 (list (car pt1) (cadr pt3)))
(setq pt4 (list (car pt3) (cadr pt1)))
(setq myrec (command "rectangle" pt1 pt3 ""))
;end of my added code------------------------


  (defun nthmassocsubst ( n key value lst / k slst p j plst m tst pslst )
    (setq k (length (setq slst (member (assoc key lst) lst))))
    (setq p (- (length lst) k))
    (setq j -1)
    (repeat p
      (setq plst (cons (nth (setq j (1+ j)) lst) plst))
    )
    (setq plst (reverse plst))
    (setq j -1)
    (setq m -1)
    (repeat k
      (setq j (1+ j))
      (if (equal (assoc key (member (nth j slst) slst)) (nth j slst) 1e-6)
        (setq m (1+ m))
      )
      (if (and (not tst) (= n m))
        (setq pslst (cons (cons key value) pslst) tst t)
        (setq pslst (cons (nth j slst) pslst))
      )
    )
    (setq pslst (reverse pslst))
    (append plst pslst)
  )

;
; ----removed original code that has user select a line or rectangle manually-----
;          (setq lw (car (entsel "\nPick LWPOLYLINE straight polygon...")))
;-----end of original code-----



;-------My new code to have a previously drawn rectangle above automatically selected-----------------------------------
            (setq lw (entlast))
;-----End of my new code------


  (setq enx (entget lw))
  (setq vs (getvar 'viewsize))
  (while (= 5 (car (setq gr (grread t))))
    (setq enxb (acet-list-m-assoc 42 enx))
    (setq p (cadr gr))
    (setq b (/ (cadr p) vs))
    (setq i -1)
    (foreach dxf42 enxb
      (setq enx (nthmassocsubst (setq i (1+ i)) 42 b enx))
    )
    (entupd (cdr (assoc -1 (entmod enx))))
  )
  (princ)
)

Here is what I hope to achieve once a rectangle is drawn by the user. A slightly concaved rectangle without user input.

enter image description here

I have added to the routine the following steps:

-Get area of rectangle by multiplying length by width. -Draw circles and rectangles with a specific size at each corner based on square footage of rectangle in inches.

Here is the working code with the additions above:

(defun c:concavearc ( / b p q s z myrecarea convertrecarea pt1 pt2 pt3 pt4 circleset24 circleset36 circleset48)
; Create concave arcs from a rectangle
    (setq s 20.0) ;; Arc sagitta
    
    (if (and (setq p (getpoint "\nSpecify first corner: "))
             (setq q ((if (zerop (getvar 'worlducs)) getpoint getcorner) p "\nSpecify opposite corner: "))
             (mapcar 'set '(p q) (mapcar '(lambda ( x ) (mapcar x p q)) '(min max)))
             (setq z (trans '(0 0 1) 1 0 t)
                   b (mapcar '(lambda ( a b c ) (/ s (- a b) -0.5)) q p '(0 0))
             )
        )
        (entmake
            (list
               '(000 . "LWPOLYLINE")
               '(100 . "AcDbEntity")
               '(100 . "AcDbPolyline")
               '(090 . 4)
               '(070 . 1)
                (cons 010 (trans p 1 z))
                (cons 042 (car b))
                (cons 010 (trans (list (car q) (cadr p)) 1 z))
                (cons 042 (cadr b))
                (cons 010 (trans q 1 z))
                (cons 042 (car b))
                (cons 010 (trans (list (car p) (cadr q)) 1 z))
                (cons 042 (cadr b))
                (cons 210 z)
            )
        )
    )


;Add width of 2" and dashed line to arc polyline -----------------------------------------
  
(setvar "cmdecho" 0); disable command echo 
(setq Plnwdth (ssget "L"));get last entity
(command "pedit" Plnwdth "W" 2 "");set last entity width to 2

 (if (null (tblsearch "ltype" "dashed"))  
  (command ".linetype" "load" "dashed" "acad.lin" "")
 )

(command "change" Plnwdth "" "p" "LType" "Dashed" "" "ltScale" "30" "");lynetype
(setvar "cmdecho" 1); restore command echo
;end add width of 2 to arc polyline-----------------------------------------



;End concave arcs-----------------------------------------


;Setting corners of rectangle to variables --------------------------------------------------------------


; Get the four points of rectangle drawn by user above
(setq pt1 p)
(setq pt3 q)
(setq pt2 (list (car pt1) (cadr pt3)))
(setq pt4 (list (car pt3) (cadr pt1)))
(setq mylength (distance pt1 pt2)); length
(setq mywidth (distance pt1 pt4)); width
(setq myrecarea (* mylength mywidth)); Get area of rectangle (length x width)


(setvar "unitmode" 1);units to be displayed as entered
(setq convertrecarea (rtos myrecarea 4 2)); converts "convertrecarea" string into architectural format:

; Change units from decimal to Architectural
  (if *decimal*
    (progn
      (command "_.-units" "2" "" "" "" "" "")
      (setq *decimal* nil)
    )
    (progn
      (command "_.-units" "4" "" "" "" "" "")
      (setq *decimal* t)
    )
  )







; create conditional "if/and" functions based on rectangle area in square inches.
; if area of rectangle below 14400 SQ.Inches, give message to redraw rectangle
      (if
        (<= myrecarea 14400)
        (prompt "Area to small. Redraw area again")
      ); End IF
; if area of rectangle is between 14401 SQ.Inches and 57600 SQ.Inches, place a 24" circle at each corner of rectangle points
  
    (if
        (and
          (>= myrecarea 14401)
          (<= myrecarea 57600)
        ); End AND
              (progn
        (command "-color" "t" "99,100,102" ""); Change color
                (command "circle" pt1 "d" 24 0 ""); Create circle
        (command "_copy" "last" "" "M" pt1 pt2 pt3 pt4 ""); Copy circle to all four points of rectangle
        (setq circleset24 (ssget "_C" pt1 pt3 '((0 . "CIRCLE")))); Create selection set of circles using fencing around rectangle
        (command "_hatch" "p" "AR-Conc" "2" "0" "s" circleset24 ""); Hatch selected circles
        
;Create rectangle from centerpoint. Copy to each corner
    (command "-color" "t" "255,255,255" "")
    (setq len 2)
    (setq wid 2)
    (setq z1 (trans '(0 0 1) 1 0 t))
    (if (setq cnt1 pt1)
        (entmake
            (list
               '(000 . "LWPOLYLINE")
               '(100 . "AcDbEntity")
               '(100 . "AcDbPolyline")
               '(090 . 4)
               '(070 . 1)
                (cons 010 (trans (mapcar '+ cnt1 '(-2 -2)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '( 2 -2)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '( 2  2)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '(-2  2)) 1 z1))
                (cons 210 z1)

            )
        )
    )
        (command "_copy" "last" "" "M" pt1 pt2 pt3 pt4 "")
        
          ); End Progn
    ); End IF
  
; if area of rectangle is between 57601 SQ.Inches and 129600 SQ.Inches, place a 36" circle at each corner of rectangle points
    (if
        (and
          (>= myrecarea 57601)
          (<= myrecarea 129600)
        ); End AND
          (progn
        (command "-color" "t" "99,100,102" "")
            (command "circle" pt1 "d" 36 0 "")
            (command "_copy" "last" "" "M" pt1 pt2 pt3 pt4 "")
        (setq circleset36 (ssget "_C" pt1 pt3 '((0 . "CIRCLE"))))
        (command "_hatch" "p" "AR-Conc" "2" "0" "s" circleset36 "")

;Create rectangle from centerpoint. Copy to each corner
    (command "-color" "t" "255,255,255" "")
    (setq len 3)
    (setq wid 3)
    (setq z1 (trans '(0 0 1) 1 0 t))
    (if (setq cnt1 pt1)
        (entmake
            (list
               '(000 . "LWPOLYLINE")
               '(100 . "AcDbEntity")
               '(100 . "AcDbPolyline")
               '(090 . 4)
               '(070 . 1)
                (cons 010 (trans (mapcar '+ cnt1 '(-3 -3)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '( 3 -3)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '( 3  3)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '(-3  3)) 1 z1))
                (cons 210 z1)

            )
        )
    )
        (command "_copy" "last" "" "M" pt1 pt2 pt3 pt4 "")

        
          ); End Progn
    ); End IF
  
; if area of rectangle is between 129601 SQ.Inches and 230400 SQ.Inches, place a 48" circle at each corner of rectangle points

    (if
        (and
         (>= myrecarea 129601)
         (<= myrecarea 230400)
        ); End AND
          (progn
       (command "-color" "t" "99,100,102" "")
           (command "circle" pt1 "d" 48 0 "")
       (command "_copy" "last" "" "M" pt1 pt2 pt3 pt4 "")
       (setq circleset48 (ssget "_C" pt1 pt3 '((0 . "CIRCLE"))))
       (command "_hatch" "p" "AR-Conc" "2" "0" "s" circleset48 "")
       

;Create rectangle from centerpoint. Copy to each corner
    (command "-color" "t" "255,255,255" "")
    (setq len 4)
    (setq wid 4)
    (setq z1 (trans '(0 0 1) 1 0 t))
    (if (setq cnt1 pt1)
        (entmake
            (list
               '(000 . "LWPOLYLINE")
               '(100 . "AcDbEntity")
               '(100 . "AcDbPolyline")
               '(090 . 4)
               '(070 . 1)
                (cons 010 (trans (mapcar '+ cnt1 '(-4 -4)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '( 4 -4)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '( 4  4)) 1 z1))
                (cons 010 (trans (mapcar '+ cnt1 '(-4  4)) 1 z1))
                (cons 210 z1)

            )
        )
    )
        (command "_copy" "last" "" "M" pt1 pt2 pt3 pt4 "")

          ); End Progn
    ); End IF


; if area of rectangle is above 230401 SQ.Inches tell user to redraw area

    (if
        (>= myrecarea 230401)
        (prompt "Area to big. Redraw area again")
    ); End IF
    (princ convertrecarea)

);End "concavearc"

The new addition is working but I would like to be able to do the following:

Put the Arc segment of code inside an "IF" Statement so if rectangle drawn by user is too big or too small, prompt user "Out of range" and end routine. Right now, it draws the arcs regardless if out of range.

I tried to put in an IF statement below but didn't work. Here is a snippet of that:

        (defun c:concavearc ( / b p q s z myrecarea convertrecarea pt1 pt2 pt3 pt4)
          
        ; ------------------ Create concave arcs from a rectangle----------
            (setq s 20.0) ;; Arc sagitta
            
        (if (and (setq p (getpoint "\nSpecify first corner: "))
                     (setq q ((if (zerop (getvar 'worlducs)) getpoint getcorner) p "\nSpecify opposite corner: "))
        
            ; Get the four points of rectangle drawn by user above
            (setq pt1 p)
            (setq pt3 q)
            (setq pt2 (list (car pt1) (cadr pt3)))
            (setq pt4 (list (car pt3) (cadr pt1)))
            (setq mylength (distance pt1 pt2)); length
            (setq mywidth (distance pt1 pt4)); width
            (setq myrecarea (* mylength mywidth)); Get area of rectangle (length x width)
             
        
        ; If area drawn is above 129601square inches or below 14400 square inches display message the "not in range". Else, draw concave arcs.   
            (if
                (and
                 (>= myrecarea 129601)
                 (<= myrecarea 14400)
                ); End and
             (prompt "Not in range. Redraw area again")   
        
             (progn
                     Concave Arc code here....
    
              );End Progn
           );End "if/and" conditional statement
   );End "get points"

Just as an internal issue. How do you put in numbers for Square feet rather than inches?

Example:

(<= myrecarea 14400)

into

(<= myrecarea 100 Sq. Ft.)

Solution

  • For this task, I would suggest a function along the lines of the following:

    (defun c:caverec ( / b p q s z )
    
        (setq s 1.0) ;; Arc sagitta
        
        (if (and (setq p (getpoint "\nSpecify first corner: "))
                 (setq q ((if (zerop (getvar 'worlducs)) getpoint getcorner) p "\nSpecify opposite corner: "))
                 (mapcar 'set '(p q) (mapcar '(lambda ( x ) (mapcar x p q)) '(min max)))
                 (setq z (trans '(0 0 1) 1 0 t)
                       b (mapcar '(lambda ( a b c ) (/ s (- a b) -0.5)) q p '(0 0))
                 )
            )
            (entmake
                (list
                   '(000 . "LWPOLYLINE")
                   '(100 . "AcDbEntity")
                   '(100 . "AcDbPolyline")
                   '(090 . 4)
                   '(070 . 1)
                    (cons 010 (trans p 1 z))
                    (cons 042 (car b))
                    (cons 010 (trans (list (car q) (cadr p)) 1 z))
                    (cons 042 (cadr b))
                    (cons 010 (trans q 1 z))
                    (cons 042 (car b))
                    (cons 010 (trans (list (car p) (cadr q)) 1 z))
                    (cons 042 (cadr b))
                    (cons 210 z)
                )
            )
        )
        (princ)
    )
    

    Here, the degree of curvature of the four sides of the rectangle is controlled by the single parameter s defined at the top of the code - this parameter corresponds to the length of the arc sagitta, spanning the distance between the arc midpoint and the midpoint of the chord between each pair of vertices.

    The code prompts the user to specify two points corresponding to opposite corners of the rectangle, and will proceed to construct a closed 4-vertex 2D polyline (LWPOLYLINE) for which the bulge of each segment is calculated by dividing the sagitta by half the chord length (which equates to the tangent of a quarter of the angle spanned by the arc, which is the definition of the bulge of a polyline segment - I describe this relationship in more detail here).

    The code acquires the two points relative to the active UCS and calculates the remaining two vertices with respect to the active UCS, before transforming all coordinates to be relative to the Object Coordinate System (OCS). This means that the rectangle will be aligned to the x-axis of the active UCS and will operate as expected under all UCS & View settings within AutoCAD.

    The function uses the getpoint function to prompt for the opposite corner when the UCS is not equal to the WCS in order to avoid confusion arising from the use of getcorner, which does not honour UCS rotation.

    The resulting polyline will be constructed on the current layer, inheriting all object properties (colour, lineweight, linetype etc.) active at the time that the program is evaluated.