0% found this document useful (0 votes)
376 views

Programming 3D. Solids - Meshes - Vol3

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
376 views

Programming 3D. Solids - Meshes - Vol3

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 260

AutoCAD expert’s Visual LISP

Volume 3

Programming 3D

Reinaldo N. Togores
AutoCAD expert’s Visual LISP.
Volume 3
Programming 3D

Copyright © 2013 by Reinaldo N. Togores

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.

PART 4. PROGRAMMING 3D.


Chapter 13. 3D Objects.
13.1. Programming options from Visual LISP.
13.2. How does AutoCAD work in 3D?.
13.3. Transformation matrices.
13.4. Sample Program: Scailin transformations specifying the base point.
13.5. Transformation between Coordinate Systems.
13.6. Viewpoint and Visual Style.
13.7. Summary.

Chapter 14. NURBS curves: The Spline entity.


14.1. Creating SPLINE entities.
14.2. SPLINE Methods and Properties.
14.3. Creating ca Helix shaped SPLINE by Control Vertices.
14.4. Sample Program: Creatins a HELIX.
14.5. Summary.

Chapter 15. VLAX-CURVE... measuring curves and something else.


15.1. Visual LISP 's VLAX-CURVE Extensions.
15.2. Common arguments.
15.3. Determining a curve 's length.
15.4. Distance between points along a curve.
15.5. Measuring Areas.
15.6. Calculating the first and second derivatives.
15.7. Sample Program: Drawing tangents to a curve.
15.8. Sample Program: UCS perpendicular to a curve at a selected point.
15.9. Determining points on a curve.
15.10. Sample Program: Breaking a curve into equal segments.
15.11. Finding intersections.
15.12. Summary.

Chapter 16. Legacy Polygon and Ployface Meshes.


16.1. Mesh building procedures.
16.2. PolygonMesh.
16.3. Smoothing the PolygonMesh.
16.4. Sample Program: Creating a PolygonMesh.
16.5. PolyfaceMesh.
16.6. Sample Program: Creating a PolyfaceMesh.
16.7. Modifying Polygon and Polyface Meshes.
16.8. Summary.

Chapter 17. Solid Modeling.


17.1. 3DSolid Primitives.
17.2. Creating a Primitive using ActiveX.
17.3. Creating 3DSolids from 2D or 3D objects.
17.4. Creating Regions.
17.5. Sample Program: Complex Regions.
17.6. Properties and Methods of the 3DSolid object.
17.7. Sample Program: Extruded Solid.
17.8. Sample Program: Solid by Sweeping along a path.
17.9. Sample Program: Sweeping along a Helix.
17.10. AddRevolvedSolid: Solids of Revolution.
17.11. Sample Program: Creating a Solid of Revolution.
17.12. Physical and Geometric Properties.
17.13. Summary.

Chapter 18. Editing 3D Solids.


18.1. Slicing Solids.
18.2. Sample Program: Polyhedra obtained by slicing 3DSolids.
18.3. Sectioning 3DSolids.
18.4. Sample Program: Sections of a Sphere.
18.5. Boolean operations on 3DSolids.
18.6. Sample Program: UNION and SUBTRACTION operations.
18.7. Sample Program: Part created by INTERSECTION.
18.8. CheckInterference: Interference operations.
18.9. Sample programs: 3DSolid TRIM and SPLIT commands.
18.10. Section objects.
18.11. Sample program C:SOL-SECT.
18.12. Summary.

Chapter 19. Subdivision Surfaces.


19.1. Programming MESH objects with Visual LISP.
19.2.Creating MESH entities with ENTMAKE.
19.3.Sample Program: Polyhedral MESH.
19.4. Sample Program: MESH approximating mathematical functions.
19.5. Creating meshes using command/vl-cmdf.
19.6. Modifying Subdivision Surfaces.
19.7. Sample Program: Modifying MESH objects.
19.8. Generalizing MESH transformations.
19.9. Sample Program: Shape up a MESH object.
19.10. Meshes created from 2D entities.
19.11. Summary.

Chapter 20. Procedural and NURBS Surfaces.


20.1. Creating surfaces.
20.2.Properties exposed by Surfaces.
20.3.Sample Program: NURBS surfaces.
20.4. Creating a Procedural surface.
20.5. Sample Program: Associative Surface with Parametric Profiles.
20.6. Modifying the cross-section’s constraint parameters.
20.7. Creating a dynamic block from the associative surface.
20.8. Summary.

Index of Function Listings.


Appendix 1. Functions Library.
Appendix 2. Contents of other Volumes.
Part 4
Programming 3D
3D represents a major breakthrough for Computer Aided Design applications like AutoCAD.
Aside from seeing a realistic representation of the designed object, understandable even for
those unfamiliar with the technical drawing conventions, it offers the advantage that, instead
of drawing each projection separately, we can now generate all the views automatically from
the three-dimensional model. We can even take advantage of new techniques of
stereolithography, generating and sending through the new _3DPRINT command a STL ile
created from our 3D model to a specialized company that will return in a few days the plastic
model of our design.

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.

Moreover, Autodesk's treatment of 3D features has been, for AutoLISP programmers,


somewhat erratic. Release 11 (1990) included the Advanced Modeling Extension (AME) for
solid modeling, which came with a very complete AutoLISP Solid Modeling API But solid
modeling as introduced by Release 11 changed with Release 13, which adopted the ACIS
modeling engine, removing all the AutoLISP API extensions. Release 14 (February 1997)
recovered the ability to program the new solids, but now in its commitment to the Microsoft
platform (AutoCAD abandoned the Apple platform with Release 14) the programming options
were only implemented in VBA. VBA's short life was not foreseen. It was discontinued by
Microsoft in July 2007. VBA programs could soon be useless, forcing us to recode them using a
different platform.

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:

Constructive Solid Geometry (CSG).


Procedural or NURBS Surfaces.
Subdivision surfaces.

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.

1 Walker, John. The Autodesk File: Bits of History, Words of Experience.


Chapter 13
3D objects
For the creation of 3D objects AutoCAD has successively introduced a number of paradigms
that are outlined in Table 13.1.

Table 13.1. AutoCAD 3D Modeling systems.


Back in 1989 Autodesk described Release 10 as “the 3D version”. This Release introduced the User Coordinate
Systems (UCS) that allowed, for the first time, drawing on any plane in 3D space. It also introduced the possibility
3D Meshes.
of approximating 3D surfaces with Polygon and Polyface meshes made up of planar triangular and rectangular
faces.
Release 11 (1990) introduced solid modeling as its Advanced Modeling Extension (AME), especially useful in
Solid Modeling. mechanical design. The modeling engine has changed, from the original PADL to ACIS and in the latest versions
to ShapeManager, developed by Autodesk from the original ACIS.
AutoCAD 2007 introduced a series of commands like_LOFT or _SWEEP that produce “true” surfaces instead
Surface Modeling.
of surfaces approximated with Polygon or Polyface meshes.
With AutoCAD 2010 MESH objects were introduced. These objects, by allowing multi-resolution techniques are
Subdivision particularly suited for modeling increasingly larger and more complex geometries. In Computer Graphics these
Surfaces. objects are known as “Subdivision surfaces” and used, for example, in generating the “Levels of detail” (LODs)
used in 3D computer games or programs such as Google Earth.
Free-form surfaces generating and editing capabilities were enhanced in Release 2011 with the introduction of
NURBS Surfaces. Splines and NURBS surfaces which interact for the creation of extremely complex forms which can be edited
interactively using gizmos instead of the traditional command style.
With AutoCAD 2011 the associativity of surfaces was introduced. Associativity makes 3D surfaces respond to
Associative any modification of the 2D linear objects used in their definition. This, used in conjunction with geometric and
surfaces. dimensional constraints added in Release 2010, enables us to try out different design options without having to
remake the surface object.
As for the ways of displaying the 3D scene, they have been enriched by the introduction of photorealistic rendering
since Release 12, with significant improvements in Release 2007 and the unification of the material definitions in
AutoCAD 2011, making them compatible with other Autodesk applications. The so-called Visual Styles,
Visualization.
_VSMODE that were added in Release 2007 facilitate the edition of 3D objects displaying them so as to make
their characteristics more easily discernible. It is an improved version of the old _SHADE command that allows the
user to customize the screen display style.

13.1 Programming options from Visual LISP.


The possibilities for managing 3D objects from Visual LISP are closely related to the type of
entity to be used. We have seen that these entities have been added at different times, and the
functions available for working with them are largely related to that circumstance.

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.

13.2 How does AutoCAD work in 3D?


To successfully undertake the programming of 3D objects it is necessary to examine how the
application works and its peculiar idiosyncrasies. We know that the position of any point of
our model is de ined by its relation to three axes labeled X, Y and Z, which are perpendicular
to each other and intersect at a point known as the origin of coordinates. A position in space
is determined by the distances measured from the origin of coordinates in the directions of
those three axes. As the chronology in Table 13.1 shows, up to Release 10 (1989) it was not
possible to draw on planes that were not parallel to the XY plane. The only possibility for
creating three-dimensional models was using the Elevation and Thickness properties. The
Elevation, established from the value of the ELEVATION system variable determines the
distance from the XY plane of the plane in which the entity is drawn and the object’s Thickness
(preset by the THICKNESS system variable) de ines the length by which the object is
extruded in the Z axis direction. The need to create graphic entities in planes not parallel to
the default XY plane was solved with the introduction of User Coordinate Systems (UCS) that
could have any origin and any orientation of its XY plane in space. The default reference
system was now known as the World Coordinate System (WCS). The creation of most of the
classic AutoCAD entities remained possible solely in the XY plane, but now this plane could be
moved and rotated in any direction by creating a new User Coordinate System (UCS). To
record its orientation in space a new data item was added, the unit vector normal to the XY
plane XY in which they are drawn.

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:

Its origin, which coincides with the WCS origin.


The orientation of the X and Y axes, which are calculated from the 3D vector indicating
the positive direction of the OCS Z axis.
The current value of the ELEVATION system variable.

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.

Table 13.2. Coordinate systems associated with entity types.


Circles and Arcs.
Entities of a planar nature. All points are expressed in object
2D Polylines (AcDb2dPolyline, AcDbPolyline).
coordinates (OCS). They are extruded according to their
2D vertices (AcDb2dVertex).
thickness value. Their extrusion direction can be different
2D Texts, Attributes and Attribute Definitions.
from the WCS Z axis.
Forms, Block References (Inserts).
A Block Reference (INSERT) is a 2D object as its insertion
Hatches and Images.
point is given in OCS but may include 3D objects.
2D Solids and Traces.
Lines, Points, 3D Faces,
These entities (except for the planar Spline, the Ellipse and
XLine, Ray
the Point) do not lie in the plane determined by the value
3D Polylines (AcDb3dPolyline)
associated to DXF group code 210. All their points are
3D 3D Vertices (AcDb3dPolylineVertex)
expressed in World coordinates (WCS).
Splines, Ellipses,
The thickness property only applies to lines and points. Their
Meshes and 3D Mesh Vertices.
extrusion direction can be different from the WCS Z axis.
3DSolids.
Some points in dimension entities are expressed in WCS and
Dimensions
Other others in OCS coordinate systems.
Viewport. Data expressed in World coordinates (WCS).

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.

Transformation between coordinate systems.

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)
_$

Which, using the trans function would be:


_$ (trans (vlax-safearray->list (vlax-variant-value pt))(entlast) 0 :vlax-false )
(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.

Table 13.3. Values used to identify Coordinate Systems.


Code Constant System Remarks
0 acWorld WCS Default coordinate system.
1 acUCS UCS The coordinate system current at the time of evaluating the expression.
If used in combination with codes 0 or 1, it indicates the Display Coordinate
System (DCS) of the current viewport. In conjunction with code 3 it indicates
2 acDisplayDCS DCS
the current ModelSpace viewport. The origin of the DCS is stored in the
TARGET system variable and its Z axis is the view direction.
PaperSpace Display Coordinate System (P SDCS). Only used in combination
with code 2. This coordinate system can only be transformed from or to the
DCS of the ModelSpace active viewport. It is essentially a 2D transformation
3 acPaperSpaceDCS PSDCS
where X and Y coordinates are scaled and shifted if the as-vector argument is
0. If the from-cs argument is 3 the to-cs argument must be 2 and vice
versa.
An entity name (associated with group code -1 in the entity list). Note that for
4 acOCS OCS some objects the OCS is equivalent to the WCS, so that in these cases the
conversion between OCS and WCS does not make any difference.
A list of three real numbers representing the UCS normal vector. The normal
UCS Normal Vector. vectors are always specified in WCS coordinates. A normal vector (0,0,1)
specifies the WCS.

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 syntax for the TranslateCoordinates method is:


(vla-TranslateCoordinates
utility-object pt from-cs to-cs as-vector ocs-normal)

The Utility-object can be obtained by (vla-get-Utility *aevl:drawing*). The


other arguments are the same as in trans. The as-vector and ocs-normal arguments in
ax-trans must be supplied. If From-UCS or To-UCS is 4 (acOCS) ocs-normal should be
supplied, otherwise it should be nil.

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.

Table 13.4. Utility object methods with AutoLISP equivalents.


Utility Method: AutoLISP: Remarks:
AngleFromXAxis angle Gets the angle of a line from the X axis.
AngleToReal angtof Converts an angle as a string to a real number.
AngleToString angtos Converts an angle from a real value to a string.
DistanceToReal distof Converts a distance from string to real number.
GetAngle getangle Prompts for an angle.
GetCorner getcorner Prompts for the corner of a rectangle.
GetDistance getdist Prompts for a distance.
GetEntity entsel Prompts for selecting an object interactively.
Gets keywords from other Get... methods. Same as AutoLISPget...
GetInput get...
functions' arbitrary input.
GetInteger getint Prompts for an Integer value.
GetKeyword getkword Prompts for a keyword string.
GetOrientation getorient Prompts for an angle ignoring ANGBASE.
GetPoint getpoint Prompts for a point.
GetReal getreal Prompts for a real number.
GetString getstring Prompts for a String.
GetSubEntity nentsel Prompts for an object or subentity interactively.
InitializeUserInput initget Initializes GetKeyword and other Get... methods
PolarPoint polar Returns a point at a specified angle and distance.
Prompt prompt Displays a prompt on the command line.
RealToString rtos Converts a real number to a string.
TranslateCoordinates trans Translates a point from one coordinate system to another.

Table 13.5. Utility object methods without AutoLISP equivalents.


Utility Method: Remarks:
CreateTypedArray Creates a variant containing a typed arguments array.
GetRemoteFile Downloads the file specified by a URL.
IsRemoteFile Gets the URL from where a file was downloaded.
IsURL Validates a given URL.
LaunchBrowserDialog Displays a dialog box in which to enter a URL.
PutRemoteFile Uploads a file to a remote FTP location.
SendModeless
Indicates a modeless operation has ended.
OperationEnded
SendModeless
Indicates a modeless operation will start.
OperationStart

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.

A vector is usually represented by an arrow, connecting a starting point A to a terminal point


B. Given two points A and B in 3D space, the vector AB is given by

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:

V1 + V2 = (x1 + x2, y1 + y2, z1 + z2)


Unlike the sum of vectors, their multiplication can take two different forms. We can multiply
two vectors so as to obtain a single numerical value, their scalar product or dot product. Or we
can multiply them to obtain another vector, their vector product or cross product.

The scalar product of two vectors is:

V1 · V2 = (x1x2 + y1y2 + z1z2)

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:

|V| = (V1 · V2)1/2 = (x2 + y2 + z2)1/2

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:

(y1 z2 – z1 y2, z1 x2 – x1 z2, x1 y2 – y1 x2)

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))

;;; Vector addition


;;; Arguments: v1, v2, lists of three real numbers.
(defun v+v (v1 v2) (mapcar '+ v1 v2))

;;; Scalar product (dot product)


;;; Arguments: v1, v2, lists of three real numbers.
(defun x-scalar (v1 v2)
(apply '+ (mapcar '* v1 v2)))

;;; Vector length (module)


;;; Argument: v, a list of three real numbers.
(defun m-vec (v)
(sqrt (apply '+ (mapcar '* v v))))

;;; Unit vector


;;; Argument: v, a list of three real numbers.
(defun v-unit (v / m)
(cond ((zerop (setq m (m-vec v))) nil)
(t (mapcar '(lambda (n) (/ n m)) v))))

;;; Cross product (vector product)


;;; Arguments: v1, v2, lists of three real numbers.
(defun vec-prod (v1 v2)
(list (- (* (cadr v1) (caddr v2))
(* (cadr v2) (caddr v1)))
(- (* (car v2) (caddr v1))
(* (car v1) (caddr v2)))
(- (* (car v1) (cadr v2))
(* (car v2) (cadr v1)))))

Listing 13.4. Vector operations.

13.3 Transformation matrices.


Perhaps the most interesting of the transformation methods is vla-TransformBy. This
method executes the processing speci ied in a 4 x 4 transformation matrix. According to the
values included it can produce translations, rotations, scaling and symmetries.

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.

Table 13.6. Values that define the translation matrix.


Configuration of the translation matrix
1.0 0.0 0.0 Tx
0.0 1.0 0.0 Ty
0.0 0.0 1.0 Tz
0.0 0.0 0.0 1.0

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)))))

Listing 13.5. Translation function.

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.

Table 13.7. Values that define the rotation about X.


Configuration of the X rotation matrix.
1.0 0.0 0.0 0.0
0.0 (cos a) (sin a) 0.0
0.0 (- (sin a)) (cos a) 0.0
0.0 0.0 0.0 1.0

Table 13.8. Values that define the rotation about Y.


Configuration of the Y rotation matrix.
(cos a) 0.0 (sin a) 0.0
0.0 1.0 0.0 0.0
(- (sin a)) 0.0 (cos a) 0.0
0.0 0.0 0.0 1.0

Table 13.9. Values that define the rotation around Z.


Configuration of the Z rotation matrix.
(cos a) (- (sin a)) 0.0 0.0
(sin a) (cos a) 0.0 0.0
0.0 0.0 1.0 0.0
0.0 0.0 0.0 1.0

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))

Listing 13.6. Radian-Degree conversions.

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)))))

Listing 13.7. Rotation about X.

(defun ax-rot-y (obj a)


(setq a (dtr a))
(vla-TransformBy
obj
(vlax-tmatrix
(list (list (cos a) 0.0 (sin a) 0.0)
(list 0.0 1.0 0.0 0.0)
(list (- (sin a)) 0.0 (cos a) 0.0)
(list 0.0 0.0 0.0 1.0)))))

Listing 13.8. Rotation about Y.

(defun ax-rot-z (obj a)


(setq a (dtr a))
(vla-TransformBy
obj
(vlax-tmatrix
(list (list (cos a) (- (sin a)) 0.0 0.0)
(list (sin a) (cos a) 0.0 0.0)
(list 0.0 0.0 1.0 0.0)
(list 0.0 0.0 0.0 1.0)))))

Listing 13.9. Rotation about Z.

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.

Table 13.10. Values defining the scale matrix.


Configuration of the scaling matrix.
Sx 0.0 0.0 0.0
0.0 Sy 0.0 0.0
0.0 0.0 Sz 0.0
0.0 0.0 0.0 1.0

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!")))

Listing 13.10. XYZ Scaling function.

3D Symmetry Transformations.

3D symmetry transformations are merely scaling changes in which some of the


transformation factors are negative. To produce symmetry about the ZX plane, a negative
scale factor for the Y axis is passed. For axial symmetries about the coordinate system’s axes,
two of the three factors will have a value of -1, and for a central symmetry (point re lection)
about the origin the three factors should be -1. The code is the same as for the scaling
transformation.

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.

Table 13.11. Shear values in the X direction


Shear Matrix in the X direction
1.0 SHx 0.0 0.0
0.0 1.0 0.0 0.0
0.0 0.0 1.0 0.0
0.0 0.0 0.0 1.0

Table 13.12. Shear values in the Y direction


Shear Matrix in the Y direction
1.0 0.0 0.0 0.0
SHy 1.0 0.0 0.0
0.0 0.0 1.0 0.0
0.0 0.0 0.0 1.0

Table 13.13. Shear values in the Z direction


Shear Matrix in the Z direction
1.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0
SHz SHz 1.0 0.0
0.0 0.0 0.0 1.0

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!")))

Listing 13.11. Shear along X.

