SMLib Tutorial

Contents

Introduction

SMLib contains NURBS and analytical geometry modeling tools, polygonal modeling tools and functions to convert between the forms.  SMLib's topology-based tessellation enables users to select from a variety of tessellation criteria to produce polygons which are optimized for various applications.  SMLib utilizes underlying geometric and topological information to produce a minimal number of polygons in a process known as "Polygon Optimization ".  SMLib produces "Crack-Free" polygonal meshes for connected sets of trimmed surfaces or solids at all tessellation tolerances.  SMLib also contains a large variety of polygonal modeling functionality including polygon decimation, polygonal Booleans, sectioning, ray-firing, classification functions, etc.  The performance of SMLib makes it suitable for highly interactive applications. It offers the following major functionalities:

  • Creation of Trimmed Surfaces, Shells and Solids via. C++ interface.  Trimmed surfaces can be created from 2D or 3D trimming curves and NURBS surfaces. (Note: IGES, and STEP File Translators are available in HwLibs )
  • Sewing of Trimmed Surfaces into connected sets such as Solids or Open Shells.
  • Generation of Triangles to the following user specified criteria (see "Tessellation Criteria" below for graphical examples):
    • Chord Height Tolerance
    • Angular Tolerance
    • Maximum Aspect Ratio
    • Maximum Polygon Edge Length
    • Minimum UV Polygon Edge Length
  • Object Oriented Curve and Surface Subdivision Classes to allow user modification of tessellation criteria during the tessellation process.  For example, in a graphical application the user could adjust tessellation criteria based on distance from an "Eye Point" of the geometry being subdivided.  This customizability feature enables the Tessellation Library to easily be adapted to different applications.  See the view dependent tessellation example in the "Tessellation Criteria" image below.
  • Presentation of Output as follows:
    • As a topological data structure which contains a connected set of polygons with 3D points, UV points and surface normals.
    • User Call Backs with 3D points and surface normals. You will need to subclass the IwPolygonOutputCallback class and implement the OutputPolygon method on to impalement the callback.
  • Polygonal Booleans - 3-D volumetric Union, Intersection, Difference with support for N-sided polygons with multiple holes.
  • Two Decimation Algorithms - a vertex removal decimation algorithm that utilizes accumulated error measurement -- an edge squeezing decimation algorithm that utilizes a fast quadric error distance to choose optimal points.  
  • Other Polygonal Modeling Tools - creation, planar sectioning, ray-firing, distance measurement, etc. 

Tessellation

Examples of how utilize many of the polygonal modeling tools (Booleans, Decimation, ray-firing, measurement, etc.) can be found in iwtess_test.cpp.  The following examples are shown below and can also be found in iwtess_test.cpp.

  • Reading IGES trimmed surfaces, sewing them and tessellating the result.
  • Creation of a PolyBrep from a Brep using an option in the tessellator.
  • Polygonal Boolean
  • Polygon Decimation
  • Ray Firing

The Boolean/Merge functionality is one of the most difficult operations in all of solid modeling and we are very pleased with the reliability of our Boolean and Merge operators. They work on many very difficult cases including tangencies, coincident surfaces, intersections through poles of surfaces, etc. If there is a failure it does not crash but it recovers gracefully. The things which might make it fail are cases where micro-topology is created. An example of micro-topology is an edge which is less than the tolerance in length. They may also fail for some tangent intersection cases where fillets are involved. We will be working on these issues if they become a problem for our users.  Another thing that you should be aware of is the fact that when Booleans or Merge fails the clean up is not done properly.  You should simply ignore the original Breps after a Boolean/Merge failure

The following example shows how to sew together faces from a set of trimmed surfaces to create a solid prior to tessellating it.  Note that all of the trimmed surfaces need to live in the same IwBrep prior to sewing.  See IwBrep::MakeFaceFromCurves,  IwBrep::CreateFaceFromSurface, and IwTrimmingTools methods for ways to create faces in a IwBrep programmatically.  Examples of how to create trimmed surfaces are in the TSLib Tutorial and the iwtopo_test.cpp file.  The actual source code for the following example can be found in iwtess_test.cpp

