General Usage
Documentation
Documentation is generated by Doxygen from the header and source files. Header documentation is available here.
Source code documentation is available to source code customers.
In addition to documentation, the manual pages available on the web site here may also be helpful as are many examples provided here.
Source code guidelines
- Class names and most files begin "Sm" with camel case convention (SmVector3d.cpp, class SmVector3d).
- Files which do not contain classes or methods for a class are lower case with underscores (smos_error.h, smos_error.cpp).
- Naming conventions for parameters begin with a lower case characters.
- r - reference ( double & rdRadius ).
- d, l, p, b - double, long, pointer, boolean (double dParameter, ULONG lCount, long lValue, SmCurve * pCurve, BOOL bTrueOrFalse).
- c - constant (const SmCurve & crCurve).
- a - array (double adParameters[5];).
- s - object on the local stack of the current scope ( SmArray sArray ).
- v - object passed or used by value as opposed to by pointer or by reference ( class List { SmArray vMyArray; } ).
- Parameters are clearly marked [in], [out], [in/out] in the header.
- Default values for optional parameters are clearly marked in the header
- Most Methods/Functions return an SmStatus flag
Interfacing Between NLib and 'Sm' Objects
SMLib provides an interface which allows users to construct 'Sm' objects directly from NLib structures. The SmBSplineCurve and the SmBSplineSurface both contain equivalent NLib NL_CURVE and NL_SURFACE structures. Structurally they are the same but the memory management utilized in each case is significantly different.The 'Sm' object allocates the corresponding NURBS curve or surface as a single block of memory. NLib allocates a NURBS curve or surface as many smaller components. In terms of usage what this means is that they are interchangeable only for operations where no editing or manipulation of these structures is required. For example you may utilize the NL_CURVE pointer inside of a SmBSplineCurve in query operation such as evaluation but you could not add a knot to it. In general we recommend that you do not try to interchange the forms directly but go through existing interfaces which create the corresponding copies. Only interchange the forms when it is absolutely necessary for performance.
We have several utilities which will help you to transfer data between the libraries. Both the SmBSplineCurve and SmBSplineSurfacehave constructors which will take a pointer to a NLib NL_CURVE or NL_SURFACE. The constructors either create a copy or reference the NLib structure directly. We recommend that you create a copy. There is a section of software below that shows how this process works. You may also obtain the internal pointer to the NL_CURVE or NL_SURFACE by invoking the GetGwNurbPointer methods. You can then use this to create a NLib native NURBS using a copy method. This is shown below also.
This first example shows how to construct a SmBSplineCurve that copies the data from a CURVE generated in NLib.
// create and manipulate curve in NLib
NL_CURVE *cur;
cur = ...
// constructs a NMTLib curve by copying the NLib cur.
SmBSplineCurve *pBSplineCurve = new (pContext)SmBSplineCurve(3, (void*)cur);
This second example shows how to construct an SmBSplineCurve which references an existing NL_CURVE from NLib. This is a little dangerous and is recommended only for expert users.
// prevents copy of cur - it utilizes cur directly inside of the SmBSplineCurve object.
BOOLEAN bIsBorrowed = TRUE;
SmBSplineCurve* pBSplineCurve = new (pContext) SmBSplineCurve(void*)cur, 3, bIsBorrowed);
This third example shows how to create a CURVE from a SmBSplineCurve. pcur is an internal pointer used by the SmBSplineCurve and must not be deleted or edited. new_cur may be edited, deleted, etc.
SmBSplineCurve *pBSplineCurve = .....;
NL_CURVE *pcur = pBSplineCurve->GetGwNurbPointer();
STACKS SL;
NL_CURVE new_cur;
N_CrvInitArrays(new_cur);
N_CrvCopy(pcur,new_cur,SL);
The fourth example shows how to convert a SmCurve into a SmBSplineCurve;
SmCurve *pCurve; SmBSplineCurve *pBSC = SM_CAST_PTR(SmBSplineCurve, pCurve);
There are equivalent methods that can be used to transfer data between the SmBSplineSurface, SmSurface, and the NLib NL_SURFACE.
The fifth example shows how to convert an analytic surface (cylinder, cone etc) into a Nurbs form;
SmCylinder *Cyl = ....;
SmBSplineSurface *pNurbsSrf = new (crContext) SmBSplineSurface (*Cyl);
Geometrical Utility Classes
- two-dimensional vector - SmVector2d, SmPoint2d
- three-dimensional vector - SmVector3d, SmPoint3d
- local coordinate system or transformation - SmAxis2Placement
- n-dimensional matrix used to solve linear equations - SmMatrix
- polynomial equation representation - SmPolynomial
- non-axis aligned bounding box - SmPseudoBox
- one-dimensional interval - SmExtent1d
- two-dimensional bounding box - SmExtent2d
- three-dimensional bounding box - SmExtent3d
- n-dimensional bounding box - SmExtentNd
Vector Classes
The vector classes SmVector3d and SmPoint2d provide a variety of math operators like cross product, dot product, etc. The vector classes also provide a reasonable set of math operator overloading to help simplify and clarify the vector operations. SmVector2d and SmPoint2d are often used in to describe the surface parameter space, where the "X" component of the vector maps to the "U" parameter of the surface and "Y" maps to the "V" parameter.
Intervals and Domains
Many of the curve methods allow the user to specify a portion of the curve's natural interval. Basically this allows the user to limit the area of interest for a given operation to a subset of the natural interval. For example, the user may request the length of a portion of a curve without actually modifying the curve. To do so, SmExtent1d can be used to specify a portion of the curve's parameterization. The entire curve interval can be determined with GetNaturalInterval().
Similarly a portion of a surface domain can be specified using the uv parameterization of the surface in SmExtent2d . The entire surface domain can be determiend with GetNaturalUVDomain().
There are four extent classes: SmExtent1d, SmExtent2d, SmExtent3d, and SmExtentNd. Extent classes define a domain of the corresponding dimension by specifying the minimum and maximum values for the range of the domain. SmExtent1d defines a one-dimensional domain on the real line. SmExtent2d defines a two-dimensional domain in the real plane. SmExtent3d defines a three-dimensional domain in three space. SmExtentNd defines an N-dimensional domain in N-Space. SmExtent1d is often used to define the parameter interval of a curve. SmExtent2d is often used to define the domain of the parameters of a surface. SmExtent3d is often used to define a three-dimensional axis-aligned bounding box. SmExtentNd is used primarily as the domain representation to limit parameters for solving N-dimensional equations.
Axis Placement
The Axis Placement, SmAxis2Placement, defines a local coordinate system. It contains a point representing the origin and two vectors representing the new X and Y axes. It is derived from the STEP representation of the equivalent name with the exception that the X and Y axes must be orthogonal. The SmAxis2Placement is used primarily to represent transformations from one coordinate system to another.
Pseudo-Box
A Pseudo-Box, SmPseudoBox, is a non-axis aligned bounding volume. In many cases it can provide a much tighter bound on curves and surfaces than the SmExtent3d object.
Parsing the SmSolution Object
The SmSolutionArray stores the result of intersections or global solve. The SmSolutionArray is an array of SmSolution objects that represent individual solution points to the global problem being solved. You will need to parse the SmSolution object to extract the required data. The SmSolution contains the objects (face, curve, edge, vertex) that yielded the given solution and corresponding parameteric values. This function demonstrates how to extract that information from the SmSolution and create a corresponding 3D point. Refer to example.
Continuities
A curve or a surface may have internal continuity changes at the knots. Continuity changes may be caused by either duplicate knots or duplicate control vertices. There are methods on the curve and surface to compute the continuities. Except where stated explicitly (DropCurve on SmSurface), all methods will handle curves and surfaces with internal C0 continuity (discontinuous tangent directions within a curve or a tangent plane discontinuity within a surface). In other words they may find answers which lie on the boundary or internal discontinuity. For example, the minimization between a point and a surface may return a point internal to the surface, on the boundary of the surface, or along a discontinuous edge of the surface.
Another question one might answer is what happens if an answer is produced which is very close to a discontinuity. For example: what if a line intersects a surface near an internal discontinuity? The results obtained would be as if the surface or curve was broken up into homogenous pieces and intersected then the results combined. In our example one might get two intersections, one internal to the surface and one on the discontinuity. Because the answers are not identical both are kept. This conforms to our general philosophy of trying to not loose information. Sometimes this may cause us to return more information than the user needs, but this is better than returning not enough.
Memory Management
The software is now multi-processor compliant. The SmContext is very important object. It contains all global data such as the cache manager and temporary working memory for certain functions. Every time you create an object (SmObject and subclasses) on the heap you will need to use the overridden "new" operator that takes a context. The standard new operator has been made private to prevent creation of heap objects without a context.
SmBSplineCurve *pBSC = new SmBSplineCurve(...); // This is will not compile
SmBSplineCurve *pBSC = new(crContext) SmBSplineCurve(...); // This is correct
Any method that creates an object takes a SmContext object as either an argument to a "Create" method or as an argument in the "new". The context must have scope which is outside of the scope of the existence of the object. Therefore creating a global context at the beginning using the following is recommended if you are keeping objects around between commands.
static SmContext *s_pContext = new SmContext();
If you are just using objects locally you can put the context on the stack as follows:
SmContext sContext;
This way all objects created in a given "Context" can be deleted by deletion of the corresponding "pool". Note that we do not provide a pool based memory manager you will have to purchase or develop one yourself. SmartHeap from MicroQuill does come with a pool based memory management system.
All memory management for the library is handled in the iwos_memory.h and iwos_memory.cpp files. You may modify iwos_memory.cpp if you wish to utilize a different memory mechanism. We also provide an interface to pool based management of memory contained in the SmMemPool object. There are a number of different ways to create an object.
On the stack - cleanup happens automatically via. destructor. In general you should avoid creating SmCurve and SmSurface and their subclasses on the stack. The array based objects (SmTArray, SmSArray, SmSolutionArray) typically should be created on the stack. If the array object is created on the heap you will need an additional context argument to the destructor.
SmTArray sDoubleArray; // This is fine
On the heap - cleanup requires an explicit "delete" or utilization of a stack based clean up object
- SmTArray *pDoubleArray = new SmTArray(); // Not legal - will produce a compile error
- SmTArray *pDoubleArray = new(crContext) SmTArray(); // Legal but not good for array objects. Legal and good for other types of objects such as curves and surfaces.
- SmTArray *pDoubleArray = new(crContext) SmTArray(crContext); // This is the correct way to create array based objects on the heap.
Here are some good "Rules of Thumb" to help you make decisions about how to use the Context:
- Never declare curves or surfaces on the stack (SmBSplineCurve sCurve(...)).
- Never allocate curves or surfaces on the heap without a context
SmBSplineCurve *pCurve = new SmBSplineCurve(....); // This is bad and will not work
SmBSplineCurve *pCurve = new (crContext) SmBSplineCurve(...); // This is good.
- Don't allocate SmTArray objects on the heap unless you give the constructor which takes a context.
- You will now not have to worry about managing the Cache. A good default size is created in the SmContext. If you have any declarations of cache managers (SmCacheMgrBrep sCache(1000, 100, 100, 100)). You should just remove them.
- We suggest that you create one global SmContext when you start up your program and use that whenever you need to create any of our objects. The only time you would want to use a SmContext on the stack is when all of your TSLib objects are created and destroyed within the given function.
There are three objects which are especially useful in the management and automatic clean up of heap based memory and objects. These objects will automatically delete objects when they go out of scope. You have the option of preventing them from deleting their contents by invoking the corresponding Clear method.
- SmObjDelete - stack based clean up of SmObject subclasses (see SmObject.h).
- SmObjsDelete - stack based clean up of arrays of SmObject subclasses (see SmTArray.h).
- SmMemDelete - stack based clean up of memory allocated on heap.
Caching
Note that the Caching Mechanism is now embedded in the SmContext object. The default is for a context to create a cache manager with a maximum of 1000 curve caches, 200 surface caches, 200 trimmed surface caches, and 200 brep caches. This is a fairly good sized cache and should be sufficient for most applications. You can increase it or decrease it by a factor of 10 and still be reasonably good. If you want to conserve memory decrease it by a factor of 10. If you want better performance for larger models, increase it by a factor of 10.
GSLib utilizes a caching system to improve performance. It caches the tessellated Bezier representation of curves and surfaces and constructs a hierarchical tree structure for fast traversal. You should always have caching turned on and it should have a minimum of 10 cache elements. That is it will keep the tessellation for the last 10 curves/surfaces which it has processed. Depending upon the performance/memory requirements for your application you may want to increase this number. The caching mechanism allows us to achieve a level of performance on global algorithms which is near that of the local Newton based algorithms.
Error Handling
GSLib utilizes an error return based error handling convention. We utilize a set of macros to simplify and hide most of the error processing. At the moment an error is detected an error handling function is called (see iwos_error.h). Currently it prints an error either to the debugger output window in Visual C++ or to stderr on other systems. We suggest that you leave the error printing turned on until you have debugged the software you are implementing using GSLib. Knowing where an error has occurred will also help us to diagnose and fix any problems with our software.
The following macros are the most heavily used (see iwos_error.h):
- SER - Status Error Return - checks the status returned from a method and returns up the call stack if the status is not SM_SUCCESS
- NER - Null Error Return - checks the value of a pointer and if it is NULL returns an error up the call stack.
Validity of Curves and Surfaces
Use SM_ASSERT_VALID() for debug messages to detemine validity of any object
- The end points of curves and boundaries of surfaces must be clamped (i.e. they have knot multiplicity equal to the degree+1).
- Internally the curve must have G0 continuity (must be a single piece)
- Control points should be distinct.
- For surfaces, the only requirement should be G1 continuity ( have no internal creases). Curves that are not G1 must be broken up into G1 segments before they are used for sweeping or skinning into surfaces.
- Curves and surfaces with up to degree 32 are allowed.
Validity of Trimmed Surfaces
- The definition of a trimmed surface corresponds to the equivalent definition found in STEP and IGES.
- The curves and surfaces must conform to the validity criteria defined in the Geometry Introduction
- The surfaces must have continuity of C0 (smooth) or better.
- The individual trimming curves must have continuity of G1 or better.
- Polyline trimming curves are not supported.
- The trimming loops must be closed to within a given model or parameter space tolerance.
- There may be only one outer loop which is oriented counter-clockwise relative to the surface normal.
- There may be zero, one or more inner loops which are oriented clockwise relative to the surface normal.
- The curves of the trim boundary must not have self-intersections (except for closed curves).
- The curves of the trim boundary must not intersect each other except where they join the loop at their ends.
- The curves of one loop must not intersect or touch the curves of another loop.
- When 3-D model space trimming curves are specified, they must lie on the surface (to within the current model space tolerance of the trimmed surface).
- When 3-D model space trimming curves are specified, the corresponding ends of the curves in a loop must be within tolerance of each other.
- When 2-D parameter space and 3-D model space curves are specified, their parameterization must match within the tolerance of the trimmed surface. In other words the point created by mapping a given parameter value of the 2D curve through the surface evaluator must be within the tolerance of the same parameter value as applied to the 3-D curve.
Classification Operations inside, outside, on boundary
- Parameter space points relative to trim boundary. Use SmFace::PointClassify
- Parameter space or model space curves relative to trim boundary. Use SmFace::Point3DClassify
- Classification of 3D point relative to a Brep. Use SmBrep::Point3DClassify