(defun ax-shear-y (obj factor / res)


(setq res
(vl-catch-all-apply
'vla-TransformBy
(list obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 0.0)
(list factor 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!")))

Listing 13.12. Shear along Y.

(defun ax-shear-z (obj factor / res)


(setq res
(vl-catch-all-apply
'vla-TransformBy
(list
obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 0.0)
(list 0.0 1.0 0.0 0.0)
(list factor factor 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!")))

Listing 13.13. Shear along Z.

13.4 Sample Program: Scaling transformation


specifying the base point.
The function C:TRANSFORM (Listing 13.14) implements a new AutoCAD command that
deforms an object along the X, Y and Z axes using different scale factors for each one. This
command is most appropriate to work with objects such as those produced using the tools
available in the Ribbon’s Mesh tab, but will produce errors with other objects, especially
Surfaces and ACIS solids (Solid tab).

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.")))

Listing 13.14. Command for XYZ scaling.

Figure 13.1 shows the form resulting form applying this command to a mesh approximating a
sphere.

Figure 13.1. Deformed mesh.

13.5 Transformation between Coordinate Systems.


As we said before, the way AutoCAD draws entities in any orientation in 3D space is based on
the creation of new coordinate systems in which the coordinate axes orientation and the
system’s origin vary. This introduces an additional degree of complexity to three-dimensional
transformations.

Determining whether the current UCS is the World Coordinate System.

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.

Obtaining the UCS Transformation Matrix.

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*".

Creating a new UCS.

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"
_$

Obtaining the current UCS Transformation Matrix.

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.

13.6 Viewpoint and Visual Style.


The representations of a 3D object on the screen or in print, in both cases projected onto a 2D
plane, must include the information necessary for the observer to identify, for a particular
orientation in space, which parts of the object are closer to the observer. These techniques
include parallel and perspective projections, especially when combined with techniques for
differentiating the representation of visible and hidden edges or directly removing hidden
lines and shading faces according to their angle relative to the viewing direction or to the
position of a light source. To automate this process so that we can immediately appreciate
the resulting volumes, we have developed the ax-view function (Listing 13.17) that can be
added to any 3D modeling program.

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:

direction: A list of three numbers.


zoom: Any value other than nil will force a ZOOM EXTENTS.
(defun ax-view (direction zoom / vport)
(setq vport (vla-get-ActiveViewport *aevl:drawing*))
(vla-put-Direction
vport
(vlax-3d-point direction))
(vla-put-ActiveViewport *aevl:drawing* vport)
(vlax-release-object vport)
(if zoom
(vla-ZoomExtents *aevl:acad*))
(princ))

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.

Table 13.14. Direction vectors for the main views.


Orthogonal Views
Direction: Vector: Direction: Vector:
Top '(0 0 1) Left '(-1 0 0)
Bottom '(0 0 -1) Front '(0 -1 0)
Right '(1 0 0) Back '(0 1 0)
Isometric Views
Direction: Vector: Direction: Vector:
NE Top '(1 1 1) NE Bottom '(1 1 -1)
NW Top '(-1 1 1) NW Bottom '(-1 1 -1)
SE Top '(-1 -1 1) SE Bottom '(-1 -1 -1)
SW Top '(1 -1 1) SW Bottom '(1 -1 -1)

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.

Table 13.15. Variables that control the visual appearance.


Variable Description Values
VSBACKGROUNDS Controls whether backgrounds are displayed. 0: Off / 1: On
VSEDGECOLOR Color for edges. Color index or RGB.
VSEDGEJITTER Edges of 3D objects appear wavy. 1: Low / 2: Medium / 3: High
VSEDGELEX Extends edges beyond intersections. From 1 to 100 pixels.
VSEDGEOVERHANG Extends edges beyond intersections. From 1 to 100 pixels.
VSEDGES Types of edge displayed. 0: None / 1: Isolines / 2: Face
VSEDGESMOOTH Angle at which crease edges are displayed. Between 0 and 180.
0: Normal / 1: Monochrome / 2: Tint / 3:
VSFACECOLORMODE Controls how the faces color is calculated.
Desaturate
Controls the display of specular highlights on
VSFACEHIGHLIGHT Between -100 and 100.
faces without materials.
VSFACEOPACITY Transparency level for 3D objects. Between 100% and 0% opacity.
VSFACESTYLE Faces display mode. 0: No / 1: Real / 2: Gooch
VSHIDEPRECISION Precision for hides and shades. 0: Simple / 1: Double
VSINTERSECTIONCOLOR Color of intersection polylines. Color index or RGB.
VSINTERSECTIONEDGES Display of intersection edges. 0: Off / 1: On
VSINTERSECTIONLTYPE Intersections Linetype. Between 0 and 11.
VSISOONTOP Isolines on top of shaded objects. 0: Off / 1: On
VSLIGHTINGQUALITY Lighting quality. 0: Faceted / 1: Smooth / 2: Smoothest
0: None / 1: Materials / 2: Materials and
VSMATERIALMODE Display of materials.
Textures
VSMONOCOLOR Faces Monochrome/Tint color. RGB color.
VSOBSCUREDCOLOR Color of hidden lines. Color index or RGB.
VSOCCLUDEDEDGES Hidden edges display. 0: Off / 1: On
VSOCCLUDEDLTYPE Linetype of hidden lines. Between 0 and 11.
0: No Shadows / 1: Ground shadows / 2: Full
VSSHADOWS Controls whether to show shadows.
shadows
PERSPECTIVE View is displayed in perspective. 0: Off / 1: On

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.

The visibility of the obscured (hidden) edges is controlled by the VSOBSCUREDEDGES


variable. Setting its value to 0 will not display the hidden edges. Another type of edges is the
one corresponding to intersections. These are the edges generated when where two different
objects intersect and their visibility is enabled or disabled by the VSOCCLUDEDEDGES system
variable. For the cover image, being a single object, there will be no difference setting its value
On (1) or Off (0).

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))))

Listing 13.18. Function that sets a custom visual style.

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.

Setting the visual mode for 3D programs.


We can prede ine views a visual styles with functions like those in Listing 13.19. These
functions include the direction vectors for eight isometric views, from above and from below,
applying ZOOM EXTENTS and the visual style defined by the var-vis function.
(defun ax-top () (defun ax-bottom ()
(ax-view '(0 0 1) t) (ax-view '(0 0 -1) t)
(var-vis)) (var-vis))
(defun ax-right () (defun ax-left ()
(ax-view '(1 0 0) t) (ax-view '(-1 0 0) t)
(var-vis)) (var-vis))
(defun ax-front () (defun ax-back ()
(ax-view '(0 -1 0) t) (ax-view '(0 1 0) t)
(var-vis)) (var-vis))
(defun ax-NEt () (defun ax-NEb ()
(ax-view '(1 1 1) t) (ax-view '(1 1 -1) t)
(var-vis)) (var-vis))
(defun ax-NWt () (defun ax-NWb ()
(ax-view '(-1 1 1) t) (ax-view '(-1 1 -1) t)
(var-vis)) (var-vis))
(defun ax-SWt () (defun ax-SWb ()
(ax-view '(-1 -1 1) t) (ax-view '(-1 -1 -1) t)
(var-vis)) (var-vis))
(defun ax-SEt () (defun ax-SEb ()
(ax-view '(1 -1 1) t) (ax-view '(1 -1 -1) t)
(var-vis)) (var-vis))

Listing 13.19. Functions that set the 3D isometric view.


13.7 Summary.
This chapter introduces some of the essential concepts that must be mastered to deal with
programming in a 3D environment. These include:

The transformation between Coordinate Systems.


Vector operations.
Transformations in space, including translation, rotation, scaling, etc. and the
transformation matrices through which they are specified.
Modification of the 3D viewpoints so as to aid in the comprehension of the generated
forms.
How to set through programming the 3D objects visualization mode including colors,
shadows and transparencies.

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.

14.1 Creating SPLINE entities.


A spline curve is de ined by a set of point coordinates that indicate the general shape of the
curve. These points are adjusted by piecewise polynomial functions according to two
different procedures:
Fit Points: Splines created with the Fit method. The curve passes through each point, in
which case (Figure 14.1, Left) it is an interpolation.
Control Vertices: Splines created from the CV method. The curve adopts the general form
of the polygon but does not necessarily pass through any of the control points, in which
case (Figure 14.1, Right) it is an approximation.

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.

System Variables affecting the creation of Splines.


In any of the methods employed in creating Splines some parameters may use default values
that are controlled from a number of system variables. These variables are described in Table
14.1.

Table 14.1. System Variables affecting the creation of SPLINE entities.


Variable: Values: Description:
Defines the Degree of the spline created from the
SPLDEGREE 1 to 5 Control Vertices method. Those created by Fit Points
are always Degree 3 Splines.
0 = Chord distance.
Default for Spline knots parametrization using the Fit
SPLKNOTS 1 = Square root of chord distance.
Points method.
2 = Uniform distance.
0 = Creates splines using Fit Points.
SPLMETHOD Default method for creating splines.
1 = Creates splines using Control Vertices.

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)

Where the arguments are:

space: may be ModelSpace, a PaperSpace layout or a block definition.


PointsArray: a safearray containing a succession of the X, Y and Z coordinate values
for all the curve’s fit points.
StartTangent, EndTangent: two safearrays with the initial and final tangent
direction vectors’ X, Y and Z coordinates. Vector values of '(0 0 0) produce Splines
similar to those produced by the Release 2012 command.
(defun ax-spline (space point-list / start-tg end-tg
points arr-pts)
(setq start-tg (vlax-3d-point '(0 0 0))
end-tg (vlax-3d-point '(0 0 0))
points (apply 'append point-list)
arr-pts (vlax-make-safearray
vlax-vbDouble
(cons 0 (1- (length points))))
arr-pts (vlax-safearray-fill arr-pts points))
(vla-AddSpline
(current-space *aevl:drawing*)
arr-pts
start-tg
end-tg))

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.

Creating SPLINES with ENTMAKE.


Its access to the drawing’s database gives entmake richer possibilities for creating Splines.
With entmake we can create Splines not only by the Fit Points method, but also by the
Control Vertices method.

Splines by the Fit Points method.

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:

The entity type "SPLINE". Associated with group code 0.


The "AcDbEntity" Subclass name. Associated with the first group code 100.
The "AcDbSpline" Subclass name. Associated with the second group code 100.
The Spline curve’s Degree associated with group code 71. Always 3 for the Fit Points
method.
Number of Fit Points. Associated with group code 74.
A sublist for each Fit Point coordinates, associated with group code 11.
(defun ent-spline-FP (point-list)
(entmake
(append (list ‘(0 . "SPLINE")
‘(100 . "AcDbEntity")
‘(100 . "AcDbSpline")
‘(71 . 3)
(cons 74 (length point-list)))
(mapcar ‘(lambda (x) (cons 11 x))
point-list))))

Listing 14.2. Function for creating a Fit Points Spline.

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.

Undocumented properties for group code 70.


The properties that depend on DXF group code 70 are poorly documented up to AutoCAD
2013. In fact when creating a Spline using the ent-spline-FP function the value associated
with group code 70 is 1064, when the values appearing in the DXF reference do not exceed
16. This value is the consequence of 10 undocumented bits. The meaning of these bits,
according to the ADN Developer Technical Services1, is shown in Table 14.2.

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))

Splines by the Control Vertices method.


We will now address the procedure for creating Splines by the Control Vertices method. This
procedure is more complex because of the parameters that must be supplied. These
parameters are:

The curve’s Degree.


Its Control Vertices (ControlPoints).
Its Knot vector (Knots).

Degree of the curve.

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.

Control Vertices (ControlPoints).

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.

Figure 14.2. Effect of Weight in control vertices.

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 curve’s Knot vector.

A curve is de ined by a sequence of polynomials. The number of polynomials needed to de ine


the curve depends on the number of control vertices and the curve’s Order. The knot vector
determines the points where polynomial curve segments come together in the parametric
space (or domain) of the Spline. The term knot stems from the fact that they de ine where
polynomials are "tied".

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.

Considering k = Order, n = number of Control Vertices and t = Knot, we can distinguish


between several types of 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.

Function for generating the Knot Vector.


A standard function named knot-vector will be used in the creation of Splines by the
Control Vertices method. It receives as arguments the number of control vertices (cv) and the
curve’s Degree, returning a list with the knot vector values needed to create a Uniform
NURBS curve. To take into account those cases where we must extend the curve to the initial
and inal vertices of the control polygon a third Boolean argument clamped is used, that in
case it is T will add an additional repetition in the initial and final knot groups.

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))

Listing 14.3. Function that creates the Knot Vector.

Function that creates a Spline by the control vertices method.

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.

From these data:

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.

14.2 Spline Methods and Properties.


ActiveX Methods and Properties can be used for the modi ication of Spline entities. Table 14.3
describes the Methods and Table 14.4 the Properties.

Table 14.3. SPLINE object Methods.


Method: Description:
Add a fit point at the specified index. If index is a negative number the point is added at the beginning of
AddFitPoint the spline. If greater than the number of fit points it is added at the end. Only supported by Splines created
by the Fit Points method.
_$ (vla-AddFitPoint objSpline -1 (vlax-3d-point '(150.0 200.0 0.0)))
nil
Deletes the fit point at the specified index. Raises an error if the fit point for the specified index does not
DeleteFitPoint
exist. Only supported by Splines created by the Fit Points method.
_$ (vla-DeleteFitPoint objSpline 2)
nil
Specifies the Spline’s Order. The Order equals Degree + 1. Elevating the Order increases uniformly
the control vertices (ControlPoints) enabling a more precise local control. Once elevated its value
cannot be reduced. When the order of a Spline is elevated it becomes a control vertices defined Spline, not
ElevateOrder
allowing access to its fit points, EndTangent nor StartTangent. To edit a Spline defined by control
vertices its ControlPoints property or its SetControlPoint and GetControlPoint methods
and can be used
$ (vla-get-Degree objSpline)
3
_$ (vla-ElevateOrder objSpline 5)
nil
_$ (vla-get-Degree objSpline)
4
GetControlPoint Gets the coordinates of the control vertex at the specified index.
_$ (setq pt (vla-GetControlPoint objSpline 1))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value punto))
(693.807 1073.25 0.0)
GetFitPoint Gets the fit point at the specified index. Only supported by Splines created by the Fit Points method.
_$ (setq pt (vla-GetFitPoint objSpline 1))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value pt))
(702.14 1080.72 0.0)
GetWeight Gets the weight of the control vertex at the specified index.
_$ (vla-GetWeight objSpline 1)
-1.0
Removes Spline’s fit data. If applied to a Spline created by fit points transforms it into a control vertices
PurgeFitData
Spline.
(vla-PurgeFitData objSpline)
nil
Reverses the order of the polygon vertices of the changes the curve's parameterization, but geometrically it
Reverse
remains the same.
_$ (vla-Reverse objSpline)
nil
SetControlPoint Sets the position of a control vertex at the specified index in a spline created by control vertices.
_$ (vla-SetControlPoint objSpline 2 (vlax-3d-point '(7.0 11.0 0.0)))
nil
SetFitPoint Sets the position of a fit point at the specified index in a Spline created by fit points.
_$ (vla-SetFitPoint objSpline 2 (vlax-3d-point '(500.0 300.0 0.0)))
nil

Table 14.4. Properties of the SPLINE object.


Property: Description:
SetWeight Sets the weight of a control vertex at the specified index.
_$ (vla-SetWeight objSpline 3 1.0)
nil
_$
Area enclosed by a planar Spline. Its Planar condition should be checked before. For open
Area Splines area is computed as if a line joined the start and end points. Read only. Equivalent to the
Visual LISP function vlax-curve-GetArea.
_$ (if (equal (vla-get-isPlanar objSpline) :vlax-true)
(vla-get-Area objSpline))
159451.0
Closed Specifies whether the spline is open or closed. Since Release 2012 Closed2 should be used, as its
Closed2 Closed property is read-only.
_$ (vla-get-Closed objSpline)
:vlax-false
_$ (vla-put-Closed2 objSpline :vlax-true)
nil
_$ (vla-get-IsPeriodic objSpline)
:vlax-true
Gets or sets the control polygon vertices of a spline. To retrieve their number the
ControlPoints
NumberOfControlPoints property is used.
_$ (setq CPs (vla-get-ControlPoints objSpline))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value CPs))
(6000.0 0.0 0.0 5000.0 2000.0 0.0 4000.0 0.0 0.0 3000.0 2000.0 0.0 2000.0 0.0 0.0)
Degree of the Spline's polynomial representation. All Fit Point Splines are Degree 3. Control Vertex
Degree
Splines allow choosing the Degree. Degree2 supports increasing the Degree but not reducing it.
Degree2
The effect is the same as that obtained from ElevateOrder, introducing new control vertices.
_$ (vla-get-Degree objSpline)
1
_$ (vla-put-Degree2 objSpline 3)
nil
Figure 14.5 Left: Degree 1 Spline. Right: Degree elevated to 3 and modification of the new vertices.

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.

Figure 14.6. Effect of increased tolerance.

_$ (vla-put-FitTolerance objSplineFP 1000)


nil
Determines whether the spline is Periodic. A Periodic Spline or NURBS surface are closed entities
IsPeriodic that have the maximum continuity (C2). The same result can be obtained with Visual LISP function
vlax-curve-IsPeriodic. Since release 2012 closing a Spline makes it periodic.
_$ (vla-get-IsPeriodic objSpline)
:vlax-true
_$ (vlax-curve-IsPeriodic objSpline)
T
IsPlanar Determines whether the spline is planar. Equivalent to the vlax-curve-isPlanar function.
_$ (vla-get-IsPlanar objSplineFP)
:vlax-true
_$ (vlax-curve-isPlanar entSplineVC)
T
IsRational Determines whether a spline is rational.
_$ (vla-get-IsRational objSplineFP)
:vlax-false
Gets the Spline's type of parameterization. In Splines created by the Fit Points method it can also set
the parameterization type. They correspond to the AcSplineKnotParameterizationType
constants:
0 acChord
KnotParameterization
1 acSqrtChord
2 acUniformParam
15 acCustomParameterization
The acCustomParameterization type is applied to Control Vertices splines.
_$ (vla-get-KnotParameterization objSplineFP)
0
_$ (vla-put-KnotParameterization objSplineFP 1)
nil
_$ (vla-get-KnotParameterization objSplineFP)
Knots Gets a Spline's knot vector.
_$ (setq knots (vla-get-Knots objSpline))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value knots))
(0.0 0.0 0.0 0.0 31.832 63.1867 96.0567 127.071 127.071 127.071 127.071)
NumberOfControlPoints Gets the number of a Spline's control vertices.
_$ (vla-get-NumberOfControlPoints objCVSpline)
5
NumberOfFitPoints Gets a Spline's number of Fit Points. For Control Vertices Splines the number of Fit Points is 0.
_$ (vla-get-NumberOfFitPoints objFPSpline)
5
Gets the control polygon's visibility status. Its values correspond to the AcSplineFrameType
SplineFrame constants:
0 acShow
1 acHide
_$ (vla-get-SplineFrame objSpline)
1
Gets the method by which the spline was created. Its values correspond to the
SplineMethod AcSplineMethodType constants:
0 acFit
1 acControlVertices
_$ (vla-get-SplineMethod objSplineFP)
0
StartTangent Gets the Spline's initial tangent. Only supported by Splines created by the Fit Points method.
_$ (setq tg-ini (vla-get-StartTangent objFPSpline))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value tg-ini))
(10.9701 48.9723 0.0)
Weights Gets the Spline's weight vector.
_$ (setq weights (vla-get-Weights objSpline))
# <variant 8197 ...>
_$ (vlax-safearray->list (vlax-variant-value weights))
(1.0 2.0 1.0)

14.3 Creating a Helix shaped Spline by Control


Vertices.
Having solved how to create Splines by the control vertices method we will now discuss the
possibility of creating a helix-shaped Spline, knowing that there is no ActiveX option to do so.
First we need a list of its vertices coordinates. We shall calculate a given number of vertices
for each turn, at a distance from the helix’s central axis computed taking into account the top
and base radii. To compute the vertices coordinates we will use the cv-helix function
(Listing 14.5) that receives as arguments the center of the helix’s base, its base and top radii,
its height, its precision (the number of vertices per turn), the number of turns and the helix’s
twist direction (clockwise or counterclockwise).

From these arguments the function calculates:

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.

14.4 Sample Program: Creating a HELIX.


Knowing that we can create a Spline shaped as a Helix we wonder whether it would be
possible to create a Helix object just like the one created by the AutoCAD HELIX command.
Consulting the ActiveX reference we find that you cannot create a Helix object with ActiveX. So
this would be a demonstration of classic AutoLISP’s power: entmake can create such an
entity. It would be much easier if AutoCAD’s documentation would be more explicit. But we
have gone most of the way. One bene it of creating a proper HELIX entity rather than settle
with the helical Spline we have just programmed is that a Helix object can be easily modi ied
taking advantage of its ActiveX properties.

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.

Table 14.5. Additional group codes for the HELIX entity.


Code: Value: Description:
100 "AcDbHelix" Subclass name
90 29 Major release number.
91 65 Maintenance release number.
10 XY Z Axis base point.
11 Xi Yi Zi Helix start point. Defines the base radius.
12 Xv Yv Zv Axis direction vector.
40 top-radius Helix radius.
41 turns Helix's number of turns.
42 pitch Turn height.
Handedness; 0: Left; 1: Right.
290 0 or 1
Considered with reference to the axis's direction vector (group code 12).
Constraint type (may be omitted):
0 = Constrains turn height
280 0 or 1
1 = Constrains turns
2 = Constrains height

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)))))

Listing 14.6. Function that creates a HELIX entity with entmake.

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:

the list of vertices,


the curve’s Degree, that for the HELIX should be 3 (Cubic),
nil as the third argument so an unclamped knot vector is applied
The point selected as the center of the base,
the top and base radii,
the height,
the precision, i.e., the number of vertices in each turn,
the number of turns for the HELIX,
the twist, indicating if its rotation is clockwise or counterclockwise.
(defun spline-helix-data (/)
(initget 1)
(setq base-center
(getpoint
"\nBase center: "))
(initget (+ 1 2 4))
(setq base-radius
(getdist
base-center
"\nBase radius: "))
(initget (+ 1 2 4))
(setq top-radius
(getdist base-center
"\nTop radius: "))
(initget (+ 1 2 4))
(setq height
(getdist base-center
"\nHelix height: "))
(initget (+ 1 2 4))
(setq turns
(getint "\nNumber of turns: "))
(initget "CW CCW")
(setq twist
(getkword
"\nTwist direction [CW/CCW] <CCW>:"))
(if (or (null twist) (= twist "CCW"))
(setq twist 1)
(setq twist 0))
(initget (+ 1 2 4))
(setq resolution
(getint
"\nVertices for each turn: ")))

Listing 14.7. Helix data input function.

(defun C:ENT-HELIX (/ base-center base-radius


top-radius height resolution
turns twist vertices-list)
(spline-helix-data)
(setq vertices-list
(cv-helix
base-center base-radius top-radius
height resolution turns twist))
(ent-helix-CV
vertices-list 3 nil base-center base-radius
top-radius turns turn-height twist)
(princ))

Listing 14.8. Main function C:ENT-HELIX.

Creating a Spline from a Helix.


Knowing in detail the relationship between the Spline and the Helix we can now de ine an
opposite function, converting an existing Helix into a Spline. We know this can be achieved
using the EXPLODE command but not by XPLODE. It is also not possible using the ActiveX
programming interface. The HELIX does not support the Explode method. But in the same
way we created a Helix adding to the Spline’s entity list a inal section starting with the
AcDbHelix subclass indicator, we can create the Spline by simply removing this section and
changing the entity type to SPLINE. The helix->spline function (Listing 14.9) receives a
Helix’s entity name and creates a Spline. If successful, it deletes the original Helix returning
the new Spline’s entity name.
(defun helix->spline (ename /)
(cond
((entmake
(subst
‘(0 . "SPLINE")
‘(0 . "HELIX")
(reverse
(cdr
(member ‘(100 . "AcDbHelix")
(reverse (entget ename)))))))
(entdel ename)
(entlast))))

Listing 14.9. Conversion of a HELIX entity into a SPLINE.

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:

The curve's Degree.


The Control Vertices.
The Knot Vector and its relationship to the curve type.
The Weights assigned to vertices and their effects.

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].

1 Information supplied by Marat Mirgaleev, Developer Technical Services.


Chapter 15
VLAX-CURVE... measuring curves and
something else
The vlax-curve... functions are one of Visual LISP’s most interesting contributions. Their
importance is especially evident when working in applications for a 3D environment.

15.1 Visual LISP's VLAX-CURVE Extensions.


Like the other Visual LISP ActiveX extensions, vlax-curve... functions are not available
when starting AutoCAD. These functions identi ied with the vlax-curve pre ix, can be
applied to the solution of a number of issues when operating on curves. To enable them it will
be necessary as always, to evaluate (vl-load-com). Any program using them should be
initiated with a call to this function.

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.

15.2 Common arguments.


We shall describe, first of all, the arguments that these functions must receive.

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))

For some of the functions this single argument will be enough.

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 .

The parametric representation of a curve is achieved by expressions like:

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:

x(u) = axu3 + bxu2 + cxu + dx

y(u) = ayu3 + byu2 + cyu + dy

z(u) = azu3 + bzu2 + czu + dz

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:

The interval between the initial and final parameter.


The position of points along the curve corresponding to evenly spaced parameter values .

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.

15.3 Determining a curve's length.


We will begin with those functions that allow us to ind length along the curve. To do this we
have:
(vlax-curve-GetDistAtParam curve-object parameter)
(vlax-curve-GetDistAtPoint curve-object point)

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)))

Listing 15.1. Obtaining the total length of a curve.

Sample Program: READ-LENGTH.


T h e ax-curvelength function (Listing 15.1) receives a curve object, obtains its end
parameter applying vlax-curve-GetEndParam and uses it as argument for vlax-
curve-GetDistAtParam. It will be tested within the function C:READ-LENGTH (Listing
15.2) that prompts the user for the selection of an entity establishing a reference to its VLA-
object.
(defun C:READ-LENGTH (/ obj)
(if (setq obj
(car (entsel
"\nCurve to measure:")))
(alert
(strcat "The object "
(cdr (assoc 0 (entget obj)))
" measures\n"
(vl-princ-to-string
(ax-curvelength
(vlax-ename->vla-object obj)))
" length units."))
(alert "No object selected.")))

Listing 15.2. Test function for AX-CURVELENGTH.

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.

15.4 Distance between points along a curve.


A more general problem would be to ind the distance between any two points on a curve.
This is the aim of the function ax-dist-points. Here vlax-curve-GetDistAtParam is
also used, but in this case the distance between the beginning of the curve and each of the
points must be retrieved, so the two distances can be subtracted to obtain the desired result.

Distance from the beginning of the curve to a point.


The function ax-dist-point (Listing 15.3) has been designed to ind each of these
distances. This function must receive as arguments the curve object and the point to which
the length must be found. Note that these functions take into account the possibility of user
mistakes. For this reason the call to (vlax-curve-GetParamAtPoint pt curve-obj)
is used as the condition in an if expression.

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)

that will then return the corresponding distance.


(defun ax-dist-point (curveobj pt / param)
(if (setq
param (vlax-curve-GetParamAtPoint
curveobj
(trans pt 1 0)))
(vlax-curve-GetDistAtParam
curveobj
param)))

Listing 15.3. Length along the curve to any point.

Sample Program: C: DIST-TO-POINT.


The C:DIST-TO-POINT function (Listing 15.4) will test the operation of the ax-dist-
point function. In this function three conditions for the if conditional are grouped by the
logical operator and, which means that all must be met for the message reporting the
measured distance to be displayed. Otherwise, there may be two problems: failure to
designate the object or that the selected point is not on the object, conditions that are
checked for selecting the error message that is displayed.
(defun C:DIST-TO-POINT (/ obj pt dist)
(prompt "\nSelect curve to measure: ")
(cond
((setq obj (car (entsel)))
(setq obj (vlax-ename->vla-object obj)
dist (ax-dist-point
obj
(getpoint "\nSelect point: ")))
(alert
(if dist
(strcat
"Distance from starting point"
"\nto selected point is\n"
(rtos dist 2 2))
"The point is not on the object")))
(t (alert "No object selected"))))

Listing 15.4. Measuring the distance from the beginning of the curve to a point.

Distance between two selected points.


