How do I access Quoted Variable Data in AutoLisp? - lisp

I'm having trouble accessing the information stored in the lists STPT1 and ENDPT1 which are x(0), y(1), and z(2) coordinates.
For instance, after getting a point: (45.4529 21.6384 0.0) when I inspect with Visual LISP (-(NTH 1 STPT1) 0.5) I get a REAL 21.1384, but the following:
(SETQ STPTP2 '((NTH 0 STPT1) (- (NTH 1 STPT1) 0.5) 0))
creates the list:
((NTH 0 STPT1) (- (NTH 1 STPT1) 0.5) 0)
instead of:
(45.4529 21.1384 0.0)
My goal is to simultaneously create two parallel lines that are 0.5 units apart from each other.
How can I access the information in different positions of the lists STPT1 and ENDPT1 and then assign them in lists STPT2 and ENDPT2?
(VL-LOAD-COM)
(DEFUN C:CURBYOURENTHUSIASM ( / STPT1 ENDPT1 STPT2 ENDPT2)
(SETQ STPT1 (GETPOINT "\nSpecify start point: "))
(SETQ ENDPT1 (GETPOINT STPT1 "\nSpecify end point: "))
(SETQ STPT2 '((NTH 0 STPT1) (-(NTH 1 STPT1) 0.5) 0))
(SETQ ENDPT2 '((NTH 0 ENDPT1) (-(NTH 1 ENDPT1) 0.5) 0))
(SETQ TOP (ENTMAKE (LIST (CONS 0 "LINE")(CONS 10 STPT1)(CONS 11 ENDPT1)(CONS 8 "CONCRETE"))))
(SETQ BOTTOM (ENTMAKE (LIST (CONS 0 "LINE")(CONS 10 STPT2)(CONS 11 ENDPT2)(CONS 8 "CONCRETE"))))
(PRINC)
)

Current Issues
There are a number of issues with your current code:
1. Unbalanced Parentheses
You have one too many closing parentheses on line 5 of your code:
(SETQ STPT2 '((NTH 0 STPT1) (-(NTH 1 STPT1) 0.5) 0)))
The final closing parenthesis at the end of the above expression is closing the defun expression, resulting in the remaining expressions being evaluated on load, rather than when the function is evaluated.
2. Quoted Variable Data
You are incorrectly quoting the following expressions as literal expressions:
(SETQ STPT2 '((NTH 0 STPT1) (-(NTH 1 STPT1) 0.5) 0))
(SETQ ENDPT2 '((NTH 0 ENDPT1) (-(NTH 1 ENDPT1) 0.5) 0))
Expressions which follow the single quote will not be evaluated by the AutoLISP interpreter, but will instead be taken at 'face-value'.
This means that the nth and - functions will not be evaluated, but will instead be interpreted simply as symbols within a nested list structure. For more information on literal expressions, you may wish to refer to my tutorial describing the Apostrophe & Quote Function.
To construct a list of variable (i.e. non-literal) data, you should use the list function, e.g.:
(setq stpt2 (list (nth 0 stpt1) (- (nth 1 stpt1) 0.5) 0))
3. Unnecessary ActiveX
You are unnecessarily loading the Visual LISP ActiveX extensions (using (vl-load-com)), but are not using any functions from this library in your code. This is a relatively minor issue, but worth mentioning nonetheless.
Correcting the above issues and formatting your code with appropriate indentation, we have the following:
(defun c:curbyourenthusiasm ( / stpt1 endpt1 stpt2 endpt2 )
(setq stpt1 (getpoint "\nSpecify start point: "))
(setq endpt1 (getpoint stpt1 "\nSpecify end point: "))
(setq stpt2 (list (nth 0 stpt1) (- (nth 1 stpt1) 0.5) 0))
(setq endpt2 (list (nth 0 endpt1) (- (nth 1 endpt1) 0.5) 0))
(setq top (entmake (list (cons 0 "line") (cons 10 stpt1) (cons 11 endpt1) (cons 8 "concrete"))))
(setq bottom (entmake (list (cons 0 "line") (cons 10 stpt2) (cons 11 endpt2) (cons 8 "concrete"))))
(princ)
)
This code will now run successfully, but there are a number of possible improvements:
Possible Improvements
1. User Input Validation
You should test for valid user input before proceeding to operate on data obtained from the user: if the user dismisses the prompts without supplying a point, any arithmetic operations on the list values will error, as such values will be nil.
You can avoid such errors by simply using an if statement:
(defun c:curbyourenthusiasm ( / ep1 sp1 )
(if
(and
(setq sp1 (getpoint "\nSpecify start point: "))
(setq ep1 (getpoint "\nSpecify end point: " sp1))
)
(progn
;; Continue with program operations
)
)
(princ)
)
2. Line Angle Variation
Currently your code will always offset the second line in the negative y-direction which will result in a variation in the line spacing as the angle of the line changes - when the line angle is vertical, the two lines will overlap.
To avoid this, you can use the polar function to calculate a point offset a predetermined distance from the specified start & end points, in a direction perpendicular to the line angle, which you can calculate using the angle function:
(defun c:curbyourenthusiasm ( / ang ep1 ep2 sp1 sp2 )
(if
(and
(setq sp1 (getpoint "\nSpecify start point: "))
(setq ep1 (getpoint "\nSpecify end point: " sp1))
)
(progn
(setq ang (- (angle sp1 ep1) (/ pi 2))
sp2 (polar sp1 ang 0.5)
ep2 (polar ep1 ang 0.5)
)
;; Continue with program operations
)
)
(princ)
)
3. Accounting for UCS
The getpoint function will return points whose coordinates are expressed relative to the current UCS (User Coordinate System) active at the time that the program is evaluated.
However, the points associated with DXF groups 10 & 11 for a LINE entity in the drawing database are expected to be expressed relative to the WCS (World Coordinate System).
We can transform points between the two coordinate systems using the AutoLISP trans function:
(defun c:curbyourenthusiasm ( / ang ep1 ep2 sp1 sp2 )
(if
(and
(setq sp1 (getpoint "\nSpecify start point: "))
(setq ep1 (getpoint "\nSpecify end point: " sp1))
)
(progn
(setq ang (- (angle sp1 ep1) (/ pi 2))
sp2 (trans (polar sp1 ang 0.5) 1 0)
ep2 (trans (polar ep1 ang 0.5) 1 0)
sp1 (trans sp1 1 0)
ep1 (trans ep1 1 0)
)
;; Continue with program operations
)
)
(princ)
)
4. Quote Constant Data
Where you have constant data (e.g. explicit numerical data or strings), you can quote such data as literal data in the code, avoiding the need for the interpreter to evaluate the list and cons functions to construct the data structures:
For example:
(cons 0 "line")
Can become:
'(0 . "line")
Since 0 and "line" are both constant data and may therefore be marked as literals.
Implementing all of the above, we have the following:
(defun c:curbyourenthusiasm ( / ang ep1 ep2 sp1 sp2 )
(if
(and
(setq sp1 (getpoint "\nSpecify start point: "))
(setq ep1 (getpoint "\nSpecify end point: " sp1))
)
(progn
(setq ang (- (angle sp1 ep1) (/ pi 2))
sp2 (trans (polar sp1 ang 0.5) 1 0)
ep2 (trans (polar ep1 ang 0.5) 1 0)
sp1 (trans sp1 1 0)
ep1 (trans ep1 1 0)
)
(entmake (list '(0 . "LINE") '(8 . "concrete") (cons 10 sp1) (cons 11 ep1)))
(entmake (list '(0 . "LINE") '(8 . "concrete") (cons 10 sp2) (cons 11 ep2)))
)
)
(princ)
)

Related

AutoCAD LISP automatizing polyline drawings

I would like to ask a question about automatizing polyline drawing. Here I have a FUNC and I want to add more specific commands. What I mean: I want the polyline to be assigned to a chosen layer automatically and i also want to set the line type to my custom line type. Thanks for the answers.
Here is my code:
You can get the newly created polyline with the entlast function and change its properties with _chprop command.
(defun c:nyomvodal (/ pt lst)
;; create a new layer
(command "_layer" "_new" "nyomvodal" "_color" 3 "nyomvodal" "")
;; get points form user
(while (setq pt (getpoint "\nPick point: "))
(setq lst (cons pt lst))
)
(if (< 2 (length lst))
(progn
;; create the polyline
(command "_pline")
(foreach p (reverse lst)
(command "_non" p)
)
(command "")
(command "_chprop" (entlast) "" "_layer" "nyomvodal" "_ltype" "axes" "")
)
)
(princ)
)
But, typically, we save current values of the OSMODE, CLAYER and CELTYPE system variables, set new values, draw the polyline, and restore the previous values.
(defun c:nyomvodal (/ osmode clayer celtype)
;; save the current osmode, clayer and celtype
(setq osmode (getvar "osmode"))
(setq clayer (getvar "clayer"))
(setq celtype (getvar "celtype"))
;; create a new layer and make it current
(command "_layer" "_make" "nyomvodal" "_color" 3 "nyomvodal" "")
;; set the current osmode and line type
(setvar "osmode" 0)
(setvar "celtype" "AXES")
;; use vla-catch-all-apply to avoid exiting code if user cancels
(vl-catch-all-apply
'(lambda (/ pt lst)
;; get points form user
(while (setq pt (getpoint "\nPick point: "))
(setq lst (cons pt lst))
)
(if (< 2 (length lst))
(progn
;; create the polyline
(command "_pline")
(foreach p (reverse lst)
(command p)
)
(command "")
)
)
)
)
;; restore the previous system variables values
(setvar "osmode" osmode)
(setvar "clayer" clayer)
(setvar "celtype" celtype)
(princ)
)

Creating a rectangle from center point with existing height and width size

Im trying to create a 10 x 10 rectangle from its center point. I found existing code that creates a rectangle by its center point, but the user has to give size by picking the opposite corner. I want to replace the manual part with known dimensions of 10 x 10. The user picks a point and a 10 x 10 rectangle is created off of that center point.
Here is the existing code that I found:
(defun C:CENRECT ( / pt1 ptc vec)
(setq pt1 (getpoint "\nSpecify the center point: "))
(setq ptc (getpoint pt1 "\nSpecify the corner point: "))
(setq vec (mapcar '- ptc pt1))
(entmake
(list
'(000 . "LWPOLYLINE")
'(100 . "AcDbEntity")
'(100 . "AcDbPolyline")
'(090 . 4)
'(070 . 1)
(cons 010 (trans (mapcar '+ pt1 (list (-(car vec))(+(cadr vec))(caddr vec))) 1 0))
(cons 010 (trans (mapcar '+ pt1 (list (+(car vec))(cadr vec)(caddr vec))) 1 0))
(cons 010 (trans (mapcar '+ pt1 (list (+(car vec))(-(cadr vec))(caddr vec))) 1 0))
(cons 010 (trans (mapcar '+ pt1 (list (-(car vec))(-(cadr vec))(caddr vec))) 1 0))
(cons 210 (trans '(0.0 0.0 1.0) 1 0 T))
)
)
(redraw)
(princ)
)
Here I am trying to add the known dimensions of 10 x 10 instead of having the user pick the size manually.
(defun C:test ( / pt1 ptc vec len wid)
(setq pt1 (getpoint "\nSpecify the center point: "))
**(setq len 10)
(setq wid 10)**
(setq ptc (getpoint pt1 **len wid**))
(setq vec (mapcar '- ptc pt1))
(entmake
(list
'(000 . "LWPOLYLINE")
'(100 . "AcDbEntity")
'(100 . "AcDbPolyline")
'(090 . 4)
'(070 . 1)
(cons 010 (trans (mapcar '+ pt1 (list (-(car vec))(+(cadr vec))(caddr vec))) 1 0))
(cons 010 (trans (mapcar '+ pt1 (list (+(car vec))(cadr vec)(caddr vec))) 1 0))
(cons 010 (trans (mapcar '+ pt1 (list (+(car vec))(-(cadr vec))(caddr vec))) 1 0))
(cons 010 (trans (mapcar '+ pt1 (list (-(car vec))(-(cadr vec))(caddr vec))) 1 0))
(cons 210 (trans '(0.0 0.0 1.0) 1 0 T))
)
)
(redraw)
(princ)
)
I get an error of too many arguments. Need to figure how to give the opposite corner of 10 x 10 instead of the user doing.
Since you know the fixed dimensions of the resulting rectangle ahead of time, the code can be reduced to the following:
(defun c:cenrect ( / c z )
(setq z (trans '(0 0 1) 1 0 t))
(if (setq c (getpoint "\nSpecify center: "))
(entmake
(list
'(000 . "LWPOLYLINE")
'(100 . "AcDbEntity")
'(100 . "AcDbPolyline")
'(090 . 4)
'(070 . 1)
(cons 010 (trans (mapcar '+ c '(-5 -5)) 1 z))
(cons 010 (trans (mapcar '+ c '( 5 -5)) 1 z))
(cons 010 (trans (mapcar '+ c '( 5 5)) 1 z))
(cons 010 (trans (mapcar '+ c '(-5 5)) 1 z))
(cons 210 z)
)
)
)
(princ)
)
Here, the polyline vertices are calculated relative to the supplied center point (with respect to the active UCS), and such vertices are then transformed relative to the Object Coordinate System (OCS).
(setq pt1 (getpoint "\nSpecify the center point: "))
(setq len 10)
(setq wid 10)
(setq vec (list len wid 0 ))
(entmake......
)
Should be OK.

Getting the measurements for stretching

I am creating an algorithm to help me expand boxes to the correct size, as such:
I made a code that asks two points that should be the new depth and measures them. Then subtracts to the original depth of the block (0.51) and then asks the side to stretch.
(defun mystretchh ( dis / pt1 pt2 sel )
(while
(and
(setq pt1 (getpoint "\nFirst point of selection window: "))
(setq pt2 (getcorner pt1 "\nSecond point of selection window: "))
(not (setq sel (ssget "_C" pt1 pt2)))
)
(princ "\nNo objects where found in the selection window.")
)
(if sel
(progn
(command "_.stretch" sel "" "_non" '(0 0) "_non" (list dis 0))
t
)
)
)
(defun c:test (/ a x c p)
;ungroup
(command "pickstyle" 0)
;variables
(initget (+ 1 2 4))
(setq p (getpoint "\nSelect first point: "))
(setq c (getpoint "\nSelect second point: "))
(command "_.dist" p c)
(setq x (rtos (getvar 'distance) 2 3))
;calculate distance to stretch
(setq a (- x 0.51))
;stretch
(mystretchh a)
;regroup
(command "pickstyle" 1)
(print "Module modified.")
(princ)
)
I have two problems:
The stretch is working backwards, I tried using negative values to no avail.
It reports a syntax error, but I cannot find it.
I haven't touched AutoLISP for half a year - maybe some of you can find the problems in a blink of an eye.
You can calculate the distance between two points using the standard distance function, rather than calling the DIST command and then retrieving the value of the DISTANCE system variable.
Hence, this:
(command "_.dist" p c)
(setq x (rtos (getvar 'distance) 2 3))
Can become:
(setq x (rtos (distance p c) 2 3))
However, you are receiving a syntax error because you have converted the distance to a string using rtos, and then you are attempting to perform arithmetic on the string here:
(setq a (- x 0.51))
There is no need to convert the distance to a string, and so these expressions can become:
(setq a (- (distance p c) 0.51))
You may also want to check whether (distance p c) is greater than 0.51 before performing this subtraction to avoid unexpected results.
To determine the appropriate direction, since your current code can only stretch along the x-axis, you'll need to check whether or not the x-coordinate of point p is greater than that of point c.

Why are circle center coordinates (key 10) not relative to the origin in DXF data?

I need this piece of information for a filter that I'm creating.
So let's say I set my grid and snap to 1 for example and then I place the origin with UCS.
And then I draw a circle with center 5, 0.
Here is what I get:
(
(-1 . <Entity name: 1f3dbb9d580>)
(0 . "CIRCLE")
(330 . <Entity name: 1f3dbba51f0>)
(5 . "270")
(100 . "AcDbEntity")
(67 . 0)
(410 . "Model")
(8 . "0")
(100 . "AcDbCircle")
(10 2495.0 1180.0 0.0)
(40 . 3.16228)
(210 0.0 0.0 1.0)
)
Why at number 10 I have those numbers?
Shouldn't it be like (10 5.0 0.0 0.0)?
The coordinates defining the geometry of the majority of planar entities (such as arcs, circles, 2D polylines etc.) are defined relative to a coordinate system known as the Object Coordinate System (OCS).
The OCS shares its origin with the World Coordinate System (WCS), with its Z-axis corresponding to the normal vector (aka extrusion vector) associated with the entity (represented by DXF group 210), and its X & Y axes defined by the Arbitary Axis Algorithm applied to the normal vector.
The Arbitrary Axis Algorithm is implemented in the standard AutoLISP trans function, which facilitates easy transformation of points from one coordinate system to another.
In your particular example, the normal vector is (0.0 0.0 1.0), which is equal to the normal vector of the WCS plane, and so for this particular example, the OCS is equal to the WCS.
However, in general, to translate points from an arbitrary OCS to either the WCS or the active User Coordinate System (UCS), you would supply the trans function with either the OCS normal vector or the entity name of the entity in question.
For example, translating from OCS to the active UCS using the OCS normal vector:
(trans (cdr (assoc 10 <dxf-data>)) (cdr (assoc 210 <dxf-data>)) 1)
Or, translating from OCS to the active UCS using the entity name:
(trans (cdr (assoc 10 <dxf-data>)) (cdr (assoc -1 <dxf-data>)) 1)
Implemented in a sample program, this might be:
(defun c:test ( / ent enx )
(cond
( (not (setq ent (car (entsel "\nSelect circle: "))))
(princ "\nNothing selected.")
)
( (/= "CIRCLE" (cdr (assoc 0 (setq enx (entget ent)))))
(princ "\nThe selected object is not a circle.")
)
( (princ "\nThe circle center relative to the UCS is: ")
(princ (trans (cdr (assoc 10 enx)) ent 1))
)
)
(princ)
)
Addressing the issues you are encountering as described in your comments, you'll need to transform the coordinates from/to the OCS & UCS to achieve the desired result, for example:
(defun c:test ( / ent enx new old xco )
(cond
( (not (setq ent (car (entsel "\nSelect circle: "))))
(princ "\nNothing selected.")
)
( (/= "CIRCLE" (cdr (assoc 0 (setq enx (entget ent)))))
(princ "\nThe selected object is not a circle.")
)
( (setq old (assoc 10 enx)
xco (car (trans (cdr old) ent 1))
new (cons 10 (trans (list xco 0.0 0.0) 1 ent))
enx (subst new old enx)
)
(entmod enx)
)
)
(princ)
)
The operation could also be condensed to a single expression, e.g.:
(setq old (assoc 10 enx)
enx (subst (cons 10 (trans (list (car (trans (cdr old) ent 1)) 0) 1 ent)) old enx)
)
(entmod enx)
However, this is less readable.
In this list coordinates are in WCS. So if You draw circle using command and active UCS is differend than WCS, everything is OK the result is as expected.
To translate coordinates between coordinate systems You can use
(trans (assoc 10 YourList) 0 1 nil)

Autocad Error "error: bad arguement type: lselsetp nil" when selecting polyline

I am currently attempting to run a polyline between two blocks (first_block, second_block) that runs along another polyline, at the end of the action an insert function is called that populates an annotation block (cable_name_tag) with the details of the start and end block.
This polyline will transect other blocks and often needs to run in paralell with an underlying polyline (cable_ducting) so the user will need to option to select an appropriate part of the polyline to drop the annotation, as space is sometimes limited.
I have noted that if I comment out the second_block and provide a harcoded value the ssget function works without issue, so I am reasonably sure the issue is with either the syntax or the handling of the first_block and second_block in that code.
(defun c:cable ()
(vl-load-com)
(setvar "clayer" "cable layer")
(setvar "celtype" "bylayer")
(setvar "osmode" 515)
(command "_.pline"
(getpoint))
(while (> (getvar ' cmdactive) 0)
(command pause)
(princ "\npress enter to finish:"))
(setq elst
(entsel "\nselect cable segment: "))
(setq ename
(car elst))
(setq pt
(cadr elst))
(setq annopt pt)
(setq pt
(vlax-curve-getclosestpointto ename pt))
(setq param
(vlax-curve-getparamatpoint ename pt))
(setq preparam
(fix param))
(setq postparam
(1+ preparam))
(list (setq pt1
(vlax-curve-getpointatparam ename preparam))
(setq pt2
(vlax-curve-getpointatparam ename postparam)))
(setq cable
(entlast))
(setq cable_start
(vlax-curve-getstartparam cable))
(setq cable_start_point
(vlax-curve-getstartpoint cable))
(setq cable_end_point
(vlax-curve-getendpoint cable))
(setq cable_end
(angtos (angle '(0 0)
(vlax-curve-getfirstderiv cable 0.0))))
(setq first_block
(ssget "_c" cable_start_point cable_end_point
(list (cons 0 "insert")
(cons 2 "first_block"))))
(setq second_block
(ssget "_c" cable_start_point cable_end_point
(list (cons 0 "insert")
(cons 2 "second_block"))))
(setq end_cable
(ssname second_block 0))
(setq start_cable
(ssname first_block 0))
(setq $end_cable
(vla-get-textstring
(cadr (vlax-safearray->list
(variant-value
(vla-getattributes
(vlax-ename->vla-object end_cable)))))))
(setq $start_cable
(vla-get-textstring
(cadr (vlax-safearray->list
(variant-value
(vla-getattributes
(vlax-ename->vla-object start_cable)))))))
(setq cable_name
(vlax-curve-getendparam cable))
(command ; insert cable param
"-insert"
"cable_name_tag"
annopt
"1"
"1"
cable_angle
cable_name
$start_cable
$end_cable
"144"
cable_length))
I am stuck in a corner on this one, and would appreciate any help, advice or pointers anyone can offer.
Thank you all for your time.
Why not just use (entlast) to get the entity that was just created?