Programming 3D. Solids - Meshes - Vol3
Programming 3D. Solids - Meshes - Vol3
Volume 3
Programming 3D
Reinaldo N. Togores
AutoCAD expert’s Visual LISP.
Volume 3
Programming 3D
All Rights Reserved. No part of this book may be reproduced or utilized in any form or by any
means, electronic or mechanical, including photocopying, recording, or by any information
storage and retrieval system, without permission in writing from the author.
All data, information and examples provided by this book are for educational purposes only.
All information is provided on an as-is basis. Although the author and publisher have made
every effort to ensure that the information in this book was correct at press time, the author
and publisher do not assume and hereby disclaim any liability to any party for any loss,
damage, or disruption caused by errors or omissions.
Autodesk, AutoCAD, AutoLISP, Visual LISP, DWG, the DWG logo, Revit, 3ds Max and Inventor
are registered trademarks or trademarks of Autodesk, Inc., and/or its subsidiaries and/or
af iliates in the USA and other countries. Autodesk screen shots reprinted with the
permission of Autodesk, Inc.
Contents
Volume 3.
The modeling of complex 3D objects in AutoCAD can also be the initial step in the
architectural design process by converting them to solids and importing them to a Revit mass
family, where enveloping structures, curtain walls, floors, etc. can be defined from them.
However AutoCAD has not been a leader in the ield of 3D modeling. The addition of 3D
capabilities were the subject of internal discussions as early as September 1983 when John
Walker proposed the strategy of what he called “Low Rent 3D.” But even someone as
visionary saw the 3D capabilities more as a marketing tool than something technically
relevant.
“If we do not have a credible response to queries about 3D, we may be in trouble selling our
package. While all drafting is 2D, and almost all users will spend all their time with AutoCAD
working in 2D mode, 3D is important more from a marketing perception standpoint than a
technical one… The impact of rotating an object in 3D space at COMDEX is many times that
of zooming in on a flat drawing.” 1
The introduction of a very limited version of what Walker had proposed did not occur until
nearly two years later with AutoCAD Version 2.1 in May 1985 in what was called “3D Level 1”,
limited to the ability to change the Z coordinate value and perform an extrusion in that
direction called "thickness". This was complemented by the ability to change the viewpoint
using the _VPOINTcommand and removing hidden lines using the _HIDE command. Other
of the September 1983 proposals had to wait another four years, until the introduction of user
coordinate systems with Release 10 in 1989.
As for the modeling of real surfaces, this was not available until Release 2007 (before they
could only be approximated using Polygon or Polyface Meshes) but only since Release 2011 is
that we really have tools to edit and modify surfaces. Subdivision surfaces introduced in
Release 2010 as MESH entities represent a signi icant improvement over the Polygon and
Polyface Meshes in terms of their editing possibilities, but they are still approximating
surfaces with planar facets.
It is with AutoCAD 2012 that we inally have complete implementations of the three classical
paradigms for modeling three-dimensional objects:
AutoCAD solves the dilemma about which three-dimensional object modeling method to use -
solids, surfaces or meshes- in a very practical way, allowing us to freely combine the three of
them. Meshes, Surfaces and Solids can be converted into each other. Solids can be sliced using
Surfaces; a portion of space completely bounded by Surfaces can become a Solid; Surfaces can
be thickened so that they become Solids. This makes AutoCAD today a perfectly valid
application for the creation and management of 3D objects. And compels us to explore in a
book like this, dedicated to Visual LISP programming, its ability to operate in this
environment.
In all cases, the command/vl-cmdf interface can be used when dealing with arguments and
options that can be introduced via the keyboard or through graphic screen designations.
Through entmake it is possible to create surface entities of the legacy AcadPolyfaceMesh
and AcadPolygonMesh types. These are complex entities of the POLYLINE type identi ied
by its DXF group code 70: bit 6 on (value = 64) for AcadPolyfaceMesh and bit 4 on (value =
16) for AcadPolygonMesh. These entities can also be created using the corresponding
ActiveX methods through the vla-Add3DMesh and vla-AddPolyfaceMesh functions.
As a consequence of ACIS data encryption, ActiveX methods are the only programming
alternative for 3DSolid objects without resorting to the command/vl-cmdf interface.
The Release 2010 Subdivision surfaces (MESH entities) can be created by entmake but the
documentation of their DXF group codes DXF is imprecise, fact which we will try to clarify
through some examples. The object model does not expose ActiveX methods for their
creation, which is only possible using entmake or the command/vl-cmdf interface, The use
of commands can save programming work, but entmake can create custom surfaces not
available through commands. Once created, the MESH entity’s properties exposed in the
ActiveX object allow the modi ication of its vertices coordinates to obtain a variety of three-
dimensional shapes using Visual LISP programs. In this manner we can pro it both from
entmake as from the AutoCAD commands to create basic shapes that can later be modi ied
by accessing its properties. PolygonMesh and PolyfaceMesh objects as well as
3DSolids can also be converted into MESH entities. The old commands for creating
surfaces, _REVSURF, _TABSURF, _RULESURF, or _EDG ESURFnow can generate
Subdivision surfaces.
And once the desired shapes are created as meshes they can be converted into the new
procedural or NURBS surfaces, as appropriate to the model we are working on. This is a
relatively unexplored ield, and the results presented are the result of ongoing investigations
that we hope will help our readers in their 3D modeling tasks.
AutoLISP / Visual LISP also offers us the opportunity to work in a way that we could call
“hybrid” using in the same program its scripting capabilities for invoking commands, the
possibility of accessing the drawing’s database through entmake/entmod, and once created
the object, by changing its ActiveX exposed properties.
But now other form of Coordinate System: the Object Coordinate System (OCS) comes into
play. For these classic entities the coordinates of the points that de ine them are expressed in
the entity’s own coordinate system, the Object Coordinate System (OCS). The OCS is
characterized by:
The orientation of the X and Y axes are determined by AutoCAD, from among the in inite
possibilities existing, using the Arbitrary Axis Algorithm, explained in the User
Documentation, into which we will not delve. It is suf icient to know that we can expect
consistent results. The speci ic data incorporated with the new entity to the drawing’s
database are the consequence of a complex series of operations that the application performs
automatically, converting data entered by the user (which are expressed in UCS and current
elevation values) into OCS values through the necessary translation and rotation
transformations. To further complicate the issue, in some entities their OCS matches the WCS,
and the values of all points are expressed in world coordinates.
The irst aspect the developer has to understand clearly is the distinction between entities
according to the coordinate system associated with them. Whenever we draw in the XY plane
or in planes parallel to it, the difference is negligible. However, when creating entities in UCS
different from the WCS a discrepancy will be found between the values of the XYZ coordinates
of the object’s reference points as read by entget and those returned, being current any
coordinate system different from that in which it was created, by the _ID query command or
the getpoint function.
An example.
To aid in understanding this we will de ine two very simple functions aimed at drawing a line
and an optimized 2D polyline (LWPOLYLINE) in the Front view plane. The unit vector that
de ines the positive direction of Z axis for this plane is (0.0 -1.0 0.0), i.e., this UCS’s Z
axis positive direction is de ined as the negative direction of the WCS’s Y axis. We want to
draw both the line and the polyline from the point (0.0 -100.0 0.0) to (0.0 100.0
0.0) of the new UCS.
To do this manually, the new UCS would be set with any of the options provided in the user
interface and after doing this, the objects would be drawn introducing the values 0, -100.0 and
0,100,0 for their endpoints. There would be no difference in doing it with the command _LINE
or the command _PLINE. Let us study now how to do it through programming, using the
entmake function.
ENT_PLIN function.
As explained before, we know that the plane in which a LWPOLYLINE lies is de ined by the
vector indicating the positive direction of Z axis, in our case (0.0 -1.0 0.0). This data, as
we know from the DXF Reference is associated with group code 210 in the entity de inition
list. To create our LWPOLYLINE group codes 0, 100, 90, 91 and 210 are needed. We will pass
as arguments pt-i as the starting point, pt-f as the end point and norm as the object’s
plane normal vector.
(defun ent-plin (pt-i pt-f norm /)
(entmake (list '(0 . "LWPOLYLINE") ;Entity
'(100 . "AcDbEntity") ;Subclass
'(100 . "AcDbPolyline") ;Subclass
'(90 . 2) ;Num vertices
(cons 10 pt-i) ;Vert1 Coords
'(91 . 1) ;Vert1 Id
(cons 10 pt-f) ;Vert2 Coords
'(91 . 2) ;Vert2 Id
(cons 210 norm)))) ;Normal vector
Listing 13.1. Function that draws a polyline in the plane defined by the normal vector.
The points passed as arguments should be 2D points, but 3D points will be accepted ignoring
the Z value. Testing this function we will verify that the polyline is drawn in the frontal plane.
The function returns the entity list it received in case it succeeds, otherwise it will return nil.
_$ (ent-plin '(0 -100) '(0 100) '(0 -1 0))
((0 . "LWPOLYLINE") (100. "AcDbEntity") (100 . "AcDbPol...
No matter what the current UCS may be when calling the function with these arguments, the
polyline is always drawn in the ZX plane and centered at the origin of the WCS.
ENT-LIN function.
Now we will try to do write an equivalent function to create a LINE entity, using the same
arguments. The group codes required to create this entity are 0 (and optionally 100) , 10
which de ines the starting point, 11 for the end point and 210 for the normal vector. But we
must remember that in this case group code 210 does not determine the plane in which the
entity is created, it only indicates the direction in which the extrusion occurs in case it has
Thickness (data associated with group code 39).
Its position in space will always be determined by the coordinates of its start and end point,
expressed in terms of the World Coordinate System (WCS). If we pass the same arguments for
starting point (0 -100), end point (0 100) and normal vector (0 -1 0) we will ind
that the line, instead of following the direction of the Z axis, is aligned with the Y axis.
(defun ent-lin (pt-i pt-f norm /)
(entmake (list '(0 . "LINE") ;Entity type
'(100 . "AcDbEntity") ;Subclass
'(100 . "AcDbLine") ;Subclass
(cons 10 pt-i) ;Vertex 1
(cons 11 pt-f) ;Vertex 2
(cons 210 norm)))) ;Normal
Listing 13.2. Test function that creates a line specifying its normal vector.
The way to solve this and manage operations in 3D will be the subject of the following
sections.
Coordinate systems.
In some cases the OCS will coincide with the WCS. This is the case of the 3D entities, i.e., the
entities that can be drawn in planes not parallel to the current UCS’s XY plane. These entities
include Lines, Points, 3DFaces, Polylines and 3D Vertices, Polygon and Polyface Meshes with
their vertices, and Splines. In all these cases, the XYZ values will be given with reference to the
WCS.
This is not so with 2D entities (Circle, Arc, 2D Solid, Trace, Text, Attribute and Attribute
De inition, Shape, Block Reference, 2D Polyline and 2D Vertex) that can only be drawn in the
XY plane of the current UCS or in planes parallel to it. Their coordinates are always expressed
in values referring to their OCS.
We have described the meaning of group code 210 as a normal vector that de ines the Z axis
direction of the OCS. The normal vector describing the UCS would be given by the list of three
real numbers (0.0 0.0 1.0). But the data associated with group code 210 not always
allows us to determine whether the OCS and the UCS match. It will be necessary to take into
account a number of exceptions in which the value associated with this code would be best
described as an extrusion vector. In general, 3D entities cannot have Thickness. There are
two exceptions to this rule: Lines and Points. In both entity types this vector does not
represent the plane in which it lies, but the direction of normal vector for the current UCS
when these entities were created and is used to determine the direction in space for its
extrusion when a non-zero value is set for its Thickness. Although they do not have
Thickness, planar Splines also present an anomaly of this kind, including group code 210.
When this group code is not present, it is understood that the coordinates are referred to the
UCS.
This means that for entities in planes non-parallel to the WCS it will be necessary to transform
the point coordinates between the different coordinate systems. This transformation can be
easily done using the trans function. The syntax for this function is:
(trans pt from-cs to-cs [as-vector])
The trans function accepts as its irst argument pt, a 3D point or displacement vector (list
of three real numbers), a second argument from-cs indicating the coordinate system in
which pt is expressed and a third argument to-cs which speci ies the coordinate system
into which pt will be transformed. A fourth optional argument as-vector indicates, if
present and not nil, that pt represents a vector instead of a point. If the UCS origin is the
same as the WCS origin, the values returned as point and vector are identical.
_$ (trans '(0.0 0.0 1.0) 1 0)
(-0.707107 0.0 0.707107)
_$ (trans '(0.0 0.0 1.0) 1 0 t)
(-0.707107 0.0 0.707107)
But if the origin of the UCS has been shifted the difference will be immediately appreciated: if
treated as a point a translation is applied but not if treated as a vector.
_$ (command "._UCS" '(10.0 10.0 10.0) "")
nil
_$ (trans '(0.0 0.0 1.0) 1 0)
(-0.707107 10.0 14.8492)
_$ (trans '(0.0 0.0 1.0) 1 0 t)
(-0.707107 0.0 0.707107)
The trans function, as shown in the following expressions, offers the same functionality as
the Document’s Utility object TranslateCoordinates method.
_$ (setq *utility* (vla-get-Utility *aevl:drawing*))
#<VLA-OBJECT IAcadUtility 000000002d635568>
_$ (setq obj (vlax-ename->vla-object (entlast)))
#<VLA-OBJECT IAcadLWPolyline2 000000002d5a8b38>
_$ (setq pt (vlax-3d-point (getpoint)))
# <variant 8197 ...>
_$ (setq result (vla-TranslateCoordinates *utility* pt
acOCS acWorld :vlax-false (vla-get-Normal obj)))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value pt))
(620.265 1672.17 0.0)
_$ (vlax-safearray->list (vlax-variant-value result))
(620.265 1448.15 836.087)
_$
For the arguments from-cs and to-cs the following ways to specify the coordinate system
are accepted: a numeric code (or its equivalent ActiveX enum constant), an entity name
(ename) or the coordinate system’s XY plane normal vector. The possible values are shown in
Table 13.3.
The Visual LISP function trans does not accept values above 1.0E+99 for X,Y or Z
coordinate values. A good workaround for this limitation is to use the ActiveX
TranslateCoordinates method of the Utility object. The ax-trans function (Listing
13.3) shows how an implementation of trans using the TranslateCoordinates method
would be successful for an input point such as (0.0 3e+099 0.0), where (trans '(0.0
3e+099 0.0) 0 1) would fail:
_$ (trans '(0.0 3e+099 0.0) 0 1)
; error: invalid point: (0.0 3.0e+099 0.0)
_$
_$ (ax-trans '(0.0 3e+099 0.0) 0 1 nil nil)
(0.0 3.0e+099 0.0)
_$
The Utility object provides the ActiveX method equivalents to user input and other AutoLISP
functions, and a group of functions related to Web ile operations that are not available in
AutoLISP. These methods are summarized in Tables 13.4 and 13.5.
(defun ax-trans (pt from-ucs to-ucs as-vector ocs-normal /
utility-object args res)
(vl-load-com)
(setq utility-object
(vla-get-Utility
(vla-get-ActiveDocument
(vlax-get-acad-object))))
(if as-vector
(setq as-vector :vlax-true)
(setq as-vector :vlax-false))
(setq args (list utility-object
(vlax-3d-point pt)
from-ucs
to-ucs
as-vector))
(if ocs-normal
(setq res
(vl-catch-all-apply
'vla-TranslateCoordinates
(append args
(list (vlax-3d-point ocs-normal)))))
(setq res (vl-catch-all-apply
'vla-TranslateCoordinates
args)))
(if (vl-catch-all-error-p res)
(prompt (vl-catch-all-error-message res))
(vlax-safearray->list (vlax-variant-value res))))
Listing 13.3. Replacement function for trans using ActiveX method TranslateCoordinates.
Three-dimensional transformations.
It isn’t necessary to highlight the complexity that all this introduces when creating graphical
entities in a 3D environment, particularly when they are no longer aligned with the coordinate
axes and away from the origin of the reference coordinate system. The solution to this
problem consists in performing a sequence of 3D linear transformations. By taking into
account the Z coordinate, a translation is done now by specifying a three-dimensional
translation vector and scaling transformations by specifying three scaling vectors. Rotation
transformations are more complex since rotation can now be performed around axes with
any spatial orientation. Geometric transformation equations can be expressed in terms of
transformation matrices. Any 3D transformation sequence is achieved by the concatenation
of transformation matrices which generally follows the following process:
1. Translation of the object to the origin of the World Coordinate System (WCS).
2. Alignment of the transformation reference axes with the WCS axes.
3. Performing the required transformations (rotation, scaling, and translation).
4. Inversion of the point 2 transformation in which the axes return to their original position.
5. Inversion of the point 1 translation returning the object to its original position in space.
In the functions that will be developed in this part of the book, we rely on the TransformBy
ActiveX method that allows us to apply the necessary translation, scaling and rotation
transformations when creating and modifying three-dimensional objects.
To simplify the process, when we create a new object we’ll do it centered at the WCS origin
and if necessary, it will then be moved to the position speci ied by the user and aligned with
the current User Coordinate System (UCS) axes. In this context a reference to certain
functions that will be used when managing coordinate systems is necessary.
Vector operations.
Before addressing the transformation matrices we will make some brief comments on the
most common vector operations that in many cases will be used to prepare the data for vla-
TransformBy.
A vector in the sense we use it in this book is a geometric object that has magnitude (length)
and direction. A vector de ines a translation from point A to point B. The magnitude of the
vector is the distance between the two points and direction refers to the displacement path
leading from A to B. A direction vector as the one used in determining the OCS of a 2D entity is
specified in relation to the coordinate system’s origin. Any vector defined from two arbitrarily
selected points can be translated so that its initial position coincides with the origin of the
coordinate system.
Two frequently used operations when manipulating these objects are the sum and
multiplication of vectors. The sum of two vectors is carried out by adding their components:
Extracting the square root of the dot product of a vector with itself we obtain the vector’s
length, or more precisely its magnitude, a number that matches the length of the vector in its
graphic representation:
With the cross product of two vectors V1 x V2 we obtain another vector that is perpendicular
to each of the two original vectors and therefore to the plane containing these vectors, i.e., its
normal vector, that as we have seen, is the data needed to de ine the orientation of a plane or
object in 3D space. The matrix expression for the cross product is:
From this determinant form of the matrix product matrix we obtain a vector with the
components:
The data necessary to de ine the orientation in space is usually the plane’s normal vector
whose length is 1 (the unit length). It is usually known as the unit vector or direction vector.
The unit vector is obtained dividing each vector component by its magnitude.
These vectors operations can be easily coded as LISP functions using the functions mapcar
and apply, as shown below in Listing 13.4.
;;; Vector A to B
;;; Arguments: A, B, lists of three real numbers.
(defun vec (A B) (mapcar '- B A))
Creating Matrices.
For the creation of these transformation matrices Visual LISP provides the vlax-tmatrix
function. Its syntax is (vlax-tmatrix lst) where lst is a list of four sublists, each of
which contains four numbers that are the values defining the transformation matrix.
Translation Matrix.
The matrix that specifies a translation is shown in Table 13.6. The Tx, Ty and Tz values define
the displacements along the axes X, Y and Z respectively.
The ax-translation function (see Listing 13.5) de ines a transformation function that
translates an object using vlax-tmatrix and vla-TransformBy. The argument obj is
the VLA-object subjected to the transformation and vector is a list of three real numbers
indicating the displacement along the X, Y and Z axes.
(defun ax-translation (obj vector)
(vla-TransformBy
obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 (nth 0 vector))
(list 0.0 1.0 0.0 (nth 1 vector))
(list 0.0 0.0 1.0 (nth 2 vector))
(list 0.0 0.0 0.0 1.0)))))
Rotation Matrix.
The rotation of an object is speci ied from an axis around which the object rotates and its
rotation angle. Although a rotation axis can have any orientation in three dimensional space,
the transformation can always be decomposed into a series of successive elementary
transformations around the axes of the coordinate system that are easier to specify in a
program. The rotation transformations about each axis are described in the following three
tables. The transformation matrices passed to vla-TransformBy to achieve the desired
effects can be defined from these specifications.
Something we should remember is that the angle values passed to AutoLISP trigonometric
functions must be expressed in radians. To do this we can resort to two classical functions
used in converting degrees to radians (dtr function) and vice versa (rtd function) shown in
Listing 13.6.
;;; Degrees to Radians
(defun dtr (g) (/ (* g pi) 180.0))
;;; Radians to Degrees
(defun rtd (r) (* (/ r pi) 180.0))
These functions allow us to pass the angle arguments to our rotation functions in degrees. We
will de ine three functions to achieve rotations around the X, Y and Z axes. All receive an
object (obj) and an angle (a) in degrees as arguments. They only differ in the values of the
transformation matrices they create.
(defun ax-rot-x (obj a)
(setq a (dtr a))
(vla-TransformBy
obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 0.0)
(list 0.0 (cos a) (sin a) 0.0)
(list 0.0 (- (sin a)) (cos a) 0.0)
(list 0.0 0.0 0.0 1.0)))))
Scaling Matrix.
With vla-TransformBy we can transform an object’s size by specifying a scaling matrix
with its homothetic center at the coordinate system’s origin as shown in Table 13.10. Sx, Sy
and Sz respectively de ine the scale factors to be applied to the object in the X, Y and Z
directions. 3DSolids and Surfaces do not admit non-uniform (anisotropic) scaling. But they
are supported by volumes modeled using meshes. It is therefore desirable to foresee possible
errors when trying to scale an object that does not support non-uniform scaling.
Listing 13.10 shows a function that changes the X, Y and Z dimensions of an object. The
arguments it receives are the same as those described in the previous section. In this case the
vla-TransformBy function is applied using vl-catch-all-apply to intercept errors.
(defun ax-scale (obj vector / res)
(setq res
(vl-catch-all-apply
'vla-TransformBy
(list
obj
(vlax-tmatrix
(list (list (nth 0 vector) 0.0 0.0 0.0)
(list 0.0 (nth 1 vector) 0.0 0.0)
(list 0.0 0.0 (nth 2 vector) 0.0)
(list 0.0 0.0 0.0 1.0))))))
(if (vl-catch-all-error-p res)
(prompt "This object cannot be transformed!")))
3D Symmetry Transformations.
Shear matrix.
Another transformation which can be produced using matrices is shearing, which is a relative
displacement of points in an object by a speci ied factor Sh. As with scaling and symmetry
transformations these transformations are not supported by all objects, so we must resort to
vl-catch-all-apply for trapping possible errors.
Shear matrices relative to the coordinate system’s axes are shown in Table 13.11, Table 13.12
and Table 13.13. Just as in previous cases the transformation matrices are passed to vla-
TransformBy via vlax-tmatrix.
(defun ax-shear-x (obj factor / res)
(setq
res (vl-catch-all-apply
'vla-TransformBy
(list obj
(vlax-tmatrix
(list (list 1.0 factor 0.0 0.0)
(list 0.0 1.0 0.0 0.0)
(list 0.0 0.0 1.0 0.0)
(list 0.0 0.0 0.0 1.0))))))
(if (vl-catch-all-error-p res)
(prompt "This object cannot be transformed!")))
The user is prompted for the object to transform, the transformation’s base point and the
three scale factors to apply. Note that before each prompt initget is invoked with the value
(+ 1 2) to prevent that the user presses ENTER or responds with zero.
The scaling transformation is the result of the combination of three transformations applied
in succession as is always done in a 3D environment:
A translation of the object to the origin of the coordinate system. For this ax-
translation (Listing 13.5) is invoked but with the base point coordinates affected by
a negative sign, which is achieved by (mapcar '- base).
The object's scaling transformation is applied by the ax-scale function (Listing 13.10),
passing as argument the list of the three scale factors specified by the user.
A translation to the original position of the object. For this ax-translation is invoked
again, this time without changing the point coordinate values (which in this case
represents a displacement vector).
(defun C:TRANSFORM
(/ obj base factor-x factor-y factor-z)
(if (setq obj
(vlax-ename->vla-object
(car
(entsel "\nSelect object to transform: "))))
(progn (setq base (getpoint "\nBase point: "))
(initget (+ 1 2))
(setq factor-x (getreal "\nScale factor X: "))
(initget (+ 1 2))
(setq factor-y (getreal "\nScale factor Y: "))
(initget (+ 1 2))
(setq factor-z (getreal "\nScale factor Z: "))
(ax-translation obj (mapcar '- base))
(ax-scale obj (list factor-x factor-y factor-z))
(ax-translation obj base))
(prompt "\nNo object selected.")))
Figure 13.1 shows the form resulting form applying this command to a mesh approximating a
sphere.
For any transformation the irst thing to check is whether the current UCS coincides with the
WCS. For this we can use the WORLDUCS system variable. If both systems match the value of
WORLDUCS will be 1. Otherwise it will return 0.
If we were in an UCS other than the WCS a coordinate system transformation will be required.
This transformation can be performed using the transformation matrix returned by the
current UCS’s GetUCSMatrix method. This method returns a 4x4 matrix that used as an
argument for any object’s TransformBy method aligns it to that UCS, applying the necessary
set of translations and rotations.
The vla-GetUCSMatrix function we use to obtain the transformation matrix receives the
UCS object as argument. In case the coordinate system is not the WCS we can obtain the
current UCS object from the Document’s ActiveUCS property. But if the current UCS had not
been previously saved, trying to get the UCS object through vla-get-ActiveUCS will
generate an error. To check whether the UCS has been saved we can use the UCSNAME
system variable which will return an empty string ("") If it had not been saved. In this case
we can add a new named UCS with the same characteristics as the active UCS and set it as
current. This way of detecting whether an UCS has been saved must take into account a
exception occurring in the case a predefined view in which the View Manager’s Restore Ortho
UCS property (System variable UCSORTHO = 1) has been set, which is the default setting.
UCSNAME will then return the view name surrounded by asterisks, for example "*FRONT*".
To create a new UCS, the Add method for the Document’s UserCoordinateSystems
collection is used. The syntax for this method is:
(vla-Add UCS-collection origin-point X-direction Y-direction name)
The data needed to reproduce the current UCS are its origin and its X and Y axes direction
vectors. These data are stored in the UCSORG, UCSXDIR and UCSYDIR system variables.
The values returned are lists of three real numbers representing the point of origin in WCS
coordinates and the unit vectors which mark the directions of the X and Y axes.
We should note that the direction vectors consider the displacement as from the WCS origin.
For this reason it will be necessary to create the new UCS in two steps: first with (0 0 0) as
its origin, changing its origin afterwards to the point obtained from UCSORG. The ax-ucs
function receives as arguments a name for the UCS, an origin, the direction vector for the X
axis (dirx) and the direction vector for the Y axis (diry). It returns the new UCS object.
(defun ax-ucs (name origin dirx diry / tmp)
(setq tmp
(vla-Add
(vla-get-UserCoordinateSystems *aevl:drawing*)
(vlax-3d-point '(0 0 0))
(vlax-3d-point dirx)
(vlax-3d-point diry)
name))
(vla-put-Origin tmp (vlax-3d-point origin))
tmp)
Listing 13.15. Function that adds a new UCS to the current document.
Including the new named UCS in the Coordinate Systems (AcadUCSs) collection is not
enough. After creating the new named UCS it will be necessary to set it as the current UCS
updating the document’s ActiveUCS property. If we examine the properties and methods of
the UCSs object we will ind it exposes the Count property which returns the number of
saved UCS, and supports the Item method, with which we can retrieve any of the collection
members either using the index number or the name (as string) of one of the saved systems.
The same name can be used for different UCS. In that case the new one overwrites the old one.
Should we wish to keep both UCS, different names must be used. When this is done from a
program it would be necessary to establish some way to create unique names, which can be
done by reading the collection’s Count property, adding this number as a suf ix to the text
naming the UCS.
$ (setq *ucs-coll* (vla-get-UserCoordinateSystems *aevl:drawing*))
#<VLA-OBJECT IAcadUCSs 000000002e797de8>
_$ (vla-get-Count *ucs-coll*)
5
_$ (vla-item *ucs-coll* 4)
#<VLA-OBJECT IAcadUCS 000000002e7980b8>
_$ (vla-get-Name (vla-Item *ucs-coll* 4))
"test5"
_$ (vla-get-Name (vla-Item *ucs-coll* "test5"))
"test5"
_$
The ax-ucs-matrix function returns a list with the name of the current UCS and a Variant
that contains the transformation matrix that can be used to align an object with this UCS. For
this purpose, we irst check whether the current UCS has been saved by examining the
content of the UCSNAME system variable. If UCSNAME contains an empty string a new UCS
is created with the current parameters obtained from UCSORG, UCSXDIR and UCSYDIR.
We must also check if it is one of the UCS established by one of the Preset Views that in this
case will have a name (retrieved from UCSNAME) that starts and ends with an asterisk.
To create a unique name the Count property of the UCSs Collection is read and the name
created by concatenating this number to the pre ix "UCS_". After creating the UCS it is set as
the current UCS and inally its GetUCSMatrix method is called to obtain the corresponding
matrix. If UCSNAME contains the name of a UCS it is only necessary to obtain the current UCS
by means of the Document’s ActiveUCS property, retrieve its name and apply the
GetUCSMatrix method.
(defun ax-ucs-matrix (/ name ucs-num new-ucs)
(setq name (getvar "UCSNAME"))
(cond
((or
(equal name "")
(and (vl-string-search "*" name 0)
(vl-string-search "*" name (1- (strlen name)))))
(setq ucs-num (vla-get-Count
(vla-get-UserCoordinateSystems
*aevl:drawing*))
name (strcat "SCP_" (itoa ucs-num)))
(setq new-ucs (ax-ucs name
(getvar "UCSORG")
(getvar "UCSXDIR")
(getvar "UCSYDIR")))
(vla-put-ActiveUCS *aevl:drawing* new-ucs)
(list name (vla-GetUCSMatrix new-ucs)))
(t
(list
(vla-get-Name (vla-get-ActiveUCS *aevl:drawing*))
(vla-GetUCSMatrix
(vla-get-ActiveUCS *aevl:drawing*))))))
Listing 13.16. Function that returns the current UCS transformation matrix.
If we apply this transformation matrix to an object at the origin of the WCS using the
TransformBy method, it will be translated to the origin of the new coordinate system and
aligned with its axes.
The current viewport is retrieved from the drawing’s ActiveViewport property. The
current drawing object is referenced by the global variable *aevl:drawing* that was
assigned by acaddoc.lsp. To obtain the Viewport object we evaluate the expression:
(setq vport (vla-get-ActiveViewport *aevl:drawing*))
The view orientation is determined by the Viewport’s Direction property. This property
requires a 3D vector whose three components correspond to the values for the X, Y and Z
directions in WCS coordinates. These values must be contained in a Variant containing a
safearray of three real numbers. We will build this vector from a list of three numbers using
the vlax-3d-point function. To establish the desired view orientation for 3D programs we
define the ax-view function. The ax-view function receives as arguments:
Listing 13.17. Function that sets the view direction and visual style.
Direction vectors.
To generate the six orthogonal views and the eight isometric ones the direction vectors
shown in Table 13.14 can be used.
Visual appearance.
Besides the Viewpoint orientation, the perception of 3D forms is enhanced by the color, the
shading and the lighting of objects. AutoCAD provides a set of predefined visual styles that can
be set using the _-VISUALSTYLES command. These predefined visual styles are:
"_W" = Wireframe; "_H" Hidden lines; "_R" Realist; "_C" Conceptual; "_S" Shaded;
"_E" Shaded with Edges; "_G" Shades of Gray; "_SK" Sketchy; "_X" X-ray.
The appearance of these styles is controlled from a number of system variables that make it
possible to program custom visual styles. These variables are described in Table 13.15.
To set a visual style for our programs we have de ined the var-vis function (Listing 13.18)
which customizes the way 3D objects are presented. The effects of the proposed settings can
be seen on this book’s cover. We begin by setting a color for the VSMONOCOLOR variable so
that there is no need for setting the Layer’s or the object’s color, as it is linked to the style set
for the viewport. The variable’s value is set to a RGB color with the string "RGB:
211,76,3" as the to obtain the shade of orange we wanted. But for this color to be applied to
our 3D objects’ surface the variable VSFACECOLORMODE must be set to 1 instead of its
default value 0.
To enhance details a Gooch shading is used instead of a realistic one. Gooch shading uses
warm and cool tones instead of dark and light, as a means to display shadows. This mode is
enabled by setting the variable VSFACESTYLE as 2. For a realistic presentation its value
should be set to 1.
In this view Isolines are displayed in a lighter color than the surfaces as a way to emphasize
the model’s relief. To attain this it has been necessary to ensure that the VSEDGES variable’s
value is 1 so that the Isolines are displayed and to set their color in the VSEDGECOLOR
variable as "RGB 255,212,82". Finally, the VSISOONTOP variable has been set to 0. This
way the Isolines in the back part of the model will remain hidden.
But care must be taken to check if the program is running in the Block Editor, because trying
to assign values to any of the VS... system variables will generate an error in that case. So
when making changes to any of these variables it is necessary to check if that is the current
environment. This can be veri ied with the BLOCKEDITOR variable whose value should be 0
for no errors to occur.
(defun var-vis ()
(if (= (getvar "BLOCKEDITOR") 0)
(progn (setvar "VSMONOCOLOR" " RGB:211,76,3")
(setvar "VSFACECOLORMODE" 1)
(setvar "VSEDGES" 1)
(setvar "VSEDGECOLOR" "RGB:255,212,82")
(setvar "VSISOONTOP" 0)
(setvar "VSFACESTYLE" 2)
(setvar "VSOBSCUREDEDGES" 0)
(setvar "VSOCCLUDEDEDGES" 0)
(setvar "PERSPECTIVE" 1))))
In the screenshots made for this book’s illustrations the values of other variables have in
some cases been set in order to enhance the display of 3D forms. For an increased relief effect
we have added specular re lections setting the VSFACEHIGHLIGHT variable as 50. The
quality of the lighting effects is controlled from the variable VSLIGHTINGQUALITY. For a
smooth facet color gradient its value is set to 1. An increased smoothness can be attained by
setting its value to 2, but this increases processing overhead. In certain cases we also regulate
the object’s transparency using the VSFACEOPACITY variable. For a minimal although
perceivable transparency an opacity value of 98% can be set.
Care must be taken when programs use on-screen selections with the PERSPECTIVE
variable which we set to 1. But this may cause on-screen selections to fail, so its value should
be restored to 0 in these cases.
These system variables controlling the graphics window display mode and the
_VISUALSTYLES command were introduced with Release 2007. In previous releases the
appearance of the graphics window is controlled from the _SHADEMODE command. This
command is kept in releases higher than 2007 for compatibility with older AutoLISP
programs, but is now just an alias for _VISUALSTYLES.
The issues have been addressed taking advantage of functions, methods and properties
readily available in the programming language, so that a great mathematical background is
not essential.
Chapter 14
The Spline entity
T h e SPLINE entity is an implementation of the Non-Uniform Basis Spline (NURBS)
mathematical model that facilitates the creation and representation of curves and surfaces
including both highly complex freeform objects and simple geometric ones. They are
characterized by:
Their ability to represent virtually any shape, from points, straight lines, or polylines to
conic sections (circles, ellipses, parabolas and hyperbolas) and completely arbitrary free
forms.
The great control that can be attained over the curve’s shape, being able to change its
curvature and continuity by manipulating its set of control vertices and knots.
Achieving the representation of highly complex shapes with an extremely reduced
amount of data.
Spline curves are similar in their properties and behavior to NURBS surfaces. However
AutoCAD does not provide so far a Visual LISP/ActiveX programming interface to deal with
them. NURBS surface creation as yet depends solely on the programming of the base curves
from which they are created, notably the Spline.
Figure 14.1. Splines defined from Fit Points and from Control Vertices.
Splines can be created through entmake and using the AddSpline ActiveX method. The
entmake function provides greater control over the creation of Splines, although this
requires a deeper understanding of the parameters involved. The procedure used to create
the curve determines the accessibility to some of its properties. We will proceed to study the
alternatives.
0 = Uses the legacy AutoCAD 2011 method. Closing Splines or NURBS surfaces generates a C2
SPLPERIODIC
1 = Creates Periodic Splines and NURBS surfaces. continuity that ensures the smoothest transition.
ActiveX.
The AddSpline method reproduces the behavior of the SPLINE command prior to Release
2011 where the values of the curve’s initial and inal tangent had to be speci ied. This method
always creates Fit Point Splines. Its syntax is:
(vla-AddSpline space PointsArray StartTangent EndTangent)
Listing 14.1. Function that creates a SPLINE entity applying the AddSpline method.
Listing 14.1 shows how to apply this method to create a Spline object . This method always
creates Degree 3 (Cubic) Splines specified by its Fit Points.
The simplest way to create SPLINE entities is by specifying its Fit Points. To create the entity
through this procedure it is enough to provide entmake the following information:
In the ent-spline-FP function (Listing 14.2) we have omitted group code 70 which
determines a number of aspects related to the type of entity to create. However, the proposed
function creates a Spline, which is perfectly suitable to the aims of this chapter.
Table 14.2. New group code 70 values for the SPLINE entity.
Releases: Value: Description:
1 Closed Spline
2 Periodic Spline
Up to 2011 4 Rational Spline
8 Planar Spline
16 Linear (planar bit is also set)
32 Chord: Chord length knots parameterization.
64 SqrtChord: Square root (centripetal) knots parameterization.
128 Uniform (equidistant) knots parameterization.
256 Custom knots parameterization.
Release
512 showCvHull: Shows the control vertices.
2012 and
1024 FP: Fit Points method.
over.
2048 unClamped.
4096 CustomChord knots parameterization.
8192 CustomSqrtChord knots parameterization.
16384 CustomUniform knots parameterization.
Accordingly the value 1064 comes from (+ 8 32 1024) meaning a lat spline (8) with
chord length knot parameterization (32) and created by the it points method (1024). By
using the id-bits function (Listing 7.5) defined in Chapter 7 we obtain the following results:
_$ (id-bits 1064)
(8 32 1024)
_$
If this curve is closed by _SPLINEDIT we will ind that group code 70 is equal to 3339 for
which id-bits will return (1 2 8 256 1024 2048), which shows that the bits were
activated for closed spline (1), periodic (2), planar (8), custom knot parameterization (256),
created by the Fit Points method (1024) and unClamped (2048).
_$ (id-bits 3339)
(1 2 8 256 1024 2048)
_$
On examining the list returned by entget we discover that although the entity retains group
code 1024, it has actually turned into a Control Vertices spline, in which the number of it
points indicated by group code 74 is 0 and group codes 11 that specify the it point
coordinates are missing. And the value associated with group code 73 is incremented by 1 as
is the group code 10 sequence (control vertices coordinates) in which the irst vertex is
repeated at the end of the sequence. Furthermore, the number of the control polygon vertices
and the number of knots will have increased.
Considering all these complexities, using entmod on lists in which the group code 70 has
been modi ied is highly problematic. Thus, the way to create a closed spline is simply setting
its Closed property as :vlax-true after the entity is created. But here we ind another
dif iculty. Since Release 2012 the Closed property is available on the spline, but it is read-
only. To close or open a Spline since Release 2012 we must use the Closed2 property. This
can be veri ied using the VBA Editor’s Object Explorer. One solution that would work for all
Releases would imply catching the error, so if the Closed property is read-only, the
Closed2 property is used.
(if (vl-catch-all-error-p
(vl-catch-all-apply
'vla-put-Closed
(list objSpline :vlax-true)))
(vla-put-Closed2 objSpline :vlax-true))
If for the Fit Points method the only valid value for Degree was 3, we can now choose for it
values between 1 and 5. A Degree 1 curve only includes linear segments. Circle arcs are
obtained from Degree 2 (quadratic) curves. For free-form curves Degrees 3 (cubic) or
higher are used. A curve’s Degree is directly related to the curve’s Order. The value of
Order for a Spline is equal to Degree + 1. Raising a curve’s Order by means of its
ElevateOrder method also increases its Degree.
Splines are de ined by a control polygon with n vertices. Control vertices are 3D points in WCS
coordinates which may be calculated from any mathematical function or obtained
interactively using getpoint. The number of control vertices of a Spline must be at least
Degree + 1 vertices. Control vertices have an associated value called Weight.
Normally each control vertex has a weight of 1.0, which means that all of them have the same
in luence on the curve’s shape. Increasing the weight of a given control vertex increases its
in luence and has the effect of pulling the curve towards itself. The curves thus de ined, with
individual weights for each control vertex are called Rational curves. When all of the control
vertices have the same weight it is said that the curve is non-rational. In AutoCAD’s
implementation group code 70 indicates a non-rational Spline only if all weights are -1. A few
NURBS curves as circles and ellipses are always rational. But not only does the weight’s
magnitude affects the curve’s shape. An equally important element is the relative difference
between weights. The more equal the neighboring weights, the smaller their in luence over
the given region.
The knot vector consists of a sequence of Degree + N + 1 numbers, where N represents the
number of control vertices. In this sequence the same value must not be repeated more times
than the Order (Degree + 1) of the curve. The term vector should not be confused with a
direction in space. This sequence must meet certain requirements. The simplest way to build
a valid knot vector is to ensure that each number is greater than or equal to the preceding one
and that the number of repetitions of the same number does not exceed the curve’s Order. The
magnitude of the value assigned to the nodes is not relevant. All that matters is the
relationship between those values. The knot vectors [0 0 0 1 2 2 2], [100 100 100
200 300 300 300] and [-0.5 -0.5 -0.5 0 0.5 0.5 0.5] all produce the same
curve.
The number of times a knot value is repeated is called multiplicity. We say that a knot has
complete multiplicity when it appears as many times as the curve’s Order. When it occurs
only once is said to be a simple knot.
Figure 14.3. Splines of Degrees 1, 2 and 3 with uniform knot vectors.
Uniform (Figure 14.3) when it holds that (k-1) <= t <= (n + 1), e.g., [0 1 2 3 4 5 6].
As shown in Figure 14.3, the splines of Degrees 2 and 3 defined from uniform knot
vectors do not start and end at the initial and final vertices. This would require
increasing the influence of these control vertices through a knot vector which includes
one more repetition of the initial and final knot values.
Figure 14.4. Effect of non-uniform knot vectors on the influence of control vertices.
Clamped Uniform (Figure 14.4) where 0 <= t <= n - k + 2. When the curve begins and ends
with full multiplicity knots and all intermediate ones are simple and equally spaced. In
these cases there are no losses in the parameter range: The curve interpolates the
starting and ending control points. These curves are also known as pinned splines.
For example: [0 0 0 1 2 2 2].
Piecewise Bézier, in this case, if the Order is k, the curve passes through each kth control
polygon vertex and only approximates the others. In other words, the curve is broken
into segments of k points each. Moving any control point within a particular segment
only affects that segment, while moving a point where two segments join affects both. If
the joint and the control points on either side of it are in a straight line, then the two
segments form a smooth continuous curve. Otherwise a sharp kink or discontinuity
occurs. A knot vector for a quadratic (Degree 2) piecewise Bézier curve and seven control
vertices will look like:
[0 0 0 1 1 2 2 3 3 3]. The first k knots would be equal, then we would have
groups of k-1 knots of equal value, ending with a final set of k equal knots. Accordingly, in
a piecewise Bézier curve the number of control vertices must be an even multiple of
Order - 1.
Non-Uniform, indicating that in this case the knots are not uniformly spaced. Duplicate
knot values within the curve make it less smooth. A full multiplicity knot within the knot
vector generates a curve supporting discontinuities (kinks) at that point. Clustering
knots increases the influence of nearby control vertices on the curve's shape.
The number of knots is calculated as Degree + Number-of-vertices + 1. The knot vector will
begin and end with knots of multiplicity equal to Degree if the argument clamped is nil
and Degree + 1 if it is T. This value is assigned to the variable degree. This means that we
must subtract from the calculated total number of knots, in the first case Degree x 2, and in
the second case (Degree + 1) x 2. This gives us the number of knots for the vector’s
central interval, which we assign to the variable center.
Once this data is calculated, as many zeros as the value of the degree variable are added to
the list representing the knot vector. Then the amount of consecutive numbers calculated for
the central interval of the vector is added to this list, and inally the following number is
repeated depending on the value of degree.
(defun knot-vector (cv degree clamped /
number center vector i)
(setq number (+ degree cv 1)
degree (if clamped
(1+ degree)
degree)
center (- number (* degree 2))
i 0)
(repeat degree
(setq vector (cons 0 vector)))
(repeat center
(setq i (1+ i))
(setq vector (cons i vector)))
(repeat degree
(setq vector (cons (1+ i) vector)))
(reverse vector))
The creation of the spline by the Control Vertices method is implemented in the ent-
spline-CV function (Listing 14.4) which takes as arguments a list of vertices (3D points) for
the control polygon, the curve’s Degree. And a Boolean that de ines if the Spline should be
clamped.
(defun ent-spline-CV (vertices-list degree
clamped / cv k-vec)
(setq cv (length vertices-list)
k-vec (knot-vector cv degree clamped))
(entmake
(append
(list ‘(0 . "SPLINE")
‘(100 . "AcDbEntity")
‘(100 . "AcDbSpline")
(cons 71 degree)
(cons 72 (length k-vec))
(cons 73 cv)
‘(74 . 0)
‘(42 . 0.0000001)
‘(43 . 0.0000001))
(mapcar ‘(lambda (k) (cons 40 k)) k-vec)
(mapcar ‘(lambda (x) (cons 10 x))
vertices-list))))
Listing 14.4. Function that creates a Spline by the control vertices method.
1. It obtains the number of vertices using (length vertices-list) which is assigned to the
variable cv.
2. Invokes the knot-vector function to obtain the list k-vec containing the knot vector values.
3. Applies entmake to the list created by joining three consecutive lists using append:
a. A list with group codes 0, 100, 17 (Degree), 72 (number of knots), 73 (number of control
points), 74 (number of fit points, that for this method is always 0), 42 (knots tolerance) and
43 (control points tolerance).
b. A list with the sequence of sublists for the values associated with the group code 40, which
is created by mapping a lambda expression on the list k-vec.
c. A list with the sequence of control points of the polygon, associated with group code 10,
also generated by mapping a lambda expression on the list received as the first argument.
Gets or sets the Spline's end tangent as a direction vector in Fit Point Splines. When the Spline's
order is elevated it is converted to a Control Vertices Spline. This means that after elevation, the
EndTangent
spline no longer has fit tangents and its StartTangent and EndTangent properties are no
longer accessible.
$ (setq eTg (vla-get-EndTangent objsplineFP))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value eTg))
(0.0 0.0 0.0)
Gets or sets a Spline's Fit Points. These points define the curve's path. The tolerance of any point
can be changed by using the FitTolerance property. The AddFitPoint method adds a fit
FitPoints point and the DeleteFitPoint method removes it. The GetFitPoint method obtains the
position of a Fit Point and the SetFitPoint method changes it. The Control Polygon Spline lacks
Fit Points, so this property returns an empty safearray.
_$ (setq FPs (vla-get-FitPoints objSplineFP))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value FPs))
(2738.88 -1630.46 0.0 4295.69 -827.319 0.0 5115.01 -1493.15 0.0 4857.84 -2348.8 ...
_$ (vlax-safearray-get-u-bound
(vlax-variant-value (vla-get-FitPoints objSplineVC)) 1)
-1
If FitTolerance is set to zero, the curve will pass through all of the fit points. Supplying a value
FitTolerance
greater than zero allows the curve to deviate from the fit points by the specified tolerance.
pitch: width of one complete helix turn, measured parallel to the axis of the helix,
n-ver: the number of vertices,
incang: the angle increment for each vertex,
incrad: the radius increment for each vertex,
ang: the initial angle value that will be increased for each vertex,
val-z: the initial value of the z coordinate that will be increased for each vertex,
f-rad: scale factor used to calculate the radius of the vertices of control polygon
vertices,
rad-v: the initial value for the control vertices radius, which is incremented for each
vertex.
This function calls a repeat loop to calculate the vertices using the polar function. Whereas
the helix is inscribed in the control polygon, the radius of the helix at each control vertex is
increased by the cosine of half the angle increment so it corresponds with the position of the
vertex in the polygon.
We must take into account the behavior of the knot parameterization we’ve studied in the
previous section, so we calculate two additional vertices, which have the effect of setting the
curve’s starting and ending directions. Another small adjustment refers to the initial Z
coordinate that is reduced by half the pitch of the helix.
Figure 14.7. Helical spline built by ent-spline-CV.
If we execute the ent-spline-CV function using the list of vertex coordinates generated by
t h e CV-helix function, setting the argument clamped as T, we ind that there is an
irregularity in the helix’s initial and inal segments, which extend to the control polygon’s
initial and final vertices (see Figure 14.7).
(defun cv-helix (center base-radius top-radius
height resolution turns twist /
n-ver ang incang incrad val-z
f-rad rad-v vertex vertices)
(setq turn-height (/ (float height)
(* turns resolution))
n-ver (* turns resolution)
incang (/ pi (/ resolution 2.0))
incrad (/ (float (- top-radius base-radius))
(* turns resolution))
ang (- 0.0 incang)
val-z (- (nth 2 center) (/ turn-height 2))
f-rad (cos (/ pi resolution))
rad-v (- (/ base-radius f-rad) incrad))
(repeat (+ n-ver 2)
(setq vertex (polar center ang rad-v)
vertex (list (nth 0 vertex)
(nth 1 vertex)
val-z)
vertices (cons vertex vertices)
ang (if (= twist 1)
(+ ang incang)
(- ang incang))
rad-v (+ rad-v incrad)
val-z (+ val-z turn-height)))
(reverse vertices))
Listing 14.5. Function that computes the helix's control vertices coordinates.
To create the helix we will have to use an unclamped knot vector in which the initial and inal
knot value repetition is equal to Degree and not Degree + 1 as would be necessary when
reproducing the behavior of the SPLINE command. Being able to specify the number of
vertices per turn allows us to experiment with the degree of approximation to a circular helix
obtained with a different number of vertices.
Figure 14.8. Left: Irregular Helix. Right: Solution obtained by modifying the knot vector.
The Helix object encapsulates a Spline object that gives it its shape. If we study the structure
of the list returned by entget for a HELIX entity and compare it with that of a SPLINE
entity we ind that the irst part of both lists has the same structure. Only the entity type,
associated with group code 0, changes. The big difference between both structures is a
section added to the end, starting with a group code 100 associated with the "AcDbHelix"
class name. The immediate conclusion is that adding this new section to the end and changing
the object type to "HELIX" we will have created a Helix object similar to the one created
using the command.
In Release 2012 group codes 90 and 91 can be omitted since they will be added automatically
by entmake. The Constraint type (group code 280) indicates the property which is kept when
others are modified. For example, changing the number of turns either the total height or
turn height can be constant.
T he ent-helix-CV function (Listing 14.6) adds an additional list to the one passed to
entmake, thus creating the Helix entity. The entity created is different from that created by
the HELIX command in two ways: the number of control vertices per turn in the original Helix
is always 17, and that knot vector is of the Piecewise Bé zier type describe above where all
knots are repeated three times, a number that is equal to the curve’s Degree that is always 3.
According to what was said about grouped knots, this increments the in luence of the control
vertices. However the object obtained from the proposed function is suf icient in most cases.
And if we apply any modi ication to the Helix created by entmake, either from the AutoCAD
user interface or using any of the ActiveX methods or properties, the entity will be
immediately transformed into a native Helix entity. For this reason, if greater precision in any
of its parameters is needed, we can adjust them with all the tools the programming
environment provides.
(defun ent-helix-CV (point-list degree clamped
base-center base-radius
top-radius turns turn-height
twist / cv k-vec)
(setq cv (length point-list)
k-vec (knot-vector cv degree clamped))
(entmake
(append
(list ‘(0 . "HELIX")
‘(100 . "AcDbEntity")
‘(100 . "AcDbSpline")
(cons 71 degree)
(cons 72 (length k-vec))
(cons 73 cv)
‘(74 . 0)
‘(42 . 0.0000001)
‘(43 . 0.0000001))
(mapcar ‘(lambda (k) (cons 40 k)) k-vec)
(mapcar ‘(lambda (x) (cons 10 x))
point-list)
(list
‘(100 . "AcDbHelix")
(cons 10 base-center)
(cons
11
(polar base-center 0.0 base-radius))
‘(12 0.0 0.0 1.0)
(cons 40 top-radius)
(cons 41 turns)
(cons 42 turn-height)
(cons 290 twist)
(cons 280 1)))))
Finally we will incorporate these functions in a sample program that shows how to build a
Helix using entmake. As in other cases, we will incorporate the data input into a separate
function, spline-helix-data (Listing 14.7). The data requested reproduce the main
options of the HELIX command. This program shows how we can incorporate into a program
the construction of a Helix entity without resorting to the command/vl-cmdf interface or
the HELIX command.
The main function C:ENT-HELIX (Listing 14.8) invokes the spline-helix-data function
and uses the returned values in a call to the cv-helix function that creates the list of control
polygon vertices. The ent-helix-CV function is responsible for creating the spline using as
arguments:
14.5 Summary.
We have devoted this chapter to the study of NURBS geometry as exposed in the SPLINE
entity. We have sought to ill an important shortcoming in AutoCAD’s documentation,
explaining fundamental concepts such as:
NURBS surfaces are a generalization of NURBS curves and respond to the same algorithms,
however they are not yet accessible to Visual LISP programming. But mastering the
programming of SPLINES it is possible to create NURBS surfaces through the command/vl-
cmdf interface .
NURBS geometry has characteristics that justify its use in CAD systems. Among them we can
highlight:
There are standard procedures for the exchange of NURBS geometry among different
design applications that use it as a basic resource, e.g., Autodesk Alias, Maya and Rhino.
NURBS geometry has a precise and well-known definition that is part of the curriculum in
most universities.
This geometry can accurately represent both simple geometries such as lines, arcs or
circles and more complex ones such as the surfaces used in automotive, naval or
aeronautic design.
The amount of information required for a NURBS representation is much less than for
other 3D objects such as Polylines, Polygon meshes and Subdivision surfaces.
Its implementation in computer programs is efficient and extremely accurate.
Exercises.
Exercise 1.
Taking into account both the de inition of the Knot Vector for the piecewise Bé zier curve,
design a function that returns a list with the knot values for a curve of this type. The function
should receive as arguments the curve’s Degree, the number of Control Vertices that must be
equal to the result of subtracting 1 to the product of the curve’s Order (Degree + 1)
multiplied by an even number. Or what would be the same, the result of subtracting 1 to the
product of the number of segments of the control polygon times the Order of the curve.
Being k the Order of the curve, the Knot Vector must start with k repetitions of the initial
value (e.g., 0), then include groups of k-1 repetitions of values that are incremented for each
group and end in a group of k repetitions of the last value. For example, for Degree 2 and
seven control vertices: [0 0 0 1 1 2 2 3 3 3].
Once available, we can explore them using the Visual LISP IDE Apropos tool. Introducing the
text vlax-curve we obtain the results shown in Figure 15.1.
Selecting any of the names of the eighteen functions found and clicking the Help button will
open the AutoLISP/Visual LISP Reference Guide for the selected item. To get acquainted with
them we will develop a series of small programs that show how they can be used.
Figure 15.1. VLAX-CURVE functions.
Which curves?
But, which curves are we dealing with? The conclusion reached after applying these functions
to most of the available graphic entities is that any inite linear object will be accepted as a
"curve", no matter its degree of curvature. This only excludes the in inite linear entities
(XLINE and RAY), Solid or Surface entities (3D faces, meshes, solids, regions or surfaces) and
those with a punctual nature (text, points and blocks). The punctual entities include Hatches
and MultiLines, because of their block nature.
This means that a straight line is, to the effects of these functions, as curve as an arc, a circle,
an ellipse, or a spline. A irst and obvious advantage that derives from this is having
procedures that apply equally to all these entities. The same functions will be able to return
the length, area or tangent’s direction at a point for any of them.
The VLA-object.
All require at least one argument: the representation of an entity as VLA-object. We can
retrieve this VLA-object from an entity name (ename) obtained by any of AutoLISP’s
selection procedures. This entity name is passed as argument to the vlax-ename->vla-
object function, as shown below:
(setq curveObj (vlax-ename->vla-object ename))
Point.
In some cases a point is required, represented by a list of three real numbers corresponding
to the XYZ coordinates. These coordinates should always be referred to the World Coordinate
System (WCS). In case these points are expressed in a different coordinate system (as can be
the case of a 2D object’s coordinates in a plane not parallel to the WCS), care must be taken to
transform it to WCS by means of the trans function.
Interactively selected points, as those returned by getpoint are also affected by the active
UCS and the current view orientation settings. Distance along the curve.
In the event that the required argument is a distance, it is understood as a real number that
represents, in drawing units, the length measured along the curve’s path starting from its
initial point (curvilinear abscissa).
Parameter.
Finally, we have the parameter argument. The program’s documentation is not too explicit
about this argument’s nature. The design of complex curves and surfaces has required that
CAD systems incorporate different types of representation for them. Basically a CAD system
should facilitate the representation and local or global modi ication, of the forms a designer
imagines. This is done using two types of representation: algebraic and parametric.
The algebraic representation can be achieved explicitly or implicitly. In the irst case closed
curves cannot be achieved while in the second case, due to the absence of a one to one
correspondence, a variable does not uniquely de ine a position within the represented form.
An additional dif iculty is derived from the dependency on coordinate systems. To solve these
drawbacks, a parametric representation is preferred .
x = f1 (u), y = f2 (u), z = f3 (u), with u1 ≤ u ≤ u2, where the functions f1, f2 and f3 can be of any
type appropriate to the geometry of the curve it represents. In the ield of Computer Aided
Design, polynomials of degree three are the most frequent, so that the parametric expression
of the curve (or of the various pieces that compose it) takes the form:
while the parameter u takes values in the interval [0,1]. The determination of boundary
conditions creates various modes of interpolating the set of points.
Any entity that represents a curve in AutoCAD is based on some kind of parameterization
defined by its programmers. This is manifested in two aspects:
With the parameterization chosen by AutoCAD for polylines and circles, the points will be
uniformly distributed along the curve, but for ellipses and splines they are grouped more
densely as the curvature increases. We will gain understanding of this argument’s nature as
we study some of the functions in which it is used.
The curve’s length is measured from the beginning of the curve to the point speci ied by its
coordinates or by its parameter.
Total length of a curve.
Listing 15.1 shows a function that calculates the total length of a curve using the value of the
curve’s end parameter.
(defun ax-curvelength (curveobj)
(vlax-curve-GetDistAtParam
curveobj
(vlax-curve-GetEndParam curveobj)))
The same result would be obtained by retrieving its endpoint with vlax-curve-
GetEndPoint and passing it to vlax-curve-GetDistAtPoint. The method chosen in
this case does not alter the result. However, some errors have been reported in functions that
resort to the determination of lengths by vlax-curve-GetDistAtPoint, so using vlax-
curve-GetDistAtParam is recommended. Therefore, in a more complex function like the
following, we do not hesitate using this second option.
In case the point is not on the curve, instead of a real number for the parameter, nil will be
returned and this will be the value returned by ax-dist-point. In the event that the
selected point actually corresponds to a point on the curve (e.g., by using the NEAr object
snap), the parameter returned will be used in the expression:
(vlax-curve-GetDistAtParam curve-obj parameter)
Listing 15.4. Measuring the distance from the beginning of the curve to a point.
Listing 15.5. Determining the distance between two points on the curve.
Listing 15.6. Program that measures the distance between two points along a curve.
It is not possible to calculate the area of a warped curve, condition that can be ruled out using
the vlax-curve-isPlanar predicate. It is also possible to discriminate between open and
closed curves using the vlax-curve-isClosed predicate. These two functions will return
nil if the condition (planar or closed) is not satisfied.
These tests are structured as clauses in a cond conditional, so that the result returned will be
the one associated with the first clause that succeeds.
This conditional is enclosed in a while loop whose test expression is that a drawing entity
has been selected. When this condition is not met, either by clicking where no entity exists or
by pressing ENTER, the program’s execution will end. If a selection has been made, once the
area has been displayed the program continues prompting the user for new objects.
The alert function is used to display the information. This function requires a string as
argument. So the real number returned by vlax-curve-GetArea is converted to a string
using rtos, which allows a certain amount of control over the format of the resulting string,
specifying the linear units modality (Scienti ic, Decimal, Engineering, Architectural or
Fractional) and the number of decimal places. This formatting depends on two optional
arguments that in this case we are not using, so that the format we obtain in this case will
depend on the LUNITS and LUPREC system variables settings.
As we also wish to express the kind of information given, other explanatory phrases will be
concatenated to the area value using the strcat function. Among the additional information
the object type is included, which in this case is obtained (since we are working with a VLA-
object) from its ObjectName property. It can be seen that the result is not the same as the
one we would get from the value associated with group code 0 in the list returned by entget.
(defun C:CURVE-AREA (/ obj)
(while (setq obj (car (entsel)))
(setq obj (vlax-ename->vla-object obj))
(cond
((not
(vlax-property-available-p obj "Area"))
(alert (strcat "The selected object "
(vlax-get-property
obj
"ObjectName")
"\nhas NO area.")))
((not (vlax-curve-isPlanar obj))
(alert (strcat "The selected object "
(vlax-get-property
obj
"ObjectName")
"\nis NOT planar.")))
((not (vlax-curve-isClosed obj))
(alert (strcat "The selected object "
(vlax-get-property
obj
"ObjectName")
"\nis NOT closed.")))
(t
(alert
(strcat "The selected object "
(vlax-get-property
obj
"ObjectName")
"\nhas an area of "
(rtos (vlax-curve-GetArea obj))
" square units."))))))
So far we have seen those vlax-curve... functions in which the purpose of measuring
curves is more evident. But those that remain to be studied enable us to solve a series of
problems so far not addressed by the language, especially when it comes to extracting
information from entities such as the SPLINE, the HELIX or the ELLIPSE.
The following expression returns a curve’s first derivative at the point defined by parameter:
(vlax-curve-GetFirstDeriv curve-obj parameter)
Both functions accept as arguments a curve object and the parameter that de ines where the
irst or second derivative will be calculated. As the required argument is the parameter and
what we probably know is the point we are interested in and not its parameter value, we use
vlax-curve-GetParamAtPoint to retrieve the parameter for the specified point.
The usefulness of the irst derivative is evident. It allows drawing the tangent (and therefore
the perpendicular plane) to the curve at any point. The second derivative could be used in
detecting a curve’s inflections.
To create a new entity of this kind only a few of the group codes represented here are needed:
These ive sub-lists would be the necessary ones for entmaking a RAY. Of these, only the
group codes 10 and 11 vary their values. Group code 10 would be associated to the selected
point. Group code 11 admits the value returned by vlax-curve-GetFirstDeriv.
The ent-ray function receives a minimum of arguments. Layer, Color and Linetype will be
the system’s current ones.
(defun ent-ray (pt vector)
(entmake (list '(0 . "RAY")
'(100 . "AcDbEntity")
'(100 . "AcDbRay")
(cons 10 pt)
(cons 11 vector))))
Listing 15.8. Function that draws a RAY.
This function requires a number of previous determinations. First, we must consider that the
vlax-curve... functions require that the point argument be expressed in the World
Coordinate System. As this function may be called when other coordinate system is current,
we must take care of converting the selected point to WCS using the trans function.
As the user may select an object that is not supported by vlax-curve... functions, we
most also foresee this by making the irst call to any of these functions from vl-catch-
all-apply and assign the value returned to a variable. In our case the irst thing we’ll do
with a vlax-curve... function is determining the point on the curve closest to the
selected one. One way to do this is using the vlax-curve-GetClosestPointTo function.
But we have found that under certain conditions, when the view orientation does not
correspond to the UCS normal plane, the VIEWDIR variable being different from (0, 0, 1), the
point returned was not the correct one. To avoid this, the vlax-curve-
GetClosestPointToProjection function can be used, one of whose arguments is the
view direction as retrieved from VIEWDIR. This function returns the closest point (in WCS)
on a curve after projecting the curve onto a plane. Its syntax is:
(vlax-curve-GetClosestPointToProjection curveobj pt normal[extend])
where curveobj is the curve’s VLA-object, pt is a point in WCS and normal is the normal
vector to the plane onto which project. The optional argument extend, if not nil, will extend
the curve when searching for the nearest point. The curve is projected onto the plane de ined
by pt and normal, and then the point nearest to pt on that projected curve is calculated. The
resulting point is then projected back onto the original curve. Although somewhat convoluted,
this way correct results are assured when working on any UCS and view direction.
User interface.
The function C:TANGENT (Listing 15.10) which acts as an AutoCAD command prompts for
the tangency point and draws the tangent RAY invoking calc-tangent and ent-ray. This
function uses entsel for the selection, taking advantage of the fact that the value returned
by this function includes the selected object’s ename and the point used for its selection. It is
important to remember that this point will be expressed in the current coordinate system
which may not match the WCS.
Clicking on a point where no entities are located, entsel will return nil and the command
will end. If the designation succeeds the ent-tangent function described above is invoked.
In case ent-tangent returns nil, the user will be informed in the command line that an error
has occurred. In case the direction vector has been successfully obtained, ent-ray is
evaluated with the point and the vector as arguments.
(defun C:TANGENT (/ selection data)
(cmd-in)
(if (setq selection
(entsel
"\nSpecify point of tangency: "))
(if (setq data (calc-tangent
(car selection)
(cadr selection)))
(ent-ray (car data) (cadr data))
(prompt "\nRuntime error.")))
(cmd-out)
(princ))
The irst point speci ied will be taken as the coordinate system’s origin and the second point
will be located on the new coordinate system’s Z axis. AutoCAD automatically adjusts the
direction of axes X and Y so that the drawing plane is perpendicular to the direction de ined
from these two points. The data returned by calc-tangent can be used to execute this
option. The designated point on the curve will be used as the system’s origin and the other
point is the sum of this point’s XYZ coordinates with those corresponding to the direction
vector.
In the same way we had to transform the selected point coordinates from the current UCS to
the WCS so we could use them with the vlax-curve... functions, to use the coordinates
returned by the calc-tangent function in the UCS command we must transform these back
to the current UCS. The trans function will again be used but in this case inverting the
arguments that indicate the source and target systems. The call to the UCS command with
the required options and coordinate transformations is performed from the cmd-vectorz
function (Listing 15.11).
(defun cmd-vectorz (pt vector /)
(setvar "CMDECHO" 0)
(vl-cmdf "._ucs"
"_zaxis"
"_none"
(trans pt 0 1)
"_none"
(trans
(mapcar '+ pt vector) 0 1))
(setvar "CMDECHO" 1))
Listing 15.11. Function that sets a coordinate system from a point and a vector using command.
The function takes the same arguments as cmd-vectorz (Listing 15.11) so that they are
interchangeable. Given that calc-tangent (Listing 15.9) returns the point where the object
was selected in WCS coordinates and the tangent vector at that point, the procedure for
finding the X and Y direction vectors would be:
The angle of the direction vector measured from the positive direction of the X axis is
found applying the angle function. As it is a vector, the first argument for angle must
be the WCS origin. Ninety degrees (pi/2 radians) will be subtracted from this angle to
obtain the perpendicular direction.
The polar function will be applied from the WCS origin to find a point on the XY plane in
a direction perpendicular to the tangent direction vector. As distance 1.0 is used so the
unit vector is obtained.
The Y direction vector should be perpendicular to the X and Z directions, and is
calculated as the cross product (vector product) of the X and Y direction vectors. To find
the cross product we use the vec-prod function, which was defined in Listing 13.4.
Having calculated the necessary arguments they are passed to the to the ax-ucs
function we already used in Chapter 13 (Listing 13.15) that creates a new UCS from those
parameters and sets it as the document's current UCS.
(defun ax-normal-ucs
(origin vec-z / ang vec-x vec-y)
(setq ang (- (angle '(0 0 0) vec-z) (/ pi 2))
vec-X (vec '(0 0 0)
(polar '(0 0 0) ang 1.0))
vec-Y (vec-prod vec-z vec-x))
(vla-put-ActiveUCS
*aevl:drawing*
(ax-ucs "TMP" origin vec-x vec-y)))
Listing 15.12. UCS from the Z axis direction vector using ActiveX.
Working in 3D frequently we will have to align an object with a given coordinate system. An
example is when we want to create a solid or a surface by sweeping a region along a trajectory
which can have any 3D orientation. Once the UCS normal to path’s starting point is set, we can
obtain its UCS transformation matrix applying the ax-ucs-matrix function de ined in
Listing 13.16 and use the vla-TransformBy function to align the object with the sweep
path. This type of transformation is usual in 3D space programming as we will see in the
sample programs that we propose further on. In cases like this it may not be necessary to set
this UCS as the drawing’s current UCS, being suf icient to obtain its transformation matrix. In
this case we can use a variation of ax-normal-ucs that will return the UCS transformation
matrix. We will name this function ax-UCSMatrix (Listing 15.13):
(defun ax-UCSMatrix
(origin vec-z / ang vec-x vec-y)
(setq ang (- (angle '(0 0 0) vec-z) (/ pi 2))
vec-X (vec '(0 0 0)
(polar '(0 0 0) ang 1.0))
vec-Y (vec-prod vec-z vec-x))
(vla-GetUCSMatrix
(ax-ucs "TMP" origin vec-x vec-y)))
User interface.
It can be seen that cmd-vectorz and ax-normal-ucs receive exactly the same arguments
a s ent-ray. So replacing ent-ray with cmd-vectorz or ax-normal-ucs in the
C:TANGENT function (Listing 15.10) we obtain C:NORMAL-UCS (Listing 15.14). This is
another example of the way of programming this book proposes.
we can calculate points on any curve. Frequently a certain operation demands determining
points on a curve, either by dividing the curve into a number of equal parts or placing them at
a ixed distance. The non-programmer’s only option is to draw points on the curve using the
DIVIDE or MEASURE commands. But using vlax-curve functions we can manage to
calculate these points in our programs.
The entities to be processed are limited to open linear entities. They are selected by ssget in
single selection mode, and the selected entity is iltered to accept only lines, polylines, splines,
arcs and helices. This selection is combined using an and operator with a test to rule out
closed entities:
(and (setq sset (ssget "_:S" '((0 . "*LINE,ARC,HELIX"))))
(not (vlax-curve-isClosed (ssname sset 0))))
The negation of this expression is used as the condition for a while loop that continues until
a suitable object is selected. After the selection is made the user is prompted for the number
of segments, providing the option of entering a distance instead of the number of segments.
This is an example of using a function such as getint to accept arbitrary input. This is
accomplished by activating bit value 128 in initget and entering a keyword as an
alternative: (initget (+ 1 2 4 128) "Distance"). If the user selects Distance from
the context menu or types a D, getint will return the string "Distance". Pressing ENTER
returns an empty string. Therefore the condition that is tested for in the conditional that
follows is whether the data type returned is 'STR (string), requesting in that case the
distance instead of the number of segments.
(defun C:BREAK-DIST
(/ sset ent obj dist points)
(vla-StartUndoMark *aevl:drawing*)
(prompt "\nSelect curve to break: ")
(while
(not (and (setq sset
(ssget
"_:S"
'((0 . "*LINE,ARC,HELIX"))))
(not (vlax-curve-isClosed
(ssname sset 0)))))
(prompt "\nSelect an open curve: "))
(setq ent (ssname sset 0)
obj (vlax-ename->vla-object ent))
(initget (+ 1 2 4 128) "Distance")
(setq dist
(getint
"\nNumber of segments or [Distance]:"))
(cond ((= (type dist) 'STR)
(initget (+ 1 2 4))
(setq dist
(getreal "\nSegment length:")))
(t
(setq dist
(/ (vlax-curve-GetDistAtParam
obj
(vlax-curve-GetEndParam obj))
dist))))
(setq points (ax-measure obj dist))
(cmd-in)
(foreach pt points
(vl-cmdf "_BREAK"
(list ent (trans pt 0 1))
"_F"
(trans pt 0 1)
"@")
(setq ent (entlast)))
(cmd-out)
(vla-EndUndoMark *aevl:drawing*))
In both cases, we are interested in the distance, so if the user speci ied the number of
segments, we obtain the entity’s total length and divide it by the desired number of segments.
With the selected object and that distance ax-measure is called to obtain a list of
coordinates for the points in which the entity should be split.
To split the entity the _BREAK command is used. This command requires as argument a list
similar to that returned by entsel, which includes the entity’s ename and the pick point. As
the pick points are obtained from a vlax-curve function they will be expressed as WCS
coordinates, but when an AutoCAD command is used these points must be expressed in the
current UCS. So again it will be necessary to use the trans function, this time to transform to
the current UCS evaluating (trans pt 0 1).
That point is taken as the break’s irst point, but since that’s not what we want, we have to
resort to the _First option. As always when using commands we must take care of canceling
all running object snap modes, including both 2D and 3D. For the second point the character
"@" returns the last point specified, expressed as UCS coordinates for the current space, value
that is stored in the LASTPOINT system variable.
An important thing to take into account when using this command is that the order of the
points determines the next entity to break. When ordered as in our sample program, from the
starting point to endpoint, the entity to break is not the original one, but the last entity
created, which can be retrieved by entlast. If this order is reversed the entity to break will
be the original one
It should be noted that the IntersectWith method is not applicable and will throw an
error with viewports, meshes, surfaces and 3DSolids. But the vlax-method-
applicable-p predicate nevertheless returns T for those objects. It is therefore
appropriate to include any call to vla-IntersectWith in a vl-catch-all-apply
expression.
If we look up the IntersectWith method in the ActiveX and VBA Reference we ind the
following syntax:
RetVal = object.IntersectWith (IntersectObject, ExtendOption)
The returned value is an array of points where one object intersects the other object. To
convert this array into a list of points it is necessary to group the values obtained in threes.
Below we will code a function that facilitates the use of this method from any Visual LISP
program, incorporating the necessary error controls and returning a list of intersection
points.
Auxiliary functions.
We will begin with the auxiliary functions. First we must develop the function that transforms
the received data into a list of XYZ coordinates. We also wish to use a more LISPish way to
specify how to extend the entities, passing a T or nil argument, but making it possible to also
specify the ActiveX enum constants.
The coord->points function (Listing 15.17) receives a list of coordinates and groups them
into three item sublists. This is done in a while loop where the index i is increased by 3 in
each iteration. The list’s components are added starting from its end, which allows, as the list
assigned to the points variable is built with cons, that the returned point list will be sorted in
the original order. In the irst round it takes the third from last (- ext 2), second from last
(- ext 1) and last (- ext 0) elements. On the next round with the increased index it
will group the (- ext 5), (- ext 4) and (- ext 3) items, and so on until the index is
equal to or greater than ext.
(defun coord->points (lst ext / i points)
(setq i 0)
(while (< i ext)
(setq points (cons
(list (nth (- ext (+ i 2)) lst)
(nth (- ext (+ i 1)) lst)
(nth (- ext i) lst))
points)
i (+ i 3)))
points)
The way to specify the extending options must support T to extend both and nil to extend
none, and the enum constants described in Table 15.1 for more detailed modes. These
constants can be passed as integers or as constant names that ultimately evaluate to an
integer. The function in Listing 15.18 takes into account these possibilities to return the
correct value.
(defun ext-mode (mode)
(cond ((null mode) acExtendNone)
((eq mode T) acExtendBoth)
(t mode)))
The selection of the appropriate value is done within a cond expression that has three
clauses:
1. The null operator is used to check if nil has been entered . This clause would return the
constant acExtendNone.
2. We will need an operator that allows us to distinguish between the symbol T and any LISP
object that not evaluating to nil can represent the true value in a conditional. This is done with
the strictest equality operator: eq. This clause will return acExtendBoth.
3. If the argument does not correspond to any of these two clauses the default clause is applied,
which simply returns the value received as argument.
But it may happen that the returned safearray is empty. This is veri ied by other if
conditional that checks that the safearray’s upper boundary extracted by vlax-variant-
value is not -1, since this would indicate that it does not contain any value.
(defun intersections
(obj1 obj2 mode / crossing limit pti)
(setq crossing
(vl-catch-all-apply
'vla-IntersectWith
(list
(vlax-ename->vla-object obj1)
(vlax-ename->vla-object obj2)
(ext-mode mode))))
(if (not (vl-catch-all-error-p crossing))
(if (< (setq limit
(vlax-safearray-get-u-bound
(setq pti
(vlax-variant-value
crossing))
1))
0)
nil
(coord->points
(vlax-safearray->list pti)
limit))))
15.12 Summary.
Although we have not exhausted the topic of vlax-curve... and related functions, we
believe that this chapter is a clear demonstration of their possibilities. As always, we have
added new useful functions to our programmer’s library that can be used within our
programs.
A good example is the program used to draw tangents to the curve, that with minimal changes
becomes another apparently very different program, which serves to establish with a single
selection a coordinate system perpendicular to the selected entity. In the following chapters
we will see how these functions can help in creating and editing objects in a 3D environment.
Chapter 16
Legacy Polygon and Polyface Meshes
The irst surface entities implemented in AutoCAD (Release 10, 1989) were the
PolygonMesh and the PolyfaceMesh. The PolygonMesh object consists in a rectangular
grid of lat faces that approximates a 3D surface. The mesh density (number of faces) is
defined from an array of M x N vertices. Each mesh face always has four vertices. The faces are
arranged in a grid of M rows by N columns.
The PolyfaceMesh is very similar. It is created by specifying the coordinates for all of its
vertices and once these are de ined, the vertices that delimit each face are identi ied. In this
case, each face of the mesh may have a different number of vertices and consequently the grid
distribution characteristic of the PolygonMesh is not present.
They are distinguished by the values associated with group code 70. For the PolygonMesh
bit 16 must be set and for the PolyfaceMesh the bit 64.
Depending on the mesh type group codes 71 and 72 have different meanings:
For the PolygonMesh, group code 71 specifies the number of vertices in the M
direction, and code 72 the vertices in the N direction.
For the PolyfaceMesh, group code 71 specifies the number of vertices in the mesh,
and group code 72 specifies the number of faces.
The entity’s structure is the same as that of a normal POLYLINE, an entity that serves as
POLYLINE header followed by a sequence of VERTEX entities that is inished with a SEQEND
entity. Its creation follows the same procedure as described for 3D polylines in Chapter 10. In
fact, only minor modi ications are needed in the code then used to create the header, vertex
and end-of sequence entities.
PolyfaceMesh objects can be created with the AddPolyfaceMesh method that receives
as arguments an array with the coordinates of all the mesh’s vertices and another array with
the indices of the vertices that make up each face.
16.2 PolygonMesh.
Unlike the procedures outlined below, the points supplied to the command are considered as
points in the current UCS.
The 3D coordinates for the vertices should be generated by some sort of calculation
procedure such as the one we will propose for these examples. The number of vertices must
be the product of multiplying the values associated with group codes 71 and 72. The creation
of each VERTEX entity requires a new call to entmake (Listing 16.3), which receives as
arguments the values associated to DXF group codes 0, 10 and 70. The only value that
changes is the one associated with the group code 10, the coordinates for that speci ic vertex
represented by the xyz argument. The coordinates are interpreted as WCS values.
(defun PolygonMesh-vertex (xyz)
(entmake
(list '(0 . "VERTEX")
'(100 . "AcDbEntity")
'(100 . "AcDbVertex")
'(100 . "AcDbPolygonMeshVertex")
(cons 10 xyz)
'(70 . 64))))
After creating all the vertices the process will conclude by creating the SEQEND entity (Listing
16.4), which marks the end of the vertices sequence. This last call to entmake only requires
group code 0 de ining the entity type. For this reason, we can de ine a single function to be
used for either type of mesh.
(defun ent-seqend ()
(entmake
(list '(0 . "SEQEND")
'(100 . "AcDbEntity"))))
We will now de ine the ent-draw-pmesh function that will create the PolygonMesh using
entmake. As we can see in Listing 16.5, this function is very simple, it takes as arguments the
m and n values and the vertices coordinate list returned by the function that performs the
calculation. The ent-draw-pmesh function merely invokes in succession the functions to
create the header, a foreach loop that iterates through the point coordinates list creating a
VERTEX entity for each one and ends up creating the SEQEND entity. The function returns the
new entity’s ename obtained through a call to entlast.
(defun ent-draw-pmesh (m n coords-list /)
(PolygonMesh-header m n)
(foreach pt coords-list
(PolygonMesh-vertex pt))
(ent-seqend)
(entlast))
As in previous cases, the arguments m and n represent the number of columns and rows in the
grid and are integers in the range from 2 to 256. The point-array argument contains a
sequence of x, y, z coordinates for all of the mesh’s vertices. Unlike the usual AutoLISP
practice, the coordinates for each point are not grouped as sub-lists, but are all included as a
single sequence in the array whose dimension will then be m x n x 3.
(defun ax-draw-pmesh
(m n coords-list / points-array)
(setq coords-list (apply 'append coords-list)
points-array (vlax-make-safearray
vlax-vbDouble
(cons 0
(- (length
coords-list)
1))))
(vlax-safearray-fill
points-array
coords-list)
(vla-Add3dMesh
(current-space *aevl:drawing*)
m
n
points-array))
It is understood that each three successive elements in the array de ine a point. Assuming we
calculate points as usual in AutoLISP, grouped as a list’s sublists, the conversion to an array
such as the one required by Add3DMesh implies transforming the points list into a single
level list that would be used in creating the array. With this in mind we can de ine a function
that replaces the functions in Listing 16.1 and Listing 16.5 in this case using the ActiveX
method. To do this we must include in the function the transformation of the points list into a
coordinates array.
When a mesh has been smoothed, bit 4 is activated in group code 70 indicating that Spline-fit
vertices have been added. When smoothing PolygonMesh with PEDIT or changing its Type,
MDensity and NDensity properties the necessary additional vertices are automatically
generated. If we try to create a smoothed PolygonMesh directly with entmake we would
have to calculate these additional vertices, since otherwise they would be created but all of
them would be placed at the coordinate system’s origin. In the sample program we propose
the smoothing option is done by changing its properties, once the PolygonMesh has been
created.
Data Entry.
The pmesh-data function (Listing 16.7) Is used for prompting the user for the necessary
data. These data are:
As we have de ined three different functions to create the PolygonMesh resorting to the
different procedures available, the program will begin by asking the user to decide which one
to use. This way we can assess the advantages and disadvantages of each one. The user will be
prompted to choose between command/vl-cmdf, entmake or ActiveX. This is done using
getkword which is initialized by evaluating initget with the string "Command Entmake
Activex". After this the user is prompted for the surface equation to use, choosing between
the types 1, 2 and 3 again using the getkword function properly initialized.
(defun pmesh-data (/)
(initget 1
"Command Entmake Activex")
(setq method
(getkword
"\nMethod [Command/Entmake/Activex]: "))
(initget 1 "1 2 3")
(setq option (getkword
"\nSurface equation [1/2/3]: ")
dimX (getreal "\nX dimension: ")
dimY (getreal "\nY dimension: ")
dimZ (getreal "\nZ dimension: ")
res (getint
"\nMesh resolution (2 to 256): "))
(while (not (< 1 res 257))
(prompt
"\nResolution must be from 2 to 256")
(setq res
(getint "\nMesh resolution: ")))
(initget 1
"None Quadratic Cubic Bezier")
(setq smoothing
(getkword
"\nSmoothing [None/Quadratic/Cubic/Bezier]: "))
(if (/= smoothing "None")
(progn (initget (+ 1 2 4))
(setq density
(getint
"\nSmoothing density (2 to 200):"))))
(cond ((= smoothing "Quadratic")
(setq smoothing 5))
((= smoothing "Cubic")
(setq smoothing 6))
((= smoothing "Bezier")
(setq smoothing 8))
(t (setq smoothing nil)))
(initget 1)
(setq origin (getpoint "\nMesh center: ")
stepX (/ dimX res)
stepY (/ dimY res)
Xmin (- (/ dimX 2))
Ymin (- (/ dimY 2))))
Listing 16.7. Function that prompts for the mesh definition data.
This done, the user will be asked for the mesh’s dimensions in X, Y and Z and its resolution,
i.e., the number of faces in the X and Y directions. This subdivision value may be a number
between 2 and 256, which is veri ied and if this condition is not met it will be prompted for
again. Finally the user is requested to de ine if some type of smoothing should be applied and
prompted for the mesh’s center point.
From the user supplied information a series of variables are assigned containing the data
necessary to calculate the mesh vertices. These data are:
The program as implemented creates the mesh with the same number of rows and columns,
but this is not mandatory. We do this to simplify it somehow, but we have already seen that
different values for group codes 71 and 72 can be specified.
Calculation of the vertices.
The z coordinate for each of the vertices of the three surface forms is calculated from the x
and y coordinate values using alternative mathematical formulas coded as three different
functions (Listing 16.8). These are just three examples. We encourage readers to experiment
with other formulas that can be easily added to the program.
;;; Function f1
(defun f1 (x y /)
(cos (sqrt (+ (* x x 2) (* y y)))))
;;; Function f2
(defun f2 (x y /) (sqrt (abs (* x y))))
;;; Function f3
(defun f3 (x y /) (/ (* x y) 10))
Since the choice between functions is made from a number that the user is prompted for it
will be necessary to determine which one was chosen. This is done through a conditional
expression in the op-equation function. The function’s name, as shown in Listing 16.9 is
preceded by the quote (') symbol.
(defun op-equation (option /)
(cond ((= option "1") 'f1)
((= option "2") 'f2)
((= option "3") 'f3)))
The pmesh-calculus function takes as arguments the selected function, and the variables
set in the pmesh-data function (Listing 16.7). Two nested while loops are used, controlled
by the variables i and j which are described below:
1. First loop: from i = 0 to i = res - 1. The variables j = 0 and y = Ymin are initialized.
a. Second loop: from j = 0 to j = res - 1.
In each repetition a sublist with three values is added to the list assigned to the lst
variable:
i. The X coordinate of the new vertex, which equals to the Xmin value.
ii. The Z coordinate of the new vertex, obtained applying the chosen formula to the
arguments Xmin and y.
iii. The Z coordinate of the new vertex, obtained applying the chosen formula to the
arguments Xmin and y.
2. Having calculated the point's coordinates, the values of j and y are incremented so j = j +
1 and y = y + stepY and the process is repeated until the ending condition is reached.
On completing the second loop i and Xmin are incremented so i = i + 1 and Xmin =
Xmin + stepX and the same process is followed until the ending condition is met. The
increased value is assigned to the same Xmin variable, as the original value will no longer be
needed.
To comply with the mesh height the user has speci ied it will be necessary to ind the
minimum and maximum Z coordinates, once the coordinates for all the vertices have been
calculated. The height of the mesh calculated from the selected formula is obtained from their
difference. The user-speci ied height will be divided by the calculated mesh height to obtain a
scale factor (f-height) that will be applied to each vertex’s Z coordinate in the de initive
vertices list that will be used to build the mesh model.
(defun pmesh-calculus (formula Xmin Ymin
dimz stepX stepY res
/ i j y lst f-height)
(setq i 0)
(while (< i res)
(setq j 0
y Ymin)
(while (< j res)
(setq lst
(cons
(list Xmin
y
(apply formula
(list Xmin y)))
lst))
(setq j (1+ j)
y (+ y stepY)))
(setq i (1+ i)
Xmin (+ Xmin stepX)))
(setq f-height (/
dimz
(-
(apply 'max
(mapcar
'(lambda (pt)
(nth 2 pt))
lst))
(apply 'min
(mapcar
'(lambda (pt)
(nth 2 pt))
lst))))
lst (mapcar
'(lambda (pt)
(list (nth 0 pt)
(nth 1 pt)
(* f-height (nth 2 pt))))
lst))
(reverse lst))
Listing 16.10. Function that calculates the coordinates of the mesh's vertices.
The function returns a list that includes all the calculated vertices as sublists. It should be
noted that the X and Y coordinates refer to the coordinate system’s origin. After creating the
mesh it should be moved to the point chosen by the user for the center of the mesh. Another
alternative would be to add the X, Y and Z values of the center point selected by the user to the
X, Y and Z coordinates for each vertex. We propose what we consider to be the simplest
alternative.
The meshes are drawn with reference to the XY plane. As usual in AutoCAD to draw the mesh
with a different spatial orientation, a UCS other than the WCS should be established. The
behavior of functions using command/vl-cmdf and those using entmake or ActiveX are
different when a UCS other than the WCS is current. The coordinates of the points that are
passed to command/vl-cmdf functions are taken as current UCS coordinates. Thus the
mesh will then be oriented according to the current XY plane.
But both entmake and ActiveX consider the point coordinates as referring to the WCS. For
this reason, it will be necessary to check if we are working in the WCS or in a custom UCS. This
is done by checking the value of the WORLDUCS system variable. If its value were 0 we
would obtain the current UCS transformation matrix using the ax-ucs-matrix function
defined in Listing 13.16 that will be assigned to the mtrans variable.
This done, the pmesh-data function (Listing 16.7) that implements user input is called. With
the data obtained the pmesh-calculus (Listing 16.10) function is called, assigning the
returned list of vertices to the coords-list variable. To decide which of the methods is
used to create the mesh we use a conditional that checks the string assigned to method.
After creating the mesh the type of smoothing chosen by the user is applied. This step is done
by modifying the object’s properties exposed through ActiveX as the simplest way of coding
it, but if we would rather stick to the command interface we could have used _PEDIT or if the
classic AutoLISP functions were preferred we could have done it with entmod.
If we create the mesh by entmake or ActiveX it will be necessary to use the TransformBy
method that will align it with the current UCS XY plane.
This done, the mesh will be centered at the current UCS origin. To bring it to the user
designated center point it will be necessary to apply a translation. This is done by means of
the ax-translation function de ined in Listing 13.5. The coordinates of the user-selected
point belong to the current UCS and to translate the mesh they must be transformed to WCS
using the trans function. The program inishes by displaying on the command line a
message informing about the program’s duration.
(defun C:POLYMESH (/ time method dimX dimY res origin stepX
stepY Xmin Ymin smoothing density
coords-list obj *error*)
(setq time (getvar "millisecs"))
(defun *error* ()
(cmd-out)
(vla-EndUndoMark *aevl:drawing*))
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq mtrans (last (ax-ucs-matrix))))
(t (setq mtrans nil)))
(pmesh-data)
(setq coords-list
(pmesh-calculus (op-equation option)
Xmin
Ymin
dimZ
stepX
stepY
res))
(cond ((= method "Command")
(cmd-in)
(setq mtrans nil
obj (vlax-ename->vla-object
(cmd-draw-pmesh res res coords-list)))
(cmd-out))
((= method "Entmake")
(setq obj (vlax-ename->vla-object
(ent-draw-pmesh res res coords-list))))
((= method "Activex")
(setq obj (ax-draw-pmesh res res coords-list))))
(if smoothing
(progn (vla-put-Type obj smoothing)
(vla-put-MDensity obj density)
(vla-put-NDensity obj density)))
(if mtrans
(vla-TransformBy obj mtrans))
(ax-translation obj (trans origin 1 0 t))
(vla-update obj)
(ax-SWt)
(prompt
(strcat "\nTiming: "
(rtos (- (getvar "millisecs") time) 2 0)
" milliseconds"))
(vla-EndUndoMark *aevl:drawing*)
(princ))
Listing 16.11. Main function C:POLYMESH.
16.5 PolyfaceMesh.
The PolyfaceMesh is created in a similar way to the PolygonMesh. However it is more
dif icult to calculate the vertices, as we must establish their connectivity (i.e., which vertices
surround each face) as well as their coordinates. Another limitation to consider is the
maximum number of four vertices per face supported by AutoCAD. This is the value of the
read-only PFACEVMAX system variable. When the representation of polygonal faces with a
larger number of vertices is required, it will be necessary to subdivide those faces into faces
with no more than four vertices. This operation, in the case of convex polygons is very simple
to program, but to attain the correct appearance, the polygon’s inner edges should be made
invisible. This subdivision with the concealment of the inner edges is done automatically by
the _PFACE command, this being the reason that this procedure is often preferred instead of
those using entmake or ActiveX. Both using the entmake function or the ActiveX methods,
the program should follow the same sequence as with the _PFACE command: irst creating
all the vertex entities and then defining their connectivity to configure the faces.
The cmd-draw-pface function invokes the _PFACE command going through the vertices
list irst, in a foreach loop which reads the vertices. An empty string is entered to indicate
the end of the vertices list and then two nested foreach loops are executed. The irst loop
traverses the list of faces and the second one introduces the index of each vertex delimiting
the current face.
(defun cmd-draw-pface
(vertices-list face-list /)
(vl-cmdf "._pface")
(foreach vert vertices-list
(vl-cmdf vert))
(vl-cmdf "")
(foreach face face-list
(foreach id face (vl-cmdf id))
(vl-cmdf ""))
(vl-cmdf "")
(entlast))
Listing 16.12 Function that creates the PolyfaceMesh by means of the PFACE command.
The function ends with a call to entlast to return the mesh’s ename that can be used for
subsequent transformations. In cases like this in which the ename is returned it can be
converted into a VLA-object with vlax-ename->vla-object.
Using the _PFACE command simpli ies programming, since the subdivision of faces with
more than four vertices is done automatically by the application, determining the edges that
should be invisible. Another issue the developer should solve when using entmake or ActiveX
is the need to duplicate one of the vertices in the case of triangular faces, as the de inition of a
face will always require the introduction of four vertices per face. To make an edge invisible a
negative value should be given to its initial vertex’s index.
To solve these problems we have designed the def-face function, Which receives as
argument the indices list of the vertices surrounding each face (the same one that cmd-
draw-pface uses directly) and returns a list of the characteristics required both by
entmake and vla-AddPolyfaceMesh. This function traverses the list of faces and
proceeds as follows for each sublist:
1. If the sublist has only three vertices (nvert = 3), it duplicates the last vertex.
2. If the sublist has four vertices (nvert = 4), it returns the sublist unchanged.
3. If the sublist has more than four vertices (nvert > 4), it creates a number of triangles equal to
the number of vertices minus two (nvert -2). These faces take the face's first vertex as a
common vertex, which is assigned to the variable vini and selects the other vertices from the
variable i that determines the vertex's index position in the list. It runs n -2 iterations where:
a. If it is the first triangle (condition i = 1), the second and third vertices are used to form
the triangle. The third vertex is duplicated and becomes negative, flagging it so the edge
between this vertex and the initial one is invisible.
b. If it is the last triangle (condition i = nvert-2), it makes the first vertex negative so
that the following edge is invisible.
c. For all the other triangles, only the index for the second vertex will be positive so that only
the edge starting from it is visible.
It should be noted that this procedure will only work properly on convex polygons. The
algorithms to be used for non-convex polygons are well known, but in this case they would
unnecessarily complicate the code.
(defun def-face (face-list / tmp res)
(foreach face face-list
(setq vini (nth 0 face)
nvert (length face)
i 1)
(cond
((= nvert 3)
(setq res
(cons
(append face
(list (nth 2 face)))
res)))
((= nvert 4)
(setq res (cons face res)))
((> nvert 4)
(repeat (- nvert 2)
(setq tmp nil)
(cond ((= i 1)
(setq tmp
(cons
(list
vini
(nth i face)
(nth (setq
i (1+ i))
face)
(- (nth i face)))
tmp)))
((= i (- nvert 2))
(setq tmp
(cons
(list (- vini)
(nth i face)
(+ (nth (setq
i
(1+ i))
face))
(nth i face))
tmp)))
(t
(setq tmp
(cons
(list
(- vini)
(nth i face)
(- (nth (setq
i
(1+ i))
face))
(- (nth i face)))
tmp))))
(setq res (append tmp res))))
(t
(prompt
"ERROR: Less than 3 vertices!")
(exit))))
(reverse res))
The header entity de ines the type of object to be created with the value associated with
group code 70: 64 in the case of a PolyfaceMesh. The other data required are those
associated with group codes 71 and 72. Group code 71 de ines the number of vertices and 72
the number of faces.
The polyface-header function (Listing 16.14) receives as arguments the list of vertices
and the list of faces assigning the number of items in each one of them (returned by the
length function) to group codes 70 and 71.
(defun polyface-header
(vertices-list face-list /)
(entmake
(list '(0 . "POLYLINE")
'(100 . "AcDbEntity")
'(100 . "AcDbPolyFaceMesh")
'(70 . 64)
(cons 71 (length vertices-list))
(cons 72 (length face-list)))))
After creating the header, the polyface-vertices function (Listing 16.15) will be invoked
to create as many VERTEX entities as the sublists contained in the vertices-list
argument. This is done in a foreach loop where the coordinates of each point are associated
to group code 10.
(defun polyface-vertices (vertices-list /)
(foreach vert vertices-list
(entmake
(list
'(0 . "VERTEX")
'(100 . "AcDbEntity")
'(100 . "AcDbVertex")
'(100 . "AcDbPolyFaceMeshVertex")
(cons 10 vert)
'(70 . 192)))))
Once the vertex entities are created the records of the vertices de ining the faces must be
created. This requires the list created by the def-face function (Listing 16.13) that is
composed by sublists each one with four indices that identify vertices in the order in which
they were created by the polyface-vertices function. The polyface-faces function
(Listing 16.16) iterates through the list passed as the face-list argument associating the
four values contained in each sublist to group codes 71, 72, 73 and 74.
(defun polyface-faces (face-list /)
(foreach face face-list
(entmake
(list '(0 . "VERTEX")
'(100 . "AcDbEntity")
'(100 . "AcDbFaceRecord")
'(10 0.0 0.0 0.0)
'(70 . 128)
(cons 71 (nth 0 face))
(cons 72 (nth 1 face))
(cons 73 (nth 2 face))
(cons 74 (nth 3 face))))))
As with the ActiveX method to create a PolygonMesh, the coordinates must be contained in
an array where the X, Y and Z values for all vertices appear in succession. The same applies to
the indices of the vertices de ining the faces, four for each one. The ax-draw-pface
function (Listing 16.18) prepares the data in the appropriate manner and calls the vla-
AddPolyfaceMesh function to create the mesh. The function returns the ActiveX object
created.
(defun ax-draw-pface (coords-list face-list
/ vertices-array
faces-array)
(setq coords-list (apply 'append coords-list)
vertices-array (vlax-make-safearray
vlax-vbDouble
(cons
0
(- (length
coords-list)
1)))
vertices-array (vlax-safearray-fill
vertices-array
coords-list)
face-list (apply 'append
(def-face face-list))
faces-array (vlax-make-safearray
vlax-vbInteger
(cons
0
(- (length face-list)
1)))
faces-array (vlax-safearray-fill
faces-array
face-list))
(vla-AddPolyfaceMesh
(current-space *aevl:drawing*)
vertices-array
faces-array))
Data Entry.
The polyhedra-data function (Listing 16.19) prompts to select from the different options
the program offers. These are:
The op-polyhedron function (Listing 16.20) loads into memory lists with the vertices and
edges data that de ine the faces for the selected type of polyhedron. In this sample program
the data are prede ined, the vertices as unit vectors of each one and the edges as lists of
vertex indices, with 1 as the initial value. In real programs, this data may result from
expressly designed calculation procedures.
(defun op-polyhedron (class /)
(cond ((= class "Tetrahedron")
(setq vertices '((0 0 1)
(0 0.9428 -0.3333)
(-0.8164 -0.4714 -0.3333)
(0.8164 -0.4714 -0.3333))
faces '((1 2 3)
(1 3 4)
(1 4 2)
(2 4 3))))
((= class "Hexahedron")
(setq vertices '((-0.5773 -0.5773 -0.5773)
(-0.5773 0.5773 -0.5773)
(0.5773 0.5773 -0.5773)
(0.5773 -0.5773 -0.5773)
(-0.5773 -0.5773 0.5773)
(-0.5773 0.5773 0.5773)
(0.5773 0.5773 0.5773)
(0.5773 -0.5773 0.5773))
faces '((1 2 3 4)
(5 6 2 1)
(6 7 3 2)
(7 8 4 3)
(8 5 1 4)
(8 7 6 5))))
((= class "Dodecahedron")
(setq vertices '((0.5773 -0.1875 0.7946)
(0.3568 0.4911 0.7946)
(-0.3568 0.4911 0.7946)
(-0.5773 -0.1875 0.7946)
(0.0 -0.6070 0.7946)
(0.9341 -0.3035 0.1875)
(0.9341 0.3035 -0.1875)
(0.5773 0.7946 0.1875)
(0.0 0.9822 -0.1875)
(-0.5773 0.7946 0.1875)
(-0.9341 0.3035 -0.1875)
(-0.9341 -0.3035 0.1875)
(-0.5773 -0.7946 -0.1875)
(0.0 -0.9822 0.1875)
(0.5773 -0.7946 -0.1875)
(0.3568 -0.4911 -0.7946)
(0.5773 0.1875 -0.7946)
(0.0 0.6070 -0.7946)
(-0.5773 0.1875 -0.7946)
(-0.3568 -0.4911 -0.7946))
faces '((1 2 3 4 5)
(1 6 7 8 2)
(2 8 9 10 3)
(3 10 11 12 4)
(4 12 13 14 5)
(5 14 15 6 1)
(6 15 16 17 7)
(8 7 17 18 9)
(10 9 18 19 11)
(12 11 19 20 13)
(14 13 20 16 15)
(16 20 19 18 17))))))
As we explained before, this sample program will allow modeling regular polyhedra in the
position and size selected by the user. But the input data correspond to polyhedra inscribed
in a sphere of radius 1, centered at the coordinate system’s origin.
It will be necessary to apply a scaling transformation so it will have the dimension speci ied
by the user. As the entity is created with its center at the WCS origin, inscribed in a sphere of
radius 1, we can use the speci ied radius as scale factor. Aligning the object with the current
UCS applying its transformation matrix obtained from the GetUCSMatrix method will also
be necessary and inally moving its center from the UCS origin to the point selected by the
user. The order in which these transformations are applied is important. First of all, the
scaling transformation using the ax-scale function (Listing.13.10) must be done. Then the
alignment to the current UCS through the vla-TransformBy method and in third place its
translation to the designated point by means of ax-translation (Listing 13.5).
AcadPolygonMesh objects.
PolygonMesh objects inherit all of the AcadObject and AcadEntity object methods, also
supporting the PolygonMesh entity’s own methods which are listed in Table 16.2. It also
exposes the properties described in Table 16.3.
A change in shape can be achieved by modifying the value of the vertices coordinates, which
can be accessed through the Coordinate or Coordinates properties. These are the
vertices originally used to de ine the mesh. These are the only editable vertices, as those
generated by smoothing depend on the algorithm AutoCAD employs. The last vertex
accessible from the Coordinate property is:
(vla-get-Coordinate
pmesh-obj
(1-
(* (vla-get-MVertexCount pmesh-obj)
(vla-get-NVertexCount pmesh-obj))))
The function in Listing 16.22 returns a list of the vertices of a PolygonMesh generated by
smoothing when the argument smoothing is t. If that argument is nil the function returns
the original vertices:
(defun pmesh-vertices-list (pmesh-ent smoothing /
vertices-list)
(while
(and (setq pmesh-ent (entnext pmesh-ent))
(/= (cdr (assoc 0
(setq dxf
(entget pmesh-ent))))
"SEQEND"))
(cond
(smoothing
(if
(/= (logand 8
(cdr (assoc 70 dxf)))
0)
(setq vertices-list
(cons
(cdr
(assoc 10
dxf))
vertices-list))))
(t
(if
(/= (logand 16
(cdr (assoc 70 dxf)))
0)
(setq vertices-list
(cons
(cdr
(assoc 10
dxf))
vertices-list))))))
(reverse vertices-list))
Listing 16.22. Function that retrieves the vertices produced by smoothing a PolygonMesh.
The total number of vertices in the returned list will be equal to:
(+ (* (vla-get-MDensity pmesh-ent)
(vla-get-NDensity pmesh-ent))
(* (vla-get-MVertexCount pmesh-ent)
(vla-get-NVertexCount pmesh-ent)))
The C:POLYMESH program (Listing 16.11) shows how to set the PolygonMesh smoothing
using the Type, MDensity and NDensity properties.
AcadPolyFaceMesh objects.
T h e PolyFaceMesh does not support other methods than those inherited from the
AcadObject and AcadEntity objects. Their specific properties are listed in Table 16.5.
T h e ax-mod-pmesh function takes as arguments the mesh object to be modi ied, the
function to be used in calculating the Z coordinate values and a numerical value used to
control the range of values obtained for Z.
To set the Z values the same procedure we saw in the pmesh-calculus function (Listing
16.10) is used, but now the list of coordinates are obtained directly from the mesh
remembering that in this case the vertices will be contained in a single level list, without
being grouped by faces. The procedure is implemented in the cal-z function shown in
Listing 16.23.
(defun cal-z
(xyz equation dim-z / z h f-esc)
(while xyz
(setq z (cons (apply equation
(list (nth 0 xyz)
(nth 1 xyz)))
z)
xyz (cdddr xyz)))
(setq h (- (apply 'max z) (apply 'min z))
f-esc (/ dim-z h))
(reverse
(mapcar '(lambda (n) (* n f-esc))
z)))
16.8 Summary.
Polygon and Polyface Meshes are the oldest among the 3D surfaces that can be created in
AutoCAD. They are de ined as a kind of POLYLINE entity. And as all variations of this type of
entity they are very inef icient in terms of the storage space required. Right now these
meshes are considered legacy objects, supported only for backward compatibility. To create
3D objects whose form is approximated by planar facets the much more versatile Subdivision
Meshes should be used. This kind of mesh will be studied in Chapter 19.
Exercises.
Three functions that incorporate surface equations have been proposed to de ine the form of
the meshes. We can enrich our options by experimenting with functions derived from other
equations. We could use try to do so using the following:
1 As an entity introduced before Release 13 group codes 100 are not necessary, however it is a sound practice to include them to
avoid distinguishing between entities prior to and following that Release.
Chapter 17
Solid Modeling
Solid Modeling refers to a series of procedures for creating computer models of three-
dimensional solids. Unlike other modeling paradigms, is characterized by the completeness of
the information provided by the model, such as distinguishing between the model’s interior
and the exterior space and the possibility of querying its physical properties. The use of solid
modeling techniques allows automating engineering calculations. The simulation, planning
and veri ication of processes such as machining and assembly was one of the main reasons
behind its development.
Release 2007 made it possible to modify the original components from which a complex
3DSolid form that was generated applying Boolean operations (union, subtraction and
intersection) is composed. This capability is available for 3DSolids created through the user
interface when the SOLIDHIST system variable is set. But for 3DSolids created by
programming, its History property must be explicitly enabled. With this in mind, the
sample programs included in this chapter will check the value assigned to SOLIDHIST so in
case it is 1 the object’s History property will be set to Record (:vlax-true).
Table 17.1. Commands for creating primitives and their equivalent ActiveX functions.
Command ActiveX Method Description
_BOX AddBox Create a solid rectangular prism.
Creates a wedge with edges parallel to the axes given the length, width, and
_WEDGE AddWedge
height.
AddCone
_CONE Create a pointed cone or a truncated cone with a circular or elliptical base.
AddEllipticalCone
AddCylinder
_CYLINDER Create a solid cylinder with a circular or elliptical base.
AddEllipticalCylinder
_SPHERE AddSphere Create a solid sphere.
_PYRAMID Create a solid pyramid with a maximum of 32 sides.
_TORUS AddTorus Create a solid circular ring (torus).
ARGUMENTS.
The irst argument of these functions is always space-obj representing the object’s owner,
which may be the ModelSpace, a PaperSpace Layout or a Block object. In the examples given
below we will use the current-space function de ined in Listing 10.31 which returns the
current space, whether it is the ModelSpace or a PaperSpace Layout.
The location in space for these objects is determined from origin, a control point located in
the center of the object’s bounding box. This control point should not be confused with the
3DSolid’ s Centroid property that returns its center of mass. The control point’s
coordinates always refer to the World Coordinate System (WCS) regardless of the User
Coordinate System (UCS) current when the solid is created. The X, Y and Z coordinate values
will be passed as a safearray of three real numbers like the one returned by vlax-3d-point.
For example, for the point (0,0,50): (vlax-3d-point 0.0 0.0 50.0).
The other arguments are real numbers that de ine the dimensions of the object, such as
length (dimX), width (dimY), height (dimZ), radius, etc. The required arguments are similar,
so their descriptions are grouped in Table 17.2.
The cone will always be created in a ixed orientation with its axis of symmetry in the positive
direction of the WCS Z axis, and as we said before, its origin corresponds to its bounding box
center. If we desire other orientation or position we would have to apply the corresponding
transformations once it is created.
To replicate the truncated cone functionality using ActiveX we would have to program a user
function in which other functions that create a 3DSolid from 2D or 3D objects by extruding
or sweeping would be used or, as an alternative, creating the Cone and slicing it afterwards.
In addition to these commands and their equivalent methods 3DSolid entities are also
created by the _CONVTOSOLID and _SURFSCULPTcommands. The _CONVTOSOLID
command converts various entity types to a 3DSolid object provided they meet the
conditions listed in Table 17.4.
The SMOOTHMESHCONVERT system variable sets the default value used in the conversion
process using commands like _CONVTOSOLID and _CONVTOSURFACE. This system
variable determines whether MESH objects converted to Surfaces or 3DSolids are softened or
faceted and if they their faces are merged. Its values are described in Table 17.5.
The _SURFSCULPT command will generate a 3DSolid from a portion of space hermetically
confined by one or more surfaces.
The arguments are described in Table 17.6. Given that this process will be necessary in all of
the functions that create solids from other objects, we will include it in a function that
receives the profiles as a list of VLA-objects and returns the Region.
A peculiarity of the AddRegion method is that it can create as many regions as closed loops
it receives. For this reason it returns a safearray with as many REGION objects as it has been
able to create. This safearray is contained in a Variant. Assuming that the reg variable
contains the result returned by vla-AddRegion when creating two regions, we can extract
the regions to a list evaluating the expression:
_$ (vlax-safearray->list (vlax-variant-value reg))
(#<VLA-OBJECT IAcadRegion2 000000002e2fe658>
#<VLA-OBJECT IAcadRegion2 000000002e2fdd58>)
The ax-region function (Listing 17.3) creates regions from the closed loops de ined by the
objects in the pro iles list and returns a list with the REGION objects created. On error, it
prints a message on the command line and returns nil. Throughout this process objects are
created whose purpose is to serve as argument for creating other objects. Entities used to
generate regions will remain in the document unless the program deletes them explicitly.
When executing the commands through the user interface the application’s behavior in this
regard is controlled by the DELOBJ system variable. When the value in DELOBJ is greater
than 0 the entities used to create solids or other 3D objects are automatically deleted. The
ax-region function checks the value of this variable deleting those objects if it is greater
than 0.
(defun ax-region
(space-obj loops / profiles region)
(setq profiles (vlax-make-safearray
vlax-vbobject
(cons 0
(1- (length loops))))
profiles (vlax-make-variant
(vlax-safearray-fill
profiles
loops))
region (vl-catch-all-apply
'vla-AddRegion
(list space-obj profiles)))
(cond
((vl-catch-all-error-p region)
(prompt
(vl-catch-all-error-message region)))
(t
(if (= (getvar "DELOBJ") 1)
(foreach loop loops
(vla-Delete loop)))
(vlax-safearray->list
(vlax-variant-value region)))))
Listing 17.3. Function that creates regions according to the profiles received.
The Boolean method is used to create complex regions. The type of Boolean operations on
Regions and 3DSolids are de ined by the AcBooleanType enum constant described in Table
17.9.
For example, to subtract region-2 from region-1 the following expression can be
evaluated:
(vla-Boolean region-1 acSubtraction region-2)
The function returns nil. If any of the objects were not the same type as the other an error
will occur. To account for this possibility error handling can be included in a function that
implements Boolean operations.
(defun ax-boolean
(obj1 operation obj2 / tmp)
(setq tmp (vl-catch-all-apply
'vla-Boolean
(list obj1 operation obj2)))
(if (vl-catch-all-error-p tmp)
(prompt
(vl-catch-all-error-message tmp))
obj1))
Listing 17.4. Function that performs a Boolean operation on two objects controlling errors.
The ax-boolean function of Listing 17.4 prints a message on the command line in the event
an error occurs and returns nil. If completed successfully it returns the modified object.
17.5 Sample Program: Complex Regions.
To demonstrate the methods discussed above for the creation and modi ication of Regions
we will de ine the C:COMP-REG program. This program creates a circular region with evenly
spaced holes at the same distance from the center. The pro iles used are circles created by the
AddCircle ActiveX method. The function that invokes this method, vla-AddCircle,
receives the arguments detailed in Table 17.10.
The program checks the current UCS, and if it is not the WCS its transformation matrix that
will be used to align the region with the XY plane is retrieved. The user is prompted for data
(Listing 17.5) such as the region’s center, its outer radius, the number of holes and the radius
for the holes. The distance from the base region’s center to the centers of the holes is
assumed to be two-thirds of the outer radius. The possibility that the hole radius speci ied by
the user produces overlapping or extends the holes beyond the base region’s outer edge is
taken into account. To avoid it the maximum admissible radius (assigned to the radmax
variable) is calculated as the lesser of two values:
In both cases a small safety factor is applied to prevent the holes being tangent to each other.
The lowest value determination is made by applying the min function.
After obtaining these data the variables origin and normal storing the WCS origin and its
normal vector are assigned. They will be used as arguments for creating the circles that will
be used as profiles for the regions.
(defun region-data (/ radmax)
(initget 1)
(setq
center (getpoint
"\nCenter of the region:"))
(initget (+ 1 2 4))
(setq extradius
(getdist center
"\nExterior Radius:"))
(initget (+ 1 2 4))
(setq numholes (getint "\nNumber of holes:")
distcenter (* 2 (/ extradius 3.0))
radmax (apply
'min
(list (/ (* pi distcenter)
(* numholes 1.1))
(/ extradius 3.1))))
(initget (+ 1 2 4))
(setq
holerad (getdist center
"\nHole radius:"))
(while (>= holerad radmax)
(prompt
(strcat
"\nHole radius must be less than "
(rtos radmax 2 2)))
(setq holerad
(getdist center
"\nHole radius:"))))
Listing 17.5. Function that prompts for the complex region's data.
The region from which the holes will be subtracted is assigned to the variable base, which
initially will contain the circle object from which the Region will be created. It should be noted
that once the circle is created its Normal property value must be set, since otherwise it
would be aligned with the current UCS. If the circle was created using entmake this step
would not be needed since the normal vector’s value can be assigned to group code 210 when
creating the entity.
The regions that will be used to subtract the holes are created in a repeat loop that depends
on the number of holes chosen by the user. The incang variable stores the angle increment
that will be used to calculate the hole centers. Hole center points are calculated using the
polar function. Note that polar returns the WCS point, so even if other UCS is current no
transformation of the point coordinates will be needed.
Once all circles are created, the ax-region function (Listing 17.3) will be used to generate
the regions that will be returned in a list. This list is traversed in a foreach loop that
repeatedly invokes the ax-boolean function with the acSubtraction mode, using the
base region as primary object and each of the regions in the holes list as second object.
After this, region is aligned with the current UCS and its center is moved to the user-de ined
location.
(defun C:COMP-REG (/ space-obj mtrans center
extradius numholes
distcenter holerad origin
normal cir-base base ang
incang cir-holes holes)
(setq space-obj (current-space
*aevl:drawing*))
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq mtrans (last (ax-ucs-matrix))))
(t (setq mtrans nil)))
(region-data)
(setq origin (vlax-3d-point '(0 0 0))
normal (vlax-3d-point '(0.0 0.0 1.0))
cir-base (vla-AddCircle
space-obj
origin
extradius))
(vla-put-Normal cir-base normal)
(setq base
(car (ax-region space-obj
(list cir-base))))
(setq ang 0
incang (/ (* 2 pi) numholes))
(repeat numholes
(setq cir-holes (cons
(vla-AddCircle
space-obj
(vlax-3d-point
(polar '(0 0 0)
ang
distcenter))
holerad)
cir-holes)
ang (+ ang incang))
(vla-put-Normal
(car cir-holes)
normal))
(setq holes
(ax-region space-obj cir-holes))
(foreach hole holes
(ax-boolean
base
acSubtraction
hole))
(if (> (getvar "DELOBJ") 0)
(progn (vla-Delete cir-base)
(foreach cir-hole cir-holes
(vla-Delete cir-hole))))
(if mtrans
(vla-TransformBy base mtrans))
(ax-translation
base
(trans center 1 0 t))
(ax-SWt)
(vla-EndUndoMark *aevl:drawing*))
The extruded 3DSolid is not generated in case the angle makes the object intersect with
itself before reaching the extrusion height, causing a run-time error. Programs can check if
the dimZ and taperAngle values produce valid solutions or at least use error handling to
exit gracefully. The following Listings offer a very simple example of this, creating cylindrical
or conical 3DSolids implementing taper angle control.
Another helper function we will use in programs that create solids is intended to set the
History property to Record for the new solid in case the value of SOLIDHIST is 1. The
sol-hist function receives as argument the 3DSolid object.
(defun sol-hist (sol)
(if (= (getvar "SOLIDHIST") 1)
(vla-put-History sol :vlax-true)))
The possibility that the current UCS is not the WCS will be taken into account. The value of the
DELOBJ system variable will also be checked so if it’s greater than 0 the base region will be
deleted.
(defun C:SOL-EXT (/ center extradius numholes holerad
distcenter holerad dim-z amax angc circ
profiles region extrusion)
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq mtrans (last (ax-ucs-matrix))))
(t (setq mtrans nil)))
(region-data)
(initget 1)
(setq dimZ (getdist center "\nExtrusion height: ")
amax (- 90.0 (rtd (atan (/ dimZ extradius)))))
(initget 1)
(setq taperangle (getreal "\nTaper angle:"))
(while (not (<= -90.0 taperangle amax))
(setq taperangle
(getreal
(strcat
"\nTaper angle must be > -90º and < than "
(rtos amax)
"º"))))
(setq region (base-reg extradius
numholes
distcenter
holerad))
(setq taperangle (dtr taperangle)
extrusion (vl-catch-all-apply
'vla-AddExtrudedSolid
(list (current-space *aevl:drawing*)
region
dimZ
taperangle)))
(cond ((vl-catch-all-error-p extrusion)
(prompt (vl-catch-all-error-message extrusion)))
(t
(sol-hist extrusion)
(if (> (getvar "DELOBJ") 0)
(vla-Delete region))
(if mtrans
(vla-TransformBy extrusion mtrans))
(ax-translation extrusion (trans center 1 0 t))
(ax-SWt)))
(vla-EndUndoMark *aevl:drawing*))
First we will de ine a function designed to systematize error control and the created solid’s
updating. The ax-ext-path auxiliary function receives as arguments the space, the pro ile
and the path and creates the extrusion within a call to vl-catch-all-apply. It also
includes a call to sol-hist to set the History property in accordance with the state of
SOLIDHIST and a call to vla-Update that updates the created object which sometimes may
not be displayed on screen.
(defun ax-ext-path
(space-obj profile path / res)
(if (vl-catch-all-error-p
(setq res
(vl-catch-all-apply
'vla-AddExtrudedSolidAlongPath
(list space-obj
profile
path))))
(princ
(strcat
"\nERROR: "
(vl-catch-all-error-message res)))
(progn (sol-hist res)
(vla-update res))))
1. The user is prompted for selecting a Line, LWPolyline, Arc or planar Spline to be used as path.
While the selected entity does not meet this requirement it will continue prompting for a
selection. This is accomplished through a while loop with a selection filtered for the entity
types, and for Splines also the condition that group code 70 bit flag 8 (Planar) is set.
2. The selection of a 2D polyline to use as a profile is prompted for. As for the path, if the correct
object is not selected, it will be prompted for again. From both the path and the profile the VLA-
objects are obtained and assigned to the variables path and prof.
3. From path, the following properties will be retrieved:
a. Its starting point that is assigned to the variable start.
b. Its tangent vector at that point, as a safearray assigned to the variable normal.
4. In case the profile LWPolyline is not closed (Closed property equal to :vlax-false)
its Closed property will be changed to :vlax-true, thus closing it. Then the profile
polyline will be aligned so that it lies in a plane perpendicular to path. This can be done by
setting its Normal property's value to that of the tangent vector at the path's starting point
assigned to the variable normal. But this will only work for those objects which have this
property and where it is not read-only. This would exclude Regions as well as all the 3DSolids
and Surfaces. A more general way of doing this is by transforming the object using a
transformation matrix like the one returned by the ax-UCSMatrix function (Listing 15.13) in
an expression like: (vla-TransformBy prof (ax-UCSMatrix start normal)).
5. The coordinates of the aligned polyline's first vertex are then retrieved. This vertex is the first
item in the polyline's Coordinate property: (vla-get-Coordinate prof 0). Once
obtained, its value it is transformed into a list. But this list will only contain the X and Y
coordinates. The Z coordinate must be retrieved from the LWPolyline's Elevation property
and appended as a third term to the coordinates list. The coordinates list is assigned to the
ptref variable. These coordinates must be transformed from the polyline's OCS (specified in
the trans function by its ename) to the WCS. The 3D displacement vector needed to bring the
LWPolyline's initial point (ptref) to the path's starting point is calculated by (mapcar '-
start ptref). Then the profile LWPolyline is translated using vla-TransformBy so
that its first vertex coincides with the path's initial point. The ax-translation function
(Listing 13.5) is used for this.
6. With the LWPolyline correctly located in space, a Region is created from it. This is done using
the ax-region function that was defined in Listing 17.3.
7. The auxiliary function ax-ext-path (Listing 17.10) is used to extrude the profile. This
function includes the necessary error handling. As ax-region returns a list of the objects
created, in this case one, its first term will be retrieved to pass it as argument to ax-ext-
path.
To present the resulting object, an isometric view is set and our custom visual style is
activated. Although for simplicity we have limited the selection of pro ile objects to 2D
polylines for the pro ile, regions or any kind of entity that may be converted to a lat region
can be used. In all these cases we must align the pro ile to a plane perpendicular to the path as
explained above and locate the pro ile in the desired position on the path. Note that these
operations are already de ined as part of the _SWEEP command that also provides the
options of aligning the pro ile and establishing its base point and for Scaling and Twisting the
pro ile along the path, so using such a powerful command should not be dismissed within our
programs.
(defun C:SOL-PATH (/ space-obj path prof
start normal ptref disp)
(setq space-obj (current-space
*aevl:drawing*))
(vla-StartUndoMark *aevl:drawing*)
(while (not path)
(prompt
(strcat
"\nSelect path "
"(Line, LWPolyline, Arc, Circle,"
" Ellipse, planar Spline): "))
(setq path
(ssget
"_:S"
'((-4 . "<OR")
(-4 . "<AND")
(0 . "SPLINE")
(-4 . "&")
(70 . 8)
(-4 . "AND>")
(-4 . "<AND")
(0
.
"LWPOLYLINE,LINE,ARC,ELLIPSE,CIRCLE")
(-4 . "AND>")
(-4 . "OR>")))))
(while (not prof)
(prompt
"\nSelect a LWPloyline as profile. ")
(setq prof
(ssget "_:S"
'((0 . "LWPOLYLINE")))))
(setq path (vlax-ename->vla-object
(ssname path 0))
prof (vlax-ename->vla-object
(ssname prof 0)))
(if (= (vla-get-closed prof)
:vlax-false)
(vla-put-closed prof :vlax-true))
(setq start (vlax-curve-GetPointAtParam
path
(vlax-curve-GetStartParam
path))
normal (vlax-curve-GetFirstDeriv
path
(vlax-curve-GetStartParam
path)))
(vla-TransformBy
prof
(ax-UCSMatrix start normal))
(setq ptref (append
(vlax-safearray->list
(vlax-variant-value
(vla-get-coordinate
prof
0)))
(list (vla-get-elevation prof)))
ptref (trans
ptref
(vlax-vla-object->ename prof)
0)
disp (mapcar '- start ptref))
(ax-translation prof disp)
(setq
prof (car (ax-region space-obj
(list prof))))
(ax-ext-path space-obj prof path)
(ax-SWt)
(vla-EndUndoMark *aevl:drawing*))
Sweeping along a 3D spline is only possible since Release 2007. The fact of VBA being
discontinued after 2007 probably explains why the corresponding ActiveX method has not
been updated to incorporate this possibility. But we can overcome these limitations using
Visual LISP, using its scripting ability combined with the possibilities of drawing database
access and the use of ActiveX properties and methods.
In the following example we will show how to create a helix using the HELIX command and
even without it (in a certain way) and program the a 3DSolid by sweeping along it.
(defun cmd-base-helix ()
(cmd-in)
(vl-cmdf "_HELIX"
(trans '(0.0 0.0 0.0) 0 1)
(trans '(1.0 0.0 0.0) 0 1)
1.0
"_Turns"
1
"_Axis"
(trans '(0.0 0.0 1.0) 0 1))
(cmd-out)
(vlax-ename->vla-object (entlast)))
We have seen in Chapter 14 that a HELIX it can be created through entmake. But we have
found the _SWEEP command to be rather choosy about Helices. The one we create with our
C:ENT-HELIX program (Listing 14.8) is not of its liking. However it is accepted in case we
extract the encapsulated Spline with our helix->spline (Listing 14.9) function. We can
create a Helix by using the _HELIX command using the command/vl-cmdf interface
(Listing 17.12) but. But we have other alternative, the quick-and-dirty entmake way.
As we already know, if we retrieve a HELIX entity list through entget and apply entmake to
this list we get an exact copy of the original one To use this method it would be enough to
draw one and evaluate the expression (entget (entlast)) in the Visual LISP console.
We can then copy the text printed on the console to an editor window, and format the list to
arrange it in a column. The lists associated to group codes -1, 330, 5, 67, 410, and 8 are
selected and deleted. The remaining list is passed as argument to entmake. It is not
necessary to transform the coordinate values associated with group code 10, since all
coordinates returned by entget refer to the WCS. Nor will it be necessary to disable
command prompts or running object snaps. In this "quick-and-dirty" way we de ined the
ent-base-helix function (Listing 17.13). In order to have a relatively short list we can
create 0.1 Turns helix with 0.1 Turn height. This way a basic helix is created whose
parameters can then be adjusted using its ActiveX properties. Although these are properties
that can be adjusted afterwards, it is desirable that the Helix center be located at the WCS
origin and its starting point is on the positive X axis.
To obtain a Helix of the desired characteristics we can adjust some of its properties once it
has been created. Table 17.14 describes the Helix’s editable properties.
Listing 17.14. Function that creates the Helix adjusting its properties.
The sample program C:SPRING (Listing 17.17) creates a 3DSolid representing a spring.
This is done using a Helix as path and a circular region used as pro ile that is swept along the
Helix.
Data Entry.
The user is prompted for the spring’s data by the spring-data function. The data required
are the spring’s base center point, its outer diameter, its length, the wire diameter and the
number of turns.
The function includes controls to prevent data inconsistencies. The wire diameter is checked
so it does not exceed one ifth of the outer diameter. For the number of turns it is checked
that it is not greater than the product of dividing the spring length by twice the wire diameter.
Once the data are entered, the variables radiuswire equal to half the wire diameter and
radiusbase equal to half the outside diameter minus radiuswire are assigned.
The variables center, height, turns, radiuswire and radiusbase remain in memory
for their use within the main program.
(defun spring-data (/ diamext diamwire)
(initget 1)
(setq center (getpoint "\nBase center"))
(initget (+ 1 2 4))
(setq diamext
(getdist center
"\nExterior diameter: "))
(initget (+ 1 2 4))
(setq
height (getdist center
"\nSpring length: "))
(initget (+ 1 2 4))
(while (> (setq diamwire
(getdist
center
"\nWire diameter: "))
(/ diamext 5.0))
(prompt
(strcat
"\nThe wire diameter must be less than "
(rtos (/ diamext 5.0) 2 2)))
(initget (+ 1 2 4)))
(initget (+ 1 2 4))
(while
(> 1
(setq
turns (getint
"\nNumber of turns: "))
(fix (/ height (* diamwire 2))))
(prompt
(strcat
"\nNumber of turns must be less than "
(itoa
(fix (/ height (* diamwire 2))))))
(initget (+ 1 2 4)))
(setq radiuswire (/ diamwire 2.0)
radiusbase (- (/ diamext 2.0)
radiuswire)))
After creating the helix the vlax-curve... functions are used to obtain its starting point
coordinates and the curve’s direction vector (as its irst derivative) at that point that will be
used to align the pro ile with the path. A circle is then created using the AddCircle method
using as center the Helix initial point and modi ied afterwards its Normal property assigning
it the Helix’s initial direction vector.
Once properly aligned, a region is created from the circle using the ax-region function
(Listing 17.3).
Listing 17.16. Function that creates a 3DSolid using the SWEEP command.
As we did in previous sections, we will implement a function that includes error handling and
updates the new solid. The auxiliary function ax-sol-rev receives as arguments the space,
a region as pro ile, a point that marks the position of the revolution axis, a vector indicating
its direction and an angle that determines the rotation’s extent.
(defun ax-sol-rev (space-obj profile pt
vector ang / res)
(if (vl-catch-all-error-p
(setq res
(vl-catch-all-apply
'vla-AddRevolvedSolid
(list
space-obj
profile
(vlax-3d-point pt)
(vlax-3d-point vector)
ang))))
(prompt
(strcat
"\nERROR: "
(vl-catch-all-error-message res)))
(progn (sol-hist res)
(vla-Update res))))
If it is a region, it will only be necessary to retrieve the VLA-object using the ename
recovered from the selection set. In the other cases, we must check whether the object is
closed, closing it if not. For this we use the object’s Closed property, but since neither Circles
nor Ellipses have this property, as they will always be closed, we will have to check if the
property is available through the vlax-property-available-p predicate. But if it is a
spline, the Closed property is read-only, and we have to resort to the Closed2 property. To
create the region it will be necessary to prepare the data in the manner required by vla-
AddRegion, creating an array of objects within a variant.
Lines, Polylines, Construction lines (XLINE), Splines or Arcs can be selected for the axis. Note
that for the first four entities the wildcard *LINE is used as the entity type filter. The value for
the angle is checked so it is not greater than 360º. The direction vector is calculated in Line,
Polyline and Spline entities from their start and end points. This entity should be open, which
is achieved by setting a ilter to reject those with bit 1 set in group code 70. In the case of
using an XLINE object as axis the start and end point cannot be extracted in the same way. In
this case we use the point in its BasePoint property as the location of the axis and the
vector contained in the DirectionVector property to define the axis orientation.
(if (= (vla-get-ObjectName axis) "AcDbXline")
(setq center (vlax-safearray->list
(vlax-variant-value
(vla-get-BasePoint axis)))
vec (vlax-safearray->list
(vlax-variant-value
(vla-get-DirectionVector axis))))
(setq center (vlax-curve-GetStartPoint axis)
vec (mapcar '-
(vlax-curve-GetEndPoint axis)
center)))
These data are of the variant type and contain a safearray, so to extract them in the list format
required by ax-sol-rev we have to apply successively vlax-variant-value and vlax-
safearray->list as shown in the code snippet above.
(defun C:SOL-REV (/ space-obj prof axis ang
center vec)
(setq space-obj (current-space
*aevl:drawing*))
(vla-StartUndoMark *aevl:drawing*)
(prompt "\nSelect the profile:")
(while
(not
(setq
prof
(ssget
"_:S"
'((0 . "REGION,LWPOLYLINE,SPLINE,CIRCLE,ELLIPSE")))))
(prompt
"\nSelect Region, 2D Polyline, Circle or Ellipse:"))
(prompt
"\nSelect the revolution axis: ")
(while (not (setq axis
(ssget "_:S"
'((-4 . "<AND")
(0 . "*LINE,ARC")
(-4 . "<NOT")
(-4 . "&")
(70 . 1)
(-4 . "NOT>")
(-4 . "AND>")))))
(prompt
"\nSelect an open lineal entity:"))
(initget (+ 2 4))
(if (not (setq
ang (getreal
"\nSweep angle <360>: ")))
(setq ang 360.0)
(while (> ang 360.0)
(initget (+ 2 4))
(setq ang
(getreal
"\nMust be less than 360 <360>:"))))
(setq prof (vlax-ename->vla-object
(ssname prof 0)))
(if (/= (vla-get-ObjectName prof)
"AcDbRegion")
(cond
((and (vlax-property-available-p
prof
"Closed")
(= (vla-get-Closed prof)
:vlax-false))
(if (vl-catch-all-error-p
(vl-catch-all-apply
'vla-put-Closed
(list prof :vlax-true)))
(vla-put-Closed2 prof :vlax-true)))))
(setq profiles (vlax-make-safearray
vlax-vbObject
'(0 . 0))
profiles (vlax-make-variant
(vlax-safearray-fill
profiles
(list prof)))
prof (vl-catch-all-apply
'vla-AddRegion
(list space-obj profiles)))
(cond
((vl-catch-all-error-p prof)
(prompt
(strcat
"ERROR:\t"
(vl-catch-all-error-message prof))))
(t
(setq prof
(vlax-safearray-get-element
(vlax-variant-value prof)
0))
(setq ang (dtr ang)
axis (vlax-ename->vla-object
(ssname axis 0)))
(if (= (vla-get-ObjectName axis)
"AcDbXline")
(setq center (vlax-safearray->list
(vlax-variant-value
(vla-get-BasePoint
axis)))
vec (vlax-safearray->list
(vlax-variant-value
(vla-get-DirectionVector
axis))))
(setq center (vlax-curve-GetStartPoint
axis)
vec (mapcar
'-
(vlax-curve-GetEndPoint
axis)
center)))
(ax-sol-rev
space-obj prof center vec ang)
(ax-SWt)))
(vla-EndUndoMark *aevl:drawing*))
(vla-EndUndoMark *aevl:drawing*))
The physical properties available for 3DSolids are listed in Table 17.11. In the case of Regions,
their properties were described in Table 17.7. We can create a utility function capable of
extracting physical and/or geometric properties from different types of objects, especially 3D
objects. The additional properties that we will consider are described in Table 17.16. The ax-
props function shown in Listing 17.20 includes a list of property names that will be obtained
from the object that it receives as argument. Since not all objects support all of those
properties, they will be queried controlling possible errors through vl-catch-all-apply.
If no error occurs, the property value will be added to an association list using the property
name as key. To extract the property value its data type must be checked. If it were of the
variant type, we must check if it is an array, which is done by using the vlax-variant-
type function. If the type value is greater than or equal to 8192 the array will be extracted to
a list using vlax-safearray->list. Otherwise the value will be extracted as a LISP data
type.
Other interesting data is the objects bounding box, i.e., the extension of the object along the X,
Y and Z axes. This data can be obtained from the GetBoundingBox method available for
most of the graphic objects. Once the values for the object’s available properties have been
obtained ax-props tries to apply the vla-GetBoundingBox function. This method
returns the values of two points that delimit the bounding box assigned to two variables it
receives as argument that in this case will be called pmin and pmax. In case the object does
not expose this method an error will be thrown and the list of properties will be returned. If
no error occurs a list with the string "BoundingBox" and coordinate lists from pmax and
pmin will be appended as the first term in the list that is returned.
(defun ax-props (obj / props value pmin pmax bbox)
(setq props
(vl-remove-if
'null
(mapcar
'(lambda (prop)
(if (vlax-property-available-p
obj
prop)
(progn
(setq value
(vl-catch-all-apply
'vlax-get-property
(list obj prop)))
(if
(not (vl-catch-all-error-p
value))
(if
(= (type value) 'variant)
(cond
((>=
(vlax-variant-type
value)
8192)
=
(cons
prop
(vlax-safearray->list
(vlax-variant-value
value))))
((t
(cons
prop
(vlax-variant-value
value)))))
(cons prop value))))))
'("Centroid" "MomentOfInertia"
"PrincipalDirections" "PrincipalMoments"
"ProductOfInertia" "RadiiOfGyration" "Volume"
"Area" "Circumference" "Radius" "Center"
"Normal" "Perimeter" "Coordinates"
"FaceCount" "VertexCount" "Smoothness"
"Elevation" "ArcLength" "EndAngle" "EndPoint"
"StartAngle" "StartPoint" "TotalAngle"
"Angle" "Delta" "Thickness" "BasePoint"
"DirectionVector" "SecondPoint" "BaseRadius"
"Height" "Position" "TopRadius" "TotalLength"
"TurnHeight" "Turns" "TurnSlope" "Twist"
"Direction" "TaperAngle" "EndDraftAngle"
"EndDraftMagnitude" "NumCrossSections"
"NumGuidePaths" "StartDraftAngle"
"StartDraftMagnitude" "SurfaceNormals"
"SurfaceType" "RevolutionAngle"
"AxisPosition" "AxisDirection"
"ProfileRotation" "Bank" "Length"
"ProfileRotation" "scale"))))
(setq bbox (vl-catch-all-apply
'vla-GetBoundingBox
(list obj 'pmin 'pmax)))
(if (vl-catch-all-error-p bbox)
props
(setq
props (cons (list "BoundingBox"
(vlax-safearray->list pmin)
(vlax-safearray->list pmax))
props))))
Listing 17.20. Function that extracts physical and geometric properties of objects.
17.13 Summary.
In this chapter we have demonstrated the use of ActiveX methods that create 3DSolids as
primitives and from 2D objects using extrusion, sweep or revolution operations. We have also
considered the available commands that can be used through the command/vl-cmdf
interface. In the case of 3DSolids the encrypted ShapeManager information makes its creation
through entmake impossible.
Chapter 18 Editing 3DSolids
3DSolid objects expose four methods, Boolean, CheckInterference, SectionSolid
a nd SliceSolid (Table 17.12). Using the Boolean method they can be combined and
edited to form new complex 3DSolids. The same Boolean operations we studied with
reference to Regions can be applied to 3DSolids. Two 3DSolids can be joined, subtracted from
each other or used to ind the volume common to both. The other methods allow creating
sections or slicing them.
Since Release 2007 complex 3DSolids can retain a record of the objects from which they were
created, allowing their subsequent modi ication. This possibility is activated for user
commands if the SOLIDHIST system variable is set to 1. But for complex 3DSolids created by
programming their History property must be expressly set to Record in the program.
When we cut a solid using the _SLICE command we can choose the part of the solid we want
to keep. We do not have this possibility with SliceSolid. For this reason special care must
be taken regarding the order of the points de ining the plane as this order de ines the plane's
normal direction. The normal direction is determined by the points sequence in accordance
with the right hand rule. In a polyhedron, if the points de ining one of its faces are speci ied in
counterclockwise order the normal to that face's plane point outwards. If speci ied clockwise,
the normal points inward. The portion vla-SliceSolid preserves is the one opposite to
the slicing plane's normal.
To demonstrate the use of this method we propose a version of the PolyfaceMesh sample
program (Chapter 16) that creates a polyhedral 3DSolid. The ax-slice function
implements the SliceSolid method incorporating error handling. If the negative argument
is :vlax-true the new 3DSolid object would be returned.
Data entry is a simpli ied version of the function polyhedra-data function in Listing 16.19
since in this case is not necessary to ask the user to de ine the procedure used in create the
object.
(defun sol-p-data (/)
(initget 1
"Tetrahedron Hexahedron Dodecahedron")
(setq
class
(getkword
"\nPolyhedron [Tetrahedron/Hexahedron/Dodecahedron]:")
center (getpoint "\nPolyhedron center: ")
radius (getdist
center
"\Circumscribed sphere radius: ")))
The C:SOL-POLYHEDRON function bears a strong resemblance to the one used in creating
polyhedra as PolyfaceMesh objects. In fact we use the same function that loads the vertices
direction vectors data that we used for the PolyfaceMesh.
The ax-slice function receives the irst three vertices of each face in a foreach cycle that
traverses the faces list. The structure of this list can be seen in Listing 16.20.
(foreach face faces
(ax-slice sphere
(nth (1- (car face)) vertices)
(nth (1- (cadr face)) vertices)
(nth (1- (caddr face)) vertices)
:vlax-false))
To retrieve each vertex's index we must subtract 1 from the value that appears in each face
sub-list, as the index for faces is one-based, while the nth function requires a zero-based
index.
(defun C:SOL-POLYHEDRON
(/ class center radius sphere)
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq mtrans (last (ax-ucs-matrix))))
(t (setq mtrans nil)))
(sol-p-data)
(op-polyhedron class)
(setq sphere
(vl-catch-all-apply
'vla-AddSphere
(list
(current-space *aevl:drawing*)
(vlax-3d-point '(0 0 0))
1.0)))
(cond
((vl-catch-all-error-p sphere)
(prompt (vl-catch-all-error-message sphere)))
(t
(sol-hist sphere)
(foreach face faces
(ax-slice sphere
(nth (1- (car face)) vertices)
(nth (1- (cadr face)) vertices)
(nth (1- (caddr face)) vertices)
:vlax-false))
;; Transformations:
(ax-scale sphere
(list radius radius radius))
(if mtrans
(vla-TransformBy sphere mtrans))
(ax-translation sphere (trans center 1 0 t))
(vla-Update sphere)
(ax-SWt)))
(vla-EndUndoMark *aevl:drawing*))
SectionSolid creates a new REGION object de ined by the intersection of a 3DSolid with
any arbitrary plane in space. Its syntax is very similar except that now there is no need to pass
an argument that defines a side to keep, the three points being enough.
(vla-SectionSolid object point1 point2 point3)
As we did for SectionSolid, we will de ine a function that implements the SliceSolid
method with error handling that prints possible error messages in the command line.
(defun ax-section (obj pt1 pt2 pt3 /)
(setq res (vl-catch-all-apply
'vla-SectionSolid
(list obj
(vlax-3d-point pt1)
(vlax-3d-point pt2)
(vlax-3d-point pt3))))
(if (vl-catch-all-error-p res)
(prompt (vl-catch-all-error-message res))
res))
Another difference is that by generating as many regions as faces the reference polyhedron
has, we must make the transformations for scaling, UCS alignment and translation to the
point speci ied by the user for each of the regions created. For this reason, when the regions
are created they are stored in a list so that afterwards the transformations can be applied
within a foreach loop.
acIntersection 1 Modifies the first 3DSolid leaving only the volume shared by both.
acSubtraction 2 Subtracts the second 3DSolid's volume from the first one.
UNION (acUnion) operations add to a 3DSolid the volume occupied by a second 3DSolid. The
syntax for the Boolean acUnion operation is:
(vla-Boolean base-obj acUnion join-obj)
The connector is constructed from a base cube centered at the WCS origin with an edge length
equal to seven times the speci ied gauge. From this base cube the program will subtract
eight cubes centered on the base cube's corners with side lengths equal to six times the
gauge and twelve cylinders aligned with the XY, YZ and ZX planes. The auxiliary functions
ax-cube and ax-cylinder have been defined for the creation of the 3DSolids.
These functions use the AddBox and AddCylinder methods to create the 3DSolids and
implement error handling with vl-catch-all-apply. They also check the value of the
SOLIDHIST system variable for setting their History property to Record. The cylinders
will be aligned into position using the rot-90-x and rot-90-y auxiliary functions.
(defun ax-cube (center side / res)
(setq res
(vl-catch-all-apply
'vla-AddBox
(list (current-space *aevl:drawing*)
(vlax-3d-point center)
side
side
side)))
(cond
((vl-catch-all-error-p res)
(prompt
(strcat "\nERROR: "
(vl-catch-all-error-message res))))
(t
(if (= (getvar "SOLIDHIST") 1)
(vla-put-History res :vlax-true))
res)))
The functions in Listings 18.9 and 18.10 rotate the VLA-object they receive as argument
90º around the X or Y axis. They are similar to those in Listings 13.7 and 13.8, only that in this
case the angle is predetermined.
(defun rot-90-x (obj / ang)
(setq ang (/ pi 2))
(vla-TransformBy
obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 0.0)
(list 0.0 (cos ang) (sin ang) 0.0)
(list 0.0 (- (sin ang)) (cos ang) 0.0)
(list 0.0 0.0 0.0 1.0)))))
Listing 18.9. Function that rotates an object 90º about the X axis.
Listing 18.10. Function that rotates an object 90º about the Y axis.
The C:CONNECTOR program initially calls the connector-data function that prompts the
user for the basic data. This done, it assigns a value of (* gauge 7) to the side variable
and a vector of three elements equal to (/ side 2.0) to the disp variable and to the
positions variable a list of the coordinates of the eight vertices of a cube centered at the
WCS origin (0,0,0) and with sides equal to 2. Each value of this list, multiplied by disp will
return a list of the coordinates for each corner of the base cube that will be assigned to the
variable centers and which will be used as centroids for the eight cubes that will be
subtracted to obtain the connector's form. The basic (* gauge 7) cube from which the
other cubes and cylinders will be subtracted is created and assigned to the variable base.
After creating this base cube the value assigned to the variable side will be changed to
(* gauge 6), value that will be used to create the cubes to be subtracted, each of them
assigned to the variable dif in a loop that traverses the centers list using mapcar.
(mapcar '(lambda (ctr)
(setq dif (ax-cube ctr side))
(vla-Boolean base acSubtraction dif))
centers)
This loop creates one by one the eight cubes that are immediately subtracted from the base
cube. After subtracting the cubes the program goes on creating and subtracting the cylinders.
For this purpose, the positions list is again used but this time the X and Y of the disp vector
are (* gauge 2) and Z equals 0. This will result in four repeated positions within the eight
positions returned. The duplicate positions are removed applying the delete-duplicates
function (Listing 18.11) to the positions list.
A while loop that will end when lst returns nil is initiated. In this loop:
1. The first term in lst is added to a new list, assigned to the local variable tmp.
2. The function vl-remove-if is called using as its predicate expression '(lambda (a)
(equal a (car tmp) 0.0001)), where 0.0001 would be the tolerance admitted to
consider two coordinate values the same. This will remove from lst the term originally
stored in tmp and any other duplicate that may exist.
3. If after the previous step lst is not yet empty, the loop is run anew including the first term
in tmp.
At the conclusion of the while loop, tmp (the list without duplicates) is returned.
It must be noted that this function will also remove duplicate LISP objects of a non-numeric
type. In such cases the tolerance value is ignored.
(defun delete-duplicates (lst / tmp)
(while lst
(setq tmp (cons (car lst) tmp)
lst (vl-remove-if
'(lambda (a) (equal a (car tmp) 0.0001))
lst)))
(reverse tmp))
This way we obtain a list of four positions that will be used in three loops implemented by
mapping on the new centers list in order to generate and subtract cylinders in the XY, YZ
and ZX planes. The function ends, as in previous cases, making the necessary transformations
that will align the part with the current UCS and move it to the position specified by the user.
Figure 18.2. Part generated by the C:CONNECTOR program, on the right showing its recorded history.
These prompts and initial calculations are implemented in the coupling-data function.
The variable r is calculated as (/ dim-x 2.0). All the dimensions for the component solids
are calculated from this value.
(defun coupling-data (/)
(initget 1)
(setq
center (getpoint
"\nCoupling's center:"))
(initget (+ 1 2 4))
(setq dim-x (getdist
center
"\nCoupling's length:")
origin '(0.0 0.0 0.0)
r (/ dim-x 2.0))
(cond
((= (getvar "SOLIDHIST") 0)
(initget 1 "Yes No")
(if
(equal
(getkword
"\nRecord Solid history? [Yes/No]:")
"Yes")
(setvar "SOLIDHIST" 1)))))
To position the base object's copy in its new orientation it is rotated 90º around the X axis
using the rot-90-x function (Listing 18.9), and 180º around the Z axis using the new
function rot-180-z (Listing 18.15).
(defun rot-180-z (obj / ang)
(vla-TransformBy
obj
(vlax-tmatrix
(list
(list (cos pi) (- (sin pi)) 0.0 0.0)
(list (sin pi) (cos pi) 0.0 0.0)
(list 0.0 0.0 1.0 0.0)
(list 0.0 0.0 0.0 1.0)))))
Listing 18.15. Function that rotates an object 180 degrees around the Z axis.
The irst object created is a cylinder that is assigned to the variable base. After that another
cylinder, assigned to the variable hole, is created as are two prisms, a bigger one that is
assigned to the box1 variable and a smaller one that is assigned to box2.
Once these component solids are created, the base cylinder is joined to the box1 prism and
from the resulting solid the hole cylinder and the box2 prism are subtracted. These
elements are created in such a way so the resulting solid is centered at the WCS origin.
A copy of this solid is assigned to the base-copy variable. This copy is then rotated 90
degrees around the X axis and 180 degrees around the Z axis. Once in position the
INTERSECTION operation is executed.
(defun C:COUPLING (/ origin center dim-x r base
hole box1 box2)
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq mtrans (last (ax-ucs-matrix))))
(t (setq mtrans nil)))
(coupling-data)
(setq base (ax-cylinder
(list r 0.0 0.0)
r
(* r 2))
hole (ax-cylinder
(list r 0.0 0.0)
(/ r 2.0)
(* r 2))
box1 (ax-box (list (- (* 0.5 r)) 0.0 0.0)
(* 3 r)
(* 2 r)
(* 2 r))
box2 (ax-box (list (- r) 0.0 0.0)
(* 2 r)
r
(* r 2)))
(vla-Boolean base acUnion box1)
(vla-Boolean base acSubtraction box2)
(vla-Boolean base acSubtraction hole)
(setq base-copy (vla-Copy base))
(rot-90-x base-copy)
(rot-180-z base-copy)
(vla-Boolean base acIntersection base-copy)
(if mtrans
(vla-TransformBy base mtrans))
(ax-translation base (trans center 1 0 t))
(ax-SWt)
(vla-EndUndoMark *aevl:drawing*))
The Visual LISP syntax for the CheckInterference method in Release 2013 is:
(vla-CheckInterference solid1 solid2
CreateInterferenceSolid 'SolidsInterfere)
The 2013 version will return :vlax-true as the value of the SolidsInterfere variable if
an interference does exist even when CreateInterferenceSolid is speci ied as :vlax-
false.
_$ (vla-CheckInterference solid1 solid2 :vlax-false 'SolidsInterfere)
nil
_$ SolidsInterfere
:vlax-true
_$
In both cases the trick will be to use the interference Solid, as this way none of the Solids on
which the command operates will be lost, they will be modi ied applying Boolean operations
using the interference Solid.
That an error captured by vl-catch-all-apply has been thrown, printing the error
message to the command line.
That the objects do not overlap, also informing about it in the command line.
And if the interference object, assigned to the variable tmp, has been created it will be
subtracted from the to-trim object.
I f SOLIDHIST is set to 1 the trimmed object's History property will be set to Record
before the SUBTRACTION operation.
(defun trimmer (trimming to-trim / tmp)
(setq tmp
(if
(>= (atof (getvar "acadver")) 19.0)
(vl-catch-all-apply
'vla-CheckInterference
(list trimming
to-trim
:vlax-true
'SolidsInterfere))
(vl-catch-all-apply
'vla-CheckInterference
(list trimming to-trim :vlax-true))))
(cond
((vl-catch-all-error-p tmp)
(prompt (vl-catch-all-error-message tmp)))
((null tmp)
(prompt
"\nSelected solids do not interfere."))
(tmp
(if (= (getvar "SOLIDHIST") 1)
(vla-put-History to-trim :vlax-true))
(vl-catch-all-apply
'vla-Boolean
(list to-trim acSubtraction tmp))))
(princ))
The user will be irst prompted for the selection of the trimming 3DSolid. This will be done
with a call to ssget in the single selection mode, iltered for 3DSolid entities. Once the
trimming object has been selected, the user will be prompted for the 3DSolid selection set
to be trimmed. In this case a iltered ssget with no limitation of the number of selected
objects will be used. A repeat loop will then begin in which the selection set will be traversed
using each of the selection set members as the to-trim argument for the trimmer function.
The command concludes by setting a custom visual style with a SW Isometric perspective
view.
(defun C:SOL-TRIM
(/ trimming trimmed to-trim *error*)
(vl-load-com)
(defun *error* (msg)
(vla-EndUndoMark *aevl:drawing*)
(vl-cmdf "_U")
(prompt msg))
(vla-StartUndoMark *aevl:drawing*)
(prompt "\Select trimming 3DSolid ")
(setq
trimming (vlax-ename->vla-object
(ssname
(ssget "_:S"
'((0 . "3DSOLID")))
0)))
(prompt "\Select 3DSolids to be trimmed ")
(setq trimmed (ssget '((0 . "3DSOLID")))
i 0)
(repeat (sslength trimmed)
(setq to-trim (vlax-ename->vla-object
(ssname trimmed i)))
(trimmer trimming to-trim)
(setq i (1+ i)))
(ax-SWt)
(vla-EndUndoMark *aevl:drawing*))
The splitter function (Listing 18.19) receives as arguments two 3DSolids, obj1 and obj2.
Once an interference object has been created from them and assigned to the interf variable
it will be copied twice before it is subtracted from each of the overlapping obj1 and obj2
3DSolids.
This is the only difference between the splitter and the trimmer functions. In both
functions possible error conditions are managed making the calls to vla-
CheckInterference and vla-Boolean using vl-catch-all-apply. The splitter
function returns the modified obj1 solid.
(defun splitter (obj1 obj2 / interf tmp res)
(setq interf (if (>= (atof (getvar "acadver")) 19.0)
(vl-catch-all-apply
'vla-CheckInterference
(list obj1
obj2
:vlax-true
'SolidsInterfere))
(vl-catch-all-apply
'vla-CheckInterference
(list obj1 obj2 :vlax-true))))
(cond ((vl-catch-all-error-p interf)
(prompt (vl-catch-all-error-message interf)))
(interf
(if (= (getvar "SOLIDHIST") 1)
(vla-put-History interf :vlax-true))
(setq tmp (vla-Copy interf))
(setq res (vl-catch-all-apply
'vla-Boolean
(list obj1 acSubtraction tmp)))
(if (not (vl-catch-all-error-p res))
(s-separate obj1))
(setq tmp (vla-Copy interf))
(setq res (vl-catch-all-apply
'vla-Boolean
(list obj2 acSubtraction tmp)))
(if (not (vl-catch-all-error-p res))
(s-separate obj2))))
obj1)
Listing 18.19. Function that makes new shapes out of the overlapping volumes.
As in the C:SOL-TRIM function, a local *error* handler is de ined that will UNDO changes
in case an unhandled error occurs.
The processing will take place in two nested repeat loops that will traverse the length of the
selection set proceeding as follows:
1. The solids are selected and the counters i and j are initialized as zero:
(setq to-split (ssget '((0 . "3DSOLID")))
i 0
j 0)
2. In the outer repeat loop the first selected solid's VLA-object is assigned to the variable
base.
3. The inner repeat loop traverses the to-split selection set assigning each solid's VLA-
object to the variable obj.
4. If obj is not equal to base, both are passed as arguments to splitter:
(if (not (equal base obj))
(splitter base obj))
5. The counter j is incremented by 1 so the next object in the to-split selection set is
processed. As the base solid may have been modified by splitter, the value assigned to
base is updated with the object returned by splitter.
6. Once the inner loop is concluded, the i counter is incremented by 1 and the whole process
is repeated for the next base solid.
In cases where extremely complex solid overlapping occurs this procedure may leave uncut
overlapping solids, as the newly generated interference solids are not added to the selection
set. This could be done, but it would introduce an unwarranted complexity which can be offset
by simply running the command twice to check if any new solids are created.
As with C:SOL-TRIM the command concludes by setting a custom visual style with a SW
Isometric perspective view, but this time including a 50% transparency so the splitting is
more evident.
(defun C:SOL-SPLIT
(/ to-split base obj *error*)
(vl-load-com)
(defun *error* (msg)
(vla-EndUndoMark *aevl:drawing*)
(vl-cmdf "_U")
(prompt msg))
(vla-StartUndoMark *aevl:drawing*)
(prompt "\Select 3DSolids to split ")
(setq to-split (ssget '((0 . "3DSOLID")))
i 0
j 0)
(repeat (sslength to-split)
(setq base (vlax-ename->vla-object
(ssname to-split i)))
(repeat (sslength to-split)
(setq obj (vlax-ename->vla-object
(ssname to-split j)))
(if (not (equal base obj))
(setq base (splitter base obj)))
(setq j (1+ j)))
(setq i (1+ i)
j 0))
(ax-SWt)
(setvar "VSFACEOPACITY" 50)
(vla-EndUndoMark *aevl:drawing*))
If more than two points are supplied, ent-section can create a jogged section provided
that these points define orthogonal directions.
Table 18.3. SECTION object DXF group codes and equivalent properties.
Group code: Description: Equivalent ActiveX Property:
0 Entity type: "SECTIONOBJECT"
100 Subclass marker ("AcDbEntity")
100 Subclass marker ("AcDbSection") ObjectName
90 Section state: AcSectionState enum. State See Table 18.5.
91 Section flags: AcSectionType enum. See Table 18.6.
1 Section name: String. Name
10 Section plane's vertical direction: 3D Vector. VerticalDirection
40 Distance to the section plane's top extents. TopHeight
41 Distance to the section plane's bottom extents. BottomHeight
70 Section plane transparency in 3D visual styles. IndicatorTransparency
62 ACI color number for section plane.
IndicatorFillColor: Property value is always
Section plane's TrueColor color number. Code 420
420 an AcCmColor object
only present if object has been assigned a TrueColor.
92 Number of vertices.
11 Vertex. One entry for each vertex.
Number of backline vertices (Boundary or Volume
93
sections).
12 Backline vertex. One entry for each vertex.
360 Hard-pointer ID to geometry settings object. Settings
where space is the Block object that exposes the method, from-point and to-point
define the SECTION's position and planevector its orientation.
As we can see, this method only admits two points, so creating a jogged SECTION as we did
with ent-section is not possible. If one is needed we must create a straight SECTION an
then add a jog to it with the vla-CreateJog method (see Table 18.6) or we can also add
new vertices to the SECTION object with the vla-AddVertex method. This second option is
the one we will use. Its syntax is:
(vla-AddVertex section-obj index point)
where section-obj is the section to which vertices will be added, index is the zero-based
index specifying the vertex's position in the SECTION and point is a 3D point in the format
returned by vlax-3d-point. The ax-section function in Listing 18.23 receives the same
arguments as ent-section in order to make both interchangeable. The additional vertices
are added through the AddVertex method (see Table 18.6) in a while loop in which the
nth function is used for retrieving based on the i index, the vertices in the list. The function
returns the SECTION object created.
(defun ax-section
(pt-lst planevector / sect-obj i pt)
(setq sect-obj
(vla-AddSection
(current-space *aevl:drawing*)
(vlax-3d-point (nth 0 pt-lst))
(vlax-3d-point (nth 1 pt-lst))
(vlax-3d-point planevector)))
(setq i 2)
(while (setq pt (nth i pt-lst))
(vla-AddVertex
sect-obj
i
(vlax-3d-point pt))
(setq i (1+ i)))
sect-obj)
Listing 18.23. Function that creates a SECTION using ActiveX methods and properties.
Once the SECTION is complete its Name, TopHeight and BottomHeight and other
properties (see Table 18.6) can be set.
sect-obj: the SECTION object for which the geometry will be generated,
model: the 3DSolid model object.
As a demonstration of the way of setting section geometry properties we propose the sect-
props function (Listing 18.26) in which a series of properties are set for the different section
view components. These properties include the Layers in which those components will be
created, their Lineweights and their colors. The Color property requires an AcCmColor
object that will be retrieved from a component and used to set all the other colors. Layers
must be created previously in order that Layer properties can be set. This is done calling the
add-layers auxiliary function (Listing 18.25). This function will receive the section's name
and a Layer names list. The purpose of this is to create unique Layer names linked to the
different section views that can be used to freeze unwanted Layers in the viewports that
display the section views. The same Layer naming convention is used in the sect-props
function.
(defun add-layers
(name spacer lyr-lst / lyr coll res)
(setq coll (vla-get-Layers *aevl:drawing*))
(foreach lyr lyr-lst
(setq lyr (strcat name spacer lyr))
(setq res (vl-catch-all-apply
'vla-Item
(list coll lyr)))
(if (vl-catch-all-error-p res)
(vla-Add coll lyr))))
The section geometry settings are contained in the AcadSectionSettings object that can
be retrieved from the Section's Settings property. These settings must be set for the
speci ic Section State and Section Type we want to generate. In our sample program we wish
to create a Plane 2D section view, so we will set the Section's State property as the
acSectionState enum constant acSectionStatePlane (see Table 18.7). Once this is
done, we will retrieve the Section's Settings and set its CurrentSectionType property
as the AcSectionType enum constant acSectionType2dSection (see Table 18.8). The
SectionTypeSettings object is then retrieved and assigned to the sec-type-sett
variable. This object exposes the properties with which the section view's components
(Lines, Arcs, Circles, Hatches) will be generated. These are the properties which we will show
how to tweak. Among those properties are:
For the IntersectionFill hatch pattern the following properties will be set:
IntersectionFillHatchPatternType = acHatchPatternTypeUserDefined,
IntersectionFillHatchPatternName = "_U",
IntersectionFillHatchAngle = 45º (in radians),
IntersectionFillHatchSpacing = 1/15 of the model's bounding box smaller
dimension.
(defun sect-props (sect-obj size name topheight
bottomheight viewdir /
settings sec-type-sett clr)
(vla-put-name sect name)
(vla-put-TopHeight sect topheight)
(vla-put-BottomHeight sect bottomheight)
(vla-put-ViewingDirection
sect
(vlax-3d-point viewdir))
(vla-put-TrueColor
sect
(vla-get-IndicatorFillColor sect))
(vla-put-Layer sect (strcat name "_Section"))
(vla-put-State sect-obj acSectionStatePlane)
(setq settings (vla-get-Settings sect-obj))
(vla-put-CurrentSectionType
settings
acSectionType2dSection)
(setq sec-type-sett
(vla-GetSectionTypeSettings
settings
acSectionType2dSection))
(vla-put-BackgroundLinesLayer
sec-type-sett
(strcat name "_" "BackgroundLines"))
(vla-put-CurveTangencyLinesLayer
sec-type-sett
(strcat name "_" "CurveTangencyLines"))
(vla-put-ForegroundLinesLayer
sec-type-sett
(strcat name "_" "ForegroundLines"))
(vla-put-IntersectionBoundaryLayer
sec-type-sett
(strcat name "_" "IntersectionBoundary"))
(vla-put-IntersectionFillLayer
sec-type-sett
(strcat name "_" "IntersectionFill"))
(vla-put-CurveTangencyLinesVisible
sec-type-sett :vlax-false)
(vla-put-ForegroundLinesVisible
sec-type-sett :vlax-false)
(vla-put-IntersectionFillVisible
sec-type-sett :vlax-true)
(vla-put-ForegroundLinesLinetype
sec-type-sett "byLayer")
(vla-put-BackgroundLinesLinetype
sec-type-sett "byLayer")
(vla-put-IntersectionFillLinetype
sec-type-sett "byLayer")
(vla-put-IntersectionBoundaryLinetype
sec-type-sett "byLayer")
(vla-put-BackgroundLinesHiddenLine
sec-type-sett :vlax-true)
(vla-put-BackgroundLinesLineweight
sec-type-sett acLnWt000)
(vla-put-IntersectionFillLineweight
sec-type-sett acLnWt000)
(vla-put-IntersectionBoundaryLineweight
sec-type-sett acLnWt030)
(setq
clr (vla-get-IntersectionBoundaryColor
sec-type-sett))
(vla-put-ColorIndex clr acBylayer)
(vla-put-IntersectionBoundaryColor
sec-type-sett clr)
(vla-put-IntersectionFillColor
sec-type-sett clr)
(vla-put-ForegroundLinesColor
sec-type-sett clr)
(vla-put-CurveTangencyLinesColor
sec-type-sett clr)
(vla-put-BackgroundLinesColor
sec-type-sett clr)
(vla-put-IntersectionFillHatchPatternType
sec-type-sett
acHatchPatternTypeUserDefined)
(vla-put-IntersectionFillHatchPatternName
sec-type-sett "_U")
(vla-put-IntersectionFillHatchAngle
sec-type-sett (/ pi 4))
(vla-put-IntersectionFillHatchSpacing
sec-type-sett (/ size 60)))
Data entry.
The sect-data function prompts the user for selecting the section view to be generated
among the options "Top", "Front" or "Side", for the section's name and for the 3DSolid whose
section is desired.
(defun sect-data (/ minPoint maxPoint xmin ymin
zmin xmax ymax zmax dx dy dz)
(initget "Top Front Side")
(if (not
(setq opt
(getkword
"\nView [Top/Front/Side] <Top>:")))
(setq opt "Top"))
(initget 1)
(setq name (getstring "\nName for section:"))
(prompt "\Select 3D Solid to be sectioned:")
(while
(not (setq obj (ssget "_:S" '((0 . "3DSOLID")))))
(prompt "\Select 3D Solid to be sectioned:"))
(setq obj
(vlax-ename->vla-object (ssname obj 0)))
(vla-GetBoundingBox obj 'minPoint 'maxPoint)
(cond ((and minPoint maxPoint)
(setq
minPoint (vlax-safearray->list minPoint)
maxPoint (vlax-safearray->list maxPoint)
xmin (nth 0 minPoint)
ymin (nth 1 minPoint)
zmin (nth 2 minPoint)
xmax (nth 0 maxPoint)
ymax (nth 1 maxPoint)
zmax (nth 2 maxPoint)
dx (- xmax xmin)
dy (- ymax ymin)
dz (- zmax zmin)
dmin (min dx dy dz))
(sect-options
opt dy dz xmin ymin xmax ymax zmax))))
Once the data is input the 3DSolid's BoundingBox is retrieved and the maximum and
minimum coordinate values are extracted. These data are processed in the sect-options
function that will calculate the parameters for creating the section plane.
18.12 Summary.
For creating a complex 3DSolid we use a series of operations that allow us to:
The sample programs show how these operations may be applied to create complex parts. An
aspect that should be highlighted is the procedure for applying these transformations, usually
referring to the WCS origin and later translating the object to the position and UCS de ined by
the user.
Exercise 1.
In the examples shown objects are created at the WCS origin. But it may happen that the
desired transformations are to be applied to existing objects so they should be translated to
the WCS origin. Using the functions already studied develop a procedure implementing this
transformation and the inverse one for restoring the object to its original position.
These meshes or Subdivision Surfaces are de ined recursively. The process begins with a
coarser mesh. This initial mesh is the smoothing Level 0, with lat faces and edges at an angle.
To this mesh a smoothing process can be applied, subdividing it to create smaller facets by
which a better approximation to curved forms is achieved. This process creates a denser
mesh corresponding to a new level of smoothness. The resulting mesh can undergo the same
process again and so on, thereby increasing the smoothing attained.
A smoothed mesh can be totally or partially subjected to re ining, incorporating the facets
generated by smoothing as faces, edges and vertices of the Level 0 base mesh. The re ined
mesh becomes a new Level 0, which can be subjected to further smoothing processes. It must
be noted that the surfaces are curved only in appearance, as the smoothing facets are still
planar.
MESH objects can also be created using the _MESH command that creates primitives
(rectangular prism, cone, cylinder, pyramid, sphere, wedge, or torus) similar to those created
as 3DSolids, and also from 2D and 3D objects using the old commands _RULESURF,
_TABSURF, _REVSURF and _EDGESURF. It is also possible to convert Polygon or Polyface
Meshes, 3DSolids or Surfaces into MESH entities with the _MESHSMOOTH command.
To explain how the MESH entity de inition list is structured we shall use as an example a
tetrahedral mesh created using one of this chapter'vs sample programs. The entity list
obtained with entget is discussed in detail in Table 19.1.
The op-polyhedron function in Listing 16.20 is used for the vertices and faces data. This
function assigns lists with the vertices coordinates to the variable vertices and lists with
face vertex indices to the variable faces. But in the case of MESH entities, the indices of the
vertices must be zero-based while the face lists returned by the op-polyhedron function
are one-based. And it will also be necessary to de ine the edges, which is not done in the op-
polyhedron function.
The indices are changed to a zero-based notation by subtracting 1 to all the indices in the face
lists:
(setq faces (mapcar '(lambda (face) (mapcar '1- face)) faces))
The face-edges function that receives as argument the faces list is used to create the list
structure for the edges. This function adds each face's successive vertex index pairs as sub-
lists, making sure they are not already in the list. This is checked using the two ordering
possibilities. The order in which edges appear in the edges list does not in luence the result,
but it is essential that no edge is repeated. For each face the index of the irst vertex is saved,
so when the last vertex is reached the edge list can be completed with the one connecting the
last vertex with the irst one. As the edge index list required by the ent-mesh function
(Listing 19.3) is a single level list, once the list is completed face edges will reverse the list
and return it flattened by applying append.
(defun face-edges
(face-indices / i v0 v1 v2 edge-indices)
(foreach face face-indices
(setq i 0
v0 (nth i face))
(repeat (1- (length face))
(setq v1 (nth i face)
i (1+ i)
v2 (nth i face))
(if (not (or (member (list v1 v2)
edge-indices)
(member (list v2 v1)
edge-indices)))
(setq edge-indices
(cons (list v1 v2)
edge-indices))))
(if
(not
(or (member (list v2 v0) edge-indices)
(member (list v0 v2) edge-indices)))
(setq edge-indices
(cons (list v2 v0)
edge-indices))))
(apply 'append (reverse edge-indices)))
The ent-mesh function receives as arguments the three lists for vertices, faces and
edges, and the values selected by the user for the smoothing level and the edges maximum
crease level. From this information it creates the entity de inition sub-lists, associated with
the corresponding DXF group codes. The process is:
1. Find the length of each list (vertices, faces and edges) and associate it with the group
code (92 for vertices, 93 for faces and 94 for edges) that indicates the number of items.
2. Add each item's sublist. For all of the group codes 140 the user-specified edge crease
value is used.
Once the vertices, faces and edges data are processed, the entity type, Subclass markers, etc.
are added at the top of the list. If the function succeeds the new VLA-object is returned.
(defun ent-mesh (vertices faces edges level
crease / res ent-list)
(setq
res (cons (cons 91 level) res)
res (cons (cons 92 (length vertices)) res))
(foreach vertex vertices
(setq res (cons (cons 10 vertex) res)))
(setq
res
(cons
(cons 93 (+ (length faces)
(length (apply 'append faces))))
res))
(foreach face faces
(setq
res (cons (cons 90 (length face)) res))
(foreach datum face
(setq res (cons (cons 90 datum) res))))
(setq
res (cons (cons 94 (/ (length edges) 2))
res))
(foreach endpt edges
(setq res (cons (cons 90 endpt) res)))
(setq
res (cons (cons 95 (/ (length edges) 2))
res))
(repeat (/ (length edges) 2)
(setq res (cons (cons 140 crease) res)))
(setq ent-list
(append (list
'(0 . "MESH")
'(100 . "AcDbEntity")
'(100 . "AcDbSubDMesh")
'(71 . 2) '(72 . 0))
(reverse res)))
(if (entmake ent-list)
(vlax-ename->vla-object (entlast))))
As a result we'll obtain polyhedra that can display sharp or rounded edges depending on the
combination of smoothing and edge crease values speci ied. If we wish to experiment with
higher smoothing values the system variable SMOOTHMESHMAXLEV must be set
beforehand to a value greater than 4, but knowing that very dense meshes may overload the
system.
(defun C:MESH-POLYHEDRON (/ mtrans class center
radius level vertices
faces edges crease
obj)
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq mtrans (last (ax-ucs-matrix))))
(t (setq mtrans nil)))
(mesh-polyhedron-data)
(op-polyhedron class)
(setq
faces
(mapcar '(lambda (face) (mapcar '1- face))
faces))
(setq edges (face-edges faces))
(setq
obj
(ent-mesh vertices faces edges level crease))
(if obj
(progn
(ax-scale
obj (list radius radius radius))
(if mtrans
(vla-TransformBy obj mtrans))
(ax-translation
obj (trans center 1 0 t))
(ax-SWt)))
(vla-EndUndoMark *aevl:drawing*)
(princ))
Data Entry.
The user is prompted by the subd-mesh-data auxiliary function (Listing 19.5) for the
function that will be used to calculate each vertex's Z value, the MESH length, width and height
and the number of subdivisions for the shorter side. In this case we will not limit the
mathematical functions to those prede ined by us. So in this case the user will be prompted
for the name of the function to be applied. The program then checks if a function with that
name is loaded in the current environment. This veri ication is done resorting to the atoms-
family function that returns a list of currently de ined symbols. This function takes two
arguments, the irst determines the format of the list that it returns (0 = list of symbol names
or 1 = list of symbol names as strings) and the second is an optional string list with the names
of symbols to be searched for. The expression (atoms-family 0 (list name)) that
returns nil if the function name were not de ined, is used to control a while loop that will
continue prompting for name until an existing one is entered. This enables the program to
create meshes from any user-defined equation.
(defun subd-mesh-data (/ name str n-faces)
(initget 1)
(setq
name (getstring
"\nEquation function to use:"))
(while
(not
(setq equation
(car (atoms-family 0 (list name)))))
(prompt
(strcat
"\nThe function "
name
" not defined for the present context."))
(initget 1)
(setq name
(getstring
"\nSpecify function to use: ")))
(initget 1)
(setq center (getpoint "\nMesh center: "))
(initget (+ 1 2 4))
(setq
dim-x (getdist center
"\nMesh X dimension: "))
(initget (+ 1 2 4))
(setq
dim-y (getdist center
"\nMesh Y dimension: "))
(initget (+ 1 2 4))
(setq
dim-z (getdist center
"\nMesh Z dimension: "))
(if (< dim-x dim-y)
(setq str "X")
(setq str "Y"))
(initget (+ 1 2 4))
(setq
n-faces
(getint
(strcat
"\nNumber of faces for dimension "
str ": ")))
(if (> dim-x dim-y)
(setq rows n-faces
columns (fix (/ dim-x (/ dim-y n-faces))))
(setq rows (fix (/ dim-y (/ dim-x n-faces)))
columns n-faces)))
From the X and Y dimensions the function calculates the number of columns and rows so that
the facets are approximately square. The data remains in memory so they can be used by the
functions to be executed next. In this case the program will not prompt for the smoothing
level or for the edge crease settings as our intention is creating a base MESH that can be used
as a step in generating a variety of surfaces that will be further on modi ied using programs
such as C:DISTORT (Listing 19.26) or the many commands offered by AutoCAD.
The X and Y coordinates for the vertices de ining each face will be done by the mesh-
vertices-calc function (Listing 19.6). Two nested repeat loops will be used for de ining
each face's vertices. The outer loop's iterations are de ined by the columns argument. For
each repetition of this outer loop an inner loop is executed with as many iterations as de ined
by the rows argument.
Initially the variables xmin and ymin are assigned to the MESH lower left corner coordinates.
For each column the xmin variable is incremented by the value of the MESH X dimension
divided by the number of columns. For each face in a column the Y coordinate's value
(assigned to the variable y0) is incremented by the value of the MESH Y dimension divided by
the number of rows.
Each face is represented by a list of four sublists, one for each vertex. These sublists initially
contain only the values for X and Y. Z values are introduced by means of the coord-z
function once all the points are calculated.
(defun mesh-vertices-calc (dim-x dim-y equation
dim-z rows columns /
xmin ymin dx dy face
faces x0 y0)
(setq xmin (- (/ dim-x 2))
ymin (- (/ dim-y 2))
dx (/ dim-x columns)
dy (/ dim-y rows))
(setq x0 xmin
y0 ymin)
(repeat columns
(setq y0 ymin)
(repeat rows
(setq face
(list
(list xmin y0)
(list (+ xmin dx) y0)
(list (+ xmin dx) (+ y0 dy))
(list xmin (+ y0 dy))))
(setq faces (cons face faces)
y0 (+ y0 dy)))
(setq xmin (+ xmin dx)))
(coord-z (reverse faces) equation dim-z))
Listing 19.6. Function that calculates the vertices coordinates for each face.
Calculating the Z value.
The Z values are calculated by the coord-z function (Listing 19.7). This function receives as
arguments the faces list created by the mesh-vertices-calc function, the function to be
used for calculation (equation variable) and the MESH height (dim-z variable). These last
two arguments were obtained by subd-mesh-data.
1. The Z coordinate values for all the vertices are calculated. This is achieved with two nested
mappings on the faces list:
(mapcar '(lambda (face)
(mapcar '(lambda (pt)(apply equation pt)) face)) faces)
2. By subtracting the minimum Z from the maximum Z value in the resulting list of Z
coordinates the height of the mesh generated from these vales is calculated.
(- (apply 'max (apply 'append lst-z))
(apply 'min (apply 'append lst-z)))
3. The scaling factor (scale-f) that would be necessary to attain the user-specified MESH
height is then calculated and applied to every value in lst-z.
4. This done, the scaled Z coordinate is added to each vertex in the faces list.
The function returns the list of face vertices including their Z coordinate.
(defun coord-z (faces equation dim-z / lst-z
height scale-f)
(setq lst-z
(mapcar
'(lambda (face)
(mapcar '(lambda (pt)
(apply equation pt))
face))
faces)
height
(- (apply 'max (apply 'append lst-z))
(apply 'min (apply 'append lst-z)))
scale-f (/ dim-z height)
lst-z
(mapcar
'(lambda (lst-face)
(mapcar
'(lambda (z) (* z scale-f))
lst-face))
lst-z))
(mapcar
'(lambda (face elev)
(mapcar
'(lambda (pt z) (append pt (list z)))
face
elev))
faces
lst-z))
Listing 19.7. Function that calculates the Z coordinates values.
As in the previous exercise, we will use the ent-mesh function (Listing 19.3) to create the
MESH applying entmake. This function requires three arguments: the list of vertices
coordinates, the vertices indices list for each face and the edge indices list. To prepare these
data in the required format the auxiliary functions unique-vertices-list, face-
indices-list and edges-indices-list are used.
The unique-vertices-list function (Listing 19.8) receives as the faces argument the
list of the vertices coordinates for all faces that was created by the mesh-vertices-calc
function (Listing 19.6).
(defun unique-vertices-list
(faces / vertices-list)
(foreach face faces
(foreach vertex face
(if
(not (member vertex vertices-list))
(setq vertices-list
(cons vertex
vertices-list)))))
(reverse vertices-list))
Listing 19.8. Function that generates the vertices list without duplicates.
This list is traversed in two nested foreach loops checking, for each coordinates sublist, if it
has already been added to vertices-list. If it were not, it is added before processing the
next one. As a result we get a list where no vertex is repeated.
The face-indices-list function (Listing 19.9) receives as the faces argument a list of
the vertices coordinates and as unique-vertices argument the list without duplicates
created by the unique-vertices-list function. To create the list of face indices the faces
list must be traversed creating a new list where the coordinates sublist is replaced by the
zero-based index that identi ies that vertex in the unique-vertices list. That index is
retrieved applying the vl-position function that receives as arguments the element
vertex and the list unique-vertices where vertex is included, returning its zero-based
index.
(defun face-indices-list (faces unique-vertices
/ indices
face-indices)
(foreach face faces
(foreach vertex face
(setq
indices (cons (vl-position
vertex
unique-vertices)
indices)))
(setq face-indices
(cons indices
face-indices)
indices nil))
(reverse face-indices))
Listing 19.9. Function that creates the list of face vertex indices.
The list of edge indices will be generated by the same face-edges function (Listing 19.2)
we used for this same purpose in the previous example that generated a polyhedral MESH. It
receives as argument the list face-indices created by face-indices-list.
The functions in Listings 19.8, 19.9 and 19.2 are called within the rectangular-subd-
mesh function (Listing 19.10) to create the arguments that are then passed to the ent-mesh
function, the same we used in the previous program to create the polyhedron shaped MESH.
(defun rectangular-subd-mesh
(faces-vertices-coords / vertices faces
edges obj)
(setq vertices (unique-vertices-list
faces-vertices-coords)
faces (face-indices-list
faces-vertices-coords
vertices)
edges (face-edges faces)
obj (ent-mesh vertices
faces edges 0 -1)))
Listing 19.10. Function that generates the data structure and creates the mesh.
The functions described above are called from the main function C:SUBD-MESH (Listing
19.11). Just as was done in previous programs the current UCS is checked so in case it is not
the WCS the appropriate alignment will be done after creating the MESH.
(defun C:SUBD-MESH (/ mtrans equation center
dim-x dim-y dim-z rows
columns n-faces
coord-vertices obj)
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq mtrans (last (ax-ucs-matrix))))
(t (setq mtrans nil)))
(subd-mesh-data)
(setq coord-vertices
(mesh-vertices-calc
dim-x dim-y equation dim-z rows columns)
obj (rectangular-subd-mesh coord-vertices))
(cond
(obj
(if mtrans (vla-TransformBy obj mtrans))
(ax-translation obj (trans center 1 0 t))
(ax-SWt))
(t
(prompt "\nError in mesh construction.")))
(vla-EndUndoMark *aevl:drawing*)
(princ))
Up to Release 2011 the _3D command could be used to create 3D primitives (3D boxes,
pyramids, wedges, domes, spheres, cones, tori, dishes and meshes) using the legacy
PolygonMesh. This command has been discontinued in Release 2012. Any programming
that uses it should be changed to the new _MESH command.
Creating Primitives.
The number of faces for the primitives created by the _MESH command depends on a series
of speci ic system variables with names identi ied by the DIVMESH pre ix. When invoking
any of the _MESH command options it will be necessary to establish previously the value for
these variables. Table 19.2 describes the variables for each kind of object.
Table 19.3. System variables for the subdivision of meshes created from 2D objects.
Mesh: Variable: Subdivisions:
Number of tabulations to be generated for the RULESURF and TABSURF
_RULESURF
SURFTAB1 commands. Mesh density in the M direction for EDGESURF and REVSURF.
_TABSURF Values between 2 and 32766.
_REVSURF Mesh density in the M direction for the EDGESURF and REVSURF commands.
_EDGESURF SURFTAB2 Values between 2 and 32766.
To aid in the creation of meshes using these commands we can de ine functions that
automate the process of setting the subdivision variables. These functions try to achieve an
acceptable aspect ratio. Listing 19.12 shows a function that creates a rectangular prism.
(defun cmd-mesh-box (center n-div dim-x dim-y
dim-z corner / dims d-inc
divs corner-pt obj)
(setq
dims (list dim-x dim-y dim-z)
d-inc (/ (apply 'max
(list dim-x dim-y dim-z))
n-div)
divs (mapcar '(lambda (dim)
(fix (/ dim d-inc)))
dims)
divs (mapcar '(lambda (div)
(if (< div 1) 1 div))
divs))
(setvar "DIVMESHBOXLENGTH" (nth 0 divs))
(setvar "DIVMESHBOXWIDTH" (nth 1 divs))
(setvar "DIVMESHBOXHEIGHT" (nth 2 divs))
(if corner
(setq
corner-pt
(mapcar '+ center (list dim-x dim-y 0))
obj (vl-cmdf
"._MESH" "_B" center corner-pt dim-z))
(setq
obj (vl-cmdf
"._MESH" "_B" "_C" center "_L"
dim-x dim-y dim-z)))
(if obj (entlast)))
The cmd-mesh-box function takes as arguments a point to be used as the box's centroid, the
number of subdivisions of the longest side of the box and its X, Y and Z dimensions. The
boolean argument corner is used to decide if the default (Specify irst corner) or the Center
command option should be used.
To determine the number of subdivisions in all three directions X, Y and Z, the program
selects the largest dimension and divides that value by the number of subdivisions it receives
as argument. The X, Y and Z dimensions are then divided by it, generating the list divs that
holds the number of divisions for each dimension. If any of the subdivisions equals 0 it is
replaced with 1.
The command is invoked with different options according to the argument corner being T
o r nil. In case it is T the coordinates of the second corner are calculated by adding to
center the X and Y dimensions If vl-cmdf returns t the function will call entlast to return
entity's ename. As we said when explaining the use of the command/vl-cmdf interface,
running object snaps and command prompts should be disabled, but this can be done in the
function which calls this one.
When running a command from the AutoLISP command function we can never be sure if it
was successful. Running it from the vl-cmdf function we can examine the value returned
that will be T if there has been no error. In this case if vl-cmdf returns T, value which we
assign to the variable obj, the entlast function is called to return the entity's ename.
We will propose another example (Listing 19.14) following the same line to create a Cone
primitive. In this case we have two different options, one for a circular cone and the other for
an elliptical cone. However, these two options are really the same from the programming
point of view, as a circle is no more than an ellipse whose two axes are equal. The function
cmd-mesh-cone receives the arguments center (for the base center point), the number of
divisions ndiv for the greater dimension and the X, Y and Z dimensions.
(defun ellipse-length (dim-x dim-y /)
(* pi
(+ dim-x dim-y)
(+ 1
(/ (* 3.0
(expt (/ (- dim-x dim-y)
(+ dim-x dim-y)) 2))
(+ 10.0
(sqrt
(- 4.0
(* 3.0
(expt (/ (- dim-x dim-y)
(+ dim-x dim-y))
2.0)))))))))
But in this case, we should take into account that the number of divisions speci ied by the
variable DIVMESHCONEAXIS is considered along the ellipse's circumference. It will
therefore be necessary to calculate its length. This will be done by the ellipse-length
auxiliary function (Listing 19.13) that calculates an approximate value for the circumference
from ellipse semi-axes lengths, i.e., the values of dim-x and dim-y.
(defun cmd-mesh-cone (center n-div dim-x dim-y
dim-z / r-max dims d-inc
divs obj)
(setq r-max (apply 'max (list dim-x dim-y))
dims (list (ellipse-length dim-x dim-y)
r-max
dim-z)
d-inc (/ (apply 'max dims) n-div)
divs (mapcar '(lambda (dim)
(fix (/ dim d-inc)))
dims)
divs (mapcar '(lambda (div)
(if (< div 1)
1
div))
divs))
(setvar "DIVMESHCONEAXIS" (nth 0 divs))
(setvar "DIVMESHCONEBASE" (nth 1 divs))
(setvar "DIVMESHCONEHEIGHT"
(nth 2 divs))
(setq
obj (vl-cmdf
"_MESH" "_C" "_E" "_C"
center
dim-x
(list (nth 0 center)
(+ (nth 1 center) dim-y)
(nth 2 center))
dim-z))
(if obj (entlast)))
The procedure for calculating the values of the three variables is similar to that proposed in
the previous example, except that now the irst term in the dims list is the ellipse's
circumference.
A similar function (Listing 19.15) could be used for creating a cylinder. Will take advantage of
the possibility of creating a cylinder with a circular or an elliptical base according to the X and
Y dimensions being equal or different. In this case we don't have different options for the
cylinder's position, as we must always use the center of the base as the reference point. The
explanations for the previous function apply to this one.
(defun cmd-mesh-cylinder (center n-div dim-x
dim-y dim-z / r-max
dims d-inc divs obj)
(setq r-max (apply 'max (list dim-x dim-y))
dims (list (ellipse-length dim-x dim-y)
r-max
dim-z)
d-inc (/ (apply 'max dims) n-div)
divs (mapcar '(lambda (dim)
(fix (/ dim d-inc)))
dims)
divs (mapcar '(lambda (div)
(if (< div 1)
1
div))
divs))
(setvar "DIVMESHCYLAXIS" (nth 0 divs))
(setvar "DIVMESHCYLBASE" (nth 1 divs))
(setvar "DIVMESHCYLHEIGHT" (nth 2 divs))
(setq
obj (vl-cmdf
"._MESH" "_CY" "_E" "_C"
center
dim-x
(list (nth 0 center)
(+ (nth 1 center) dim-y)
(nth 2 center))
(list (nth 0 center)
(nth 1 center)
(+ (nth 2 center) dim-z))))
(if obj (entlast)))
The procedure for creating the spherical MESH (Listing 19.16) would be somewhat different,
since it involves only two system variables, DIVMESHSPHEREHEIGHT and
DIVMESHSPHEREAXIS which de ine the longitude and latitude subdivisions. The n-div
argument in this case is always taken as the number of subdivisions along the sphere's
equator. To maintain a balanced ratio for the faces the distance in the Z direction is computed
as half the equator's circumference. Care should be taken not to assign to
DIVMESHSPHEREAXIS values less than 3 or to DIVMESHSPHEREHEIGHT values less than
2. There are also maximum values, 512 for DIVMESHSPHEREAXIS and 1024 for
DIVMESHSPHEREHEIGHT. But these values are hardly ever used, as they will probably
cause serious memory problems.
(defun cmd-mesh-sphere (center n-div radius /
dims d-inc divs obj)
(setq dims (list (* 2 pi radius) (* pi radius))
d-inc (/ (car dims) n-div)
divs (mapcar '(lambda (dim)
(fix (/ dim d-inc)))
dims))
(if (> (nth 0 divs) 2)
(setvar "DIVMESHSPHEREAXIS"
(nth 0 divs))
(setvar "DIVMESHSPHEREAXIS" 3))
(if (> (nth 1 divs) 1)
(setvar "DIVMESHSPHEREHEIGHT"
(nth 1 divs))
(setvar "DIVMESHSPHEREHEIGHT" 2))
(setq obj
(vl-cmdf "._MESH" "_S" center radius))
(if obj (entlast)))
Listing 19.16. Function that creates a Spherical MESH.
This function is designed so it can accept the object to be modi ied both as an ename or a
VLA-object. To do so, the function checks with (eq (type ename) 'VLA-OBJECT) if
the object received is a VLA-object and if so retrieves from it the entity's ename.
(defun entmod-mesh (ename equation dim-z /
ent-list vertices lst-z
height scale-f x y z
vertices-mod new-list)
(if (eq (type ename) 'VLA-OBJECT)
(setq
ename (vlax-vla-object->ename ename)))
(setq ent-list (entget ename)
vertices (values 10 ent-list)
lst-z
(mapcar
'(lambda (vert)
(apply equation
(list (nth 0 vert)
(nth 1 vert))))
vertices)
height (- (apply 'max lst-z)
(apply 'min lst-z))
scale-f (/ dim-z height))
(foreach vertex vertices
(setq x (nth 0 vertex)
y (nth 1 vertex)
z (+ (nth 2 vertex)
(* scale-f
(apply equation (list x y))))
vertices-mod
(cons (list x y z) vertices-mod)))
(setq vertices-mod (reverse vertices-mod))
(setq i 0
new-list ent-list)
(while (< i (length vertices))
(setq
new-list (subst
(cons 10 (nth i vertices-mod))
(cons 10 (nth i vertices))
new-list)
i (1+ i)))
(entmod new-list)
(entupd ename))
As an example of MESH modi ication using ActiveX procedures we propose the ax-mod-
mesh function (Listing 19.18) that modi ies the Z coordinate calculating the new values with
a user de ined function. The arguments ax-mod-mesh receives are the MESH object (obj),
the function used to calculate the Z values (equation) and the height (dim-z) used for
calculating the Z scaling factor.
The function used to calculate the value of Z (cal-z) is the same as we saw in Chapter 16
(Listing 16.23) regarding the modi ication of PolygonMesh and PolyfaceMesh vertices.
To prevent an exception being thrown, its Smoothness property is checked to determine if
the MESH is currently smoothed. If its value is greater than 0 smoothing should be removed
prior to modifying the vertices, and restored once the changes are made.
This function takes advantage of the different format returned by the Coordinates and
Coordinate properties. For calculating Z, the list of X, Y and Z values obtained from the
Coordinates property is used. After calculating the Z value the Coordinate property for
each vertex item is obtained and modi ied accordingly. The number of repetitions for the
repeat loop that performs this modi ication is determined by the read-only VertexCount
property's value. As with entmod-mesh the function will accept both an ename or a VLA-
object.
The procedure will consist in calculating a value Z from the X and Y values. To that end we will
de ine the functions rev-par (Listing 19.19) for the paraboloid of revolution and hyp-par
(Listing 19.20) for the hyperbolic paraboloid.
(defun rev-par (x y /)
(- (+ (expt x 2.0) (expt y 2.0))))
(defun hyp-par (x y /)
(- (expt x 2) (expt y 2)))
This program's main purpose is exploring the procedures that can be followed when creating
3D objects using command/vl-cmdf and transforming them to obtain the desired 3D form.
As we have seen before, AutoCAD commands use data speci ied in the current UCS, but for
their modi ication the data must be expressed, as always is when dealing with 3D objects, in
WCS coordinates. In cases in which objects are created by entmake or ActiveX there no
dif iculties, since in both cases the data belong to the WCS. But in this case, we may ind
ourselves in a situation where the command is invoked while the current UCS is not the WCS.
The procedure we will follow is verifying if the current UCS is the WCS. This is done by
checking the value of the WORLDUCS system variable. In case the current UCS is not the
WCS:
Listing 19.21. Function that establishes the World Coordinate System as current.
The user will have now the possibility of choosing the base-MESH's form: Rectangular,
Circular, Elliptical or Spherical. Depending on this choice, one of the functions explained
above that create meshes using the _MESH command will be applied: cmd-mesh-box
(Listing 19.12) , cmd-mesh-cylinder (Listing 19.15) or cmd-mesh-sphere (Listing
19.16). If the Rectangular option is selected the user will be prompted for using its center or
its corner as the origin.
Once these aspects have been de ined, the user can specify the MESH position and size by
picking a point and a distance. The user will also be prompted for the MESH's height, which
refers to its vertical dimension and for all except the spherical ones, for its thickness, which
refers to the distance between the upper and lower Layers of the MESH. In circular, elliptical
or spherical base meshes a default perimetrical subdivision of 20 is proposed, although the
user may choose other value.
As explained earlier in this section, if we are not in the WCS the program will switch to it for
creating the MESH, later returning to the original UCS, aligning the MESH with it and
translating it to the point that was specified by the user.
According to the data obtained from the user C:PARABOLOID will call cmd-mesh-box,
cmd-mesh-cylinder or cmd-mesh-sphere to create the MESH and if the command
succeeds it will apply either entmod-mesh or ax-mod-mesh according to the user's
preference.
Figure 19.4. Mesh created by the C:PARABOLOID program.
In our new function which will be called entmod-mesh-xyz (Listing 19.24) a new argument
axis will be added, intended to specify in which direction the transformation will be applied.
This argument will be an integer, 0 for the X axis, 1 for the Y axis and 2 for the Z axis.
According to the axis value, the indices for the other two coordinates are assigned to the
variables i and j so that, for example, if axis = 0, i would be equal to 1 and j equal to 2.
Thus, the expression:
(mapcar
'(lambda (vert)
(apply equation
(list (nth i vert) (nth j vert))))
vertices)
will always return the list of values for the coordinate modified for the selected axis.
To compose the list of X, Y and Z values for each vertex a conditional based on the value of the
axis variable is used to set the order of values:
(cond ((= axis 0)
(setq vertices-mod
(cons (list v-mod v-i v-j)
vertices-mod)))
((= axis 1)
(setq vertices-mod
(cons (list v-i v-mod v-j)
vertices-mod)))
((= axis 2)
(setq vertices-mod
(cons (list v-i v-j v-mod)
vertices-mod))))
This function can be applied an unlimited number of times to the same MESH, applying
transformations in three directions until the desired form is achieved. This form can later be
smoothed, refined and edited manually.
(defun entmod-mesh-xyz (ename equation dim-max
axis / ent-list
vertices i j k lst-mod
height scale-f v-i v-j
v-mod vertices-mod)
(if (eq (type ename) 'VLA-OBJECT)
(setq
ename (vlax-vla-object->ename ename)))
(setq ent-list (entget ename)
vertices (values 10 ent-list))
(cond ((= axis 0)
(setq i 1
j 2))
((= axis 1)
(setq i 0
j 2))
((= axis 2)
(setq i 0
j 1)))
(setq lst-mod (mapcar
'(lambda (vert)
(apply equation
(list (nth i vert)
(nth j vert))))
vertices)
height (- (apply 'max lst-mod)
(apply 'min lst-mod))
scale-f (/ dim-max height))
(foreach vertex vertices
(setq v-i (nth i vertex)
v-j (nth j vertex)
v-mod (+ (nth axis vertex)
(* scale-f
(apply equation
(list v-i v-j)))))
(cond ((= axis 0)
(setq vertices-mod
(cons
(list v-mod v-i v-j)
vertices-mod)))
((= axis 1)
(setq vertices-mod
(cons
(list v-i v-mod v-j)
vertices-mod)))
((= axis 2)
(setq vertices-mod
(cons
(list v-i v-j v-mod)
vertices-mod)))))
(setq vertices-mod (reverse vertices-mod))
(setq k 0
new-list ent-list)
(while (< k (length vertices))
(setq new-list (subst
(cons 10
(nth k vertices-mod))
(cons 10 (nth k vertices))
new-list)
k (1+ k)))
(entmod new-list)
(entupd ename))
This way the string "_EDGESURF" is added as the irst term to the list that already contains
the lines' enames and 'vl-cmdf is applied to this list. To erase the lines the mapcar
function that executes a function with a list supplied as argument is used.
(defun cmd-square-mesh (ins-pt side div / x-min
x-max y-min y-max z
edges)
(setvar "MESHTYPE" 1) ; MESH type object
(setvar "SURFTAB1" div) ; U tesselations
(setvar "SURFTAB2" div) ; V tesselations
(setq x-min (nth 0 ins-pt)
x-max (+ x-min side)
y-min (nth 1 ins-pt)
y-max (+ y-min side)
z (nth 2 ins-pt))
(vl-cmdf "_LINE"
ins-pt
(list x-max y-min z)
"") ; Line 1
(setq edges (cons (entlast) edges))
(vl-cmdf "_LINE"
(list x-max y-min z)
(list x-max y-max z)
"") ; Line 2
(setq edges (cons (entlast) edges))
(vl-cmdf "_LINE"
(list x-max y-max z)
(list x-min y-max z)
"") ; Line 3
(setq edges (cons (entlast) edges))
(vl-cmdf "_LINE"
(list x-min y-max z)
ins-pt
"") ; Line 4
(setq edges (cons (entlast) edges))
(apply 'vl-cmdf (cons "EDGESURF" edges))
; Create MESH
(mapcar 'entdel edges) ; Erase lines
(princ))
Listing 19.26. Creating a planar square MESH using the EDGESURF command.
Data Entry.
The data from which the hyperboloid will be created are obtained by the hyperb-data
function (Listing 19.27). They are the center and radius of the base and top circles, the
hyperboloid's height, the angle of rotation between the circles, the number of tabulations and
the smoothing level. The latter is important to avoid the appearance of holes in the MESH, 2
being proposed as default.
(defun hyperb-data (/)
(initget 1)
(setq base-cen
(getpoint "\nCenter of the base:"))
(initget (+ 1 2 4))
(setq base-rad (getdist base-cen
"\nBase radius:"))
(initget (+ 1 2 4))
(setq height
(getdist base-cen
"\nHyperboloid height:"))
(initget 1)
(setq top-cen
(getpoint
"\nCenter for the top circle:"))
(initget (+ 1 2 4))
(setq top-rad
(getdist top-cen
"\nTop circle radius:"))
(initget (+ 1 2 4))
(while
(not (< 0.0
(setq
ang (getreal "\nTurn angle:"))
180.0))
(prompt
"\nTurn angle must be from 0º to 180º")
(initget (+ 1 2 4)))
(setq ang (dtr ang))
(initget (+ 2 4))
(if (not
(setq n
(getint
"\nCircumference subdivisions <32>:")))
(setq n 32))
(initget (+ 2 4))
(if (not (setq k
(getint "\nSmoothing level <2>:")))
(setq k 2))
(setvar "SURFTAB1" n)
(setvar "MESHTYPE" 1))
The circular pro iles are created as polylines with the ent-polcirc function (Listing 19.28)
that takes as arguments the elevation above the XY plane, the radius and the name of the
Layer where the pro ile will be created as a polyline using entmake. Group code 42 (bulge) is
assigned the value 1.0 so each of the polyline's segments will be a semicircle.
After creating the pro ile, it is moved to the point speci ied as its center and rotated. The base
pro ile's rotation will be 0 and the top pro ile will be rotated by the user-speci ied angle. This
is done by the ax-trans-rot function (Listing 19.29) combining in a single transformation
matrix the translation and the rotation.
(defun ent-polcirc (elev radius lyr)
(entmake
(list '(0 . "LWPOLYLINE")
'(100 . "AcDbEntity")
(cons 8 lyr)
'(100 . "AcDbPolyline")
'(90 . 2)
'(70 . 1)
(cons 38 elev)
(cons 10 (list (- radius) 0.0))
'(42 . 1.0)
'(91 . 0)
(cons 10 (list radius 0.0))
'(42 . 1.0)
'(91 . 0))))
The main function C:HYPERBOLOID (Listing 19.30) invokes the functions described above
to create the pro iles and once they are created invokes the _RULESURF command to create
the MESH. the Layers PROFILES and SURFACE are used for the pro iles and the MESH. If the
hyperboloid has been successfully created its smoothing value is set and the shaded
isometric view is activated.
(defun C:HYPERBOLOID (/ base-cen base-rad
height top-cen top-rad
ang base-profile
top-profile layer-coll
hyper)
(hyperb-data)
(cond
((ent-polcirc 0.0 base-rad "PROFILES")
(setq base-profile (entlast))
(ax-trans-rot
(vlax-ename->vla-object base-profile)
base-cen
0.0))
(t
(prompt
"\nERROR creating the base profile.")
(exit)))
(cond
((ent-polcirc height top-rad "PROFILES")
(setq top-profile (entlast))
(ax-trans-rot
(vlax-ename->vla-object top-profile)
top-cen
ang))
(t
(prompt
"\nERROR creating the top profile.")
(exit)))
(setq layer-coll
(vla-get-layers *aevl:drawing*))
(vla-put-ActiveLayer
*aevl:drawing*
(ax-layer layer-coll
"SURFACE"
"4"
"Continuous"))
(if (vl-cmdf "_rulesurf"
base-profile
top-profile)
(progn (setq hyper (entlast))
(vla-put-smoothness
(vlax-ename->vla-object hyper)
k)
(ax-SWt))
(prompt
"\nError creating the hyperboloid.")))
19.11 Summary.
This chapter includes a complete documentation on the implementation of Subdivision
Surfaces as MESH entities in AutoCAD. Contrary to 3DSolids, which could not be created but
from ActiveX methods, there are no exposed equivalent methods for the creation of
AcadSubDMesh class objects.
But these meshes expose their entity de inition lists in the classic AutoLISP manner. This
makes their creation using entmake possible. This only requires a clear understanding of the
data structure exposed as DXF group codes. The DXF reference information is not easy to
understand, something which I have sought to clarify by means of a series of sample
programs.
However, we have ActiveX properties to modify MESH objects in addition to the AutoLISP
entmod function. The ActiveX and VBA Reference has only recently been updated to include
these objects, but it must be downloaded from the ADN Open1 website, as it is not included in
the standard installation. We hope this Chapter will compensate for this shortcoming.
1 http://www.autodesk.com/adnopen
Chapter 20
Procedural and NURBS Surfaces
AutoCAD offers two basic types of surfaces: Procedural surfaces and NURBS surfaces. A
Procedural surface is a surface object with no control vertices which is de ined from its
relationship to other graphic objects. Therefore, a procedural surface is associative by default,
meaning that it is modi ied automatically when editing the geometry used in its de inition or
any other associated surface. The SURFACEASSOCIATIVITY system variable when set to its
default value 1 determines that associative surfaces will be created.
The term NURBS refers to curves and surfaces generated from the position of points. A
NURBS curve is generated from a series of points and a NURBS surface is generated from a
series of curves. Each NURBS curve or surface has a speci ic parametric equation that de ines
its shape. Both NURBS curves and surfaces have control vertices that can be manipulated to
edit their shape. This type of surface is never associative.
Once the surfaces are created we can access the VLA-object’s exposed properties, and in the
case of Associative Surfaces which are de ined from entities such as Polylines, Arcs, Circles
and Splines, we can modify these entities changing the shape of the associated surfaces.
Commands for creating surfaces include those which are also used for creating solids
according to the _MOde option selected.
Surfaces created from the different commands can belong to seven general types:
Extruded (AcadExtrudedSurface),
Lofted (AcadLoftedSurface),
Network (AcadNetworkSurface)1,
NURBS (AcadNurbSurface),
Planar (AcadPlaneSurface)2,
Revolved (AcadRevolvedSurface),
Swept (AcadSweptSurface).
Each of these object types expose a set of characteristic properties that in certain cases can
be modified. These properties are described in Table 20.3 for the Extruded surface, Table 20.4
for the Lofted surface, Table 20.7 for the Swept surface and Table 20.8 for the Revolved
surface. NURBS surface objects only expose, in addition to the AcadSurface generic
properties, the CvHullDisplay property (see Table 20.9). Its DXF group codes are not
documented. Planar surfaces do not expose any other properties besides those exposed by
the generic surface object.
The second proposal is aimed at exploring associativity in procedural surfaces. We will also
create a lofted surface spanning several cross-sections, but in this case we shall apply 2D
geometric and dimensional constraints to those cross-sections, so the surface can be
modi ied by changing the constraint parameters. This exercise explores these new features
introduced with Release 2010.
This function will be employed to calculate the it points for a group of splines that will be
added to the drawing via entmake. The Splines will be de ined by the fit-points method.
This method is usually enabled by setting the SPLMETHOD system variable to 0, but when
using entmake the setting of this variable is irrelevant as the method will depend on the
entity’s group code 70.
In the information the user is prompted for the knot parameterization type is included. This
option will also be re lected in the value set for group code 70. This program will use the new
group code 70 values introduced in Release 2012. For earlier versions this setting will depend
on the system variables. These new values were described in Table 14.2.
In versions prior to 2012 the value of the SPLKNOTS system variable must be set to 0 for
Chord-Length, 1 for Square Root (Centripetal) and 2 for Uniform (Equidistant) method.
Another system variable to consider is SPLDEGREE which establishes the Spline’s degree,
but as its default value (3) is assigned automatically when using the it-points method it is
not necessary to prompt the user for this value.
Once the splines are generated, they are included in a selection set that will be used to build
the NURBS surface using the LOFT command. It must be remembered the system variable
SURFACEMODELINGMODE must be previously set to 1 in order that a NURBS surface and
not a Procedural surface is be created. If the DELOBJ system variable is set to a value greater
than 1 the original curves will be deleted.
It then prompts for the surface’s center point, its X, Y and Z dimensions, the number of cross-
sections, which will always be aligned with the Y direction, and the number of it points along
each cross-section. To examine the effect derived from the knots parameterization the user is
prompted to choose between three options, Chord, Square root or Uniform. The selected
option will be re lected in the group code 70 value. This value is the sum of the bit values
corresponding to the Planar Spline condition (8), the Parameterization type (32, 64 or 128)
and the one setting the fit points method (1024):
Chord: (+ 8 32 1024)
Square Root: (+ 8 64 1024)
Uniform: (+ 8 128 1024)
The variables whose value is set by function remain in memory for their use in this program’s
other functions.
(defun nurbs-data (/ name)
(initget 1)
(setq name
(getstring
"\nSurface equation:"))
(while (not (setq equation
(car (atoms-family
0
(list name)))))
(prompt
(strcat
"\nFunction "
name
" is not defined!"))
(initget 1)
(setq name
(getstring
"\nSurface equation function:")))
(initget 1)
(setq center
(getpoint "\nSurface center:"))
(initget (+ 1 2 4))
(setq dim-x
(getdist center
"\nSurface X dimension:"))
(initget (+ 1 2 4))
(setq dim-y
(getdist center
"\nSurface Y dimension:"))
(initget (+ 1 2 4))
(setq dim-z
(getdist center
"\nSurface Z dimension:"))
(initget (+ 1 2 4))
(setq n-sec
(getint
(strcat
"\nNumber of cross sections:")))
(initget (+ 1 2 4))
(setq n-pts
(getint
(strcat
"\nCross section fit points:")))
(initget 1 "Chord Square Uniform")
(setq
param
(getkword
"\nKnot option [Chord/Square root/Uniform]:"))
(cond ((= param "Chord")
(setq param (+ 8 32 1024)))
((= param "Square")
(setq param (+ 8 64 1024)))
((= param "Uniform")
(setq param (+ 8 128 1024)))))
Listing 20.1. Prompting for data for the lofted NURBS surface.
As in all 3D object creation programs, the it points will be speci ied as WCS points. To do so,
the starting point’s coordinates are taken as X = (- (/ dim-x 2)) and Y = (- (/ dim-
and 2)), the Y increment as (/ dim-and (1- n-sec)) and the X increment as (/
dim-x (1- n-pts)).
Two nested repeat loops are used in the calculations, the irst one for the number of cross-
sections, and within it, a second loop for the number of it points in each cross-section. In
these loops only the X and Y coordinates are calculated . Once the X and Y for all the it points
are calculated, the coord-z function (Listing 19.7) de ined in the previous chapter adds the
third coordinate value to each of the points.
(defun calc-sect (dim-x dim-y dim-z equation
n-sec n-pts / d-sec d-pts
x0 y0 section sections)
(setq xmin (- (/ dim-x 2))
ymin (- (/ dim-y 2))
d-sec (/ dim-y (1- n-sec))
d-pts (/ dim-x (1- n-pts))
x0 xmin
y0 ymin)
(repeat n-sec
(setq x0 xmin
section nil)
(repeat n-pts
(setq section (cons (list x0 y0)
section))
(setq x0 (+ x0 d-pts)))
(setq
sections (cons (reverse section)
sections))
(setq y0 (+ y0 d-sec)))
(coord-z (reverse sections)
equation
dim-z))
After creating the last cross-section the _LOFT command is invoked to create the surface. In
certain cases the cross-sections may form auto-intersecting loops. These are not supported
by the _L O F T command causing an error in the surface’s creation. But unlike other
commands where on inding a non-valid argument vl-cmdf will not execute the command
returning nil, in this case the value returned is always T. So we will check if the last entity
created is an "AcDbNurbSurface" by reading its ObjectName property. If so, the normal
process continues, aligning the surface to the current UCS and setting the visual style and the
viewpoint speci ied in the ax-SWt function. Otherwise, the user is noti ied of the error and
zooms on the cross-sections so they may be inspected. In many cases the solution may be
found in applying a different knots parameterization, reducing the surface’s Z dimension or
specifying fewer fit points.
Parametric design.
When we talk about parametric design we are referring to two issues, relations between a
design’s different components and the management of such relationships through the
evaluation of expressions. Both aspects are included in the constraint concept.
A constraint is a relationship that limits the behavior of an entity or a group of entities. The
notion of constraint implies the concept of degrees of freedom. A model can be
conceptualized as the topological description of a complex form with n independent
variables. Each constraint is a step leading to the reduction of possible alternatives. In a fully
constrained model the degrees of freedom are reduced to zero. AutoCAD supports constraints
only in 2D models, this implying a signi icant reduction in the possible degrees of freedom.
The constraints that may be applied to a model can be of two types:
Geometric constraints.
Geometric constraints are implemented with reactors. There are no exposed ActiveX
properties that allow us access to geometric constraints. We can verify that an entity has
geometric constraints by analyzing the list returned by entget, where we ind the group
ACAD_REACTORS.
((-1 . <Entity name: 7ffff623ae0>)
(0 . "LINE")
(5 . "226")
(102 . "{ACAD_REACTORS")
(330 . <Entity name: 7ffff623b40>)
(102 . "}")
(330 . <Entity name: 7ffff6099f0>)
(100 . "AcDbEntity")
(67 . 0)
(410 . "Model")
(8 . "0")
(100 . "AcDbLine")
(10 3475.3 -41.6405 0.0)
(11 3475.3 1248.26 0.0)
(210 0.0 0.0 1.0))
The group code 330 within the ACAD_REACTORS section contains the entity name of the
ACDBASSOCGEOMDEPENDENCY object. The 330 group code in the entity list for this object
leads to the ACDBASSOC2DCONSTRAINTGROUP that apparently contains all the information
related to the geometric constraints. But none of this is documented in the DXF reference so
for now this information is not easy to use. To work with geometric constraints we must
resort to commands (See Table 20.10).
Dimensional constraints.
Dimensional constraints control the model’s size and proportions. The aspects that may be
constrained include the distances or angles between entities or between points on those
entities and the radii of arcs and circles. In the case of the polylines constrains may be applied
between segments as if they were separate entities. The values of dimensional constraints
are known as Parameters. Parameters can be numeric values, but can also be variables or
equations. By varying a parameter in a restricted model all the restrictions are re-evaluated,
immediately updating the model.
Applying dimensional constraints.
Dimensional constraints and associative dimensions depend on reactors that establish their
relationship with the constrained object. No methods are exposed that can create
dimensional constraints, being necessary to invoke the application’s commands to create
them. Their properties are not documented. The commands available for applying
dimensional constraints are listed in Table 20.11.
The _DELCONSTRAINT command can be used to remove all constraints in a selection set,
both geometric and dimensional.
Although not documented, the ActiveX API exposes a set of properties that allow a program to
manage dimensional constraints once they are created. This makes it possible to develop
applications that manage a design’s parameters. This can be used for modifying associative
procedural surfaces. Dimensional constraint properties are described in Table 20.12.3
Dimensional constraints inherit their basic characteristics from the DIMENSION object, the
same used for normal dimensions. We will now study the DXF group codes that are relevant
for applications that can be developed using Visual LISP. The AutoCAD documentation doesn’t
include information on the meaning of DXF group codes for dimensional constraints. The
relevant group codes are listed in Table 20.13.
We have two values where the measured dimension appears. The value in group code 1 is a
string formed by the name of the constraint (DimConstrName property), the character "="
and the expression (DimConstrExpression property). Group code 42 contains the
numerical result of that expression. To modify the dimensional constraint we have to modify
the content associated with group code 1, not group code 42. This behavior is similar to when
modifying the constraint using the ActiveX properties. In that case, the property to change is
DimConstrExpression, not DimConstrValue.
The associativity of surfaces and pro iles in AutoCAD is extremely fragile. Should the surface
move without moving the pro iles, the association would be lost and cannot be re-
established. The technique we used in previous examples for aligning objects to the current
UCS and moving it to the point selected by the user once would make this program too
complex for the purposes of this chapter. For this reason in this case we will dispense with all
this.
This function receives as arguments the elevation (elev) that determines the polyline’s Z
coordinate, the cross-section’s height (height), its width and the name of the Layer (lyr)
that will contain it. In the proposed program we will place the cross-sections in one Layer and
the surface in another, in order to control the display of both types of entity. The curvature of
the upper arc is attained by setting group code 42 to 1.0 which is the tangent of one fourth of
the 180º swept angle.
(defun ent-profile
(elev height width lyr /)
(setq x (/ width 2.0)
y (- height x))
(entmake
(list '(0 . "LWPOLYLINE")
'(100 . "AcDbEntity")
'(100 . "AcDbPolyline")
(cons 8 lyr)
(cons 38 elev)
'(90 . 4) ;Number of vertices
'(70 . 1) ;Closed flag (1)
(list 10 x 0.0) ;1st vertex coords (OCS)
'(42 . 0.0) ;Straight segment bulge
'(91 . 0) ;End of data vertex 1
(list 10 x y) ;2nd vertex coords (OCS)
'(42 . 1.0) ;Arc segment bulge
'(91 . 0) ;End of data vertex 2
(list 10 (- x) y) ;3rd vertex coords (OCS)
'(42 . 0.0) ;Straight segment bulge
'(91 . 0) ;End of data vertex 3
(list 10 (- x) 0.0);4th vertex coords (OCS)
'(42 . 0.0) ;Straight segment bulge
'(91 . 0) ;End of data vertex 4
'(210 0.0 1.0 0.0) ;Normal vector.
)))
Data entry.
The data for the creation of the cross-sections are requested by the assoc-surf-data
function (Listing 20.6) which prompts for their number, the distance between them, their
height and their width. Due to the cross-section’s shape (see Figure 20.2) an error will occur if
their width is equal to or greater than its height, a condition which is checked, entering a
while loop that only stops when the user has entered a satisfactory value. The possibility of
introducing zero or negative values is avoided by calling (initget (+ 1 2 4)) before
each prompting.
(defun assoc-surf-data (/)
(initget (+ 1 2 4))
(setq
n (getint "\nNumber of profiles:"))
(initget (+ 1 2 4))
(setq
interval
(getreal
"\nDistance between profiles:"))
(initget (+ 1 2 4))
(setq height
(getreal "\nHeight:"))
(initget (+ 1 2 4))
(while (>= (setq width
(getreal "\nWidth:"))
(* height 2))
(prompt
(strcat "\nWidth must be < "
(rtos (* height 2.0) 2 2)))
(initget (+ 1 2 4)))
(initget 1)
(setq id (getstring "\nSurface ID:")))
The cross-sections will be created using the ent-profile function, but to apply the
geometric constraints the commands described in Table 20.10 must be used, and for the
dimensional constraints those in Table 20.11 as the Visual LISP/ActiveX API does not expose
methods to accomplish this. We must not forget that constraints in AutoCAD are only 2D. This
implies that no constraint may be established if the objects do not lie in the current UCS XY
plane. For this reason, in the make-profiles function (Listing 20.8) before attempting to
apply the restrictions the ax-trans-ucs function (Listing 20.7) is called. This function
receives as arguments the UCS collection, the UCS origin, the X-axis direction, the Y-axis
direction (all as WCS 3D points) and the UCS name and sets the corresponding UCS as current.
Another requisite is that all the objects must be displayed on the graphics screen and that
they must not overlap so as to avoid selecting the wrong object. For this reason before
applying the constraints the ax-view function (Listing 13.17) is used in the expression
(ax-view '(-1 0 0) t) which sets an orthogonal left lateral view. Other condition that
may cause an erroneous selection is if the current viewport displays a perspective view. To
avoid this the PERSPECTIVE system variable is set to 0.
1. Those constraints arising from the relative position and orientation of the cross-section
segments will be applied first. These are assigned by the _AUTOCONSTRAIN command. The
base segment will be constrained as Perpendicular to the sides, these vertical sides will be
constrained as Parallel and as Tangent to the upper arc.
2. We must avoid that the cross-sections are displaced laterally when their dimensions are
modified. To attain this a Fix constraint will be applied to the midpoint of the horizontal base
segment. This is done using the _GCFIX command using the point returned by the osnap
function using the MIDpoint object snap.
3. Finally, the only dimensional constraint we will use will be applied to the arc's radius by the
_DCRADIUS command.
4. As the cross-section is totally constrained, any change in this dimension will cause the rest
of the cross-section dimensions to be reevaluated changing its width and height accordingly.
Figure 20.3. Cross-section showing its constraints.
Listing 20.7. Function that sets the correct UCS right before applying the constraints.
The constraints applied to the cross-section are shown in Figure 20.3. The dimensional
constraint’s name (DimConstrName property) will be changed pre ixing it with the
identi ier speci ied by the user, which also will be used for naming the Layers in which surface
and cross-sections are placed. This will help in identifying them in case the user must make
changes after creating the surface. But we’ve found an undocumented limitation that is the
reason for including in our code the expression (vla-get-DimConstrName dimconst),
without having any need for the value returned. If we don’t do this, either by obtaining this
property or any other, our attempt to change any of the dimensional constraint’s properties
will fail. Apparently, reading one of the properties opens the object for reading/writing. This
is a conclusion derived from our experience. We will also include a description for this
restriction, assigning it to the DimConstrDesc property.
(defun make-profiles (origin-list height
width lyr id /
ucs-coll pt-o pt-x
pt-y nom i p1 p2
dimconst)
(setq ucs-coll
(vla-get-UserCoordinateSystems
*aevl:drawing*))
(foreach elev origin-list
(ent-profile elev height width lyr)
(setq profiles
(cons (entlast) profiles)))
(setq profiles (reverse profiles))
(ax-view '(-1 0 0) t)
(setvar "PERSPECTIVE" 0)
(setq i 0)
(foreach profile profiles
(setq
pt-o
(list 0 (nth i origin-list) 0)
pt-x
(list 100 (nth i origin-list) 0)
pt-y
(list 0 (nth i origin-list) 100)
nom (strcat "SCP-" (itoa i))
i (1+ i))
(ax-trans-ucs
ucs-coll pt-o pt-x pt-y nom)
(vl-cmdf "_AutoConstrain" profile "")
(vl-cmdf "_GcFix"
(osnap '(0 0 0) "_mid"))
(setq p1 (osnap (list 0 height 0)
"_qua")
p2 (list 0 (/ height 3.0) 0))
(vl-cmdf "_DcRadius" p1 p2 "")
(setq dimconst
(vl-catch-all-apply
'vlax-ename->vla-object
(list (entlast))))
(cond
((vl-catch-all-error-p dimconst)
(prompt
(strcat
"\nERROR:"
(vl-catch-all-error-message))))
(t
(vla-get-DimConstrName dimconst)
(vla-put-DimConstrName
dimconst
(strcat id "_rad" (itoa i)))
(vla-put-DimConstrDesc
dimconst
(strcat
"Surface " id
"; Profile radius " (itoa i)))
))))
The surface is built using the _LOFT command. Since we want to create an associative
surface we must irst set the SURFACEASSOCIATIVITY system variable to 1. But it is also
necessary to ensure that the SURFACEMODELINGMODE system variable is set to 0, since
otherwise a non-associative NURBS surface would be created.
The make-assoc-surf function (Listing 20.9) receives a list with the enames of the cross-
sections that will be used in creating the surface. It sets the correct values for the
SURFACEMODELINGMODE and SURFACEASSOCIATIVITY system variables and creates
the Layer in which the surface will be placed and sets it as the current Layer. This is done via
ActiveX methods, retrieving the Layers collection and using the auxiliary function ax-layer
(Listing 10.43) to create the new Layer.
When running the _LOFT command its _MOde option must be set as _SUrface as otherwise
we would get a 3DSolid rather than a Surface. An empty string will be entered to indicate
that all the cross-sections have been passed to the _LOFT command, which will end when
receiving the "_Cross" option.
(defun make-assoc-surf (profiles /)
(setvar "SURFACEASSOCIATIVITY" 1)
(setvar "SURFACEMODELINGMODE" 0)
(vla-put-ActiveLayer
*aevl:drawing*
(ax-layer
(vla-get-layers *aevl:drawing*)
(strcat id "_SURFACE")
"4"
"Continuous"))
(vl-cmdf "_LOFT" "_MOde" "_SUrface")
(foreach profile profiles
(vl-cmdf profile))
(vl-cmdf "" "_Cross"))
After creating the dimensional constraints selection set any algorithm may be used to change
their values. In this case we will modify the current value by adding half of that value in the
event that its selection set index is even and subtracting it if odd. This simple algorithm is
de ined in the val function that takes as arguments the constraint’s index (i) and its value
(n). To decide if the argument i is odd or even the logand function is used. This function
returns the binary AND of a list of numbers. For any odd integer i, (logand 1 i) returns 1.
In the case of an even number it returns 0.
(defun val (i n /)
(if (zerop (logand 1 i))
(+ (/ n 2.0))
(- (/ n 2.0))))
Listing 20.10. Function that calculates the modified dimensional constraint value.
This list will be passed as one of the arguments to make-profiles (Listing 20.8), the
function that will create the cross-sections. After the cross-sections have been created the
surface is created calling the make-assoc-surf function and the dimensional constraints
modi ied calling mod-constraint. This illustrates the possibilities for working with
associative surfaces from Visual LISP.
(defun C:ASSOC-SURF (/ *error* n interval
height width
origin-list profiles)
(defun *error* ()
(cmd-out)
(vla-EndUndoMark *aevl:drawing*))
(vla-StartUndoMark *aevl:drawing*)
(cmd-in)
(if (= (getvar "WORLDUCS") 0)
(vl-cmdf "_UCS" "_W"))
(assoc-surf-data)
(setq i 0)
(repeat n
(setq origin-list (cons (* interval i)
origin-list)
i (1+ i)))
(make-profiles
(reverse origin-list)
height
width
(strcat id "_PROFILE")
id)
(make-assoc-surf profiles)
(mod-constraint)
(cmd-out)
(ax-SWt)
(vla-EndUndoMark *aevl:drawing*))
But if we analyze the information on the removed restrictions we can see that the number of
constraints that are reported as deleted equals the number of cross-sections times the
number of geometric constraints. This leads us to suspect that at least the dimensional
constraints were not lost. But the Parameters Manager’s Parametric tab will not show any
that corresponds to our dimensional constraints.
Make it dynamic.
Let’s do a double-click on the block. This displays Edit Block Definition dialog box with the
double-clicked block’s name highlighted. We click OK to enter the Block Editor. As I suspected,
in the Block Editor (Figure 20.4) the surface and its cross-sections with all their constraints,
both geometric and dimensional appear.
Figure 20.4. Associative surface in the Block Editor.
Then, why is it that its parameters are not available in the drawing? If I open the Block Editor’s
Parameters Manager palette I ind that those dimensional constraints appear as Dimensional
Constraint Parameters. But now we have a column titled Show that did not appear before.
And all of this column’s values are set to "No".
If I select one of the restrictions and right-click, a context menu appears (Figure 20.5)
displaying the options Delete Parameter and Convert Parameter. If we choose this second
option the entry in the Show column changes from No to Yes.
For us the most interesting option is the one that converts dimensional constraints into
constraint parameters. This option can be included in our C:ASSOC-SURF program
supposing we can detect if it is running within the Block Editor. This way we would have a
more universal program, creating a normal associative surface if we work in Model Space or
creating a dynamic block by working in the Block Editor.
Figure 20.6. Dialog to create a new empty block with the Block Editor.
The criterion we can use to check if the program is running in the Block Editor is the value of
the BLOCKEDITOR system variable which in that case would be set to 1. We can then add an
auxiliary function that will only operate if in the block editor, automatically converting the
dimensional constraints into block dimensional parameters. This would be done by creating a
selection set of dimensional constraints following the same criteria used in Listing 20.11 that
will be traversed applying to each dimensional constraint the _Convert option of the
_BCPARAMETER command.
(defun cmd-conv-param
(id / dim-constraints i)
(if (= (getvar "BLOCKEDITOR") 1)
(progn
(setq
dim-constraints
(ssget "X"
(list
'(0 . "DIMENSION")
'(8 . "*ADSK_CONSTRAINTS")
(cons
1
(strcat id "_rad*"))))
i 0)
(repeat (sslength dim-constraints)
(vl-cmdf
"_bcparameter"
"_Convert"
(ssname dim-constraints i)
"")
(setq i (1+ i))))))
Listing 20.13. Converting dimensional constraints into parameters when working in the Block Editor.
Including a call to this function in C:ASSOC-SURF we will have a program capable of creating
a normal associative surface in Model Space or a dynamic block if executed within the Block
Editor.
Figure 20.7. Properties palette showing the dimensional constraints.
Figure 20.8. Associative surface with initial (left) and modified parameter values (right).
20.8 Summary.
We have considered in this chapter the programming options for Procedure and NURBS
surfaces. There are no ActiveX methods for creating them and the data associated to most of
their DXF group codes is encrypted, so the only way to create them is the through the
command/vl-cmdf interface.
In the case of Procedural surfaces which are defined from linear cross-sections it is
possible to retain the association between the surface and them. The cross sections can
be assigned geometric and dimensional constraints so that they can be modified by
parameters. Parameter modification can be implemented in a program. This way we can
have surfaces whose shape changes by manually or programmatically modifying these
parameters. A very practical way for using these surfaces is the creation of dynamic
blocks with the aid of the Block Editor.
The most widely used surfaces in industrial design are the NURBS surfaces. This is due to
their great advantages, which include rich interactive design capabilities and accurate
representation of closed shapes such as conics and quadrics. In fact, many CAD/CAM,
virtual reality, visualization and animation applications use models built with NURBS
surfaces.
The SPLINE entities explained in Chapter 14 are especially suitable for creating NURBS
surfaces. These surfaces cannot be modified through Visual LISP programming. They can
only be modified interactively using their control vertices and gizmos.
1 The ObjectName for a "Network" surface is "AcDbLoftedSurface". Its properties are those shown in Table 20.4.
3 Dimensional constraints are dynamic by default. That means they maintain their size when the zoom level changes, they can be
shown or hidden in the drawing, they are displayed with a fixed style, their text is placed automatically and they display
triangular grips for changing their value and are not printed. By changing their type to annotative they behave as normal
dimensions and can be printed. To display the text as in normal dimensions the CONSTRAINTNAMEFORMAT system variable
should be set to 1.
Index of Function Listings.
Chapter 13
Listing 13.1. Function that draws a polyline in the plane defined by the normal vector.
Listing 13.2. Test function that creates a line specifying its normal vector.
Listing 13.3. Replacement function for trans using ActiveX method TranslateCoordinates.
Listing 13.4. Vector operations.
Listing 13.5. Translation function.
Listing 13.6. Radian-Degree conversions.
Listing 13.7. Rotation about X.
Listing 13.8. Rotation about Y.
Listing 13.9. Rotation about Z.
Listing 13.10. XYZ Scaling function.
Listing 13.11. Shear along X.
Listing 13.12. Shear along Y.
Listing 13.13. Shear along Z.
Listing 13.14. Command for XYZ scaling.
Listing 13.15. Function that adds a new UCS to the current document.
Listing 13.16. Function that returns the current UCS transformation matrix.
Listing 13.17. Function that sets the view direction and visual style.
Listing 13.18. Function that sets a custom visual style.
Chapter 14
Listing 14.1. Function that creates a SPLINE entity applying the AddSpline method.
Listing 14.2. Function for creating a Fit Points Spline.
Listing 14.3. Function that creates the Knot Vector.
Listing 14.4. Function that creates a Spline by the control vertices method.
Listing 14.5. Function that computes the helix's control vertices coordinates.
Listing 14.6. Function that creates a HELIX entity with entmake.
Listing 14.7. Helix data input function.
Listing 14.8. Main function C:ENT-HELIX.
Listing 14.9. Conversion of a HELIX entity into a SPLINE.
Chapter 15
Listing 15.1. Obtaining the total length of a curve.
Listing 15.2. Test function for AX-CURVELENGTH.
Listing 13.3. Replacement function for trans using ActiveX method TranslateCoordinates.
Listing 15.4. Measuring the distance from the beginning of the curve to a point.
Listing 15.5. Determining the distance between two points on the curve.
Listing 15.6. Program that measures the distance between two points along a curve.
Listing 15.7. Determining the area enclosed by the curve.
Listing 15.8. Function that draws a RAY.
Listing 15.9. Function that calculates the tangent to a curve.
Listing 15.10. Command that draws a RAY tangent to a curve.
Listing 15.11. Function that sets a coordinate system from a point and a vector using command.
Listing 15.12. UCS from the Z axis direction vector using ActiveX.
Listing 15.13. Function that returns a UCS transformation matrix.
Listing 15.14. Command to establish a UCS perpendicular to a curve.
Listing 15.15. Calculating points at fixed distances.
Listing 15.16. Breaking an entity into equal segments.
Listing 15.17. Creating a points list from a list of coordinates.
Listing 15.18. Determining how to extend the entities.
Listing 15.19. INTERSECTIONS function.
Chapter 16
Listing 16.1. Drawing the PolygonMesh using the 3DMESH command.
Listing 16.2. Function to generate the PolygonMesh header.
Listing 16.3. Function to generate each mesh vertex.
Listing 16.4. Function that creates the End of Sequence entity.
Listing 16.5. Drawing the mesh with entmake.
Listing 16.6. Creating the PolygonMesh with the Add3dMesh method.
Listing 16.7. Function that prompts for the mesh definition data.
Listing 16.8. Functions for calculating different surface shapes.
Listing 16.9. Function that determines the equation to be used.
Listing 16.10. Function that calculates the coordinates of the mesh's vertices.
Listing 16.11. Main function C:POLYMESH.
Listing 16.12 Function that creates the PolyfaceMesh by means of the PFACE command.
Listing 16.13. Discretization of the faces.
Listing 16.14. Creation of the PolyfaceMesh header entity.
Listing 16.15. Creating the VERTEX entities.
Listing 16.16. Faces creation (FaceRecord entities).
Listing 16.17. Function that draws the PolyfaceMesh using entmake.
Listing 16.18. Creating the mesh using vla-AddPolyfaceMesh.
Listing 16.19. User data entry.
Listing 16.20. Loading the polyhedron's vertices and faces data.
Listing 16.21. Main Function C:POLYHEDRON-PFACE.
Listing 16.22. Function that retrieves the vertices produced by smoothing a PolygonMesh.
Listing 16.23. Function for calculating the Z coordinate.
Listing 16.24. Modification of the vertices of a Polygon or Polyface mesh.
Chapter 17
Listing 17.1. Creation of a Cone primitive.
Listing 17.2. Creating a cone through command/vl-cmdf.
Listing 17.3. Function that creates regions according to the profiles received.
Listing 17.4. Function that performs a Boolean operation on two objects controlling errors.
Listing 17.5. Function that prompts for the complex region's data.
Listing 17.6. Sample program that creates complex regions.
Listing 17.7. Function that creates the base region.
Listing 17.8. Enabling the Registry property in a 3DSolid.
Listing 17.9. Creating an Extruded Solid.
Listing 17.10. Auxiliary function ax-ext-path.
Listing 17.11. Creating a solid by sweeping along a path.
Listing 17.12. Function that creates a HELIX object through command/vl-cmdf.
Listing 17.13. Function that creates a HELIX by entmake.
Listing 17.14. Function that creates the Helix adjusting its properties.
Listing 17.15. Function that prompts for the spring's data.
Listing 17.16. Function that creates a 3DSolid using the SWEEP command.
Listing 17.17. Main function C:SPRING.
Listing 17.18. Function that creates a solid of revolution.
Listing 17.19. Sample program that creates a solid of revolution.
Listing 17.20. Function that extracts physical and geometric properties of objects.
Chapter 18
Listing 18.1. Function that slices the 3DSolid.
Listing 18.2. Function-sol-p-data requesting user input.
Listing 18.3. Main function C:SOL-POLYHEDRON.
Listing 18.4. Function that creates a solid's section as a REGION object.
Listing 18.5. Main function C:SECT-POLYHEDRON.
Listing 18.6. Connector data entry.
Listing 18.7. Function used to create cubes as 3DSolids.
Listing 18.8. Function used to create cylinders as 3DSolids.
Listing 18.9. Function that rotates an object 90º about the X axis.
Listing 18.10. Function that rotates an object 90º about the Y axis.
Listing 18.11. Function that removes duplicates from a list.
Listing 18.12. Main function C:CONNECTOR.
Listing 18.13. Function that prompts for the coupling's data.
Listing 18.14. Function that creates a 3DSolid rectangular prism.
Listing 18.15. Function that rotates an object 180 degrees around the Z axis.
Listing 18.16. Main function C:COUPLING.
Listing 18.17. Function that trims a 3DSolid against another.
Listing 18.18. Main function C:SOL-TRIM.
Listing 18.19. Function that makes new shapes out of the overlapping volumes.
Listing 18.20. Function that separates composite 3DSolids.
Listing 18.21. Main function C:SOL-SPLIT.
Listing 18.22. Function that creates a SECTION using entmake.
Listing 18.23. Function that creates a SECTION using ActiveX methods and properties.
Listing 18.24. Function that creates the section geometry.
Listing 18.25. Function that adds layers to the drawing.
Listing 18.26. Section object properties.
Listing 18.27. Data entry function.
Listing 18.28. Section options.
Listing 18.29. Main function C:SECTIONOBJ.
Chapter 19
Listing 19.1. Data entry for a Polyhedron shaped MESH.
Listing 19.2. Function that creates the MESH edges list.
Listing 19.3. Creating MESH entities with ENTMAKE.
Listing 19.4. Main Function C:MESH-POLYHEDRON.
Listing 19.5. Function that prompts for the MESH data.
Listing 19.6. Function that calculates the vertices coordinates for each face.
Listing 19.7. Function that calculates the Z coordinates values.
Listing 19.8. Function that generates the vertices list without duplicates.
Listing 19.9. Function that creates the list of face vertex indices.
Listing 19.10. Function that generates the data structure and creates the mesh.
Listing 19.11. Main Function C:SUBD-MESH.
Listing 19.12. Creating a Rectangular Prism MESH.
Listing 19.13. Function that calculates an ellipse's circumference.
Listing 19.14. Function that creates a cone-shaped MESH.
Listing 19.15. Function that creates a cylindrical MESH.
Listing 19.16. Function that creates a Spherical MESH.
Listing 19.17. Modifying a MESH entity using entmod.
Listing 19.18. Modifying a MESH object using ActiveX.
Listing 19.19. Function for calculating the vertices of a paraboloid of revolution.
Listing 19.20. Function for calculating the vertices of a hyperbolic paraboloid.
Listing 19.21. Function that establishes the World Coordinate System as current.
Listing 19.22. Function that prompts for the Paraboloid's data.
Listing 19.23. Main function C:PARABOLOID.
Listing 19.24. Function that transforms a MESH in the X, Y or Z directions.
Listing 19.25. Main function C:SHAPE-M.
Listing 19.26. Creating a planar square MESH using the EDGESURF command.
Listing 19.27. Function that prompts for the hyperboloid data.
Listing 19.28. Function that creates circular profiles.
Listing 19.29. Function that translates and rotates a profile.
Listing 19.30. Main function C:HYPERBOLOID.
Chapter 20
Listing 20.1. Prompting for data for the lofted NURBS surface.
Listing 20.2. Function that calculates the fit point coordinates.
Listing 20.3. Function that creates the cross-section as a SPLINE.
Listing 20.4. Main function C:NURBS-SURF.
Listing 20.5. Function that creates the profile.
Listing 20.6. Prompting for cross-section data.
Listing 20.7. Function that sets the correct UCS right before applying the constraints.
Listing 20.8. Function that creates the fully constrained cross-sections.
Listing 20.9. Function that creates the associative surface.
Listing 20.10. Function that calculates the modified dimensional constraint value.
Listing 20.11. Function that modifies the dimensional constraints.
Listing 20.12. Main Function C:ASSOC-SURF.
Listing 20.13. Converting dimensional constraints into parameters when working in the Block Editor.
Functions library.
These functions from previous Volumes are called in programs defined in the present text.
(defun replace (new old string / pos len)
(setq pos 0
len (strlen new))
(while (setq pos (vl-string-search old string pos))
(setq string (vl-string-subst new old string pos)
pos (+ pos len)))
string)
Listing 10.9. Retrieving the value associated with a DXF group code.
(defun ax-layer
(layer-coll name color ltype / lyr)
(setq lyr (vla-add layer-coll name))
(vlax-put-property lyr "color" color)
(vlax-put-property lyr "linetype" ltype)
lyr)
Listing 11.15. Function to detect and remove objects that belong to a Group.
(defun ax-add-group
(name obj-list / groups-coll group)
(setq groups-coll (vla-get-Groups *aevl:drawing*)
group (vl-catch-all-apply
'vla-Item
(list groups-coll name)))
(cond
((vl-catch-all-error-p group)
(setq group (vla-Add groups-coll name))
(vla-AppendItems
group
(ax-list->variant obj-list))
group)
(t
(if (setq objects
(ax-no-group obj-list group))
(vla-AppendItems
group
(ax-list->variant objects)))
group)))
(defun ax-add-group
(name obj-list / groups-coll group)
(setq groups-coll (vla-get-Groups *aevl:drawing*)
group (vl-catch-all-apply
'vla-Item
(list groups-coll name)))
(cond
((vl-catch-all-error-p group)
(setq group (vla-Add groups-coll name))
(vla-AppendItems
group
(ax-list->variant obj-list))
group)
(t
(if (setq objects
(ax-no-group obj-list group))
(vla-AppendItems
group
(ax-list->variant objects)))
group)))
(pragma
'((unprotect-assign *aevl:acad* *aevl:drawing*)))
(setq *aevl:acad* (vlax-get-acad-object)
*aevl:drawing* (vla-get-ActiveDocument
*aevl:acad*))
(pragma
'((protect-assign *aevl:acad* *aevl:drawing*)))
(vl-load-com)
(load "aevl-globals"
"AEVL-GLOBALS not found")
(load "./Utility/vlisp-library"
"VLISP-LIBRARY not found")
(autoload "./Numera/FAS/numera.fas"
'("NUMERA" "NUM-OPTIONS"))
PART 1. INTRODUCTION.
Chapter 1. AutoLISP/Visual LISP.
1.1 Visual LISP.
1.2 New LISP functions.
2.3. Summary.
Volume 2
Volume 4.