The function ax-dist-betweenpoints (Listing 15.5) makes two successive calls to ax-
dist-point (Listing 15.3) and then subtracts the results. In this case we must also
consider the possibility that one or both points may not be on the curve. For this reason the
logical operator and will be applied to the values returned by the two calls to ax-dist-
point, checking that none of them is nil. If the condition is met, the values assigned to the
variables dist1 and dist2 are subtracted. As we have no control over the order in which the
user will select the points, it might be possible to obtain a negative number if dist2 is
greater than dist1. This dif iculty is solved by applying the abs function that returns the
absolute value of the numeric argument it receives.
(defun ax-dist-betweenpoints
(curveobj pt1 pt2 / dist1 dist2)
(if (and (setq dist1 (ax-dist-point
curveobj
(trans pt1 1 0)))
(setq dist2 (ax-dist-point
curveobj
(trans pt2 1 0))))
(abs (- dist1 dist2))))

Listing 15.5. Determining the distance between two points on the curve.

Sample Program: C:DIST-BETWEEN-POINTS.


As for the above functions, we will de ine the test function C:DIST-BETWEEN-POINTS
(Listing 15.6) that includes the prompts for the curve to measure and for the two points that
define the desired distance.
(defun C:DIST-BETWEEN-POINTS
(/ obj pt1 pt2 dist)
(prompt "\nSelect curve to measure: ")
(if (and (setq obj (car (entsel)))
(setq pt1
(getpoint "\nPoint 1: "))
(setq pt2
(getpoint "\nPoint 2: "))
(setq dist
(ax-dist-betweenpoints
(vlax-ename->vla-object obj)
pt1
pt2)))
(alert
(strcat
"\nThe distance between the points"
"\nequals \n"
(rtos dist 2 2)))
(alert
(if (null obj)
"No object selected"
"The point is not on the object"))))

Listing 15.6. Program that measures the distance between two points along a curve.

15.5 Measuring Areas


We will now consider the vlax-curve-GetArea function that returns the area bounded by
a curve, with results largely similar to the Object option in the AREA command. If the object
is an open curve, its area will be computed as if closed by a line joining its end points. In this
case, if that line de ined different enclosures, it would return the absolute value of the
difference between them. Conversely if it were a closed curve that intersects itself, the value
returned is the sum of the areas of all the enclosures. The values returned in these cases can
be inconsistent, so it would be appropriate to implement some test to rule out self-
intersecting curves.

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.

Sample Program: C:CURVE-AREA.


To demonstrate the use of vlax-curve-GetArea, we propose the C:CURVE-AREA
function (Listing 15.7) that on selecting a curve object reports the value of the enclosed area.
This function will previously check the that the curve is planar and closed, rejecting those that
were not. But before it must verify that the object has the Area property to prevent errors
that would occur if for example, a POINT entity, were supplied as argument to a vlax-
curve... function. Actually, if the entity is linear, such as a LINE entity, no error would
occur despite the absence of an Area property. It would simply return 0.0. But lacking a
predicate that checks if it is a curve, this would be an acceptable solution.

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."))))))

Listing 15.7. Determining the area enclosed by the curve.

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.

15.6 Calculating the first and second derivatives.


Other functions related to curves have the purpose of obtaining the curve’s irst and second
derivatives. The irst derivative of the curve represents the slope of the curve at a speci ied
point. The second derivative represents the variation of the slope at that point.

The following expression returns a curve’s first derivative at the point defined by parameter:
(vlax-curve-GetFirstDeriv curve-obj parameter)

and to obtain the second derivative:


(vlax-curve-GetSecondDeriv 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.

15.7 Sample Program: Drawing tangents to a curve.


Tangents to a curve at any point can be drawn by retrieving its slope at that point and
creating a linear entity following that direction. The value returned by vlax-curve-
GetFirstDeriv is a direction vector. The easiest way would be to use a graphic entity that
uses this vector as data. We have two entities that meet this objective: XLINE and RAY. The
difference between them is that the XLINE (or construction line) extends inde initely to both
sides of a reference point (which in this case would be the tangency point) while the RAY
extends only in one direction. We have chosen the RAY for this example as this way the
curve’s direction is also represented.. To create the RAY entity AutoCAD’s command will not
be used. It shall be created through an AutoLISP function using entmake.

Creating the RAY entity.


As in previous cases, we will begin by drawing the entity we are interested in and retrieving
its entity list using (entget (entlast)). We obtain something like:
((-1 . <Entity name: 4006ad58>)
(0 . "RAY")
(330 . <Entity name: 4006acf8>)
(5 . "2B")
(100 . "AcDbEntity")
(67 . 0)
(410 . "Model")
(8 . "0")
(100 . "AcDbRay")
(10 140.274 174.366 0.0)
(11 0.964588 0.263761 0.0))

To create a new entity of this kind only a few of the group codes represented here are needed:

(0 . "RAY") Indicates the type of entity to be created.


(100 . "AcDbEntity") Generic subclass marker.
(100 . "AcDbRay") Specific subclass marker
(10 140.274 174.366 0.0) First point (in WCS)
(11 0.964588 0.263761 0.0) Unit direction vector (in WCS)

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.

Tangent vector calculation.


The calculation to obtain the tangent vector, is performed by the calc-tangent function
(Listing 15.9). We approach it as a standard library function that may be used in different
programs.

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.

Passing the value returned by vl-catch-all-apply to the vl-catch-all-error-p


predicate we check if an exception has been thrown. In case it has, the function will terminate
returning nil. Otherwise it will call vlax-curve-GetParamAtPoint to retrieve the
parameter for the speci ied point and invoking with this parameter the vlax-curve-
GetFirstDeriv function to obtain the tangent vector. The value returned by calc-
tangent is a list where the irst term is the point on the curve closest to the selected point
and the second one is the tangent’s direction vector.
(defun calc-tangent (ename-curve pt / dir
curveobj param)
(setq dir (trans (getvar "viewdir") 1 0 t)
pt (trans pt 1 0))
(if (and
(setq
curveobj
(vlax-ename->vla-object
ename-curve))
(setq
pt
(vl-catch-all-apply
'vlax-curve-GetClosestPointToProjection
(list curveobj pt dir))))
(if (vl-catch-all-error-p pt)
nil
(progn
(setq param
(vlax-curve-GetParamAtPoint
curveobj
pt))
(list pt
(vlax-curve-GetFirstDeriv
curveobj
param))))))

Listing 15.9. Function that calculates the tangent to a curve.

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))

Listing 15.10. Command that draws a RAY tangent to a curve.

15.8 Sample Program: UCS perpendicular to a


curve at a selected point.
The previous example illustrates the possibilities of the vlax-curve functions but is not a
very useful tool. However, with a slight modi ication we can have a very useful 3D modeling
tool. It can be used to establish a UCS perpendicular to any curve, regardless of its orientation
by simply selecting on it the point where the system’s origin should be placed.

Changing the User Coordinate System.


We have seen in Chapter 13 how to establish a new UCS using ActiveX methods. Using
entmake, the necessary data are the same as for ActiveX. Although these data could be
calculated from the vector obtained from vlax-curve-GetFirstDeriv, we have a much
simpler way to do it taking advantage of the UCS command with the _ZAxis option that
establishes the XY plane’s orientation by specifying two points. As we can see when running it
from the command line, a set of options is proposed among which we choose ZAxis. On
selecting this option the user is prompted for two points:
Command: (command "UCS")
UCS
Current ucs name: *WORLD*
Specify origin of UCS or [Face/NAmed/OBject/Previous/View/World/X/Y/Z/ZAxis] <World>: ZA
Specify new origin point or [Object] <0,0,0>:
Specify point on positive portion of Z-axis <0.0000,0.0000,1.0000>:

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.

Drawing environment conditions.

Whenever we employ to the command/vl-cmdf interface we must pay attention to system


variables that may affect its operation. In this case we are particularly concerned about the
possibility that running object snap modes can affect the designated points. Since this
problem can only affect the two points used to de ine the Z axis, we could use a different
technique to ensure that the object snap modes do not affect just these two points. This
procedure involves passing the _NONe object snap mode when before each of the points’
coordinate lists. In fact, it is advisable to use the _NEAr object snap mode or another one that
ensures that the point is really placed we expect it to be on the curve, especially for 3D
models. To avoid confusion caused by messages that are printed on the command line it is
convenient to turn off the CMDECHO system variable before invoking the command,
reactivating it later.

Transforming values to the current UCS.

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.

Normal UCS using ActiveX.


To establish the UCS only having the normal vector as returned by calc-tangent (Listing
15.9) both entmake and ActiveX require the X and Y direction vectors as arguments. This
increases the procedure’s complexity, but this complexity is offset by the security afforded by
not having to depend on environmental conditions such as running object snaps.

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)))

Listing 15.13. Function that returns a UCS transformation matrix.


(defun C:NORMAL-UCS (/ selection data)
(if (setq selection
(entsel
"\nOrigin point on curve: "))
(if (setq data (calc-tangent
(car selection)
(cadr selection)))
(ax-normal-ucs (car data) (cadr data))
(prompt "\nRuntime error.")))
(princ))

Listing 15.14. Command to establish a UCS perpendicular to a curve.

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.

15.9 Determining points on a curve.


Using the functions
vlax-curve-GetPointAtDist
vlax-curve-GetPointAtParam

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.

Points at a fixed distance.


To obtain the same result as that obtained with MEASURE without having to draw points we
can use the ax-measure function (Listing 15.15). This function operates through a loop that
successively increases the distance at which the points on the curve are calculated. In a
similar way a function equivalent to DIVIDE can be programmed.
(defun ax-measure (obj dist / len step tmp)
(setq len (ax-curvelength obj)
step dist)
(while (<= step len)
(setq tmp (cons (vlax-curve-GetPointAtDist
obj
step)
tmp)
step (+ step dist)))
(reverse tmp))

Listing 15.15. Calculating points at fixed distances.

15.10 Sample Program: Breaking a curve into equal


segments.
Sometimes we may need to break a linear entity into equal length segments. This can be a
tiresome process, initially marking points along it using the DIVIDE or MEASURE commands
and then selecting those points one by one to apply the BREAK command. To demonstrate
the utility of the ax-measure function we propose a small program that automates this task.

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*))

Listing 15.16. Breaking an entity into equal segments.

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

15.11 Determining intersections.


Although it’s not one of the vlax-curve... functions we do not want to conclude this
chapter without referring to the vla-IntersectWith function, which determines the
intersection between two objects using the ActiveX method.

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)

That translated to a Visual LISP function would be:


(vla-IntersectWith object IntersectObject ExtendOption)

B o t h object and IntersectObject must be VLA-objects. The ExtendOption


argument will be one of the constants listed in Table 15.1. Either the numerical value or the
enum constant name can be used as arguments, although in these cases the name is preferred
as the equivalent numeric values may change in future AutoCAD releases.

Table 15.1. Object extending options.


Value: Name: Effect:
0 acExtendNone Does not extend any of the objects.
1 acExtendThisEntity Extends the base object.
2 acExtendOtherEntity Extends the object passed as the second argument.
3 acExtendBoth Extend both objects.

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.

Transforming the returned array into a list of points.

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)

Listing 15.17. Creating a points list from a list of coordinates.

Specifying how to extend objects.

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)))

Listing 15.18. Determining how to extend the entities.

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.

Determining the intersection of two objects.


INTERSECTIONS function.

T h e intersections function (Listing 15.19) receives two VLA-objects and the


extending modality indicated in any of the ways which were explained above. The first thing it
does is to use vl-catch-all-apply to invoke the vla-IntersectWith function to the
objects received as arguments. If no error has been captured processing continues.
Otherwise, the function terminates returning nil.

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))))

Listing 15.19. INTERSECTIONS function.


If the safearray contains data, it is converted to a list by vlax-safearray->list and that
list, together with the upper boundary value is passed to the coord->points function. As
this is the last expression evaluated, the value returned by intersections is the points list
returned by coord->points.

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.

16.1 Mesh building procedures.


Visual LISP allows the creation of these surfaces using the command/vl-cmdf interface,
entmake or the ActiveX extensions. In this section we will examine these three options,
command/vl-cmdf, entmake and the ActiveX extensions.

Creating meshes with entmake


Bo t h PolyfaceMesh and PolygonMesh are represented in DXF as variations of the
POLYLINE entity.

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.

Creating meshes with ActiveX methods.


Legacy meshes can be created with the Add3DMesh a n d AddPolyfaceMesh ActiveX
methods of the ModelSpace, PaperSpace and Block objects.

To create a PolygonMesh the Add3DMesh method is used. This method requires as


arguments the number of vertices in the M direction, the number of vertices in the N direction
and an array with the coordinates for all the mesh’s vertices. After creating the mesh, the
MClose and NClose properties can be used to close the mesh in the M or N directions.

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.

Creating Polygon Meshes through COMMAND.


A PolygonMesh is very easy to program using the command/vl-cmdf interface. In this
case factors such as running object snaps that can affect the results should be taken into
account. Also the program’s appearance during execution is less tidy as if the adequate
caution is not taken, prompts for all of the vertices positions will be displayed in the
command line. To avoid this, and to prevent problems related with running object snaps the
functions cmd-in (Listing 7.8) and cmd-out (Listing 7.9) de ined in Chapter 7 will be used.
The function cmd-draw-pmesh (Listing 16.1) shows how it can be programmed. This
function ends with the expression (entlast) so that the function will return the newly
created entity’s ename.
(defun cmd-draw-pmesh (m n coords-list /)
(cmd-in)
(apply 'vl-cmdf
(append (list "_3dmesh" m n)
coords-list))
(cmd-out)
(entlast))

Listing 16.1. Drawing the PolygonMesh using the 3DMESH command.

Unlike the procedures outlined below, the points supplied to the command are considered as
points in the current UCS.

Procedure using entmake.


Using entmake, we must create the object header irst, then the vertex objects, and inally
lag the entity’s completion creating a SEQEND entity. To create the entity’s header (Listing
16.2) in the case of a PolygonMesh entity only DXF group codes 10, 70, 71 and 72 are
indispensable1. Of these, only the values for group codes 71 and 72 vary in each case,
assigned to the arguments m and n. Group codes 1 and 70 are always the same, as they are the
ones that indicate the type of entity to create.
(defun PolygonMesh-header (m n)
(entmake
(list '(0 . "POLYLINE")
'(100 . "AcDbEntity")
'(100 . "AcDbPolygonMesh")
'(70 . 16)
(cons 71 m)
(cons 72 n))))

Listing 16.2. Function to generate the PolygonMesh header.

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))))

Listing 16.3. Function to generate each mesh vertex.

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"))))

Listing 16.4. Function that creates the End of Sequence entity.

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))

Listing 16.5. Drawing the mesh with entmake.

Procedure using Add3DMesh.


The syntax for the Add3DMesh method would be:
(vla-add3Dmesh space m n point-array)

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))

Listing 16.6. Creating the PolygonMesh with the Add3dMesh method.

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.

16.3 Smoothing the PolygonMesh.


To enhance the surfaces’ continuity they can be smoothed in a fashion somewhat similar to
the curving of a polyline with _PEDIT. In fact, this same command can be applied to a
PolygonMesh, in this case adjusting them as quadratic B-Splines, cubic B-Splines or Bé zier
surfaces. When surfaces are smoothed with the _PEDIT command, the type of transformation
is determined by the system variables SURFTYPE, SURFU and SURFV. These variables are
explained in Table 16.1.

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.

Table 161. Variables that control the smoothing of the PolygonMesh.


Variable Description
Controls the type of surface fitting performed by using the PEDIT Smooth surface option.
5 = Quadratic B-Spline, 6 = Cubic B-spline, 8 = Bézier.
SURFTYPE
This value is assigned to the POLYLINE entity's DXF group code 75 and to the PolygonMesh object's Type
property.
Sets the surface density for P EDIT command's Smooth surface option in theM direction, and of the U Isolines
SURFU density in surface objects. Values between 0 and 200. For meshes, a minimum of 2. This value is associated with
the POLYLINE entity's group code 73 and as SURFU + 1 to the PolygonMesh object's MDensity property.
Sets the surface density for P EDIT command's Smooth surface option in theN direction, and of the V Isolines
SURFV density in surface objects. Values between 0 and 200. For meshes, a minimum of 2. This value is associated with
the POLYLINE entity's group code 74 and as SURFV + 1 to the PolygonMesh object's NDensity property.
16.4 Sample Program: Creating a PolygonMesh.
As an example of a program that creates a PolygonMesh we will resort to the idea originally
proposed by Kelvin R. Throop in FPLOT, a program included as a demonstration of AutoCAD’s
3D capabilities back in 1988. This program creates different meshes according to the formula
chosen from among the several ones it offers. We have chosen the three producing more
interesting forms. This program prepares the data and can draw the mesh using any of the
three procedures explained in the preceding sections.

The program is structured in several auxiliary functions:

Data Entry.
The pmesh-data function (Listing 16.7) Is used for prompting the user for the necessary
data. These data are:

Method to use for creating the model.


Surface formula to be used.
X, Y and Z dimensions.
Number of X and Y subdivisions.
Type of smoothing to be applied.
Mesh center's location.

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:

res: The number of mesh rows and columns.


origin: The mesh’s central point.
stepX: the 2D distance between vertices along the X axis
stepY: the 2D distance between vertices along the Y axis
Xmin: the minimum value of x.
Ymin: the minimum value of y.

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))

Listing 16.8. Functions for calculating different surface shapes.

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)))

Listing 16.9. Function that determines the equation to be used.

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.

Main function C:POLYMESH.


The main function is de ined as an AutoCAD command, by the name of C:POLYMESH. Given
that this is a function oriented to experimenting with three graphic entities creation
paradigms, we will time its execution. This is done using the MILLISECS system variable. Its
value on starting the program will be assigned to the variable time, value that will be
subtracted from MILLISECS when its execution ends. To enable undoing an UNDO begin
mark will be established when beginning of the program using the Document’s
StartUndoMark method, which will be matched by an EndUndoMark on the program’s
completion.

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.

Figure 16.1. Type 2 surface created by the program.

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.

Creating a PolyfaceMesh using COMMAND.


As we did for the PolygonMesh, we will de ine three different functions to demonstrate the
creation of the PolyfaceMesh from the command/vl-cmdf interface, the entmake
function and ActiveX methods . To make these functions interchangeable, they will receive the
same arguments. In this case the required arguments are:

vertices-list: A list of sublists for each vertex's X, Y, and Z coordinates,


face-list: A list that includes a sublist for each of the mesh's faces with the indices
that identify the face edge vertices. The indices identifying the vertices start with
number 1.

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.

Discretization of the faces.

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))

Listing 16.13. Discretization of the faces.

Procedure using entmake.


To create the PolyfaceMesh through entmake a sequence of four entity types must be
created: The header as a POLYLINE entity of the AcDbPolyFaceMesh subclass, the vertices
as VERTEX entities of the AcDbVertex subclass, the records that de ine each face’s vertices
connectivity also as VERTEX entities of the AcDbPolyFaceMeshVertex subclass, and the
corresponding SEQEND entity to complete the process. In all of these entities the sublists
associated with group codes 100 can be dispensed with, but it is our practice not to omit
them.

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)))))

Listing 16.14. Creation of the PolyfaceMesh header entity.

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)))))

Listing 16.15. Creating the VERTEX entities.

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))))))

Listing 16.16. Faces creation (FaceRecord entities).


The functions de ined in the three Listings shown above are successively invoked by the
ent-draw-pface function (Listing 16.17), which concludes with the ent-seqend function
that was de ined in Listing 16.4. This function receives as arguments the same lists of vertices
and faces that were used to create the PolyfaceMesh through command/vl-cmdf, being
necessary to preprocess face-list so it attains the structure required by entmake. A inal
call to entlast returns the ename of the newly created entity.
(defun ent-draw-pface
(vertices-list face-list /)
(setq face-list (def-face face-list))
(polyface-header
vertices-list
face-list)
(polyface-vertices vertices-list)
(polyface-faces face-list)
(ent-seqend)
(entlast))

Listing 16.17. Function that draws the PolyfaceMesh using entmake.

Procedure using AddPolyfaceMesh.


The AddPolyfaceMesh method takes as arguments the space in which to create the entity,
an array containing the x, y and z coordinates of the vertices and an array containing the
indices sequence of the vertices defining each face. Its syntax is:
(vla-AddPolyfaceMesh space vertices-array faces-array)

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))

Listing 16.18. Creating the mesh using vla-AddPolyfaceMesh.

16.6 Sample Program: Creating a PolyfaceMesh.


To demonstrate the creation of PolyfaceMesh we will set up a program to build regular
polyhedra models. The program will offer three options: creating Tetrahedrons, Hexahedrons
(Cubes) and Dodecahedrons. This way we will demonstrate how to program the
Tetrahedron’s triangular faces , the Hexahedron’s square faces and the Dodecahedron’s
pentagonal faces. The program follows the structure we used to demonstrate the creation of a
PolygonMesh.

Data Entry.
The polyhedra-data function (Listing 16.19) prompts to select from the different options
the program offers. These are:

Method to use for creating the model.


Type of Polyhedron.
Polyhedron's center location.
Radius of the circumscribed sphere.
(defun polyhedra-data (/)
(initget 1
"Command Entmake Activex")
(setq method
(getkword
"\nMethod [Command/Entmake/Activex]: "))
(initget
1
"Tetrahedron Hexahedron Dodecahedron")
(setq
class
(getkword
"\nPolyhedron [Tetrahedron/Hexahedron/Dodecahedron]:")
center (getpoint "\nPolyhedron center: ")
radius (getdist
center
"\Circumscribed sphere's radius: ")))

Listing 16.19. User data entry.

Loading data for the selected kind of polyhedron.

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))))))

Listing 16.20. Loading the polyhedron's vertices and faces data.

Translation and scaling transformations.

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).

Main Function C:POLYHEDRON-PFACE.


Just as we did with the C:POLYMESH function, this program will be written so as to enable
experiment with the three different procedures, using command/vl-cmdf, entmake and
ActiveX. In order to compare their performance we will control the execution time, reporting
the milliseconds elapsed. The explanation given for the previous program applies to this one
almost entirely.
(defun C:POLYHEDRON-PFACE (/ *error* time
mtrans method
class center
radius obj)
(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)))
(polyhedra-data)
(op-polyhedron class)
(cond ((= method "Command")
(cmd-in)
(setq mtrans nil
obj (vlax-ename->vla-object
(cmd-draw-pface
vertices
faces)))
(cmd-out))
((= method "Entmake")
(setq obj (vlax-ename->vla-object
(ent-draw-pface
vertices
faces))))
((= method "Activex")
(setq obj
(ax-draw-pface vertices faces))))
;; Transformations:
(ax-scale obj
(list radius radius radius))
(if mtrans
(vla-TransformBy obj mtrans))
(ax-translation
obj
(trans center 1 0 t))
(ax-SWt)
(prompt
(strcat
"\nTiming: "
(rtos (- (getvar "millisecs") time)
2
0)
" milliseconds"))
(vla-EndUndoMark *aevl:drawing*)
(princ))

Listing 16.21. Main Function C:POLYHEDRON-PFACE.

16.7 Modifying Polygon and Polyface Meshes.


The entities that expose values associated to DXF group codes in an unencrypted format can
be modi ied by entmod. But being complex entities, Polygon or Polyface Meshes include, as
we saw in creating them from entmake, a sequence of VERTEX entities which can be
modi ied but at the cost of an increased program complexity. It is far simpler to use ActiveX
properties and methods. There are some differences between the two types of meshes which
we will proceed to analyze.

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))))

And the number of vertices exposed by the Coordinates property is:


(/ (length
(vlax-safearray->list
(vlax-variant-value
(vla-get-Coordinates obj-malla))))
3)

If information of the coordinates of the vertices generated by smoothing were necessary we


would have to access the drawing database using the ent... functions. To distinguish the
vertices used to create the mesh from those added by smoothing we can inspect the value of
group code 70. This group code will have bit 8 on (Spline vertex created by spline- itting) in
vertices produced by smoothing and bit 16 (Spline frame control point) if it were one of the
original vertices. The logand function can be used to check this.

Table 16.2. PolygonMesh object methods.


Method. Description.
Adds an row N of vertices to a PolygonMesh. For example, for a 4 x 3 PolygonMesh 12 vertices are
AppendVertex defined. Adding a vertex would make it a matrix of 5 x 3, making it necessary to specify three additional
points in the form of a 3 x M array .
Explode Decomposes a PolygonMesh object returning an array of Acad3DFace objects.