Read In  A Brep, Sew, and Tessellate

    {  
    IwContext sContext;
    IwTArray<IwBrep*> sBreps;

    // Read a Brep which consists of trimmed surfaces from
    // the IGES file.
    IwStatus sStatus = iwiges_ReadIges(sContext,
           "../igesfiles/solids/fillet_ts.igs",        
            NULL, FALSE, NULL, FALSE,
            &sBreps, TRUE, TRUE);

    IwBrep *pBrep = sBreps[0];
    pBrep->SetTolerance(0.0001);
    IwObjDelete sCleanBrep(pBrep);
    ULONG lNumSewn, lNumLamina;
    double dMaxVGap, dMaxEGap;
   
    pBrep->m_bEditingEnabled = TRUE;
    SE(pBrep->SewFaces(0.001,lNumSewn,lNumLamina,dMaxVGap,dMaxEGap));
    pBrep->m_bEditingEnabled = FALSE;

    // Note that 0.001 is not large enough to sew all of the faces
    // and that there will still be lamina edges - so we sew again
    // with a larger tolerance.
    if (lNumLamina > 0) {
        pBrep->m_bEditingEnabled = TRUE;
        SE(pBrep->SewFaces(0.005,lNumSewn,lNumLamina,
            dMaxVGap,dMaxEGap));
        pBrep->m_bEditingEnabled = FALSE;
    }

    pBrep->Dump();

    double dChordHeightTol = 0.3;  // Maximum chord height of polygon
    // from original geometry. 
    double dAngleTolInDegrees = 20.0; // Maximum angle in U or V that
    // the polygon will span. 
    ULONG lMinCurveDivisions = 0; // Don't use this tolerance
    double dMax3DEdgeLength = 2.0; // Maximum length of an edge
    double dMin3DEdgeLength = 0.0; // Minimum length of an edge - not used
    double dMaxAspectRatio = 4.0; // 4:1 aspect ratio
    double dMinUVRatio = 0.001; // Smallest subdivision of a surface is 
    // 1/1000 of the surface parameter space size in U and V.

    // Create a curve tessellation driver and a surface tessellation
    // driver to control how subdivision is performed.  Note that
    // you can subclass these method to specialize how tessellation
    // performs the subdivision.  For example you may want to have
    // a view specific tessellation that subdivides based on the
    // view volume and eye position.  
    IwCurveTessDriver sCrvTess(lMinCurveDivisions,
                           dChordHeightTol,dAngleTolInDegrees);
    IwSurfaceTessDriver sSrfTess(dChordHeightTol,dAngleTolInDegrees,
                             dMax3DEdgeLength,dMin3DEdgeLength,
                             dMinUVRatio,dMaxAspectRatio);

    IwTess sTess(sContext,sCrvTess,sSrfTess);
    // Note that the Brep sent into the Tessellator gets modified
    // so you usually will want to make a copy of it is follows:
   IwBrep *pCopy = new (sContext) IwBrep(*pBrep);

    // Now do the tessellation process to produce triangles in UV
    // space of the surfaces.
    IwBoolean rbFailedFaces;
    SER(sTess.DoTessellation(pCopy,rbFailedFaces));
    IW_ASSERT(rbFailedFaces == FALSE);

    // Output polygons converts to 3D polygons and calls my_output_triangle
    s_lNumberTriangles = 0; 
    IwOutputPolygonCallback sPolyOutput;
    SER(sTess.OutputPolygons(sPolyOutput));

    // Here we are done.  Note that if you declare IwTess on the stack
    // it will automatically clean up the pCopy IwBrep and all of it's internal
    // structures when it goes out of scope.

   
}

Creation of a PolyBrep

