(continued...)

A particular case of a pipe with a constant section is a pipe with a constant radius. In ACIS such surfaces are called tubes.

Here is a sample screenshot: Here is an example of creation:

aTube.Perform (aTol, Standard_False, (GeomAbs_Shape)Min (GeomAbs_C1, thePath->Continuity()), aMaxDeg, aMaxSeg);

Pipe with constant radius and two rail curves
As a convenience, the pipe algorithm allows to specify two rail curves. Rails are those which limit the cross section.
This algorithm flavor can be used to model so-called rolling ball surfaces. ACIS defines rolling-ball surfaces as if you have a ball of a constant radius that rolls along the path and always touches two limiting faces. The traces that the ball makes on those boundary faces are called spring or rail curves.

Open CASCADE algorithm accepts the radius, the path and two rail curves and creates a surface with limited circular sections. Each section is constructed by intersecting a plane at every path point and perpendicular to it with rail curves.

Here are sample screenshots of rolling ball surfaces:  On both images the pipes are shown in red and rail curves in blue. The 2nd screenshot also contains a full tube of which a fragment is constructed using 2 rail curves. This tube is a trace that a full rolling ball would make.

Important note to make is that Open CASCADE requires that rail curves follow the path parametrization. This means that the rail curves' ranges must be at least as big as the path's and be consistent between each other.

In addition to constant radius, you might want to create tubes (i.e. pipes with circular sections) with variable radii. For instance, like this: Open CASCADE does not offer direct API to construct such surfaces but you can do these using lower level API it offers. For instance, here is my code excerpt:

/*! Set radius evolution function with SetEvol() before calling this method.

If \a theIsPolynomial is true tries to create polynomial B-Spline, otherwise - rational.

\sa Surface(), Error().
*/
void ACISGGeom_Pipe::Perform (const Standard_Real theTol,
const Standard_Boolean theIsPolynomial,
const GeomAbs_Shape theContinuity,
const Standard_Integer theMaxDegree,
const Standard_Integer theMaxSegment)
{
mySurface.Nullify();
myError = -1.;

if (myEvol.IsNull())
return;

//circular profile
Handle(Geom_Circle) aCirc = new Geom_Circle (gp::XOY(), 1.);
aCirc->Rotate (gp::OZ(), PI / 2.);

//code inspired by GeomFile_Pipe when using for constant radius and corrected
//trihedron orientation

//perpendicular section
Handle(GeomFill_SectionLaw) aSec = new GeomFill_EvolvedSection (aCirc, myEvol);
Handle(GeomFill_LocationLaw) aLoc = new GeomFill_CurveAndTrihedron (
new GeomFill_CorrectedFrenet);
aLoc->SetCurve (myPath);

GeomFill_Sweep Sweep (aLoc, myIsElem);
Sweep.SetTolerance (theTol);
Sweep.Build (aSec, GeomFill_Location, theContinuity, theMaxDegree, theMaxSegment);
if (Sweep.IsDone()) {
mySurface = Sweep.Surface();
myError = Sweep.ErrorOnSurface();
}
}

In this case myEval is Handle_Law_BSpFunc object constructed from 2D B-Spline which defines radius evolution:

/*! Creates an internal Law_BSpFunc object which represents an evolution function. Uses X
coordinates of the \a theEvol B-Spline curve.

\a theFirst and \a theLast are boundaries of the path curve.
*/
static Handle(Law_BSpFunc) CreateBsFunction (const Handle(Geom2d_BSplineCurve)& theEvol,
const Standard_Real theFirst,
const Standard_Real theLast)
{
//knots are recalculated from theEvol prorate to [theFirst, theLast] range
Standard_Integer i;
const Standard_Integer aNbP = theEvol->NbPoles();
TColgp_Array1OfPnt2d aPArrE (1, aNbP);
theEvol->Poles (aPArrE);
TColStd_Array1OfReal aPArr (1, aNbP);
for (i = 1; i <= aNbP; i++)
aPArr(i) = aPArrE(i).X();

const Standard_Integer aNbK = theEvol->NbKnots();
TColStd_Array1OfReal aKArrE (1, aNbK), aKArr (1, aNbK);
theEvol->Knots (aKArrE);
TColStd_Array1OfInteger aMArr (1, aNbK);
theEvol->Multiplicities (aMArr);

const Standard_Real aKF = aKArrE(1), aKL = aKArrE (aNbK);
const Standard_Real aKRatio = (theLast - theFirst) / (aKL - aKF);
for (i = 1; i <= aNbK; i++) {
aKArr(i) = theFirst + (aKArrE(i) - aKF) * aKRatio;
}

Handle(Law_BSpline) aBs;
if (theEvol->IsRational()) {
TColStd_Array1OfReal aWArrE (1, aNbP);
theEvol->Weights (aWArrE);
aBs = new Law_BSpline (aPArr, aWArrE, aKArr, aMArr, theEvol->Degree(),
theEvol->IsPeriodic());
} else {
aBs = new Law_BSpline (aPArr, aKArr, aMArr, theEvol->Degree(), theEvol->IsPeriodic());
}

Handle(Law_BSpFunc) aFunc = new Law_BSpFunc (aBs, theFirst, theLast);
return aFunc;
}

/*! Uses X coordinates of the \a theEvol B-Spline curve to set evolution function.
*/
void ACISGGeom_Pipe::SetEvol (const Handle(Geom2d_BSplineCurve)& theEvol)
{
myEvol = ::CreateBsFunction (theEvol, myPath->FirstParameter(), myPath->LastParameter());
}

Below are examples of radius function (as 2D B-Spline) and resulting surface:  Pipe surfaces are parametrized in U along the cross section and in V along the path. Surface parametrization inherits the path range and adjusts U to a section range. For instance, tubes are parametrized from 0 to 2*PI in U.

To be continued...

#### You May Also Like

1. Great post & blog. very informative and supportive for opencascade and developers in general.

2. Another informative post - thank you, Roman!
Is it possible to vary the shape of the cross-section, rather than just its size?

3. Thanks, Mark! You probably mean skinning technique when a surface is built through a set of profiles. If so then yes, it's supported.
I'm going to consider it in this set of posts.

4. What I'm trying to do with a variable cross-section is to model the material removed by an endmill as gcode is processed. It would be easiest to sweep a 3d model, but that is not possible, is it? If it were possible to sweep a continuously variable cross-section, perhaps I could come up with a way of calculating that cross-section.

5. If your section can change its nature along the path (e.g. from circle to ellipse to rectangular, etc) then you could use a an approach described in Part4 (subclassing Approx_SweepFunction). If it's just a size (e.g. ellipse with evolving major and minor radii) then the example from Part3 should work. Just specify a required profile (not necessarily circular) and a radius function.

6. Thank you for this very interesting blog and the informative article Roman!
It would be also very nice to get some ideas about the skinning or lofting technique when a surface is built through a set of profiles.
Chris

7. Hi Chris,
Yes, skinning and lofting are likely to appear in the next post ;-). Stay tuned.

8. Doesn't BiTgte_Blend create a rolling ball surface?
The corresponding Draw-command is "rollingball". But it is not documented in the Draw User's guide. I tried to create fillets on a box with it, but without success. Maybe I'm not giving it the right parameters. I also asked on the OCC forum about this class, if it is deprecated, but there was no answer (http://www.opencascade.org/org/forum/thread_22773/).