Table 16.3. AcadPolygonMesh object properties.


Property. Description.
Coordinate Gets or modifies the coordinates of the vertex whose index it receives as argument. Returns an array
representing a 3D point in the WCS.
Gets or modifies the coordinates of all of the vertices. Returns an array of 3 x M x N elements. Every 3
Coordinates
successive elements represent a 3D point (WCS).
MClose Specifies whether the mesh is closed in the M direction.
Gets or sets the surface's smoothing density in the M direction. Accepts values between 2 and 255. Its
initial value is the SURFU system variable + 1. The density of the surface in the M direction is the number
MDensity
of vertices in this direction for AcadPolygonMesh objects of the acQuadSurfaceMesh,
acCubicSurfaceMesh and acBezierSurfaceMesh types.
MVertexCount Amount of PolygonMesh vertices in the M direction. Read-only.
NClose Specifies whether the mesh is closed in the N direction.
Gets or sets the surface's smoothing density in the N direction. Accepts values between 2 and 255. Its
initial value is the SURFV system variable + 1. The density of the surface in the N direction is the number
NDensity
of vertices in this direction for AcadPolygonMesh objects of the acQuadSurfaceMesh,
acCubicSurfaceMesh and acBezierSurfaceMesh types.
NVertexCount Gets the number of vertices in the PolygonMesh N direction. Read-only.
Gets or sets the type of the PolygonMesh derived from its kind of smoothing. The supported types are
Type
listed in Table 16.4.

Table 16.4. Possible values for AcPolymeshType.


Type Value Description
acSimpleMesh 0 Mesh with no smoothing.
acQuadSurfaceMesh 5 Quadratic B-spline surface fit.
acCubicSurfaceMesh 6 Cubic B-spline surface fit.
acBezierSurfaceMesh 8 Bezier surface fit.

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.

Table 16.5. PolyfaceMesh object properties.


Property. Description.
Gets or modifies the coordinates of the vertex whose index it receives as argument. Returns an array of
Coordinate
three elements of type Double representing the coordinates of a 3D point in the WCS.
Gets or modifies the coordinates of all of the vertices. Returns an array of 3 x M x N elements of type
Coordinates
Double. Every 3 successive elements specifies a 3D point in WCS.
NumberOfFaces Gets the number of faces in the mesh. The value is read-only.
NumberOfVertices Gets the number of vertices in the mesh. The value is read-only.

Editing possibilities for Polygon and Polyface Meshes.


The modi ications that can be applied using Visual LISP programming to these objects are as
many as their exposed read-write properties and methods. As an example of these
possibilities we propose the function ax-mod-pmesh (Listing 16.24) that changes the Z
coordinate of all vertices using the values calculated as in functions f1, f2 and f3.

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)))

Listing 16.23. Function for calculating the Z coordinate.

(defun ax-mod-pmesh (pmesh-obj equation


dim-z / xyz lst-z i
vertices pt)
(setq xyz (vlax-safearray->list
(vlax-variant-value
(vla-get-coordinates
pmesh-obj)))
lst-z (cal-z xyz equation dim-z)
i 0
name (vla-get-ObjectName pmesh-obj))
(cond
((= name "AcDbPolygonMesh")
(setq vertices
(* (vla-get-MVertexCount
pmesh-obj)
(vla-get-NVertexCount
pmesh-obj))))
((= (vla-get-ObjectName pmesh-obj)
"AcDbPolyFaceMesh")
(setq vertices
(vla-get-NumberOfVertices
pmesh-obj)))
(t
(prompt
"\nSelect a Polygon or Polyface mesh.")
(exit)))
(repeat vertices
(setq
pt (vlax-safearray->list
(vlax-variant-value
(vla-get-coordinate
pmesh-obj
i))))
(vla-put-coordinate
pmesh-obj
i
(vlax-3d-point
(list (nth 0 pt)
(nth 1 pt)
(+ (nth 2 pt) (nth i lst-z)))))
(setq i (1+ i)))
(vla-Update pmesh-obj))

Listing 16.24. Modification of the vertices of a Polygon or Polyface mesh.

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:

z = cos(x) + cos(y) z = cos(x2 + y2)


z = (cos(x)+cos(y))2 z = sin(x2 + z2)
z = (cos(x)+cos(y))3 z = cos(x2 + z2)7
z = (cos(x)+cos(y))4 z = sin(x2 + z2)3
z = (cos(x)+cos(y))5 z = |cos(x2 + z2)|(1/2)
z = |(cos(x)+cos(y))|(1/2) z = |cos(x2 + z2)|(1/8)

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.

AutoCAD’s solid modeling functionality is limited. Autodesk markets Inventor, an application


specifically oriented to mechanical design with all the solid modeling functionality required in
these cases. 3DSolids and Surfaces are created and managed by an external modeling kernel.
Up to AutoCAD 2002, the ACIS modeling engine developed by Spatial (www.spatial.com) was
used. Since Release 2004, Autodesk’s own ShapeManager modeling technology derived from
the ACIS geometric modeling kernel, is used for solid modeling in AutoCAD. The
ShapeManager API is so far only available in ObjectARX, although the ActiveX methods
exposed in previous releases and can be used from Visual LISP.

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).

17.1 3DSolid Primitives.


A complex 3DSolid is usually made up from the combination of a series of basic forms,
known as primitives. Solid primitives cannot be created by entmake. Visual LISP allows their
creation using the commands described in Table 17.1 or through their equivalent ActiveX
methods. Using ActiveX methods we can create rectangular prisms, cones, cylinders, elliptical
cones and cylinders, spheres, tori and wedges.

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).

Primitive creation functions.


The syntax for the functions that create solid primitives are:
(vla-AddBox space-obj origin dimX dimY dimZ)
(vla-AddCone space-obj origin radius dimZ)
(Vla-AddCylinder space-obj origin radius dimZ)
(Vla-AddEllipticalCone space-obj origin dimX dimY dimZ)
(Vla-AddEllipticalCylinder space-obj origin dimX dimY dimZ)
(vla-AddSphere space-obj origin radius)
(vla-AddTorus space-obj origin torusRadius tubeRadius)
(Vla-AddWedge space-obj origin dimX dimY dimZ)

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.

Table 17.2. Arguments to the solid primitive creating functions.


Argument Type: Description
space-obj Object ModelSpace, PaperSpace or Block
Coordinates of a point in 3D space. These methods require a safearray of Doubles in a Variant
type variable. In the functions defined in this chapter we will pass the argument as a list of three
origin Variant
real numbers similar to those we obtain with functions like getpoint, transformed to the WCS
and converted into a safearray variant with vlax-3d-point.
dimX Double A non-zero real number which represents the dimension along the X axis.
dimY Double A non-zero real number which represents the dimension along the Y axis.
A non-zero real number representing the extrusion height along the Z axis. Positive values are
dimZ Double
extruded in the positive Z axis direction. Negative ones in the opposite direction.
torusRadius Double A non-zero real number that represents the radius of the tube.
tubeRadius Double A non-zero real number that represents the radius of the tube.

17.2 Creating a Primitive using ActiveX.


Using these expressions we can de ine functions that create a 3DSolid according to our
speci ications. As an example we propose a function that adds a cone to the current space
receiving as arguments the origin or control point, its radius and height (dim-z). The
origin will be provided as a list of three real numbers that the function will convert into the
correct data type.
(defun ax-cone (control radius dim-z /)
(vla-AddCone
(current-space *aevl:drawing*)
(apply 'vlax-3d-point control)
radius
dim-z))

Listing 17.1. Creation of a Cone primitive.

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.

Creating a CONE primitive through command/vl-cmdf.


We must highlight the differences with the command line _CONE command which provides a
number of alternative base circle options and offers the possibility of creating truncated
cones. The cmd-cone function invokes the _CONE command receiving an argument for the
top radius (toprad) that if non-zero uses the "_Top radius" option to enter this value.
(defun cmd-cone
(ctrbase baserad toprad dim-z /)
(cmd-in)
(vl-cmdf "._CONE" ctrbase baserad)
(if (/= toprad 0)
(vl-cmdf "_t" toprad dim-z)
(vl-cmdf dim-z))
(cmd-out))

Listing 17.2. Creating a cone through command/vl-cmdf.

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.

17.3 Creating 3DSolids from 2D or 3D objects.


It is also possible to create 3DSolid objects based on 2D entities using the EXTRUDE,
SWEEP, LOFT and REVOLVE commands. The methods offered by the ActiveX interface for
creating a 3DSolid from other objects are extrusion, sweeping along a trajectory and
revolution (circular sweep) about an axis. These functions always require a planar Region
object that de ines the solid’s section and in the case of sweeping, a linear path that may be a
2D or 3D polyline, a planar spline, a circle, an ellipse or an arc. These methods exhibit a
greater complexity, so they will be treated in more detail later.

Table 17.3. Commands creating 3DSolids from 2D entities.


Command ActiveX Method
_EXTRUDE AddExtrudedSolid
_SWEEP AddExtrudedSolidAlongPath
_LOFT
_REVOLVE AddRevolvedSolid
_POLYSOLID AddExtrudedSolidAlongPath

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.

Table 17.4. Conversion of graphic entities into 3DSolids by CONVTOSOLID.


Object Description
MESH A volume must completely enclosed by them. There can be no holes.
LWPOLYLINE Open or closed uniform non-zero width with thickness. Creates a solid by sweeping.
LWPOLYLINE Closed, uniform zero-width with thickness. Creates an extrusion solid.
CIRCLE, TRACE Circles and legacy Traces with thickness. Creates extrusion solids.
Surfaces Provided they enclose a volume with no holes.

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.

Table 17.5. Values for the SMOOTHMESHCONVERT system variable.


Value: Description:
0 Creates a smoothed model. Coplanar faces are optimized or merged.
1 Creates a smoothed model. The original mesh faces are retained in the converted object.
2 Creates a model with flat faces. Coplanar faces are optimized or merged.
3 Creates a model with flat faces. Original mesh faces are retained in the converted object.

17.4 Creating Regions.


The object from which these solids are created is always the Region. The AcadRegion is a 2D
solid object that belongs to the same AcDbModelerGeometry subclass as the 3DSolid,
and as such possesses physical properties and can be subjected to UNION, SUBTRACTION
and INTERSECTION Boolean operations. As with the 3DSolid, the information returned by
entget is encrypted. Therefore, the only ways to create REGION objects are using the
command/vl-cmdf interface or the AddRegion ActiveX method. The AddRegion
method’s syntax is:
(vla-AddRegion space-obj profiles)

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.

Table 17.6. Arguments for vla-AddRegion.


Argument: Type: Description
space-obj VLA object ModelSpace, PaperSpace or Block that will own the REGION.
The AddRegion method is designed to receive any number of objects that make up
the region's outer edge. For this reason an array must be created previously with vlax-
make-safearray. The number of objects to include is one of the parameters needed to
profiles Array of Objects
create the array. The array's type must be vlax-vbObject. Once created, the
array must be filled using vlax-safearray-fill, considering that the objects are
received in a list.

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.

Boolean operations on Regions.


All properties exposed by the AcadRegion class object are read-only. Its modi ication
capabilities are limited to the Boolean and Explode methods.

Table 17.7. Properties of the AcadRegion object.


Property: Description:
Gets the area enclosed by the region which is equal to the combined area of all objects that make up
Area
the region, expressed in drawing units squared. Read only.
Centroid Gets the region's center of mass as a 2D point. Read only.
MomentOfInertia Gets the region's moment of inertia as a 3D vector. Read only.
Normal Gets the region's 3D unit normal vector (Z Axis). Read only.
Perimeter Gets the total length of the inner and outer region loops. Read only.
Gets the region's principal directions as current coordinate system X, Y and Z coordinates. Read
PrincipalDirections
only.
Get the region's principal moments as current coordinate system X, Y and Z coordinates. Read
PrincipalMoments
only.
Gets the region's product of inertia as current coordinate system X, Y and Z coordinates. Read
ProductOfInertia
only.
RadiiOfGyration
Gets the region's radii of gyration as current coordinate system X, Y and Z coordinates. Read only.

Table 17.8. AcadRegion object Methods.


Method: Description:
Creates regions composed by the UNION, SUBTRACTION or INTERSECTION of two regions.
Boolean The type of operation is defined by one of the AcBooleanType enum constants described in
Table 17.9.
Explodes the region returning an array of sub-entities. The loops delimiting the regions are
Explode
transformed into lines, circular or elliptical arcs or splines.

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.

Table 17.9. AcBooleanType constants.


Constant: Value: Description:
acUnion 0 Performs a Union operation that combines two Region or 3DSolid objects in a single one.
Performs an Intersection operation to create a Region or 3DSolid from the common
acIntersection 1
area/volume of two Regions or 3DSolids.
Performs a Subtraction operation to create a Region or 3DSolid subtracting a Region or
acSubtraction 2
3DSolid from another object of the same type.

The Boolean method’s syntax is:


(vla-Boolean region-1 operation-type region-2)

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.

Table 17.10. Arguments for vla-AddCircle.


Argument: Type: Description:
This point can be obtained with getpoint or calculated using the polar function that in both
cases, treat it as a list. That value will be used as argument in the getdist function that
origin Variant prompts for the radii and height. But vla-AddCircle requires a safearray in a variant type
variable. To do this we convert it with vlax-3d-point before passing it to vla-
AddCircle.
radius Double The value returned by getdist can be used.

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:

Half of the circumference divided by the number of holes.


One third of the base region's radius.
(apply ‘min
(list (/ (* pi distcenter)
(* numholes 1.1))
(/ extradius 3.1)))

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*))

Listing 17.6. Sample program that creates complex regions.


17.6 Properties and Methods of the 3DSolid object.
The properties exposed by Acad3DSolid class objects are listed in Table 17.11. All of them
are read-only. This object’s methods are described in Table 17.12.

Table 17.11. Acad3DSolid class object properties.


Property: Description:
3D point describing the solid's center of mass considering uniform density. Expressed in UCS
Centroid
for the REGION object, in WCS for 3DSolids.
Moments of inertia about the X, Y and Z axes. Expressed as mass units times the distance
MomentOfInertia
squared.
X, Y and Z coordinates for the center of the base or center of the solid in certain 3DSolid
Position objects. It may or may not coincide with its Centroid. Not available in all 3DSolid
objects.
PrincipalDirections Principal directions.
The moment is highest through a certain axis at the centroid of an object and lowest through a
PrincipalMoments second axis normal to the first axis also passing through the centroid. The third value included
is somewhere in between.
ProductOfInertia Products of inertia. Is expressed in mass units times the length squared.
RadiiOfGyration Radii of gyration. Expressed in distance units.
Volume Enclosed 3D space. In drawing volume units.

Table 17.12. Methods exposed by the Acad3DSolid class object.


Method: Description:
Performs a UNION, SUBTRACTION or INTERSECTION operation between the object and
Boolean
another 3DSolid. Returns the modified object.
Checks for interference between the object and other 3DSolid. Optionally creates a new
CheckInterference
3DSolid representing the space where interference occurs.
Creates a region that represents the intersection of the object with a plane defined from three
SectionSolid
points.
Slices the object by a plane defined from three points. The part of the object that is kept can be
SliceSolid
specified.

17.7 Sample Program: Extruded Solid.


An extruded 3DSolid is created by extruding a Region along the Z axis. This extrusion can
be done in a positive or negative direction, and a taper angle can be specified.
(vla-AddExtrudedSolid space-ob region dimZ taperAngle)

The AddExtrudedSolid method receives the arguments described in Table 17.13.

Table 17.13. Arguments for AddExtrudedSolid.


Argument: Type: Description
space-obj Object ModelSpace, PaperSpace or Block
region AcadRegion A planar region to use as a profile.
A non-zero value representing the extrusion height. Positive values are extruded in the
dimZ Double
positive Z axis direction. Negative ones in the opposite direction.
The extrusion's taper angle, in radians. Values between +π/ 2 and -π/ 2 are admitted.
taperAngle Double Positive values taper in from the base and negative angles taper out.

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.

Creating the base region.


To create the base region we will draw upon the code developed for our previous example
that created a circular region including holes. The base-reg function used in this sample
program receives the arguments obtained by the same region-data function (Listing 17.5)
used in the previous one. Being based on the program shown in Listing 17.6 we refer the
reader to the explanation given in the previous section.
(defun base-reg (extradius numholes
distcenter holerad /
space-obj origin normal
cir-base base ang incang
circles regions)
(setq space-obj (current-space
*aevl:drawing*)
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 circles (cons
(vla-AddCircle
space-obj
(vlax-3d-point
(polar '(0 0 0)
ang
distcenter))
holerad)
circles)
ang (+ ang incang))
(vla-put-Normal
(car circles)
normal))
(setq regions
(ax-region space-obj circles))
(foreach region regions
(ax-boolean
base
acSubtraction
region))
base)

Listing 17.7. Function that creates the base region.

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)))

Listing 17.8. Enabling the Registry property in a 3DSolid.

Main function C:SOL-EXT.


The function proposed in Listing 17.9 prompts the user for the data that will be used to create
an extruded solid, checking if the extrusion angle is valid. Prompting for the angle is done in a
loop that checks if the speci ied angle will produce a self-intersecting solid. ActiveX error
handling functions are used to display possible error messages on the command line. If the
program succeeds in creating the extruded solid, SOLIDHIST’s value is checked so if it is 1
the solid’s History property will be set to Record.

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*))

Listing 17.9. Creating an Extruded Solid.


Figure 17.1. Extruded Solid.

17.8 Sample Program: Solid by Sweeping along a


path.
The generation of a solid by sweeping a pro ile along a path implies a higher degree of
complexity derived from the fact that the pro ile must be oriented in a plane perpendicular to
the path. This means that the application should ensure that this is so.
Solids by sweeping along a path created by the AddExtrudedSolidAlongPath method.
The syntax for the function that invokes this method is:
(vla-AddExtrudedSolidAlongPath space-obj region path)

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))))

Listing 17.10. Auxiliary function ax-ext-path.

Main Function C:SOL-PATH.


The C:SOL-PATH program (Listing 17.11) will be used as an example of this method’s usage.
It operates as follows:

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.

Figure 17.2. Sweep extrusion along a 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*))

Listing 17.11. Creating a solid by sweeping along a path.

17.9 Sample Program: Sweeping along a Helix.


As we have seen in Chapter 14, the HELIX object encapsulates a SPLINE that de ines its
shape. And as we saw in discussing the AddExtrudedSolidAlongPath method it does not
support 3D Splines and therefore does not admit Helices.

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.

Creating the Helix.

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)))

Listing 17.12. Function that creates a HELIX object through command/vl-cmdf.

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.

Figure 17.3. Obtaining a HELIX entity list.

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.

A call to (vlax-ename->vla-object (entlast)) returns the entity created as a VLA-


object. In the event that entmake were not successful the function returns nil, which can be
used for error handling.
(defun ent-base-helix ()
(if (entmake '((0 . "HELIX")
(100 . "AcDbEntity")
(100 . "AcDbSpline")
(70 . 0)
(71 . 3)
(72 . 8)
(73 . 4)
(74 . 0)
(42 . 1.0e-010)
(43 . 1.0e-010)
(40 . 0.0)
(40 . 0.0)
(40 . 0.0)
(40 . 0.0)
(40 . 0.628319)
(40 . 0.628319)
(40 . 0.628319)
(40 . 0.628319)
(10 1.0 0.0 0.0)
(10 1.0 0.20944 0.0333333)
(10 0.932122 0.418345 0.0666667)
(10 0.809017 0.587785 0.1)
(100 . "AcDbHelix")
(90 . 31)
(91 . 8)
(10 0.0 0.0 0.0)
(11 1.0 0.0 0.0)
(12 0.0 0.0 1.0)
(40 . 1.0)
(41 . 0.1)
(42 . 1.0)
(290 . 1)
(280 . 1)))
(vlax-ename->vla-object (entlast))))

Listing 17.13. Function that creates a HELIX by entmake.

Adjusting the Helix’s properties.

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.

Table 17.14. Helix object's editable properties.


Property: Values: Description:
BaseRadius Double Helix base radius.
acHeight
Constrain acTurnHeight Determines which property is constrained when editing other property values.
acTurns
Height Double Determines the Helix height.
Position Variant 3D point which determines the Helix base's center.
TopRadius Double Helix top radius.
TurnHeight Double Specifies the height of one turn of the helix.
Turns Double Number of turns.
acCCW Controls the Helix's twist direction: acCCW = counter-clockwise; acCW =
Twist
acCW clockwise.
The properties that will be adjusted are the position, height, number of turns and the upper
and lower radii. This is done in the ax-helix function of Listing 17.14, which starts by
creating the base helix with a call to ent-base-helix and once it is created adjusts its
properties.
(defun ax-helix (position height turns
radiusbase radiustop / obj)
(if (setq obj (ent-base-helix))
(progn
(vla-put-Height obj height)
(vla-put-Turns obj turns)
(vla-put-BaseRadius obj radiusbase)
(vla-put-TopRadius obj radiustop)
(vla-put-Position
obj
(vlax-3d-point position))
obj)))

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)))

Listing 17.15. Function that prompts for the spring's data.

Creating the path and the profile.


The path is created calling the ax-helix function which receives as position the WCS origin,
since as we have done in previous programs, once created the 3D object it will be aligned to
the current UCS and the center of its base moved to the user speci ied point. The other data
are obtained through the spring-data function.

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).

Creating the Sweep Solid.


The path and region to sweep are returned as VLA-objects. However, to use them with the
_SWEEP command they must be included in selection sets. Besides it is necessary to heed all
the precautions we have explained about running object snaps and command prompts. In this
case, as we will be using the AutoCAD command, deleting the path and the region object will
be done automatically according to the value of the DELO BJ system variable. The cmd-
sweep function, receives the enames of the path and the pro ile and returns the VLA-object
for the solid it creates.
(defun cmd-sweep (profile path / ssprofile
sspath res)
(cmd-in)
(setq ssprofile (ssadd)
sspath (ssadd))
(ssadd profile ssprofile)
(ssadd path sspath)
(vl-cmdf "._SWEEP"
ssprofile
""
sspath)
(cmd-out)
(setq res
(vlax-ename->vla-object (entlast)))
(sol-hist res)
res)

Listing 17.16. Function that creates a 3DSolid using the SWEEP command.

Main function C:SPRING.

Figure 17.4. Spring created by sweeping a circular profile along a Helix.


The main function C:SPRING calls the functions described above for creating the 3DSolid
in spring form. The program starts by checking if the current UCS is not the WCS and if so gets
its transformation matrix that is assigned to the mtrans variable. Once the process of
creating the object concludes, it is aligned with the current UCS and moved to the point
speci ied by the user. This program shows how Visual LISP provides us with tools that can
overcome the programming API’s current limitations.
(defun C:SPRING (/ *error* space-obj mtrans
center height turns
radiuswire radiusbase path
ptref normal profile
profile-reg spring-obj)
(setq space-obj (current-space
*aevl:drawing*))
(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)))
(spring-data)
(setq path (ax-helix
'(0 0 0) height turns
radiusbase radiusbase)
ptref (vlax-curve-GetPointAtParam
path
(vlax-curve-GetStartParam
path))
normal (vlax-curve-GetFirstDeriv
path
(vlax-curve-GetStartParam
path))
profile (vl-catch-all-apply
'vla-AddCircle
(list space-obj
(vlax-3d-point ptref)
radiuswire)))
(cond
((vl-catch-all-error-p profile)
(prompt
(vl-catch-all-error-message
profile)))
(t
(vla-put-normal
profile
(vlax-3d-point normal))
(setq profile-reg (vlax-vla-object->ename
(car (ax-region
space-obj
(list profile))))
path (vlax-vla-object->ename path))
(setq spring-obj
(cmd-sweep profile-reg
path))
(if mtrans
(vla-TransformBy spring-obj mtrans))
(ax-translation
spring-obj
(trans center 1 0 t))
(vla-Update spring-obj)
(ax-SWt)
(vla-EndUndoMark *aevl:drawing*))))

Listing 17.17. Main function C:SPRING.

17.10 AddRevolvedSolid: Solids of Revolution.


A solid of revolution is the result of sweeping a pro ile around a revolution axis. It is created
using the AddRevolvedSolid method, whose syntax is:
(vla-AddRevolvedSolid space-obj region axisPoint axisDir angle)