IW_EXPORT IwStatus Brep_to_PolyBrep_Conversion(
                      const IwContext & crContext, // Context for new PolyBrep

              IwBrep * pBrep,
              double dCHTol, // Chord Height Tolerance - if equal to
              // 0.0 it is not used.             
double dAngleTolDeg, // Angle Tolerance in Deg - if 0.0 it
              // is not used.              double dMax3DEdge, // Maximum length in 3D of a polygon edge -
              // if 0.0 it is not used              double dMaxAspect, // Maximum aspect ratio - if 0.0 it is not
              // used.  Note that 2.2 is as small as you should let this one
              // get.                IwBoolean & rbFailedFaces, // If TRUE then there were failures
              // in the tessellation              IwPolyBrep'*& rpNewPolyBrep, // Newly create polybrep containing
              // triangular tessellation of the original brep              ULONG & rlNumLamina
              // This tells the number of lamina edges remaining if any.  If there
              // are none then it is a nice closed poly brep.              )
{
    rbFailedFaces = FALSE;
    IwCurveTessDriver sCrvTess(0,dCHTol,dAngleTolDeg);
    IwSurfaceTessDriver> sSrfTess(dCHTol,dAngleTolDeg,
dMax3DEdge,0.0,0.001,dMaxAspect);

    IwTess sTess(crContext,sCrvTess,sSrfTess);
   IwBrep *pCopy = new (*pBrep->GetContext()) IwBrep(*pBrep);
   IwTArray<IwFace*> sFaces;
    IwBoolean bFailedFaces;
    SER(sTess.Phase1SetupBrep(pCopy,bFailedFaces));
    if (bFailedFaces) {
        rbFailedFaces = TRUE;
    }
    pCopy->GetFaces(sFaces);
    IwPolygonOutputCallback  s3DBrepOutput;
    s3DBrepOutput.SetOutputType(IW_PO_CREATE_POLYBREP);

    for (ULONG kk=0; kk<sFaces.GetSize(); kk++) {
        if (sTess.Phase2CreateFacePolygons(sFaces[kk]) == IW_SUCCESS) {
            if (sTess.OutputFacePolygons(sFaces[kk],s3DBrepOutput) != IW_SUCCESS) {
                rbFailedFaces = TRUE;
            }
            sTess.Phase3DeleteFacePolygons(sFaces[kk]);
        }
        else {
            rbFailedFaces = TRUE;
        }
    }
   SER(s3DBrepOutput.CompletedPolygonOutput());

    // Remove the 3D poly brep and set it to null to prevent deleting it.
       IwPolyBrep *pPolyBrep = sTess.Get3DPolyBrep();
    ULONG lNumEdgesSewn;
    SER(pPolyBrep->Sew(pPolyBrep->GetTolerance(),TRUE,FALSE,lNumEdgesSewn,rlNumLamina));
    rpNewPolyBrep = pPolyBrep;
    sTess.Set3DPolyBrep(NULL);


    return IW_SUCCESS;
}

Polygon Booleans

IwStatus my_test_ppu_poly(const IwContext & crContext,
                          IwPolyBrep *pPolyBrep1,
                          IwPolyBrep *pPolyBrep2,
                          ULONG lOperation,  // 0 - union, 1 - intersection, 2 - difference, 3 - merge
                          IwPolyBrep *& rpResult)
{
    clock_t start, finish;
    start = clock();
    // Define some reasonable tolerances
    double dAngleTol = 20.0*IW_PI/180.0;
    double dTol = iwos_Max(pPolyBrep1->GetTolerance(),pPolyBrep2->GetTolerance());
    // Set up the Merge Object
    IwPolyMerge sMerge(crContext,pPolyBrep1,pPolyBrep2,dTol,dAngleTol);
    // This merge method will remove coplanar edges and may not be needed
    // in many cases.  Merge will work just fine if you don't do this.
 
   sMerge.SetRemoveCoPlanarEdges(FALSE);
   // Now do the actual merge operation.  It consumes both of the original
    // breps.  Actually the pPolyBrep1 is rpResult.

    if (lOperation==0) {
        SER(sMerge.ManifoldBoolean(IW_PBO_UNION,rpResult));
    }
    else if (lOperation==1) {
        SER(sMerge.ManifoldBoolean(IW_PBO_INTERSECTION, rpResult));
    }
    else if (lOperation==2) {
        SER (sMerge.ManifoldBoolean(IW_PBO_DIFFERENCE ,rpResult));
    }
    else if (lOperation==3) {
        SER(sMerge.ManifoldBoolean(IW_PBO_MERGE,rpResult));
    }
    else SER(IW_ERR);
    finish = clock();
    IwTArray<IwPolyFace*> sFaces;
    rpResult->GetPolyFaces(sFaces);
    my_print_gs_stats("Poly Brep Boolean",start,finish,sFaces.GetSize());

    return IW_SUCCESS;
}

Polygon Decimation

// This function will read an STL triangle file and
// decimate it.
IwStatus my_test_decimation(const IwContext§ & crContext,
                            const char * pFileName, // Name of STL file
                                                        double dPercentReduction,
                            double dMaximumReductionError,
                            IwBoolean bQuadricDecimation,
                            IwTArray (IwPolyBrep*> & rBreps)
{
        SER(IwPolyBrep::ReadFromSTLFile(crContext,pFileName,
            pPolyBrep,IW_ASCII));
        IwTArray§<>IwPolyFace*> sFaces;
        pPolyBrep->GetPolyFaces(sFaces);
        ULONG lStartFaces = sFaces.GetSize();
        char sBuff[256];
        sprintf(sBuff,"Original PolyBrep # Triangles = %ld\n",
            lStartFaces);
        iwos_WriteBuffer(sBuff);
        clock_t start, finish;
        start = clock();
       
        double dPercentOfReduction = dPercentReduction;
        double dMinFeatureAngle = 45.0;
        double dMinDistToAveragePlane = dMaximumReductionError;
        double dInteriorEdgeWeight = 0.5;
        double dBoundaryEdgeWeight = 0.2;
        IwPolyDecimate sDecimator(pPolyBrep,dPercentOfReduction,
            dMinDistToAveragePlane,dMinFeatureAngle,
            dInteriorEdgeWeight,dBoundaryEdgeWeight);
        if (!bQuadricDecimation) {
            SER(sDecimator.DoMeasuredDecimation());
        }
        else {
            >SER(sDecimator.DoQuadricDecimation());
        }
        finish = clock();
        my_print_gs_stats("Polygon Decimation Time",start,finish,0);
        pPolyBrep->GetPolyFaces(sFaces);
        ULONG lEndFaces = sFaces.GetSize();
       
        char sBuff[256];
        double dReduction = (lStartFaces-lEndFaces) / (1.0*lStartFaces);
        sprintf(sBuff,"Initial # Triangles = %ld,  Reduction Percent = %lf,  Final # Triangles = %ld\n",
            lStartFaces,dReduction*100.0,lEndFaces);
        iwos_WriteBuffer(sBuff);
    }

    return IW_SUCCESS;
}

Ray Firing

This example shows the firing of just a single ray.  After the set up is done, it is ready to fire many rays very quickly.  

if (TRUE) { // Test ray-firing for poly brep
        IwContext sContext;
        IwPolyBrep * pPolyBrep = new (sContext) >IwPolyBrep(IW_EFF_ZERO);
        pPolyBrep->ReadFromFile(sContext,"../igesfiles/tess/Extrusion.out",>IW_ASCII
        IwObjDelete sClean1(pPolyBrep);
#ifdef IW_GFX_CODE
        iwgfx_Erase();
        pPolyBrep->Draw();
        my_gfx_loop();
#endif

        IwExtent3d sBBox;
        pPolyBrep->CalculateBoundingBox(sBBox);
        sBBox.ExpandAbsolute(1.0);
        ULONG lSize[3];
        lSize[0] = 20;
        lSize[1] = 20;
        lSize[2] = 20;
        IwGrid *pGrid = new (sContext) IwGrid(sBBox,lSize);
        IwRayTracer sRayTracer(sContext,pGrid,1.0e-5,1.0e-8);
        SER(sRayTracer.AddPolyBrepToGrid(pPolyBrep,0.001));
        double sDData[256];
        double sDData2[256];
        IwTArray<double> sMinDistances(256,sDData);
        IwTArray<double> sMaxDistances(256,sDData2);
        IwGridElement *sEData[256];
        IwSArray<IwGridElement*> sGridElements(256,sEData);
        IwSolution sSData[16];
        IwSolutionArray sSolutions(16,sSData);
        IwSolution sSolution;
        ULONG lHits = 0;
        IwBoolean bHitsSomething;
        IwPoint3d sRayPoint = sBBox.Evaluate(0.5,0.5,0.5);
        IwVector3d sRayVector(1.0,1.0,1.0);
        sRayVector.Unitize();
        sRayPoint = sRayPoint-sRayVector*15.0;
        sRayTracer.m_bHitAnyThing = FALSE;
        SER(sRayTracer.FireRay(sRayPoint,sRayVector,40.0,//IW_BIG_DOUBLE,
            bHitsSomething,sSolution,
            sGridElements,sMinDistances,sMaxDistances));
        IW_ASSERT(bHitsSomething == TRUE);
#ifdef IW_GFX_CODE
        iwgfx_SetColor(1,0,0);
        sRayPoint.Draw();
        IwVector3d sDir = sRayVector * sSolution.m_vStart[0];
        sDir.Draw(&sRayPoint);
        my_gfx_loop();
        IwPoint3d sPntEnd = sRayPoint + sDir;
Imag        iwgfx_SetColor(0,0,1);
        sPntEnd.Draw();
        my_gfx_loop();
#endif
    }

Topology Filleting

Below is a simple example of how to fillet all of the edges of a Brep.  Also shown are ways to change the cross section and the fillet solver type.

// Read an IGES solid nurb_box in the fillet directory
IwStatus sStatus = iwiges_ReadIges(crContext,"../igesfiles/fillet/nurb_box.igs",
NULL, FALSE,
NULL, FALSE,
&sBreps, TRUE,
FALSE);

// The following code creates a fillet at each edge of a Brep

IwBrep *pBrep = sBreps[0];
rPartBreps.Add(pBrep);
IwTArray<IwEdge*> sEdges;
pBrep->GetEdges(sEdges);


// Creates circular cross section fillet
// that approximates the arc to within 0.001 of it's radius.

IwCircularCrossSectionFSG sFSG(TRUE, 1.0e-3);
// Creates a linear cross section fillet - i.e. chamfer
IwLinearCrossSectionFSG sLinearFSG();
// Define self-intersection handler for edge fillets
IwMakeSurfaceBlendSIH sSIH(1.0);

// Create a new fillet executive to control the overall filleting
// process.

IwFilletExecutive * pFilExec = new (crContext) IwFilletExecutive(crContext, pBrep);
pFilExec->SetSelfIntersectionHandler(&sSIH);
IwObjDelete sClean(pFilExec);

for (ULONG i=0; i<sEdges.GetSize(); i++) {
// Find an edgeuse on the inside of the solid with the same orientation
// as the curve of the edge.
IwEdgeuse *pEU = my_get_fillet_edgeuse(sEdges[i]);
if (pEU == NULL) continue;

// Set the radius of the fillet
double dBallRadius = 1.0;

// Create a fillet solver for the edge and put it into the fillset executive
// This solver uses a constant radius fillet
IwConstantRadiusFS * pFS = new(crContext) IwConstantRadiusFS(crContext,
1.0e-5,30.0*IW_PI/180.0, 2.0*IW_PI/180.0, dBallRadius, pEU);

// Here are some examples of other types of fillet solvers

// This solver uses a constant distance between the surfaces - good for
// chamfering and thumb nail fillets
// IwConstantDistanceFS sFS(sContext,1.0e-3,
// 30.0*IW_PI/180.0, 2.0*IW_PI/180.0, 0.5, pEU);

// This solver uses a variable radius that varies according to some law.
// This particular one uses a linear law but any law will work.
// IwLinearFilletLaw sLaw(0.5,1.5);
// IwVariableRadiusFS sFS(sContext,1.0e-3,
// 30.0*IW_PI/180.0, 2.0*IW_PI/180.0, 1.0, pEU, sLaw, FALSE);

// Attach the cross section fillet surface generator to the fillet solver
// Note - we can use a single FSG for many fillet solvers.
pFS->SetFilletSurfaceGenerator(&sFSG);

// Load the solver into the fillet executive
pFilExec->LoadFilletSolver(pFS);
}
// Now that we have the executive loaded - do all of the filleting
SER(pFilExec->CreateFilletCorners());
SER(pFilExec->DoFilleting());
}


Legal Stuff

All of the software and documentation received with this release is copyrighted by Solid Modeling Solutions, Inc. You may not distribute source code or documentation for this software outside of the company and the site which owns the license. The standard license agreement allows you to freely distribute object code in any application which does not contain a programmatic interface. All software and documentation is considered proprietary information and the intellectual property of Solid Modeling Solutions, Inc. This document contains trade secret information which is deemed proprietary.

Copyright 1998-2010 Solid Modeling Solutions All rights reserved.
Information in this document is subject to change without notice.