The arguments it receives are described in Table 17.15.

Table 17.15. Arguments for vla-AddRevolvedSolid.


Argument: Type: Description
space-obj VLA object ModelSpace, PaperSpace or Block.
region VLA object A planar region to use as a profile.
A safearray of three real numbers that determine the WCS position of the rotation axis'
axisPoint Variant
starting point.
axisDir Variant A safearray of three real numbers that specifies the rotation axis' direction vector .
angle Double The angle of revolution in radians.

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))))

Listing 17.18. Function that creates a solid of revolution.

17.11 Sample Program: Creating a Solid of


Revolution.
As an AddRevolvedSolid method sample we will implement the C:SOL-REV program
shown in Listing 17.19, which prompts the user for the selection of Region, 2D Polyline,
Spline, Circle or Ellipse objects to be used as a pro ile. The selection will be done with ssget
limited to a single object and iltered to allow only those entities. According to the selected
object different actions will be taken.

Figure 17.5. Solids of revolution created with C:SOL-REV.

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*))

Listing 17.19. Sample program that creates a solid of revolution.

17.12 Physical and Geometric Properties.


A 3DSolid has a number of physical properties which are precisely those that allow a series
of engineering calculations and as we saw earlier, is one of the motives for the development of
this modeling system. These properties can be consulted using the _MASSPROP command
that allows saving the data to an external ile. This command also applies to Region objects.
But obtaining the physical properties using this command is not practical in a programming
environment. To consult the value of any ActiveX object property we can use the standard
function vlax-get-property, whose syntax is:

(vlax-get-property VLA-object "PropertyName") or

(vlax-get-property VLA-object 'PropertyName)

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.

Table 17.16. Physical/geometric properties of other objects.


Property: Description:
Circumference Circumference of circles.
Radius Radius of arcs and circles.
Center Center point of arcs, circles, ellipses and viewports.
Normal Vector perpendicular to the object's plane.
Coordinates Coordinates of the vertices in 3DFaces, Polylines and Meshes.
FaceCount Number of faces in MESH objects.
Smoothness Smoothing level in MESH objects.
Elevation Elevation relative to the current UCS in Hatch, Polyline or Section objects.
ArcLength Length of Arc objects.
EndAngle End angle in Arc and Ellipse objects.
EndPoint End point in Arc, Ellipse and Line objects.
StartAngle Initial angle in Arc, and Ellipse objects.
StartPoint Initial point in objects Arc, Ellipse and Line objects.
TotalAngle Included angle in Arc objects.
Angle Angle relative to the X axis positive direction in Line objects.
Delta X, Y, Z Increments in Line objects.
Distance a 2D AutoCAD object is extruded above or below its elevation. For Arcs, Circles,
Thickness
Lines, Polylines, Points, 2D Solids, Text and legacy Traces.
BasePoint Initial point for XLINE and RAY objects.
DirectionVector Direction vector for XLINE and RAY objects.
SecondPoint Second point that defines the orientation of XLINE and RAY objects.
BaseRadius Base radius for HELIX objects.
Height Height for Attribute, Helix, Shape, Text, Underlay, ExtrudedSurface or PointCloud objects.
Position Center of the base of Helix and 3DSolid objects.
TotalLength Total length of a Helix object.
TurnHeight Height of one full turn for a Helix object.
Turns Total number of turns in a Helix object.
TurnSlope Constant incline angle for the Helix path.
Twist Direction of rotation for a SweptSurface or Helix object.
Direction Extrusion vector for ExtrudedSurface objects.
TaperAngle Taper angle for ExtrudedSurface objects.
EndDraftAngle End draft angle (in radians) for LoftedSurface objects.
EndDraftMagnitude Final draft magnitude for LoftedSurface objects.
NumCrossSections Number of cross sections for LoftedSurface objects.
NumGuidePaths Number of guide curves for LoftedSurface objects.
StartDraftAngle Initial draft angle (in radians) for LoftedSurface objects.
StartDraftMagnitude Initial draft magnitude for LoftedSurface objects.
SurfaceNormals Specifies to which cross-section curve is the LoftedSurface normal.
SurfaceType Indicates the type of surface.
RevolutionAngle Revolution angle for RevolvedSurface objects.
AxisPosition Position of the axis of revolution for RevolvedSurface objects.
AxisDirection Orientation of the axis of revolution for RevolvedSurface objects.
ProfileRotation Rotation profile for SweptSurface objects.
Bank Specifies whether the profile of a SweptSurface object revolves around a 3D trajectory.
Returns the length of a Line, Polyline, 3DPolyline, LWPolyline,SweptSurface or PointCloud
Length
object.
ProfileRotation Specifies the rotation of the profile of a SweptSurface object.
scale Specifies the scale factor between start and end in a SweptSurface object.

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.

18.1 Slicing Solids.


The SliceSolid method cuts a 3DSolid objects by an arbitrary slicing plane de ined from
three points. The part of the 3DSolid in the negative half-space is discarded by default.
Optionally it can be specify that both parts are kept, creating then a new 3DSolid.

The syntax for this method is:


(vla-SliceSolid object point1 point2 point3 negative)

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.

Table 18.1. Arguments for vla-SliceSolid.


Argument: Type: Description:
Point1 Variant First 3D point in the slicing plane, in the format returned by vlax-3d-point.
Point2 Variant Second 3D point in the slicing plane.
Point3 Variant Third 3D point in the slicing plane.
Determines whether an object is created from portion of the 3DSolid in the half-space to
which the slicing plane's normal points. If :vlax-false a new object is not returned. If
Negative Boolean
:vlax-true the method returns a new 3DSolid. In both cases the original object is
retained.

(defun ax-slice (obj pt1 pt2 pt3 negative /)


(setq res (vl-catch-all-apply
'vla-SliceSolid
(list obj
(vlax-3d-point pt1)
(vlax-3d-point pt2)
(vlax-3d-point pt3)
negative)))
(if (vl-catch-all-error-p res)
(prompt (vl-catch-all-error-message res))
res))

Listing 18.1. Function that slices the 3DSolid.

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: ")))

Listing 18.2. Function-sol-p-data requesting user input.

18.2 Sample Program: Polyhedra obtained by


slicing 3DSolids.
Figure 18.1. Results obtained with C:SOL-POLYHEDRON and C:SECT-POLYHEDRON.

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*))

Listing 18.3. Main function C:SOL-POLYHEDRON.

18.3 Sectioning 3DSolids.


SectionSolid is very much alike the SliceSolid method. This method shouldn't be
confused with the creation of SECTION objects of the kind created with the
_SECTIONPLANE command. Because of its relation to 3DSolids we will study these
SECTION objects in this same Chapter (Section 18.10).

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))

Listing 18.4. Function that creates a solid's section as a REGION object.

18.4 Sample Program: Sections of a Sphere.


The C:SECT-POLYHEDRON program introduces very little changes to the previous one that
creates polyhedra by slicing. We simply replace ax-slice with the ax-sect function and
add the option to delete the original sphere if the DELOBJ system variable has a value
greater than 0.
(defun C:SECT-POLYHEDRON
(/ class center radius sphere regions)
(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
(setq
regions
(cons
(ax-section
sphere
(nth (1- (car face)) vertices)
(nth (1- (cadr face)) vertices)
(nth (1- (caddr face)) vertices))
regions)))
(if (> (getvar "DELOBJ") 0)
(vla-Delete sphere))
;; Transformations:
(foreach region regions
(ax-scale region
(list radius radius radius))
(if mtrans
(vla-TransformBy region mtrans))
(ax-translation
region
(trans center 1 0 t)))
(ax-SWt)))
(vla-EndUndoMark *aevl:drawing*))

Listing 18.5. Main function C:SECT-POLYHEDRON.

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.

18.5 Boolean operations on 3DSolids.


The information on Boolean operations regarding regions is also valid in the case of 3DSolids.
The added complexity when programming the construction of a complex 3DSolid has to do
with the position and spatial orientation of its components. The Boolean method for
3DSolid objects takes three arguments: the object whose Boolean method is invoked, the
acBooleanType enum constant that de ines the type of operation and the second object to
be used in that operation. As a result, the object received as the irst argument is modi ied.
The AcBooleanType constants are:

Table 18.2. AcBooleanType constants.


Constant: Value: Description:

acUnion 0 Joins the second 3DSolid to the first one.

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)

INTERSECTION (acIntersection) operations create a solid from the volume that is


shared by two 3DSolids. If the solids used as arguments do not intersect, both disappear. The
syntax for this operation is:
(vla-Boolean object1 acIntersection object2)
The 3DSolid that remains is the one received as object1.

SUBTRACTION (acSubtraction) operations eliminate from the irst 3DSolid object's


volume the volume that the second 3DSolid occupies. In the event that solids do not
intersect, the second solid will be deleted. The syntax for the Boolean acSubtraction
operation is:
(vla-Boolean base-obj acSubtraction subtract-obj)

18.6 Sample Program: UNION and SUBTRACTION


operations.
As an example of UNION and SUBTRACTION operations we propose a program that creates a
part to be used in connecting space frame bars. The program prompts the user for the
connector's center position, the connector plates gauge and the diameter of the holes. The
other dimensions are calculated based on the gauge value. For the holes diameter a default
value is suggested (gauge x 2) that the user can accept by pressing ENTER or he can specify
other value. The program checks that the speci ied value does not exceed gauge x 2.5 and
if that were the case the user is prompted for a new value. To demonstrate how the 3DSolid
can be set to retain the History of its original components in case the SOLIDHIST system
variable is not enabled (value = 0) the program asks the user if it should be activated,
changing its value to 1 if the user chooses to do so. Data entry is done within the
connector-data function (Listing 18.6) which is invoked from the main function
C:CONNECTOR.
(defun connector-data (/ maxdiam)
(setq
origin '(0 0 0)
center
(getpoint "\nSpecify connector's center:")
gauge
(getdist center "\nSpecify gauge:")
maxdiam (* gauge 2.5))
(initget (+ 2 4))
(while (or (not diam) (> diam maxdiam))
(setq
diam
(getreal
(strcat
"\nHole diameter <"
(rtos (* gauge 2))
">: ")))
(cond
((not diam) (setq diam (* gauge 2)))
(t
(if (> diam maxdiam)
(prompt
(strcat
"\nDiameter must be less than "
(rtos maxdiam 2 2))))
(initget (+ 2 4)))))
(cond
((= (getvar "SOLIDHIST") 0)
(initget 1 "Yes No")
(if
(equal
(getkword
"\nRecord Solids history? [Yes/No]:")
"Yes")
(setvar "SOLIDHIST" 1)))))

Listing 18.6. Connector data entry.

Creation of the component 3DSolids.

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)))

Listing 18.7. Function used to create cubes as 3DSolids.

(defun ax-cylinder (center radius dim-z / res)


(setq res
(vl-catch-all-apply
'vla-AddCylinder
(list (current-space *aevl:drawing*)
(vlax-3d-point center)
radius
dim-z)))
(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)))

Listing 18.8. Function used to create cylinders as 3DSolids.

Functions that rotate objects around the X and Y axes.

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.

(defun rot-90-y (obj / ang)


(setq ang (/ pi 2))
(vla-TransformBy
obj
(vlax-tmatrix
(list (list (cos ang) 0.0 (sin ang) 0.0)
(list 0.0 1.0 0.0 0.0)
(list (- (sin ang)) 0.0 (cos ang) 0.0)
(list 0.0 0.0 0.0 1.0)))))

Listing 18.10. Function that rotates an object 90º about the Y axis.

Main Function C:CONNECTOR.

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.

The delete-duplicates removes them applying vl-remove-if. This function uses as


its comparison predicate a lambda expression applying equal. This expression takes two
LISP objects of any type as arguments and returns T if they are equal. It is the most general
way to compare objects, supporting both atoms and lists. An optional argument de ines the
admitted tolerance when comparing real number values, either as isolated atoms or
contained in lists, as is the case with 2D or 3D point coordinates. Any of the coordinates can
differ by a value so small that it is negligible for practical purposes and yet that can make the
system consider them unequal, so for this purpose the tolerance argument is crucial. The
process is as follows:

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))

Listing 18.11. Function that removes duplicates from a list.

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.

(defun C:CONNECTOR (/ mtrans origin center gauge


side diam disp positions
centers base dif)
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq mtrans (last (ax-ucs-matrix))))
(t (setq mtrans nil)))
(connector-data)
(setq side (* gauge 7)
base (ax-cube origin side)
disp (list (/ side 2.0)
(/ side 2.0)
(/ side 2.0))
positions '((1 1 1)
(1 1 -1)
(1 -1 1)
(1 -1 -1)
(-1 1 1)
(-1 1 -1)
(-1 -1 1)
(-1 -1 -1))
centers (mapcar '(lambda (pos)
(mapcar '* disp pos))
positions)
side (* gauge 6))
(mapcar '(lambda (ctr)
(setq dif (ax-cube ctr side))
(vla-Boolean base acSubtraction dif))
centers)
(setq side (* gauge 2.0)
disp (list side side 0.0)
centers (delete-duplicates
(mapcar '(lambda (pos)
(mapcar '* disp pos))
positions)))
(mapcar '(lambda (ctr)
(setq dif (ax-cylinder
ctr
(/ diam 2.0)
side))
(vla-Boolean base acSubtraction dif))
centers)
(mapcar '(lambda (ctr)
(setq dif (ax-cylinder
ctr
(/ diam 2.0)
side))
(rot-90-y dif)
(vla-Boolean base acSubtraction dif))
centers)
(mapcar '(lambda (ctr)
(setq dif (ax-cylinder
ctr
(/ diam 2.0)
side))
(rot-90-x dif)
(vla-Boolean base acSubtraction dif))
centers)
(if mtrans
(vla-TransformBy base mtrans))
(ax-translation base (trans center 1 0 t))
(ax-SWt)
(vla-EndUndoMark *aevl:drawing*))

Listing 18.12. Main function C:CONNECTOR.


18.7 Sample Program: Part created by
INTERSECTION.
As an example of the use of INTERSECTION operations we propose a program that creates a
mechanical transmission coupling. In this case the piece is obtained from the intersection of
two identical volumes achieved by copying and rotating one of them 90° about the X and Z
axes. To create the initial 3DSolid UNION and SUBTRACTION operations are employed.

Prompting for Data.


This program only prompts the user for the position of its center (center variable) and the
coupling's total length (dim-X variable). The remaining dimensions are calculated from the
dim-X value. Just as in the previous example the program asks the user whether to set the
History property to Record in case it were not preset in the SOLIDHIST system variable.

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)))))

Listing 18.13. Function that prompts for the coupling's data.

Creation of the component 3DSolids.


To create the coupling's component solids the ax-cylinder function in Listing 18.8 will be
used and we will de ine the ax-box function (Listing 18.14) which is a variant of the ax-
cube function taking in this case as arguments the length (dim-x), width (dim-y) and
height (dim-z).
(defun ax-box (center dim-x dim-y dim-z / res)
(setq res
(vl-catch-all-apply
'vla-AddBox
(list
(current-space *aevl:drawing*)
(vlax-3d-point center)
dim-x
dim-y
dim-z)))
(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)))

Listing 18.14. Function that creates a 3DSolid rectangular prism.

Functions to rotate objects around the X and Z axes.

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.

Main Function C:COUPLING.


The base 3DSolid is made up from the union of a cylinder and a rectangular prism, from
which other cylinder and other prism are subtracted.
(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)))

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*))

Listing 18.16. Main function C:COUPLING.

Figure 18.3. 3DSolid created by C:COUPLING.

18.8 CheckInterference: Interference operations.


When modeling mechanical assemblies using 3DSolids it is often necessary to verify if a part
interferes with other and if so, we can need data on the interference's physical characteristics.
But to do this using the INTERSECTION operation we would have to apply it to copies of the
two objects to be checked, since these operations are destructive in the sense that both solids
it receives as arguments disappear if there is no intersection or if there were, we would be left
only with the common volume. To meet this need we have the CheckInterference
method that operates like the Boolean INTERSECTION operation, but retains the two objects
it receives as arguments creating a new object with the intersection volume. This new object
can be preserved to retrieve its physical properties or for any other use. This functionality is
the same which on which the _INTERFERE command is based.

The Visual LISP syntax for the CheckInterference method in Release 2013 is:
(vla-CheckInterference solid1 solid2
CreateInterferenceSolid 'SolidsInterfere)

The CreateInterferenceSolid argument is a boolean type data that if :vlax-true


will determine that the interference solid will be created. The 'SolidsInterfere
argument is an optional variable name that will hold :vlax-true if the solids do interfere
and :vlax-false if they don't.

T h e 'SolidsInterfere argument was added in Release 2013. Using it in previous


Releases will generate an error. This solves a problem with this method in previous versions,
where if called with the CreateInterferenceSolid argument as :vlax-false nothing
would be returned so it would be completely useless. For Releases 2000 to 2012 its syntax
should be:
(vla-CheckInterference solid1 solid2
CreateInterferenceSolid)

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
_$

I f CreateInterferenceSolid is :vlax-true and there is an interference, vla-


CheckInterference will return the interference solid's VLA-object and in case there is
not it will return nil. Programs must take into account the new Release 2013 syntax, as the
SolidsInterfere argument is not optional. The correct syntax can be selected checking
the ACADVER variable's value, which for Release 2013 will be "19.0s (LMS Tech)" as
the condition for an if expression:
(if (wcmatch (getvar "acadver") "19*")
(vl-catch-all-apply 'vla-CheckInterference
(list solid1 solid2 :vlax-true 'SolidsInterfere))
(vl-catch-all-apply 'vla-CheckInterference
(list solid1 solid2 :vlax-true)))
As it is not compatible with previous releases, the value assigned to 'SolidsInterfere
should not be used when the program could be used in previous Releases.

18.9 Sample programs: 3DSolid TRIM and SPLIT


commands.
The CheckInterference method can have many uses besides that of checking for possible
interferences. In this tutorial we will add two new 3DSolid editing commands to AutoCAD.
These will reproduce the functionality of the Solid modeling tools TRIM and SPLIT
introduced in Sketchup PRO version 81. The TRIM tool allows picking a 3DSolid as the
trimming object and a selection set of overlapping 3DSolids from which the overlapping
portion will be removed without deleting the trimming solid as would be the case if a
SUBTRACTION operation was performed. The SPLIT command is more elaborate, as it will
create separate 3DSolids wherever the solids overlap.

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.

The TRIMMER function.


The basic functionality for 3DSolid trimming is de ined in the trimmer function (Listing
18.17). This function receives two VLA-object Solids as arguments, the trimming object and
the object to-trim. As explained above, the function must recognize the AutoCAD Release
running to use the appropriate syntax for the vla-CheckInterference method. A cond
expression is used to discriminate between three possible conditions:

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))

Listing 18.17. Function that trims a 3DSolid against another.

Figure 18.4. Trimming 3DSolids.

The C:SOL-TRIM main function.


The trimmer function will be called from the C:SOL-TRIM main function (Listing 18.18)
that reproduces the Sketchup TRIM functionality. As this function will perform a series of
changes in the drawing, it will de ine a local *error* handler function which will UNDO the
changes in case an unforeseen error takes place.

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*))

Listing 18.18. Main function C:SOL-TRIM.

The SPLITTER function.


The 3DSolid splitting functionality is a bit more complex than the trimming one. In this case
the main differences are in that:

The interference solid must be subtracted from both overlapping objects.


The two original objects and the interference object must be kept as part of the drawing.
Figure 18.5. Splitting 3DSolids.

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.

Separating Composite 3DSolids.

In certain overlapping conditions subtracting the interference solid may generate a


composite solid with two or more disjointed volumes (sometimes called lumps). Foreseeing
this possibility the _SOLIDEDIT command will be applied to each modi ied solid in order to
extract independent 3DSolid objects from any composite solid that may have been created.
This command is applied by means of the s-separate auxiliary function de ined in Listing
18.20. The command requires an ename so the vlax-vla-object->ename function is
applied to the VLA-object received.
(defun s-separate (obj /)
(vl-cmdf "_solidedit"
"_body"
"_separate"
(vlax-vla-object->ename obj)
"_exit"
"_exit"))

Listing 18.20. Function that separates composite 3DSolids.

The C:SOL-SPLIT main function.


The splitter function will be called from the C:SOL-SPLIT main function (Listing 18.21),
designed to reproduce a behavior which resembles the Sketchup SPLIT tool functionality. In
this case the user is prompted for all the objects to split. The selection set, assigned to the
to-split variable, is iltered to accept only 3DSolid entities. The entities in the to-
split selection set will be checked for interference against each other. In very complex
overlapping situations some objects may remain uncut. In these cases is would be advisable
to apply the command more than once to check if further splitting is possible.

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*))

Listing 18.21. Main function C:SOL-SPLIT.

18.10 Section objects.


Section objects act as cutting planes through 3D objects. These section planes can be used to
analyze models by activating live sectioning and to create section drawings. The de inition of
section objects is exposed as DXF group codes in the classical AutoLISP fashion, so it is
possible to create and modify them using entmake/entmod. Although, ine tuning their
properties will require using the ActiveX interface.

Creating SECTION objects through ENTMAKE.


DXF group codes that apply to SECTION objects are described in Table 18.3. To create a
SECTION object group codes 0, 100, 92 and 11 are the bare minimum that must be included.
The section's properties will depend on current Section settings that will be used as default
values. To enable a higher degree of control on the resulting object we should also include
group codes 1 to specify a name for the section, 10 to specify the section plane's spatial
orientation, 40 to specify the top height and 41 to specify the bottom height.

The ent-section function (Listing 18.22) receives as arguments:

pt-lst: a list of points for the SECTION vertices,


planevector: a vector that specifies the SECTION's spatial orientation,
name: a String specifying the SECTION's name.
topheight: a real number specifying the SECTION's top extents,
bottomheight: a real number specifying the SECTION's bottom extents.
(defun ent-section (pt-lst planevector name
topheight bottomheight /)
(entmake
(append (list '(0 . "SECTIONOBJECT")
'(100 . "AcDbEntity")
'(100 . "AcDbSection")
(cons 1 name) ;Name
(cons 10 planevector) ;VerticalDirection
(cons 40 topheight) ;TopHeight
(cons 41 bottomheight);BottomHeight
(cons 92 (length pt-lst)))
;NumVertices
(mapcar '(lambda (pt) (cons 11 pt))
pt-lst))))

Listing 18.22. Function that creates a SECTION using entmake.

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

Creating SECTION objects through ActiveX.


Sections are created by the AddSection method. This method is exposed by a Block object
of the kind returned by the current-space function defined in Listing 10.31. Its syntax is:
(vla-AddSection space from-point to-point planevector)

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.

Table 18.4. AcadSection methods.


Method: Description
AddVertex Adds a vertex to the section.
(vla-AddVertex section point)
CreateJog Creates a jog on the section plane.
(vla-CreateJog section point)
GenerateSectionGeometry Generates 2D or 3D section geometry
(vla-GenerateSectionGeometry sect-obj model 'intbound-objs
'intfill-objs 'backg-objs 'foreg-objs 'curvetang-objs)
Detects if a point is on the section plane. For subitem types see
HitTest
Table 18.4.
(vla-HitTest section point 'bool-hit 'segment-index
'point-on-seg 'subitem-type)
RemoveVertex Removes a vertex in the section line.
(vla-RemoveVertex section vertex-index)

Table 18.5. AcSectionSubItem enumeration constants.


Constant: Value
acSectionSubItemkNone 0
acSectionSubItemSectionLine 1
acSectionSubItemSectionLineTop 2
acSectionSubItemSectionLineBottom 4
acSectionSubItemBackLine 8
acSectionSubItemBackLineTop 16
acSectionSubItemBackLineBottom 32
acSectionSubItemVerticalLineTop 64
acSectionSubItemVerticalLineBottom 128

Table 18.6. AcadSection properties.


Property: Description:
BottomHeight Distance to the section plane's bottom extents.
Vertex. One indexed entry for each vertex, including backline
Coordinate
vertices in Boundary or Volume type sections. 3D point.
Elevation Current section elevation.
EntityTransparency Section line's transparency.
IndicatorFillColor Section plane's TrueColor AcCmColor object number
IndicatorTransparency Section plane transparency in 3D visual styles.
LiveSectionEnabled Boolean. Turns live section on or off for this section object.
Name Section name: String.
NumVertices Number of vertices, including backlines, in the section line.
Settings Gets the AcadSectionSettings object.
Specifies the Section state as an AcSectionState enum. See
State
Table 18.5.
TopHeight Distance to the section plane's top extents.
VerticalDirection Section plane's vertical direction as a 3D vector.
Gets the vertices in the section line as an array of X, Y, Z
Vertices
coordinates.
ViewingDirection Viewing direction for the section plane as a 3D vector.

Table 18.7, AcSectionState enumeration constants.


Constant: Value
acSectionStatePlane 1
acSectionStateBoundary 2
acSectionStateVolume 4

Table 18.8. AcSectionType enumeration constants.


Constant: Value
acSectionTypeLiveSection 1
acSectionType2dSection 2
acSectionType3dSection 4

Generating the SECTION Geometry.


Enabling Live Sectioning, SECTION object planes can be moved interactively along a 3D
model in order to examine it in detail. Accurate section drawings may be generated from
these section planes in order to use them in the product's documentation. The section view's
geometry can be created applying the SECTION object's GenerateSectionGeometry
method (see Table 18.4). Its Visual LISP syntax is:
(vla-GenerateSectionGeometry sect-obj model 'intbound-objs 'intfill-objs 'backg-objs
'foreg-objs 'curvetang-objs)
where the arguments received are:

sect-obj: the SECTION object for which the geometry will be generated,
model: the 3DSolid model object.

The new graphic entities will be assigned to the following symbols:

'intbound-objs: section object plane's intersection surface outline segments.


'intfill-objs: hatch displayed inside the boundary area of the cut surface.
'backg-objs: 2D and 3D section background lines.
'foreg-objs: lines representing cut-away objects.
'curvetang-objs: 2D section curved lines that are tangent to the section plane.

The GenerateSectionGeometry method is called from the sect-geom function (Listing


18.24). This function receives as arguments the section object (sect-obj), a 3DSolid object
(model) and as string with the section object's name. It will create the section view's
geometry and will create a Group with the objects created. In order to create the group the ax-
add-group function we de ined in Chapter 11 (Listing 11.16) will be used. This function relies
on the auxiliary functions ax-list->variant (Listing 11.14) and ax-no-group (Listing 11.15)
that must also be loaded.
(defun sect-geom (sect-obj model name / objs
intbound-objs intfill-objs
backg-objs foreg-objs
curvetang-objs)
(vla-GenerateSectionGeometry
sect-obj model 'intbound-objs 'intfill-objs
'backg-objs 'foreg-objs 'curvetang-objs)
(setq objs
(apply
'append
(mapcar
'(lambda (a)
(if
(>=
(vlax-safearray-get-u-bound
a
1)
0)
(vlax-safearray->list a)))
(list
intbound-objs intfill-objs
backg-objs foreg-objs
curvetang-objs))))
(ax-add-group
name
(apply
'append
(mapcar
'(lambda (a)
(if
(>=
(vlax-safearray-get-u-bound
a
1)
0)
(vlax-safearray->list a)))
(list
intbound-objs intfill-objs backg-objs
foreg-objs curvetang-objs)))))

Listing 18.24. Function that creates the section geometry.

Setting SECTION geometry objects properties.


The appearance of the section's graphic entities are de ined in the AcadSectionSettings
object. These settings depend on the value of the CurrentSectionType property's value,
which may be one of the constants shown in Table 18.8. The properties that can be set for
each of the section drawing components and their generation parameters are summarized in
Table 18.9.

Table 18.9. AcadSectionTypeSettings properties.


Objects: Properties:
Lines behind the section plane: Color, Layer, Linetype,
BackgroundLines
LinetypeScale, Lineweight, PlotStyleName, Visible.
Curved lines that are tangent to the section plane in 2D sections: Color, HiddenLine,
CurveTangencyLines Layer, Linetype, LinetypeScale, Lineweight, PlotStyleName,
Visible.
Cut-away objects: Color, EdgeTransparency, FaceTransparency,
ForegroundLines HiddenLine, Layer, Linetype, LinetypeScale, Lineweight,
PlotStyleName, Visible.
Line segments that outline the intersection surface of the
IntersectionBoundary section object plane: Color, DivisionLines, Layer, Linetype,
LinetypeScale, Lineweight, PlotStyleName, Visible.
Hatch displayed within the area where the section object
intersects the 3D object: Color, FaceTransparency,
IntersectionFill HatchAngle, HatchPatternName, HatchPatternType, HatchScale,
HatchSpacing, Layer, Linetype, LinetypeScale, Lineweight,
PlotStyleName, Visible.
DestinationBlock Destination block for section generation: Object.
DestinationFile Destination file for section generation: String.
GenerationOptions Sum of source and destination constants shown in Table 18.10.
SourceObjects Source objects for section generation: Array of objects.

Table 18.10. GenerationOptions enumeration constants.


Constant: Value
acSectionGenerationSourceAllObjects 1
acSectionGenerationSourceSelectedObjects 2
acSectionGenerationDestinationNewBlock 16
acSectionGenerationDestinationReplaceBlock 32
acSectionGenerationDestinationFile 64

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))))

Listing 18.25. Function that adds layers to the drawing.

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:

Layers: Each component type shall be placed in its own Layer.


Visibility: CurveTangencyLines and ForegroundLines will be disabled.
Colors: Al components shall be assigned the ByLayer color.
Hatch pattern: A user-defined Hatch will be specified for the IntersectionFill
component.

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)))

Listing 18.26. Section object properties.

18.11 Sample program C:SOL-SECT.


To demonstrate the use of the Section object's methods and properties we will develop a
program that automates the creation of Section objects and the generation of 2D section
views. This program will prompt the user for the section's orientation (Top, Front or Side), a
name for the new section and the selection of a 3DSolid object. A section plane with the
selected orientation will be centered on the 3DSolid and the corresponding section view
generated.

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))))

Listing 18.27. Data entry function.

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.

Calculating Section plane parameters.


The parameters needed for creating the section plane are its PlaneVector and two points.
These are the arguments that will be passed to the ax-section function (Listing 18.23).
After creating the section its size will be de ined by setting its TopHeight and
BottomHeight properties so the plane will be offset a certain distance (* dy 0.2) from
the solid's extents. The values for TopHeight and BottomHeight are calculated in sect-
options. These calculations will depend on the view selected, Top, Front or Side, so they
will be done within a cond expression. This function also establishes the ViewDirection
vector.
(defun sect-options
(opt dy dz xmin ymin xmax ymax zmax /)
(cond ((= opt "Top")
(setq
planeVector '(0 1 0)
viewdir '(0 0 1)
bottomheight (* dy 0.2)
topheight (+ dy bottomheight)
pt-lst (list
(list (- xmin bottomheight)
ymin
(/ (+ zmin zmax) 2.0))
(list (+ xmax bottomheight)
ymin
(/ (+ zmin zmax) 2.0)))))
((= opt "Front")
(setq planeVector '(0 0 1)
viewdir '(0 -1 0)
bottomheight (* dz 0.2)
topheight (+ dz bottomheight)
pt-lst (list (list (- xmin bottomheight)
(/ (+ ymin ymax) 2.0)
zmin)
(list (+ xmax bottomheight)
(/ (+ ymin ymax) 2.0)
zmin))))
((= opt "Side")
(setq planeVector '(0 0 1)
viewdir '(-1 0 0)
bottomheight (* dz 0.2)
topheight (+ dz bottomheight)
pt-lst (list (list (/ (+ xmin xmax) 2.0)
(- ymin bottomheight)
zmin)
(list (/ (+ xmin xmax) 2.0)
(+ ymax bottomheight)
zmin))))))

Listing 18.28. Section options.

Main function C:SOL-SECT.


The C:SOL-SECT main function will de ine its local *error* function that will undo any
changes made in case of error. Once the UNDO Start mark is set, the user is prompted for
the necessary data calling the sect-data function (Listing 18.25). Then the Layers for the
section view components are created by the add-layers function (Listing 18.25). The
Section object is created by the ax-section function and assigned to the sect variable.
This object's section type and properties are assigned calling the sect-props function
(Listing 18.26). Finally, the section view components are generated by the sect-geom
function (Listing 18.24).
(defun C:SOL-SECT (/ *error* opt name obj
planevector viewdir
bottomheight topheight viewdir
pt-lst)
(defun *error* (msg)
(vla-EndUndoMark *aevl:drawing*)
(vl-cmdf "_U")
(prompt msg))
(vla-StartUndoMark *aevl:drawing*)
(sect-data)
(add-layers
name
"_"
'("Section" "BackgroundLines"
"CurveTangencyLines" "ForegroundLines"
"IntersectionBoundary" "IntersectionFill"))
(setq sect (ax-section pt-lst planevector))
(sect-props
sect dmin name topheight bottomheight viewdir)
(sect-geom sect obj name)
;;; (ax-view-dir sect t)
(vla-EndUndoMark *aevl:drawing*))

Listing 18.29. Main function C:SECTIONOBJ.

Figure 18.6 . Sections created by C:SOL-SECT.

18.12 Summary.
For creating a complex 3DSolid we use a series of operations that allow us to:

Slice an object by an intersecting plane, retaining both parts or only one.


Create a flat region from the intersection of a plane with a 3DSolid.
Remove from a 3DSolid the volume occupied by other 3DSolid.
Add the volumes of two 3DSolids.
Create a 3DSolid that occupies the common volume of two overlapping 3DSolids,
replacing the two original objects.
Create a new 3DSolid that occupies the common volume of two overlapping 3DSolids
without deleting the original objects.

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.

1 TRIMBLE Sketchup© PRO 8: http://sketchup.google.com/intl/en/product/gsup.html


Chapter 19
Subdivision Surfaces
The Mesh Modeler introduced in Release 2010 is based on Subdivision Surfaces, objects that
make possible a very intuitive way of 3D objects modeling through the manipulation of mesh
vertices, edges or faces. Subdivision Surfaces are presented in AutoCAD as MESH entities
(AcadSubDMesh objects).

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.

Figure 19.1. Mesh smoothing.


19.1 Programming MESH objects with Visual LISP.
Having been introduced with AutoCAD Release 2010, when VBA had already been
discontinued, methods to create AcadSubDMesh objects through ActiveX are not exposed.
However, unlike 3DSolids or Surfaces in which information is encrypted, the values for DXF
group codes in the de inition list of MESH entities are directly accessible from AutoLISP. This
means that MESH entities can be created by entmake and modi ied by entmod. The
information on faces, edges and vertices to which the ent... functions have access always
belong to smoothing Level 0. But using ActiveX it is possible to access a series of MESH
properties, including the facet vertices positions.

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.

19.2 Creating MESH entities with ENTMAKE.


Faces are de ined from the sequence of vertices that surround them. The sequence of two
vertices de ines an edge and face adjacency is determined by their shared edges. This basic
MESH structure, its faces, edges and vertices, are designated as a Level 0.

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.

Table 19.1. MESH entity definition list.


((-1 . <Entity name:
7ffff605b00>)
(0 . "MESH") ;Entity
(330 . <Entity name: Common group codes.
7ffff6039f0>) Group codes -1, 330 and 5 are specific to each object and are not included in the
(5 . "228")
(100 . "AcDbEntity") ;Subclass list passed to entmake.
(67 . 0) Group codes 67, 410 and 8 corresponding to ModelSpace/Paper, Layout and
(410 . "Model") Layer can be omitted. Current values are automatically assigned.
(8 . "0")
(100 .
"AcDbSubDMesh");Subclass
Version and smoothing.
(71 . 2) ; Version number Version number is always 2. Value 1 is admitted. Any other will cause an error.
(72 . 0) ; Blend Crease Blend Crease determines whether the crease will be smoothed according to the
property ZOOM value. This does not produce any effect.
(91 . 0) ; Subdivision level Subdivision level number determines the object's smoothness.
(92 . 4); Num. vertices Level
0
(10 0.0 0.0 100.0) ; Vert.
0 Vertices position.
(10 0.0 94.2 -33.3) ; Vert. Group code 92 indicates the number of vertices. Must be followed by as many
1 group codes 10 as vertices. Each group code 10 sub-list holds a vertex's
(10 -81.6 -47.1 -33.3); Vert. coordinates.
2
(10 81.6 -47.1 -33.3) ; Vert.
3
(93 . 16); Level 0 face list
size
(90 . 3) ; Num. vertices, Face
1
(90 . 0)
(90 . 1)
(90 . 2) Faces Definition.
(90 . 3) ; Num. vertices, Face Group code 93 indicates the number of sublists included in Level 0 faces
2 definition. Each face is defined from a block of sublists associated to group code
(90 . 0) 90, the first of which holds the number of vertices around the face and is followed
(90 . 2) by as many group code 90 sublists as vertices. That is, each block consists of
(90 . 3) number-of-vertices + 1 sublists.
(90 . 3) ; Num. vertices, Face The associated value for each sublist is the index (zero-based) identifying the
3 vertex in the group code 10 sequence.
(90 . 0) Unlike what happens with the PolyfaceMesh, the number of vertices is not limited
(90 . 3) to 4.
(90 . 1)
(90 . 3) ; Num. vertices, Face
4
(90 . 1)
(90 . 3)
(90 . 2)
(94 . 6) ; Edge count of level
0
(90 . 0) ; Edge 1
(90 . 1)
(90 . 0) ; Edge 2
Edges definition.
(90 . 2)
Group code 94 indicates the number of edges for Level 0.
(90 . 0) ; Edge 3
The sublists that follow associated with group code 90 double that number, since
(90 . 2)
each edge is defined by two successive sublists. For example, Edge 1 is in this
(90 . 0) ; Edge 4
case the one that connects vertices 0 and 1.
(90 . 3)
(90 . 0) ; Edge 5
(90 . 3)
(90 . 0) ; Edge 6
(90 . 3)
(95 . 6) ; Number of
creases
Edge creases.
(140 . 0.0) ; Edge 1
Group code 95 matches group code 94 as it counts the number of edges. For each
(140 . 0.0) ; Edge 2
edge, group code 140 indicates the highest level of smoothing for which the
(140 . 0.0) ; Edge 3
crease is maintained. If -1 the crease is always maintained. A value of 0 indicates
(140 . 0.0) ; Edge 4
there is no crease.
(140 . 0.0) ; Edge 5
(140 . 0.0) ; Edge 6
(90 . 1) ; Modified Properties modification.
subentities Group code 90 that follows the 140 group codes indicates the number of sub-
(91 . 9) ; Subentity ID entities with modified properties. Code 91 indicates the modified subentity. Sub-
(92 . 1) ; Number of entity numbers are zero-based, starting with edges. The index 9 identifies the last
properties face (5 edges + 4 faces). The following code 90 indicates the modified property: 0
(90 . 0) ; Property (0=Color) = Color 1 = Material,
(63 . 1) ; Value (1=Red) 2 = Transparency, 3 = Material mapping.

19.3 Sample Program: Polyhedral MESH.


To demonstrate the creation of MESH entities using entmake we'll retake the regular
polyhedra program used for the PolyfaceMesh. The mesh-polyhedron-data function
will be used for data entry. It bears a great resemblance to the one in Listing 16.19 used for
the PolyfaceMesh polyhedron. In this case the user is prompted for the selection of the
polyhedron type, its center point, the circumscribed sphere's radius, the smoothing level and
the maximum level of smoothing for edge creases. When the user proposes the smoothing
level the function checks that it does not exceed the value established by the
SMOOTHMESHMAXLEV system variable. If so, the user is informed and prompted again for
this value. For the edge crease data the options Always, Never, or the values 1, 2 or 3 are
offered.
(defun mesh-polyhedron-data (/)
(initget
1 "Tetrahedron Hexahedron Dodecahedron")
(setq
class
(getkword
"\nType: [Tetrahedron/Hexahedron/Dodecahedron]:"))
(initget 1)
(setq center
(getpoint "\nPolyhedron's center:"))
(initget (+ 1 2 4))
(setq radius
(getdist
center
"\Circumscribed sphere's radius:"))
(initget (+ 1 4))
(while (> (setq level
(getint "\nSmoothing level:"))
(getvar "SMOOTHMESHMAXLEV"))
(prompt
(strcat
"\nSmooting level must not be more than "
(itoa (getvar "SMOOTHMESHMAXLEV"))))
(initget (+ 1 2 4)))
(initget 1 "Always Never 1 2 3")
(setq crease
(getkword
"\nEdge crease level [Always/Never/1/2/3]: "))
(cond ((= crease "Always") (setq crease -1))
((= crease "Never") (setq crease 0))
(t (setq crease (atof crease)))))
Listing 19.1. Data entry for a Polyhedron shaped MESH.

Adapting the data for the MESH entity.

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)))

Listing 19.2. Function that creates the MESH edges list.

Creating the MESH through entmake.

Figure 19.2. Polyhedral meshes with different levels of smoothness.

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))))

Listing 19.3. Creating MESH entities with ENTMAKE.

Main Function C:MESH-POLYHEDRON.

The main function C:MESH-POLYHEDRON relies on the mesh-polyhedron-data, op-


polyhedron and face-edges functions to obtain and format the required data that ent-
mesh will use to create the MESH entity. If successful, the new object will be subject to scaling,
alignment to the current UCS and translation to the user-speci ied position. Details about
these operations can be found in Chapter 13.

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))

Listing 19.4. Main Function C:MESH-POLYHEDRON.

19.4 Sample Program: MESH approximating


mathematical functions.
T h e C:POLYMESH program (Listing 16.11) proposed the creation of the legacy
PolygonMesh where the Z coordinate value for each vertex was de ined according to its X
and Y coordinate values. We'll now reproduce that functionality for the shaping of MESH
entities, which will allow us to take advantage of the advanced features Subdivision Surfaces
offer.

Creating the MESH.


For this purpose we will code a program that creates a MESH with approximately square faces
with user-speci ied dimensions and calculates the Z coordinate of each vertex using the
selected mathematical function.

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)))

Listing 19.5. Function that prompts for the MESH data.

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.

Calculating the vertices coordinates for each face.

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.

The procedure followed is as follows:

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.

Generating the Data Structure and creating the MESH.

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.

Generating the vertices list without duplicates.

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.

Generation of the list of face indices.

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.

Generation of the edges indices list.

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.

Preparing the Data Structure and generating the MESH.

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.

Main Function C:SUBD-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))

Listing 19.11. Main Function C:SUBD-MESH.

Figure 19.3. Meshes created with C:SUBD-MESH.

19.5 Creating meshes using command/vl-cmdf.


In many cases it may be desirable to start from a primitive MESH form that will be modi ied
afterwards. So the possibility of creating MESH objects using AutoCAD commands should not
be neglected. We can create rectangular prisms, cones, cylinders, pyramids, spheres, wedges
or tori using the _MESH command. The _RULESURF, _TABSURF, _REVSURF and
_EDGESURFcommands can also be used to create MESH objects if the MESHTYPE system
variable is set to 1.

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.

In the case of meshes created by the _RULESURF, _TABSURF, _REVSURF and


_EDGESURF commands subdivisions are controlled by the SURFTAB1 and SURFTAB2
variables (see Table 19.3).

Table 19.2. System variables that control MESH subdivisions.


Mesh: Variable: Subdivisions:
DIVMESHBOXHEIGHT Along the Z axis. Values between 1 and 256.
_Box DIVMESHBOXLENGTH Along the X axis. Values between 1 and 256.
DIVMESHBOXWIDTH Along the Y axis. Values between 1 and 256.
DIVMESHCONEAXIS Along the base perimeter. Values between 3 and 256.
_Cone DIVMESHCONEBASE Along the radius. Values between 1 and 256.
DIVMESHCONEHEIGHT Along its height. Values between 1 and 256.
DIVMESHCYLAXIS Along the base perimeter. Values between 3 and 256.
_CYlinder DIVMESHCYLBASE Along the radius. Values between 1 and 256.
DIVMESHCYLHEIGHT Along its height. Values between 1 and 256.
DIVMESHPYRBASE Along the base radius. Values between 1 and 256.
Pyramid DIVMESHPYRHEIGHT Along its height. Values between 1 and 256.
DIVMESHPYRLENGTH Along each side of the base. . Values between 1 and 256.
DIVMESHSPHEREAXIS Along its equator Values between 3 and 256.
_Sphere
DIVMESHSPHEREHEIGHT Along the Z axis. Values between 2 and 1024.
DIVMESHTORUSPATH Along the sweep path. Values between 3 and 512.
_Torus
DIVMESHTORUSSECTIONAlong the profile's circumference. Between 3 and 512.
DIVMESHWEDGEBASE From the triangular face's centroid to its sides. 1 to 64.
DIVMESHWEDGEHEIGHT Along the Z axis. Values between 1 and 64.
_Wedge DIVMESHWEDGELENGTHAlong the X axis. Values between 1 and 64.
DIVMESHWEDGESLOPE Along the inclined face. Values between 1 and 64.
DIVMESHWEDGEWIDTH Along the Y axis. Values between 1 and 64.

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.

Creating a Rectangular Prism MESH.

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)))

Listing 19.12. Creating a Rectangular Prism MESH.

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.

Creation of a Cone-shaped MESH.

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)))))))))

Listing 19.13. Function that calculates an ellipse's circumference.

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)))

Listing 19.14. Function that creates a cone-shaped MESH.

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.

Creation of a Cylinder-shaped MESH.

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)))

Listing 19.15. Function that creates a cylindrical MESH.

Creating a Sphere-shaped MESH.

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.

19.6. Modifying Subdivision Surfaces.


It is not only possible to create MESH entities with Visual LISP, they can also be modi ied. Its
geometry can be modi ied by changing vertex positions, as well as other properties including
the smoothness level. This way programming may be used to create complex shapes,
speeding up the generation of 3D models. And even if it is not documented in the ActiveX
Reference, they can also be modi ied by means of the ActiveX API. In this section we discuss
both ways to do it: the entmod function and ActiveX properties.

Modifying Meshes through ENTMOD.


As explained in Chapter 12 any entity that exposes its de inition list may be modi ied by
changing the values associated with any of the DXF codes. The MESH entity is no different
from those already studied, for example, the LWPOLYLINE or the SPLINE. If we must modify
the vertices coordinates of an existing MESH entity, we can replace those sub-lists associated
with group code 10 with sublists containing the new values. This would also apply to other
group codes. Specially interesting is the value associated with group code 91 which de ines
the subdivision level number. We should bear in mind that group codes 90, 91 and 92 appear
several times in the list, so we must discriminate which of them the one to change is. For
example, for the subdivision level, the irst occurrence of group code 91 must be modi ied,
while if what must be modi ied are the properties of any sub-entity we must modify group
codes 90, 91 and 92 only after the appearance of the group codes 95 and 140. Another aspect
to consider is that the vertices that can be modi ied with entmod are only those for
smoothing level 0.

As an example we propose the entmod-mesh function (Listing 19.17) that takes as


arguments the ename of the entity to be modi ied and the name of the function to apply, as in
previous examples, to change the value of Z, but that modifying the entmod-mesh code could
also be used to set new values also for X, Y or both. The third argument dim-z is used to set a
scale factor for the deformation. As group code 10 is repeated we will use the auxiliary
function values (Listing 10.17) to extract the information associated with them.

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))

Listing 19.17. Modifying a MESH entity using entmod.

Modifying Meshes with ActiveX.


In addition to those inherited from AcadObject and AcadEntity, the AcadSubDMesh
class objects expose the properties described in Table 19.4. This objects only expose their
inherited methods.

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.

Table 19.4. AcadSubDMesh object properties.


Property Description
Array of vertex coordinates.
To get the 3D point for the first vertex: (vla-get-Coordinate obj 0) and to set its value
Coordinate
(vla-put-Coordinate obj 0 3DPoint).
This expression throws an error if Smoothness is greater than 0.
Coordinates array. The X, Y and Z values appear one after the other, as elements of the safearray's same
dimension. To get the number of vertices:
(/ (1+
Coordinates (vlax-safearray-get-u-bound
(vlax-variant-value (vla-get-Coordinates obj))
1))
3)

FaceCount Gets the number of faces in the MESH. Read only.

Smoothness Gets or sets the smoothing level.

VertexCount Gets the number of vertices.

(defun ax-mod-mesh (obj equation dim-z / level


xyz lst-z i pt)
(if (eq (type obj) 'ENAME)
(setq ename (vlax-ename->vla-object obj)))
(setq level (vla-get-Smoothness obj))
(if (> level 0)
(vla-put-Smoothness obj 0))
(setq xyz (vlax-safearray->list
(vlax-variant-value
(vla-get-Coordinates obj)))
lst-z (cal-z xyz equation dim-z)
i 0)
(repeat (vla-get-VertexCount obj)
(setq
pt (vlax-safearray->list
(vlax-variant-value
(vla-get-Coordinate obj i))))
(vla-put-Coordinate
obj
i
(vlax-3d-point
(list (nth 0 pt)
(nth 1 pt)
(+ (nth 2 pt) (nth i lst-z)))))
(setq i (1+ i)))
(if (> level 0)
(vla-put-Smoothness obj level)))

Listing 19.18. Modifying a MESH object using ActiveX.

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.

19.7. Sample Program: Modifying MESH objects.


As an example of the edition of a MESH object's vertices we propose the program
C:PARABOLOID (Listing 19.23) that deforms MESH primitives into the form of Hyperbolic or
Revolution paraboloids. The starting point are primitives created using the _MESH command
whose vertices shall be modified using the functions proposed in Listings 19.17 or 19.18.

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))))

Listing 19.19. Function for calculating the vertices of a paraboloid of revolution.

(defun hyp-par (x y /)
(- (expt x 2) (expt y 2)))

Listing 19.20. Function for calculating the vertices of a hyperbolic paraboloid.

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:

1. The current UCS transformation matrix is retrieved calling ax-ucs-matrix, which


returns a list with the name of the current UCS and its transformation matrix.
2. A coordinate system equivalent to the WCS is set as current using the ucs->w function
(Listing 19.21).
3. The MESH is created working in this coordinate system, centered at its origin and using the
data requested in the paraboloid-data function. Its vertices are then transformed
according to the paraboloid type selected.
4. Once the MESH has been created and transformed the original User Coordinate System is
restored based on the UCS name returned by ax-ucs-matrix, the new object aligned to it
using the UCS transformation matrix and translated to the point specified by the user.
(defun ucs->w (/ tmp)
(setq tmp (ax-ucs "World"
'(0.0 0.0 0.0)
'(1.0 0.0 0.0)
'(0.0 1.0 0.0)))
(vla-put-ActiveUCS *aevl:drawing* tmp))

Listing 19.21. Function that establishes the World Coordinate System as current.

Paraboloid data entry.


T h e paraboloid-data function (Listing 19.22) prompts the user for the information
needed for generating the MESH, offering the choice of doing the transformation using
entmod or ActiveX. Then the paraboloid type to be modeled, either Revolution or Hyperbolic,
is prompted for. The user's choice is used to determine the function that will be used to
calculate the Z coordinate, rev-par or hyp-par.

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.

Main function C:PARABOLOID.


Since a command is used to create the initial MESH, the appropriate precautions regarding
running object snaps and command messages are taken. This is done by calling the cmd-in
function (Listing 7.8) on starting the program and the cmd-out function (Listing 7.9) on
ending it. And, anticipating any error that may interrupt the program, an *error* function is
de ined that calls cmd-out to restore the initial values, sets the _UNDO end mark and calls
_UNDO to reverse any changes.

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.

(defun paraboloid-data (/)


(initget 1 "Entmod Activex")
(setq proc
(getkword "Method: [Entmod/Activex]:"))
(if (= proc "Entmod")
(setq proc 'entmod-mesh)
(setq proc 'ax-mod-mesh))
(initget 1 "Revolution Hyperbolic")
(setq
option
(getkword
"\nParaboloid type [Revolution/Hyperbolic]:"))
(cond ((= option "Revolution")
(setq option 'rev-par))
((= option "Hyperbolic")
(setq option 'hyp-par)))
(initget 1
"Rectangle Circle Ellipse Sphere")
(setq
form
(getkword
"\nForm [Rectangle/Circle/Ellipse/Sphere]:"))
(if (= form "Rectangle")
(progn
(initget "Center cOrner")
(if (not
(setq
origin
(getkword
"Origin [Center/cOrner]<Center>")))
(setq origin "Center"))))
(initget 1)
(setq pos (getpoint "\nMesh origin position:")
z-mesh (getdist pos "\nMesh height: "))
(cond
((= form "Rectangle")
(initget (+ 1 2 4))
(setq
dimX (getdist pos "\nX dimension:"))
(initget (+ 1 2 4))
(setq
dimY (getdist pos "\nY dimension:"))
(initget (+ 1 2 4))
(setq dimZ
(getdist pos "\nMesh thickness:"))
(initget (+ 1 2 4))
(setq prec (getint "\nMesh resolution:"))
(while (not (< 1 prec 257))
(prompt
"\nResolution must be from 2 to 256")
(initget (+ 1 2 4))
(setq prec
(getint "\nMesh resolution: "))))
((= form "Circle")
(initget (+ 2 4))
(if (not
(setq prec
(getint
"\nPerimeter subdivision <20>:")))
(setq prec 20))
(initget (+ 1 2 4))
(setq dimX (getdist pos "\nMesh radius: ")
dimY dimX)
(initget (+ 1 2 4))
(setq dimZ
(getdist pos "\nMesh thickness: ")))
((= form "Ellipse")
(initget (+ 2 4))
(if (not
(setq prec
(getint
"\nPerimeter subdivision <20>:")))
(setq prec 20))
(initget (+ 1 2 4))
(setq dimX
(getdist
pos
"\nMesh X semi-axis length:"))
(initget (+ 1 2 4))
(setq dimY
(getdist
pos
"\nMesh Y semi-axis length:"))
(initget (+ 1 2 4))
(setq dimZ
(getdist pos "\nMesh thickness:")))
((= form "Sphere")
(initget (+ 2 4))
(if (not
(setq
prec (getint
"\nEquatorial subdivision <20>:")))
(setq prec 20))
(initget (+ 1 2 4))
(setq dimX
(getdist pos "\nSphere radius: ")))))

Listing 19.22. Function that prompts for the Paraboloid's data.

(defun C:PARABOLOID (/ *error* proc option form


origin pos z-mesh dimX
dimY dimZ prec obj)
(defun *error* (msg)
(vla-EndUndoMark *aevl:drawing*)
(vl-cmdf "_U")
(cmd-out)
(prompt msg))
(vla-StartUndoMark *aevl:drawing*)
(cond ((= (getvar "WORLDUCS") 0)
(setq curr-ucs (ax-ucs-matrix)
mtrans (last curr-ucs))
(ucs->w))
(t (setq mtrans nil)))
(paraboloid-data)
(cmd-in)
(cond ((= form "Rectangle")
(if (= origin "Center")
(cmd-mesh-box
'(0 0 0) prec dimX dimY dimZ nil)
(cmd-mesh-box
'(0 0 0) prec dimX dimY dimZ t)))
((= form "Circle")
(cmd-mesh-cylinder
'(0 0 0) prec dimX dimY dimZ))
((= form "Ellipse")
(cmd-mesh-cylinder
'(0 0 0) prec dimX dimY dimZ))
((= form "Sphere")
(cmd-mesh-sphere '(0 0 0) prec dimX)))
(setq obj (vlax-ename->vla-object (entlast)))
(cmd-out)
(if (= (vla-get-ObjectName obj)
"AcDbSubDMesh")
(progn
(apply proc (list obj option z-mesh))
(if mtrans
(progn
(vla-put-ActiveUCS
*aevl:drawing*
(vla-item
(vla-get-UserCoordinateSystems
*aevl:drawing*)
(car curr-ucs)))
(vla-TransformBy obj mtrans)))
(ax-translation obj pos)
(ax-SWt)))
(vla-EndUndoMark *aevl:drawing*)
(princ))

Listing 19.23. Main function C:PARABOLOID.

19.8 Generalizing MESH transformations.


So far we have considered the modi ication of the MESH vertices affecting only the vertex's Z
value. To conclude this topic we propose a generalization of these changes enabling
transformations also along the X and Y directions. This can be achieved with a few small
modi ications to any of the MESH transformation functions, either using entmod or ActiveX.
We will examine the changes needed in the entmod-mesh function (Listing 19.17), leaving to
our readers the task of doing the same with the ax-mod-mesh function (Listing 19.18).

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))

Listing 19.24. Function that transforms a MESH in the X, Y or Z directions.

19.9 Sample Program: Shape up a MESH object.


This generalized transformation is applied in the main function C:SHAPE-M (Listing 19.25)
which prompts for the selection of a MESH entity and for the transformation's data.
(defun C:SHAPE-M
(/ ent axis equation var-dim)
(vla-StartUndoMark *aevl:drawing*)
(prompt "\nSelect MESH to shape: ")
(while
(not (setq
ent (ssget "_:S" '((0 . "MESH")))))
(prompt "\nSelect MESH to shape: "))
(initget (+ 2 4) "X Y Z")
(setq axis
(getkword
"\nSpecify shaping axis [X/Y/Z]<Z>"))
(cond ((= axis "X") (setq axis 0))
((= axis "Y") (setq axis 1))
(t (setq axis 2)))
(setq ent (ssname ent 0))
(initget 1)
(setq equation
(getstring
"\nSpecify shaping equation function: "))
(cond
((car (atoms-family 0 (list equation)))
(initget (+ 1 2 4))
(setq var-dim
(getdist "\nShaping offset: "))
(entmod-mesh-xyz
ent
(read equation)
var-dim
axis))
(t
(prompt
(strcat
"\nFunction "
equation
" is not defined."))))
(vla-EndUndoMark *aevl:drawing*)
(princ))

Listing 19.25. Main function C:SHAPE-M.

19.10 Meshes created from 2D entities.


The old _RULESURF, _TABSURF, _REVSURF and _EDGESURF commands produce
MESH entities if the value of the MESHTYPE system variable is set to 1. The MESH subdivision
is controlled in this case from the system variables SURFTAB1 and SURFTAB2. We shall
conclude this chapter with two sample programs, one of them creating a flat square _EDGESURF
command mesh and the other one that creates a one-sheeted circular hyperboloid using the
_RULESURF command.

Sample Program: EDGESURF Mesh.


The lat square mesh created with this function can be used to experiment with the
generation of surfaces using the C:SHAPE-M program.
Figure 19.5. Mesh obtained with the C:SHAPE-M program.

Figure 19.6. Hyperboloid shaped MESH.

The CMD-SQUARE-MESH function (Listing 19.26) receives as arguments a point (ins-pt)


which will be the lower left corner of the mesh, the length of the square's side and the number
of divisions which will be the same along the X and Y directions. As the lines for the edges are
created their ename (obtained through entlast) is added to the EDGES list. This list will be
used for supplying the lines to _EDGESURF and to erase them once the MESH has been
created. As the edges are contained in a list to create the MESH the apply function is used
which passes a list of arguments to, and executes a specified function:
(apply 'vl-cmdf (cons "EDGESURF" edges))

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.

Sample Program: Hyperboloid.


This surface is generated by joining points of two circular pro iles which are rotated a certain
angle.

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))

Listing 19.27. Function that prompts for the hyperboloid data.


Creating the circular profiles.

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))))

Listing 19.28. Function that creates circular profiles.

(defun ax-trans-rot (obj vector ang)


(vla-TransformBy
obj
(vlax-tmatrix
(list (list (cos ang)
(- (sin ang))
0.0
(nth 0 vector))
(list (sin ang)
(cos ang)
0.0
(nth 1 vector))
(list 0.0 0.0 1.0 (nth 2 vector))
(list 0.0 0.0 0.0 1.0)))))

Listing 19.29. Function that translates and rotates a profile.

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.")))

Listing 19.30. Main function C: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.

The type of surface that is created is determined by the SURFACEMODELINGMODE system


variable . Procedural surfaces are created If its value is 0 and NURBS surfaces if 1.
SURFACEMODELINGMODE overrides SURFACEASSOCIATIVITY, meaning that if the
creation of NURBS surfaces is enabled, the resulting surfaces will not be associative even if
SURFACEASSOCIATIVITY is also enabled.

20.1 Creating surfaces.


As with 3DSolids, the information contained in the list returned by entget for a Surface
entity includes encrypted binary information that does not allow creating them from
entmake. Neither are ActiveX methods that enable their creation exposed. However, we can
c r e a t e MESH entities or 3DSolids and convert them into surfaces using the
_CONVTOSURFACEcommand, taking advantage of the editing options they allow. This
command also converts into surfaces the 2D Solids, 3DFaces, Regions, closed Polylines,
Circles and Ellipses, as well as open linear entities (Lines, 2D Polylines, Arcs) having
thickness. And as usual in AutoLISP/Visual LISP, any of the AutoCAD commands can be called
from our programs in order to create surfaces. These commands are summarized in Table
20.1.

Table 20.1. Commands for creating Surfaces.


Command: Description:
In SUrface mode, creates a surface by extending an object in the Z direction or along a path. A
_EXTRUDE Procedure or a NURBS surface will be created according to the SURFACEMODELINGMODE
setting.
In SUrface mode, creates a surface spanning several cross sections that define its form. A Procedure
_LOFT or a NURBS surface will be created according to the SURFACEMODELINGMODE setting.
In SUrface mode, creates a surface by sweeping an object around an axis. A Procedure or a NURBS
_REVOLVE
surface will be created according to the SURFACEMODELINGMODE setting.
In SUrface mode, creates a surface by sweeping an object along a path. A Procedure or a NURBS
_SWEEP
surface will be created according to the SURFACEMODELINGMODE setting.
Create a planar surface by specifying opposite corners for a rectangular surface or by selecting closed
_PLANESURF
objects. When corners are specified the surface is created parallel to the current UCS work plane.
Creates a surface spanning multiple curves, including edge subobjects, in the U and V. The type of
_SURFNETWORK
object created is a LoftedSurface
_SURFBLEND Creates a continuous blend surface between two existing surfaces.
_SURFPATCH Creates a new surface by fitting a cap over a surface edge that forms a closed loop.
_SURFOFFSET Creates a parallel surface a specified distance from the original surface.
_SURFEXTEND Lengthens a surface by a specified distance.
_SURFFILLET Creates a filleted surface between two other surfaces.

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.

T he _CONVTONURBS command converts Procedural surfaces and 3DSolids into NURBS


surfaces. To convert MESH entities into NURBS surfaces it will be necessary to convert them
into Surfaces using the _CONVTOSURFACE command before converting them into NURBS
using _CONVTONURBS.

20.2 Properties exposed by Surfaces.


Surface objects inherit AcadObject and AcadEntity class properties. In addition, a
number of speci ic properties are exposed depending on the surface type. All of the surfaces
expose the AcadSurface generic object’s properties.

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.

Table 20.2. Properties of generic AcadSurface class objects.


Property: Description:
Specifies the distance by which the edges of surface are extended to merge to an existing
EdgeExtensionDistances
surface.
Specifies if a surface maintains its associativity with another surface.
0: Yes; associativity is maintained
MaintainAssociativity 1: Remove; associativity is removed
2: No; associativity is not maintained
Setting this property to 1 or 2 removes the associativity. It cannot be set to 0 again.
Boolean. Determines whether the associated objects are highlighted. To highlight them
ShowAssociativity
ShowAssociativity = :vlax-true.
Specifies if associativity is maintained between the surface and the objects used to trim the
SurfTrimAssociativity
object.
SurfaceType String. Indicates the type of surface.
UIsolineDensity Isolines density in the U direction.
VIsolineDensity Isolines density in the V direction.
WireframeType Wireframe type displayed: acIsolines3 or acIsoparms4

Table 20.3. AcadExtrudedSurface class objects properties.


Property: Description:
Direction Vector which determines the extrusion direction. Read only.
Height Specifies the extrusion height.
TaperAngle Taper angle, in radians. Positive in, negative out.

Table 20.4. AcadLoftedSurface class objects properties.


Property: Description:
Boolean. If set, the resulting surface will start and end at the first cross section. If all of the
cross sections are closed, then this will result in a torus-like shape. If this option is not set, then
Closed the surface will be open at the first and last cross sections. The default value of this option is
false. To set this option there must be at least three cross sections. Only applies if
SurfaceNormals equals acSmooth or acRuled and Periodic is :vlax-true.
Surface take-off direction at the first cross section, in radians. The default value is 0, which
EndDraftAngle
indicates that no draft angle is defined and the system will compute the optimum angle.
Controls the magnitude of the surface tangent vector at the last cross-section. The default
EndDraftMagnitude
value is 0, which means that the system will compute the optimum magnitude.
EndSmoothContinuity Smoothing continuity at the last cross section. See Table 20.5.
Smoothing of the final cross section of the surface where it joins another surface. Valid values
EndSmoothMagnitude
between 0 and 1, Default 0.5.
NumCrossSections Number of cross sections. Read only.
NumGuidePaths Number of guide paths. Read only.
Smoothed closed surface with no kinks. Only available when SurfaceNormals is acRuled or
Periodic
acSmooth and Closed are :vlax-true.
StartDraftAngle Surface take-off direction at the first cross section. The default value is 0, which indicates
that no draft angle is defined and the system will compute the optimum angle.
Controls the magnitude of the surface tangent vector at the first cross-section. The default
StartDraftMagnitude
value of this option is 0, which means that the system will compute the optimum magnitude.
StartSmoothContinuity Smoothing continuity at the first cross section. The admitted values are shown in Table 20.5.
StartSmoothMagnitude Smoothing of the first cross section of the surface where it joins another surface. The default
value is 0.5. Valid values are between 0 and 1.
Controls a loft object's normal when passing through the cross sections. Admitted values are
SurfaceNormals
described in Table 20.6.

Table 20.5. StartSmoothContinuity and EndSmoothContinuity properties values.


Type: Value: Description:
Positional continuity. If the edges of both surfaces are collinear, the surfaces are continuous in
G0 (Position) 0 position (G0) at the edge curves. Two surfaces can be joined at any angle and maintain a
positional continuity.
Includes positional and tangential continuity (G0 + G1). The end tangents coincide at the common
G1 (Tangency) 1 edges. The two surfaces are oriented in the same direction, but may have different change of
curvature rates.
Includes positional, tangential and curvature continuity (G0 + G1 + G2). The two surfaces share
G2 (Curvature) 2 the same curvature. The curvature magnitude is specified in the StartSmoothMagnitude and
EndSmoothMagnitude properties.

Table 20.6. Values for the SurfaceNormals property.


Constant: Value: Description:
A ruled surface is generated between each pair of cross sections. Results in a coarser
acRuled 0
object with sharp edges at the cross sections.
Plane normal lofting is turned off and the system will compute the surface direction at each
acSmooth 1
cross-section generating a smoothed surface with sharp edges at the first and last.
The direction of the surface at the first cross-section will be the same as the cross-section
acFirstNormal 2
plane normal.
The direction of the surface at the last cross-section will be the same as the cross-section
acLastNormal 3
plane normal.
The direction of the surface at the first and last cross-sections will be the same as the cross-
acEndsNormal 4
section plane normal.
The direction of the surface at the each cross-section will be the same as the cross-section
acAllNormal 5
plane normal.
acUseDraftAngles 6 Use the draft angles defined for the initial and final cross sections.

Table 20.7. AcadSweptSurface class objects properties.


Property: Description:
Boolean. Determines if the swept surfaces are rotated along the 3D sweep path (Helix, Spline or 3D
Bank
Polyline).
ProfileRotation Specifies the rotation of the sweep profile.
scale Value that controls the resizing of the object from the beginning of the sweep to finish.
Twist Specifies the amount of rotation along the entire length of the sweep path. .

Table 20.8. AcadRevolvedSurface class objects properties.


Property: Description:
AxisDirection Direction vector for the axis of revolution
AxisPosition Specifies the starting point of the axis of revolution.
RevolutionAngle Specifies the angle of revolution.

Table 20.9. AcadNurbSurface class objects properties.


Property: Description:
Boolean. Determines whether to display the control vertices:
CvHullDisplay
vlax-false= No; :vlax-true = Yes

Programming possibilities for Surfaces.


So far Visual LISP programming options for the creation of surfaces is restricted to the
command/vl-cmdf interface. We have developed two samples that will serve to assess
these possibilities. In the irst case is a proposal similar to those we have developed for the
PolygonMesh or the Subdivision Surface (MESH), in this case generating a series of curves in
space which are then used as cross-sections in the _LOFT command to create a NURBS
surface. NURBS surfaces offer plenty of possibilities for manual editing, although not from the
Visual LISP programming environment. But being created from 2D curves a workaround can
be found in programming of these curves, especially Splines.

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.

20.3 Sample Program: NURBS surfaces.


In this program we will calculate the Z coordinate from an equation, just as we have done in
previous examples. We assume a series of functions of this type are de ined and loaded. The
program will prompt for the name of the one to use.

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.

User data entry.


The prompting for user data is done by the nurbs-data function (Listing 20.1). This
function starts by prompting the user for the name of the function to be used in calculating
the Spline fit points, checking that it is defined for the current environment.

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.

Calculating Spline fit points.


The it point coordinates are calculated by the calc-sect function (Listing 20.2) that
returns a list in which each sublist contains the coordinates of a cross-section’s it points.
This function takes as arguments the surface’s X, Y and Z dimensions (dim-x, dim-y and
dim-z), the equation to be used in calculating the points, the number of cross sections (n -
sec) and the number of fit points (n-pts) in each cross section.

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))

Listing 20.2. Function that calculates the fit point coordinates.

Drawing the cross-sections.


SPLINE entities created by entmake will be used for the cross-sections. This is done by the
auxiliary function ent-sect (Listing 20.3) that receives as arguments a list of points, the
value for group code 70 and the normal vector for group code 210. As noted above, the value
assigned to code 70 will depend on the type of knots parameterization, which will in luence
the Spline’s shape. This function is a simpli ied version of the ent-spline function
proposed in Chapter 12. It operates by creating an initial list with those values that appear
only once, appending to it another list generated in a foreach loop with the group codes 11
associated to each it point’s coordinates. The list resulting from this is the argument passed
to entmake.
(defun ent-sect
(point-list cod70 normal-vec)
(entmake
(append
(list '(0 . "SPLINE")
'(100 . "AcDbEntity")
'(100 . "AcDbSpline")
(cons 70 cod70)
'(71 . 3)
(cons 74 (length point-list))
(cons 210 normal-vec))
(mapcar '(lambda (x) (cons 11 x))
point-list))))
Listing 20.3. Function that creates the cross-section as a SPLINE.

Main function C:NURBS-SURF.


The main function C:NURBS-SURF checks the current coordinate system so in case it is not
the WCS the necessary transformations are done after creating the surface. It also sets the
SURFACEMODELINGMODE value to ensure the creation of NURBS surfaces when invoking
the _LOFT command. Once the user has been prompted for the necessary data, the it point
coordinates are calculated and an empty selection set assigned to the sel-sect variable is
created. The cross-section Splines are added to this selection set as they are created.

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.

Figure 20.1. NURBS surface created by C:NURBS-SURF.

(defun C:NURBS-SURF (/ *error* mtrans dim-x


dim-y equation dim-z
n-sec n-pts param
sections sel-sect obj i
xmin ymin)
(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)))
(setvar "SURFACEMODELINGMODE" 1)
(nurbs-data)
(setq sections (calc-sect dim-x dim-y dim-z
equation n-sec
n-pts)
sel-sect (ssadd))
(foreach secc sections
(if (ent-sect secc
param
'(0.0 -1.0 0.0))
(ssadd (entlast) sel-sect)))
(cmd-in)
(vl-cmdf "_loft" "_mode" "_surface"
sel-sect "" "")
(if (= (vla-get-ObjectName
(vlax-ename->vla-object
(entlast)))
"AcDbNurbSurface")
(progn
(setq
obj (vlax-ename->vla-object
(entlast)))
(if (> (getvar "DELOBJ") 0)
(progn
(setq i 0)
(repeat (sslength sel-sect)
(entdel (ssname sel-sect i))
(setq i (1+ i)))))
(if mtrans
(vla-TransformBy obj mtrans))
(ax-translation
obj
(trans center 1 0 t))
(vla-put-CvHullDisplay obj 1)
(ax-SWt))
(progn
(vla-ZoomWindow
*aesl:acad*
(vlax-3d-point (list xmin ymin))
(vlax-3d-point
(mapcar '- (list xmin ymin))))
(alert
(strcat
"\nError building the surface."
"\nCheck intersecting loops"))))
(cmd-out))
Listing 20.4. Main function C:NURBS-SURF.

20.4 Creating a Procedural surface.


Once created, NURBS surfaces can only be edited manually in AutoCAD using gizmos to
manipulate their vertices. So far there is no possibility to modify them using exposed ActiveX
properties or applying entmod. However, we can create Procedural surfaces that maintain
associativity to the linear features from which they are de ined. And as normal AutoCAD
entities: lines, arcs, circles, 2D polylines, etc. we know how to modify them, and as they are
associated with the surface it would also be changed. And since Release 2010 we can also
control these changes through geometric and dimensional constraints.

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: relationships between design elements, such as coincidence,


parallelism, perpendicularity, horizontality, verticality, and so on.
Dimensional constraints: dimensional parameters affecting distances and angles in the
model. The parameters can take the form of variables and equations.

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).

Table 20.10. Commands used to apply geometric constraints.


Command: Description:
Applies geometric constraints to a selection set of objects according to their relationships and
_AUTOCONSTRAIN
orientation.
Applies the following geometric constraints between objects or points in objects: Horizontal, Vertical,
_GEOMCONSTRAINT Perpendicular, PArallel, Tangent, SMooth, Coincident, CONcentric, COLlinear, Symmetric, Equal, Fix.
These constraints can be applied from independent commands.
_GCHORIZONTAL Orients pairs of points or linear objects to lie parallel to the X axis of the current UCS.
_GCVERTICAL Orients pairs of points or linear objects to lie parallel to the Y axis of the current UCS.
_GCPERPENDICULAR Orients the designated linear objects so they lie at an angle of 90 degrees to each other.
_GCPARALLEL Orients the designated linear objects so they are parallel.
_GCTANGENT Constrains two curves to maintain a point of tangency to each other or their extensions.
Constrains a Spline to be contiguous and maintain a G2 continuity with another Spline, Line, Arc, or
_GCSMOOTH
Polyline.
_GCCOINCIDENT Constrains two points to coincide in space, or a point on a curve or its extension.
_GCCONCENTRIC Constrains two Arcs, Circles or Ellipses so the positions of their centers coincide.
_GCCOLLINEAR Causes two or more linear segments to lie along the same straight line.
Causes the selected objects to be symmetrically constrained about a selected line. This restriction does
_GCSYMMETRIC
not imply equality in the length of objects, only affecting their orientation.
_GCEQUAL Resizes selected arcs and circles at the same radius, or selected lines to the same length.
_GCFIX Locks the position of points and curves, preventing their displacement.

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 are implemented by Dimension entities that display a number of


differences with dimensions used in design documentation. Regular dimensions re lect the
size of the model, while dimensional constraints determine those dimensions. Moreover,
dimensional constraints are not graphic objects in the sense that they are not printed, their
appearance does not respond to the current dimension styles and they keep the same size
when zooming.

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.

Table 20.11. Commands used to apply dimensional constraints.


Command: Description:
Applies dimensional constraints to selected objects or points on objects, or converts associative
dimension into dimensional constraints. Options:
_DIMCONSTRAINT Linear/Horizontal/Vertical/Aligned/ANgular/Radius/Diameter/Form/Convert
After applying the constraint an expression value can be entered or the default value can be accepted
(constraintname=value). These restrictions can be applied by the individual commands listed below.
Creates a horizontal, vertical, or rotated constraint based on the locations of the extension line origins
_DCLINEAR
and the dimension line position.
Constrains the distance in X between points in the same object or between two points on different
_DCHORIZONTAL
ones.
Constrains the distance in Y between points in the same object or between two points on different
_DCVERTICAL
ones.
_DCALIGNED Constrains the distance between two points on the same or in different objects.
Constrains the angle between line or polyline segments, the angle swept by an arc or a polyline arc
_DCANGULAR
segment or the angle between three points.
_DCRADIUS Constrains the radius of a circle or an arc.
_DCDIAMETER Constrains the diameter of a circle or an arc.
_DCFORM Specifies whether the dimensional constraint that is created is dynamic or annotational.1
_DCCONVERT Converts associative dimensions into dimensional constraints.

The _DELCONSTRAINT command can be used to remove all constraints in a selection set,
both geometric and dimensional.

Access to dimensional constraints from a program.

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.

Table 20.12. Dimensional constraint properties.


Property: Description:
DimConstrDesc String with a parameter description or comment.
String with the expression that determines the constraint value. It may be a number, the name of
DimConstrExpression
a user variable created in the Parameters Manager or an equation.
Boolean.
DimConstrForm
Specifies whether the dimensional constraint is dynamic (:vlax-true) or annotational (:vlax-false).
DimConstrName String with the name that identifies the restriction.
DimConstrReference Boolean. Indicates or sets whether the object is a reference dimension (:vlax-true).
DimConstrValue Character string containing the current numerical value of the dimensional constraint.
The Layer property will always contain the name of the special Layer
Layer
"*ADSK_CONSTRAINTS". Any attempt to change it causes an error.

Associativity, as explained in the section on geometric constraints, depends on the allocation


of reactors, so we will always have the ACAD_REACTORS group as part of the list returned by
entget. It is also essential that the Layer name "*ADSK_CONSTRAINTS" is associated with
group code 8.

Table 20.13. Dimensional constraints relevant DXF group codes.


Group Code: Description:
0 Type of object, in this case "DIMENSION"
8 Layer name, always "*ADSK_CONSTRAINTS"
1 String with the text displayed when CONSTRAINTNAMEFORMAT is equal to 2 (constraintname = value).
42 Dimensional constraint's numerical value.

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.

20.5 Sample Program: Associative Surface with


Parametric Profiles.
To demonstrate the possibilities offered by Visual LISP for creating associative surfaces and
for their subsequent modi ication we propose the following series of functions. We will start
by creating a series of properly constrained pro iles. We will give names to the constraints we
want to manipulate so we can identify them when making the desired changes. Once the
pro iles are created, a procedural surface associated with them will be generated. As a inal
step we will modify a parameter in each of the pro iles so as to model the desired surface
form. We will be able to make further changes to it using the Parameters Manager.

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.

Figure 20.2. Profile used for creating the Procedural surface.

Creating the profiles.


2D polylines lying in the Front (ZX) plane will be used to create the pro iles (Figure 20.2).
They will be created using entmake calling the ent-profile function (Listing 20.5).

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.
)))

Listing 20.5. Function that creates the profile.

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:")))

Listing 20.6. Prompting for cross-section data.

Creating the constrained cross-sections.

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.

The constraints will be applied as follows:

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.

(defun ax-trans-ucs (ucs-coll origin


x-axis-pt y-axis-pt
name / ucs curr-ucs)
(setq
ucs
(vl-catch-all-apply
'vla-add
(list ucs-coll
(vlax-3d-point origin)
(vlax-3d-point x-axis-pt)
(vlax-3d-point y-axis-pt)
name)))
(cond
((vl-catch-all-error-p ucs)
(prompt
(strcat
"\nERROR:"
(vl-catch-all-error-message ucs))))
(t
(setq
curr-ucs (vl-catch-all-apply
'vla-put-ActiveUCS
(list *aevl:drawing*
ucs)))
(if (vl-catch-all-error-p curr-ucs)
(prompt
(strcat
"\nERROR:"
(vl-catch-all-error-message
ucs)))))))

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)))
))))

Listing 20.8. Function that creates the fully constrained cross-sections.

Creating the associative Procedural surface.

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"))

Listing 20.9. Function that creates the associative surface.

20.6 Modifying the cross-section’s constraint


parameters.
Once the associative surface is created the program will show how to change its parameters.
This is done by calling the mod-constraint function. Actually we could have changed these
values when the cross-sections were created, just as we changed the dimensional constraint’s
default name or its description. But here we will demonstrate how to create a selection set
that includes the desired dimensional constraints. This selection will be done using a iltered
ssget expression. The elements used in the filter are:

The "DIMENSION" entity type, associated with group code 0:


'(0 . "DIMENSION").
The Layer name "*ADSK_CONSTRAINTS" associated with group code 8:
'(8 . "*ADSK_CONSTRAINTS").
The prefix assigned to the constraint's name followed by a wildcard character, associated
with group code 1: (cons 1 (strcat id "_rad*"))

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.

The current value of the constraint is obtained by vla-get-DimConstrValue. But


changing this property will not modify the constraint. To modify it we must change the
DimConstrExpression property’s value using vla-put-DimConstrExpression.
(defun mod-constraint (/ dim-constraints i n
dim-constr pos
curr-value increm)
(if (setq
dim-constraints
(ssget
"X"
(list
'(0 . "DIMENSION")
'(8 . "*ADSK_CONSTRAINTS")
(cons 1 (strcat id "_rad*"))
)))
(progn
(setq i 0
n (sslength dim-constraints))
(repeat n
(setq
dim-constr
(ssname dim-constraints i)
pos
(cdr (assoc 10 (entget dim-constr)))
dim-constr
(vlax-ename->vla-object dim-constr)
curr-value
(atof
(vla-get-DimConstrValue dim-constr))
increm (val i curr-value)
i (1+ i))
(vla-put-DimConstrExpression
dim-constr
(rtos (+ curr-value increm)))))
(alert
"\nError modifying constraints.")))

Listing 20.11. Function that modifies the dimensional constraints.

Main Function C:ASSOC-SURF.


We propose the main function C:ASSOC-SURF that calls the functions discussed in the
preceding paragraphs to create an associative procedural surface using a set of geometrically
and dimensionally constrained cross-sections. As explained before, we will ensure that this
program operates in the WCS, setting it as current if it was not. As we will be using the
command/vl-cmdf interface we will assure that running object snaps, both 2D and 3D, and
command prompts are disabled, restoring them to their previous values on ending the
program. After prompting the user for the required data a list (origin-list) of the
successive cross-section positions is created.
(setq i 0)
(repeat n
(setq origin-list (cons (* interval i) origin-list)
i (1+ i)))

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*))

Listing 20.12. Main Function C:ASSOC-SURF.


20.7 Creating a dynamic block from the associative
surface.
I have already mentioned that the associativity of surfaces in AutoCAD is extremely frail. Any
displacement can destroy it. A possibility in order to avoid this is including the surface in
dynamic block. Dynamic blocks were introduced with Release 2006, but there is no
programming interface for their creation. Although here, as in many other aspects we have
AutoLISP’s scripting possibilities. If it can be done manually, there are many possibilities that
we can reproduce this behavior in our programs. The irst step is to check if what we wish is
possible.

Creating the block.


If I make an associative surface with C:ASSOC-SURF and attempt to create a block selecting
both the surface, the cross-sections and all of its constraints, I will get a message indicating
that all constraints have been removed. When inserting it we will have the surface, but its
parameters cannot be modified.

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.

Figure 20.5. Options for deleting or converting Parameters.


If I do this with all the constraints and save the block, all these parameters will now be
available in the block’s Properties palette. Having the surface as a block it can be moved
without fear of losing its associativity and its parameters can be modified any way I please.

Automating the conversion of Dimensional Constraints into


Parameters.
Knowing what to do, we must now ind the commands and options that nay be used for
executing this conversion in our program. As was shown in Chapter 10, Visual LISP programs
can be executed within the Block Editor. This holds for C:ASSOC-SURF. It is in the Block
Editor where we can ind the commands allowing us to create or covert the Dimensional
Constraints into parameters. The Block Editor’s speci ic commands include
_BCPARAMETER which is equivalent to _DIMCONSTRAINT. These commands are
practically the same. The options it offers (see Table 20.14) can create in the Block Editor
constraint parameters for every situation where it was possible to create dimensional
constraints.

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.

Table 20.14. _BCPARAMETER command options.


Option: Description:
Creates a horizontal or vertical parameter constraint based on the extension line origins and the location of
_Linear
the dimension line
Constrains the X distance of a segment or between two points of different objects. Applicable to polyline
_Horizontal
segments and lines.
Constrains the Y distance of a segment or between two points of different objects. Applicable to polyline
_Vertical
segments and lines.
Constrains dimensions depending on the options:
_Object: length of a linear segment.
_Aligned
_Point & Line: distance between a point and the nearest point of a line.
_2Lines: distance between two selected lines that become parallel.
_ANgular Constrains the angle between two line segments. Applied to two line segments, 3 points or an arc.
_Radial Constrains the radius of a circle, arc or polyline arc segment.
_Diameter Constrains the diameter of a circle, arc or polyline arc segment.
_Convert Converts dimensional constraints on constraint parameters.

(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.

(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-conv-param id)
(cmd-out)
(ax-SWt)
(vla-EndUndoMark *aevl:drawing*))

Listing 20.14. C:ASSOC-SURF with constraints conversion into parameters.

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.

2 AcadPlaneSurface objects only expose the generic AcadSurface object properties.


3 Isolines: equally spaced lines that visually define a 3D shape. Their number depends on the ISOLINES variable.
4 Isoparms: lines connecting points with constant U or V coordinate values representing cross sections of a NURBS surface

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 5.12. Replacement of characters in a string.

(defun flatten (lst)


(cond ((null lst) nil)
((atom lst) (list lst))
(t
(append (flatten (car lst))
(flatten (cdr lst))))))

Listing 5.18. Flatten function removing nil terms.

(defun ax-data-type (lst)


(if (apply 'and
(mapcar '(lambda (x y) (eq (type x) (type y)))
lst
(cdr lst)))
(ax-type (car lst))
vlax-vbVariant))

Listing 6.4. AX-DATA-TYPE.

(defun ax-type (datum)


(setq datum (type datum))
(cond ((eq datum 'INT) vlax-vbLong)
((eq datum 'REAL) vlax-vbDouble)
((eq datum 'STR) vlax-vbString)
((eq datum 'VLA-OBJECT) vlax-vbObject)
(t vlax-vbVariant)))

Listing 6.5. AX-TYPE function.

(defun ax-list>array (lst)


(vlax-safearray-fill
(vlax-make-safearray
(ax-data-type lst)
(cons 0 (1- (length lst))))
lst))

Listing 6.6. AX-LIST>ARRAY function.


(defun ax-exists? (item collection / result)
(if (not (vl-catch-all-error-p
(setq result (vl-catch-all-apply
'vla-item
(list collection item)))))
result))

Listing 6.13. AX-EXISTS? function that returns the VLA-object.

(defun default-value (func message value / tmp)


(if (setq
tmp (apply func
(list (strcat message
"<"
(vl-princ-to-string value)
">": "))))
tmp
value))

Listing 7.1. User input request including default values.

(defun default-string (message value / tmp)


(setq tmp (apply 'getstring
(list (strcat message "<" value ">: "))))
(if (/= tmp "")
tmp
value))

Listing 7.2. Prompting for a string with default value.

(defun value-with-options (func message options / tmp)


(initget options)
(if (setq
tmp (apply func
(list (strcat message
" ["
(replace "/" " " options)
"]: "))))
tmp))

Listing 7.3. Prompting for data including options.

(defun id-bits (value /)


(vl-remove 0
(mapcar '(lambda (i) (logand i value))
'(1 2 4 8 16 32 64 128 256 512 1024 2048
4096 8192 16384 32768 65536))))

Listing 7.5. Function to detect the enabled bits.


(defun bits-on? (bits value)
(= bits (logand bits value)))

(defun enable-bits (bits value) (logior bits value))

(defun disable-bits (bits value)


(logand (~ bits) value))

Listing 7.6. Functions to check, enable or disable bits.

(defun switch-sysvars (varsis bits)


(setvar varsis (boole 6 (getvar varsis) bits)))

Listing 7.7. Variables switch.

(defun cmd-in (/ 3dosm)


(if (and (setq 3dosm (getvar "3DOSMODE"))
(not (bits-on? (lsh 1 0) 3dosm)))
(switch-sysvars "3DOSMODE" (lsh 1 0)))
(if (not (bits-on? (lsh 1 14) (getvar "OSMODE")))
(switch-sysvars "OSMODE" (lsh 1 14)))
(if (bits-on? (lsh 1 0) (getvar "CMDECHO"))
(switch-sysvars "CMDECHO" (lsh 1 0))))

Listing 7.8. Setting system variables before entering the command.

(defun cmd-out (/ 3dosm)


(if (and (setq 3dosm (getvar "3DOSMODE"))
(bits-on? (lsh 1 0) 3dosm))
(switch-sysvars "3DOSMODE" (lsh 1 0)))
(if (bits-on? (lsh 1 14) (getvar "OSMODE"))
(switch-sysvars "OSMODE" (lsh 1 14)))
(if (not (bits-on? (lsh 1 0) (getvar "CMDECHO")))
(switch-sysvars "CMDECHO" (lsh 1 0))))

Listing 7.9. Restoring the original values on exiting the command.

(defun make-point-list (/ pt tmp)


(while (setq pt (getpoint "\nSpecify point: "))
(setq tmp (cons pt tmp)))
tmp)

Listing 8.9. MAKE-POINT-LIST function for creating a coordinates list.

(defun code-value (key ename)


(cdr (assoc key (entget ename))))

Listing 10.9. Retrieving the value associated with a DXF group code.

(defun ent-text (txt-string style pt1 pt2


txt-height h-just v-just)
(entmake (list '(0 . "TEXT")
'(100 . "AcDbEntity")
'(100 . "AcDbText")
(cons 1 txt-string)
(cons 7 style)
(cons 10 pt1)
(cons 11 pt2)
(cons 40 txt-height)
(cons 72 h-just)
(cons 73 v-just))))

Listing 10.12. Fuction that creates a single line text entity.

(defun ent-draw-text (pt-ins height numbering)


(ent-text numbering
(getvar "textstyle")
pt-ins
pt-ins
height
1
2))

Listing 10.13. Replacement for draw-text function using entmake.

(defun values (key lst / sublist result)


(while (setq sublist (assoc key lst))
(setq result (cons (cdr sublist) result)
lst (cdr (member sublist lst))))
(reverse result))

Listing 10.17 Extraction of multiple values from an association list.

(defun current-space (drawing /)


(vla-get-block
(vla-get-ActiveLayout drawing)))

Listing 10.31. Function that retrieves the current space.

(defun 3d->2d (pt) (list (car pt) (cadr pt)))

Listing 10.32. 3d->2d auxiliary function.

(defun can-use? (lyr)


(zerop
(logand
(cdr (assoc 70 (tblsearch "LAYER" lyr)))
(+ 1 4))))

Listing 10.40. Checking if a layer is not off, frozen or locked.

(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 10.43. Creating a Layer with ActiveX.

(defun ax-list->variant (lst)


(vlax-make-variant
(vlax-safearray-fill
(vlax-make-safearray
vlax-vbObject
(cons 0 (1- (length lst))))
lst)))

Listing 11.14. Conversion of the list into an array.

(defun ax-no-group (obj-list group-obj / tmp)


(vlax-for obj group-obj
(setq tmp (cons (vla-get-handle obj) tmp)))
(foreach obj obj-list
(if (member (vla-get-handle obj) tmp)
(setq obj-list (vl-remove obj obj-list))))
obj-list)

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)))

Listing 11.16. Function for adding objects 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)))

Listing 11.16. Function for adding objects to a 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*)))

Setting the references for the Application and Document objects.

(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"))

Code for the ACADDOC.LSP file.


Contents of other Volumes
Volume 1.

PART 1. INTRODUCTION.
Chapter 1. AutoLISP/Visual LISP.
1.1 Visual LISP.
1.2 New LISP functions.
2.3. Summary.

Chapter 2. A Visual LISP Project, Step by Step.


2.1. Work Space and Project Structure.
2.2. A custom dictionary.
2.3. The calculus Function.
2.4. The drawing function.
2.5. The user interface.
2.6. Placing the labels.
2.7. Updating the dictionary.
2.8. On error...
2.9. Compiling the program.
2.10. Demand loading the program.
2.11. Summary.

Chapter 3. The Visual LISP IDE.


3.1. The Visual LISP IDE user interface.
3.2. Interactivity: The Visual LISP console.
3.3. The Programming Editor.
3.4. Interaction between the Editor and the Console.
3.5. Summary.

Chapter 4. Evaluating Expressions.


4.1. Data.
4.2. Expressions.
4.3. Symbols and assignment.
4.4. Lists.
4.5. Variables and data types.
4.6. Manipulating the elements of a list.
4.7. Lambda.
4.8. Summary.

Chapter 5. User-defined Functions.


5.1. Defun.
5.2. Loading and executing user functions.
5.3. Global and local variables.
5.4. Predicates and Conditionals.
5.5. Recursion.
5.6. Iteration.
5.7. Summary.

Chapter 6. ActiveX Data and Structures.


6.1. Safearrays.
6.2. Variants.
6.3. VLA-Objects.
6.4. Collections.
6.5. Working with methods and properties.
6.6. Collections processing.
6.7. Managing exceptions.
6.8. Summary.

Chapter 7. Data Entry.


7.1. Integrated error control.
7.2. Default values.
7.3. Prompting for data with options.
7.4. Input control through INITGET.
7.5. Data coded as binary values.
7.6. File search dialog box.
7.7. Summary.

Chapter 8. File Operations.


8.1. Opening files.
8.2. File reading.
8.3. Writing files.
8.4. Files and Folders.
8.5. Summary.

Chapter 9. Debugging Visual LISP Code.


9.1. Finding the error 's origin.
9.2. The debugging session.
9.3. Data inspection tools.
9.4. Error tracing.
9.5. Summary.

Volume 2

PART 3. CONTROLLING AUTOCAD FROM AUTOLISP/VISUAL LISP.


Chapter 10. Drawing with Visual LISP.
10.1. Three ways to draw.
10.2. The COMMAND/VL-CMDF interface.
10.3. Creating entities with ENTMAKE.
10.4. Creating complex entities with ENTMAKE.
10.5. Sample Program: Defining a Block with ENTMAKE.
10.6. Using AutoLISP/Visual LISP in the Block Editor.
10.7. The ActiveX interface.
10.8. Complex objects with ActiveX methods.
10.9. Non-graphic objects.
10.10. Non-graphic objects from ActiveX extensions.
10.11. VLA-Objects and the use of available memory.
10.12. Summary.

Chapter 11. Selecting Entities.


11.1. Selection sets.
11.2 Creating selection sets.
11.3 Preselected sets.
11.4 Modifying selection sets.
11.5 ActiveX selection sets.
11.6 Groups.
11.7 Summary.

Chapter 12. Modifying entities.


12.1 Modifying properties using COMMAND/VL-CMDF.
12.2 Sample Program: Editing Geometry.
12.3 The ENTMOD function.
12.4 Differences between 2D and 3D entities.
12.5 Modifying entities using the ActiveX extensions.
12.6 Creating a Hyperlink.
12.7 Lineweight assignment.
12.8 Setting the TrueColor property.
12.9 Sample Program: Color scales.
12.10 Object Properties and Methods.
12.11 AutoLISP non-ActiveX property modification functions.
12.12 Summary.

Volume 4.

PART 5. ADVANCED PROGRAMMING.


Chapter 21. Reacting to Events: Reactors.
21.1. The VLR functions.
21.2. Events that trigger a reactor.
21.3. Actions.
21.4. Tutorial: An application using Reactors.
21.5. Enabling persistent reactors functionality.
21.6. Summary.

Chapter 22. DCL: The Graphical User Interface.


22.1. The DCL language.
22.2. Programming a dialog in the Visual LISP Editor.
22.3. Tutorial: Dialog box for generating Parametric models.
22.4. Controlling the dialog.
22.5. Event callback functions.
22.6. Assignment of the callback functions.
22.7. Activating the Dialog Box.
22.8. Generating the Model.
22.9. Summary.

Chapter 23. Associating information to Graphic Objects.


23.1. Blocks with attributes.
23.2. Extended Entity Data (XDATA).
23.3. XRECORD objects and Dictionaries.
23.4. Sample Program: Data in Dictionaries.
23.5. LDATA Dictionaries.
23.6. Access to external databases.
23.7. Summary.

Chapter 24. Tables.


24.1. Fundamental Methods for working with TABLES.
24.2. Sample Program: Block attributes Table.
24.3. Summary.

Chapter 25. Visual LISP as ActiveX client.


25.1. Tutorial: From AutoCAD to Excel.
25.2. Writing in the Worksheet.
25.3. The Dialog box.
25.4. Project Structure.
25.5. Summary.

Chapter 26. VLX: The Visual LISP Executable.


26.1. Managing an Application.
26.2. The VLISP Project Manager.
26.3. Namespaces.
26.4. Creating the Application Module.
26.5. Summary.

Chapter 27. OpenDCL.


27.1. The OpenDCL project.
27.2. The OpenDCL development environment.
27.3. Form Types.
27.4. Control Property Wizard.
27.5. Tutorial: An application developed with OpenDCL.
27.6. Adding the necessary controls.
27.7. Distributing the Application.
27.8. Summary.

You might also like