2010-11-24

Parasolid developer community ?

By any lucky chance, does anyone reading my blog, know any community of Parasolid developers ?

I tried to register at http://developer.hoops3d.com/forums but it seems dead - no forum threads, no approval/rejection replies, etc.

Asked the same question on http://forums.spatial.com but people there did not know :-(.

I even had a phone call with Siemens and their rep promised to help but no follow up after that :-(.

I had some clarifying questions when working on Parasolid support in CAD Exchanger but could figure them out myself. But it would still be useful moving forward.

Thanks in advance!

2010-11-08

Adapters. Part 1

Those of you who have been developing software for quite some time likely came across the adapter pattern. It's one of the classical patterns described in the famous Design Pattern's book by Erich Gamma et al. The concept is simple yet powerful allowing you to adapt one interface to another.

How different is computing an intersection between two curves and between two edges ? Not that much – you just need to minimize a function representing a distance between any two points along each. What about a distance between two wires ? Just the same. And what about an intersection between an edge and a wire ? Or between a curve and an edge? What if you need to build a tube (or pipe) surface along the curve or along the wire ? Would that make any difference? Nope, either.

OK, but how would your algorithm API look like ? Will it accept Geom_Curve, TopoDS_Edge and TopoDS_Wire all at once and in any possible combination? Wouldn't it be weird and unreadable ? What about efforts to maintain it ?

The adapter pattern is the answer. You just have an abstract class that would provide an interface, which the algorithm could use. And particular implementation (for curves, edges or wires) would be provided by subclasses.

That's exactly what is provided by Open CASCADE adapters – abstract Adaptor3d_Curve, implementation for curves - GeomAdaptor_Curve, for edges – BrepAdaptor_Curve, and for wires – BRepAdaptor_CompCurve. The pipe algorithm (GeomFill_Pipe) accepts adapters (not Geom_Curve) to construct a surface. This provides a good flexibility for you to feed your objects.

Here is a sample code:

TopoDS_Wire aSpineWire = ...;
TopoDS_Edge aGuide1 = ..., aGuide2 = ...;
Handle(BRepAdaptor_HCompCurve) aSpineAdaptor = new BRepAdaptor_HCompCurve (aWire);
Handle(BRepAdaptor_HCurve) aGuideAdaptor1 = new BRepAdaptor_HCurve (aGuide1), aGuideAdaptor2=new BRepAdaptor_HCurve (aGuide2);
GeomFill_Pipe aPipe (aSpineAdaptor, aGuideAdaptor1, aGuideAdaptor2, aRadius);
aPipe.Perform (aTol, anIsPolynomial);

In the code above, *_H* are just handle-based equivalents, subclasses of Adaptor3d_HCurve.

Another good example is Extrema that is used to compute distances between the curve (and surface) adapters. You can feed curves, edges or wires to calculate extremum distances (including intersections).

Unfortunately, Open CASCADE API is not consistent in terms of use of adapters and often abuses hard-coded types where adapters would have been a better choice. For instance, the very
GeomFill_Pipe abuses Geom_Curve in some constructors while Adaptor3d_Curve could be used.GeomConvert_ApproxCurve which approximates a curve with B-Spline could have accepted an adapter thereby providing powerful possibilities to create a single NURBS-curve from TopoDS_Wires, for example.

In the follow-up post I'll try to show some example of how you could create your own adaptor, but hopefully this one already sheds some light on the concept and will give you some food for thoughts and experimenting.

(to be continued...)

2010-10-29

Data model highlights – Parasolid, ACIS, Open CASCADE

Working on the Parasolid importer of CAD Exchanger I had a new chance to observe specificities of another modeling kernel used to describe a B-Rep (Boundary Representation) model. This post highlights a few high-level differences between Parasolid, ACIS and Open CASCADE related to data models.

Comparing to ACIS, Parasolid seems to use a smaller set of primitives. Of course, there are typical elementary curves and surfaces (lines, circles, planes, cylinders, ...), NURBS, basic swept surfaces (revolution, extrusion), offsets. Parasolid defines geometrical object orientation right at the geometrical level by specifying a boolean flag at each curve or surface. An original surface normal is defined by cross-product of U and V derivatives, and can be confirmed or reversed by that boolean flag. ACIS defines normals by using special values of some key parameters, ignoring U and V parameterization. For instance, a sphere with a normal oriented inward would be defined in ACIS as having a negative radius. If you remember, Open CASCADE defines orientation as attached to the topology entity (face for surface, edge for curve).

Transformations in Parasolid can only be applied to component instances in assemblies. ACIS uses transformations only attached to bodies (the top-level entity which is a collection of solids). Open CASCADE can use location at any topology level, from vertex to arbitrary compound. This flexibility allows to reduce the model memory footprint – for instance, a solid box can be represented by single face with 6 different locations. The downside, however, is that you must take extra pre-cautions to not unintentionally modify shared objects, e.g. by creating copies as needed.
Unlike Open CASCADE, both ACIS and Parasolid often share underlying geometries – faces referring to the same surface are quite common in each. Open CASCADE discourages this to prevent modifications. Another benefit, which I realized recently – reduce data race risks in multi-threaded applications.

Among Parasolid geometrical types, I found just a couple of most interesting. The first one, which does not present in ACIS and Open CASCADE, is an intersection curve. An intersection curve is defined by a set of points, two surfaces being intersected and a law to define tangents and parameterization of the curve.
Another one is a rolling_ball_blend surface defined by two support surfaces, spine curve and a ball radius (see image below). Although, ACIS also has such type, the kinds of supports are often different. Whereas ACIS often uses support curves (which it calls ‘spring curves’), Parasolid most often uses surfaces. This added extra challenge to implement conversion as Open CASCADE’s GeomFill_Pipe (I described in the past) does not offer such combination.



Parasolid does not offer that broad set of procedural surfaces that ACIS does (variable_blend, net, skin, tube, law, etc). However it still has a larger set than Open CASCADE which is based on the STEP ISO010303-42 standard. Open CASCADE set is really a subset of what two other offer, except maybe support of Bezier curves and surfaces, and the fact that pcurve can have any type, not just B-Spline as two kernels require.

All 3 kernels have a concept of 3D and 2D representations for face boundaries. Parasolid has a notion of ‘fin’ or ‘half-edge’, and ACIS uses ‘coedge’ which is an entity referred by face’s loops and in its turn, refers to edge and optionally a p-curve. Interestingly, in Parasolid it can have either 3D or 2D representation but not both (though I did encounter one file in 1000+) that violated this requirement.

As far as tolerances are concerned, Parasolid, like Open CASCADE, does use local tolerances attached to the topological entities. ACIS introduced local tolerances in some intermediate versions only.

Similar to ACIS-SAT, the Parasolid-XT file format has evolved from version to version. Unlike ACIS, it is virtually human-unreadable due to use of numbers as type identifiers. Parasolid uses a concept of schemas that define entities type number, field layout, etc. To support backward compatibility, they introduced dynamic extension by embedding schema definitions into the file itself. Open CASCADE file format has never changed for last 15 years at least.

2010-10-27

CAD Exchanger 2.0.2 Beta now available

(Repost from www.cadexchanger.com)

October 26, 2010.
CAD Exchanger 2.0.2 Beta is available
This version introduces Parasolid-XT importer. We decided to follow an effective approach used for ACIS-SAT by first proposing it to Beta customers and addressing their feedback. If you would like to join the Invitational Beta, drop us an email at info@cadexchanger.com. Public release should become available later this quarter or early 2011.

GUI and CLI (Command Line Interface) versions are available immediately. SDK should be available shortly.

2010-09-13

CAD Exchanger 2.0.1 is now available!

Version 2.0.1 is a maintenance release featuring improvements and bug fixes over v2.0. Consult the CHANGES file for details and visit the download page to get the release. For SDK evaluation please contact us at info@cadexchanger.com.

Feel free to share your feedback on the forum or email directly.
Thanks!

2010-08-06

Handles and Templates

Sometimes you might want to use a template class that would inherit Standard_Transient (or its subclass). As you need to pair it with a handle counterpart (which would have to inherit respective Handle_Standard_Transient subclass), you can't do this with the DEFINE_STANDARD_HANDLE macro directly. This post explains how you could do this.

ACIS and Parasolid formats (like IGES or STEP for instance) represent a file as a list of entities that have id's. To represent an ACIS or a Parasolid file in memory, the Base_FileModel class is used. It basically provides access to the file header and manages a set of entities where an entity is of course specific to each format. To ease sharing Base_FileModel it should be manipulated by handle (i.e. inherit Standard_Transient). Here is how this is done:

template class Base_FileModel : public Base_Transient
{
public:

//! Defines a type to index entities.
/*! @todo It must be signed (e.g. to address -1 in ACIS. Should it be consistent with size_t (for 64bit port)?
*/
typedef int Index_t;


//! Defines an entity type.
/*! Equals to the template parameter.*/
typedef En Entity_t;


//! Returns a number of entities.
/*! An empty model returns 0.*/
size_t NbEntities() const { return myRevEntMap.Size() - 1; }

//! Adds an entity.
/*! Returns a rank number with which \a theEntity has been added into the model. By
default, a new rank number is maximum rank number + 1.
Null entities are ignored.

This method is not thread-safe.

\sa Bind().
*/
Index_t Add (const Entity_t& theEntity)
{
Index_t aRank = myNullRank;
if (!theEntity.IsNull()) {
aRank = myEntVec.Size() + myNullRank;
Bind (aRank, theEntity);
}
return aRank;
}
...
protected:

//! Constructor.
/*! \a theNullRank defines a null rank used to designate a null entity (e.g. 0 for
Parasolid or -1 for ACIS).
*/
Base_FileModel (const FileHeader_t& theHeader, const Index_t theNullRank) :
myHeader (theHeader), myNullRank (theNullRank)
{
const Entity_t aNull;
myEntVec.SetValue (Index (theNullRank), aNull);
myRevEntMap.Bind (aNull, theNullRank);
}

...

NCollection_Vector myEntVec;
NCollection_DataMap myRevEntMap;
const Index_t myNullRank;


private:
Base_FileModel (const Base_FileModel&);
Base_FileModel& operator= (const Base_FileModel&);
};


Here is how a particular class (for ACIS) is defined:

DEFINE_STANDARD_HANDLE(ACISBase_Model,Base_Transient)

class ACISBase_Model : public Base_FileModel
{
public:

//! Constructor
/*! Creates a model with a new ACISBase_FileHeader.*/
ACISBase_Model() : Base_FileModel (
new ACISBase_FileHeader(), EntityNullRank) {}

...
};

Similarly in ACISBase_Model.cxx:

IMPLEMENT_STANDARD_HANDLE(ACISBase_Model,Base_Transient)
IMPLEMENT_STANDARD_RTTIEXT(ACISBase_Model,Base_Transient)

That's it !

2010-07-20

Side effects of the Handle

Last week-end, working on refactoring existing CAD Exchanger ACIS converter code (to maximize its reusability for the Parasolid one which is under development), I encountered an issue which might be interesting for a broader audience.

Inside the CAD Exchanger SDK, there is a function that accepts a pointer to the object (Base_Generator*) that inherits Standard_Transient (i.e. which can be manipulated by handle).

DEFINE_STANDARD_HANDLE(Base_Generator,Standard_Transient)
class Base_Generator : public Standard_Transient
{
...
};


class Base_GDriver
{
...

//! Translates an object into a result.
virtual void Paste (const Source& theSource,
Target& theTarget,
Base_Generator* theGenerator) const = 0;
};


I don’t remember the fundamental reason why the pointer was originally preferred to a const reference (i.e. const Handle(Base_Generator)&) , likely becase to avoid splitting Base_Generator.hxx into two headers – one defining the Base_Generator class and the other – Handle_Base_Generator, as Base_Generator.hxx itself included the header defining Base_GDriver. That does not matter much in this context anyway.

Inside the upper-level code which creates an instance of Base_Generator subclass (ACISGTopo_RGenerator), I decided to avoid using a handle and allocating a dynamic memory and simply created it on stack. That is, instead of:
Handle(ACISGTopo_RGenerator) aRGenerator = new ACISGTopo_RGenerator();
I simply use:
ACISGTopo_RGenerator aRGenerator;
This is fine as the object is not used outside the scope and no smart pointer is required.

However, when I started regression testing, I have encountered an access violation exception. Debugging led to a code that has been modified during the refactoring:

void ACISGGeom_BaseIntCurDriver::Paste (
const Handle(Standard_Transient)& theSource,
Handle(Standard_Transient)& theTarget,
Base_Generator* theGenerator) const

{
...

const Standard_Real aTol =
Handle(ACISGTopo_RGenerator)::DownCast (theGenerator)->Model()->Header()->ResAbs();

...
}

Anyone sees the issue now ?
That should be obvious if you have dealt with handles quite enough. Here is the explanation. During DownCast() a temporary handle object created. During its creation it increments the reference counter (which is 0 by default in aRGenerator). Upon its destruction it decrements it back to 0 and the handle’s destructor calls the object destructor thereby destroying the aRGenerator and deallocating its memory. That’s it!

I ended up changing the ACISGGeom_BaseIntCurDriver::Paste() to use static cast:
const Standard_Real aTol = static_cast(theGenerator)->Model()->Header()->ResAbs();
as this is a guaranteed cast in this particular case.

In general, the most reliable approach would be to wrap aRGenerator with a handle (instead of creating it on a stack).

So, a few closing thoughts:
1. Test, test, test !
2. Even if you understand bolts and nuts of the handle, you can still be surprised;
3. If you have to supply a pointer to your object that inherits Standard_Transient into a third-party code, make sure your always wrap it with a Handle;
4. If the Handle_ constructors that accepts a pointer to an object (e.g. Handle_Standard_Transient (const Standard_Transient*) ) were declared with ‘explicit’ keyword then the compiler would fail at DownCast() and this could be a hint to a developer to think carefully what might happen. Perhaps a candidate for a next modification of the OCC source code ?

Take care!

2010-06-30

CAD Exchanger 2.0 is available!

(Repost of the announcement)

CAD Exchanger 2.0 is available!

We are excited to announce a new release - version 2.0, which includes multiple new features, enhancements and bug fixes.

Version 2.0 features X3D support and is based on significantly redesigned and improved SDK, including its port to new OSes (Linux and MacOS), broader use of multi-threading which significantly improves performance, better support of some ACIS entities and much more. For a full list of changes, please refer to the CHANGES file.

As usual, free evaluation is available for download at http://www.cadexchanger.com/download.html

You know, we always value your feedback. Please drop us a line at info@cadexchanger.com, even if you simply like the tool.

Enjoy !
The development team

2010-05-02

Open CASCADE and multi-threading again. B-Splines…

This a small continuation of a series on parallelism and Open CASCADE started last year. This post is just about a particular class but it is fundamental enough deserving a separate mention.

Geom_BSplineCurve, Geom_BSplineSurface, Geom2d_BSplineCurve, Geom_BezierCurve, Geom_BezierSurface, Geom2d_BezierCurve. They implement B-Spline and Bezier curves and surface.

Supposedly for optimization of calculation of points and derivatives, they use a cache that stores information used in calculation of the lastest B-Spline segment. In 2008, when I was working on Extrema performance improvements, I made some experiments about this cache efficiency. I tried several different workloads – Boolean operations, curve projections, IGES and STEP imports, and several others – but cache misses were ~50%-75%. That is, in more than a half cases, the cache had to be flushed out and recalculated. This made me wonder if this technique makes sense at all, if such computation-intensive algorithms do not really take advantage of it. Of course, to claim that I had to conduct more thorough experiments which I did not have time for. So I just left that issue alone. (But if anyone has some insights I would be interested to hear.)

The issue popped up in a year (December 2009 or early January 2010) when I was preparing a public release of the ACIS converter of CAD Exchanger. Until that it was parallelized and took advantage of multi-core machines performing translation in multi-threaded mode. However on some models from time to time I was receiving spontaneous crashes which made me wonder about root-causes. As symptoms looked as data races, I launched Intel Parallel Inspector to see if it's true.



What was my surprise when I saw that the root-cause was ... Geom_BSplineCurve::D0() ! My first impression was that Inspector was probably too jealous about OCC and is plain wrong, as D0() is just calculation of a point on the curve. Moreover it's a const method. How come a data race can be between two const methods ? But looking deeper at stacks, source code and applying some brain power, I realized that. Yep! D0() first casts a const pointer to a non-const pointer and calls a non-const method:

Geom_BSplineCurve * MyCurve = (Geom_BSplineCurve *) this ;
MyCurve->ValidateCache(NewU) ;

So, what does that mean ? It means that when the same B-Spline curve is used in 2 or more threads they read/write the cache without synchronization, thereby overriding data and creating a data race. Obviously, the value returned by D0() can be totally wrong what leads to subsequent crashes in the algorithm that calls it (e.g. projection of a 3D curve on a B-Spline surface). As this situation (simultaneous use of the same surface object in different threads) is rather rare, crashes did not always happen.

I did not have time before that release to fully solve the problem, so had to simply disable parallelism in the ACIS converter. Now I'm returning to this issue and I'm going to introduce object copying before use in multiple threads (what would add some overhead but this will be a 'cold path' as such cases would be still be rare).

So if you consider use of Open CASCADE in multi threads beware of this potential issue. Make sure you have protected access (e.g. copy the objects, access synchronization, etc) or independent copies of the B-Splines.

2010-04-22

Porting on MacOS

This is probably a sort of “me too” story. There are several posts on the Open CASCADE forum explaining how different developers struggled with making it work on Mac. These posts were great sources for me to make my way. As Isaac Newton said “If I have seen further it is only by standing on the shoulders of giants”. So this post is primarily to give credit back to those experimentors and to help those who will follow up.

This entire effort has started to meet a CAD Exchanger SDK prospect who requested its version on MacOS. CAD Exchanger SDK was Windows-only and to fill this gap I first started Linux port (as Linux is officially supported by OCC, and I had Linux experience unlike Mac). “Eat an elephant – one bite at a time”. Linux port helped to port the entire build system, to solve some compilation problems (gcc is often more paranoic than Microsoft compiler), to solve some non-Windows specific problems (like crash of an application with MMGT_REENTRANT set to 1. If you are on Linux or Mac, can you try to see it happens ;-) ?)

My major starting points for Mac were the following posts:
http://www.opencascade.org/org/forum/thread_15606/
http://www.opencascade.org/org/forum/thread_14128/. So many thanks go to Emmanuel, Torsten Sadowski, pythonOCC team (jelle and Thomas Paviot), and others.

I have eventually used MacOS 10.5 (Leopard) with gcc4.0.1. My exact sequence of actions was the following:

Made sure that there is a /usr/X11 directory (I first used a machine with only /Developer/SDKs/MacOSX10.4u.sdk/usr/X11R6 and faced with issue of isysroot and other headache. Though build was almost fine, the executable won’t start and eventually I gave up)
Downloaded and unpacked OpenCASCADE_src.tgz (e.g. into /users/rlygin/dev/Dev/OCC/fix-mac32)
export CASROOT=/users/rlygin/dev/Dev/OCC/fix-mac32
# remove shipped ltmain.sh – see previous post
cp ${CASROOT}/make/ltmain.sh ${CASROOT}/make/ltmain.sh.sav
ln –s /usr/share/libtool/ltmain.sh ${CASROOT}/make/ltmain.sh
cd ${CASROOT}
aclocal
automake
autoconf
export INSTALL_DIR=${CASROOT}/install
export CPPFLAGS="-I/usr/X11/include -arch i386"
export LDFLAGS="-arch i386 -Wl,-dylib_file,/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib:/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib"
The latter option is to work-around 10.5 OpenGL bug described here and here.
For release mode:
./configure --with-xmu-include=/usr/X11R6/include --with-xmu-library=/usr/X11R6/lib --with-gl-include=/usr/X11R6/include --with-gl-library=/usr/X11R6/lib --with-dps-include=/usr/X11R6/include --with-dps-library=/usr/X11R6/lib --with-x-include=/usr/X11R6/include --with-x-libraries=/usr/X11R6/lib --with-x --enable-static=no --enable-shared=yes --enable-production=yes --without-tcl --without-tk --disable-draw --disable-wok --disable-wrappers --prefix=${INSTALL_DIR}
Though I have explicitly specified all those --with-xmu- etc options they seemed to have no effect and what matters is CPPFLAGS and LDFLAGS. Notice also that I disabled Draw, WOK and the Wrappers modules.

Watch for the output and ensure that all your intended modules are correctly configured. Then type:
make
make install

On a properly configured machine the above worked just fine and after a few hours there was a working build. However lauching the unit test framework I quickly faced with an exception on a test case related to use of Standard_Transient in multi-threaded mode. The root-cause came up quickly – race condition in Standard_Transient reference count. See Standard_Atomic.hxx:
//===================================================
// Default stub implementation, not atomic actually
//===================================================
#else

inline void Standard_Atomic_Increment (int volatile* var)
{
++(*var);
}

...

Thus, it was simply not thread-safe at all. Fortunately the fix was easy to make – just use Linux implementation (as gcc is also used on MacOS):
#elif defined(LIN) || defined(__APPLE__)

inline void Standard_Atomic_Increment (int volatile* var)
{
// C equivalent:
// ++(*var);

__asm__ __volatile__
(
"lock incl %0"
: "=m"(*var) // out
: "m" (*var) // in
);
}
...

So, hopefully the Open CASCADE team will integerate this fix as it already kindly did for all other fixes previous pioneers did for MacOS.
After the fix everything worked as expected.

I have currently only ported CAD Exchanger SDK. GUI and Command Line (CLI) remain Windows-only. So no real attempts to make OpenGL-related code work on Mac yet. Perhaps it will uncover some new problems. If anyone knows this one upfront it would be great to learn their experience …

2010-04-05

Porting on Linux

Another brief post, now about my experience to port on Linux. This may sound something trivial to Linux experts but perhaps will save a few hours of tedious work to someone who is not a guru.

I was following the Build Instructions outlined in the OCC user’s documentation to use autoconf/automake tools. My build box was RHEL4 Update 4. So I diligently downloaded the OCC source archive, ungzipped’ed it and ran ./configure with some required options (e.g. --disable-wrappers,...). Then make.

There were multiple hiccups along the way but after several hours I could get built libraries inside ./ros/adm/make/*/.libs. To my surprise they did not contain the .so suffix and so I was stuck as I could not do anything about it. I emailed to my colleagues at OCC begging for some help. They were generous enough to offer some hints, so thanks for that again Igor ;-) ! In parallel, Google gave a couple of helpful links as well:
http://lists.kde.org/?l=kdevelop&m=111840274221191&w=2
http://osdir.com/ml/gnu.libtool.bugs/2003-07/msg00005.html

Root-cause: it was a mismatch between the libtool pre-installed on the system (which was of version 1.5.6) and ltmain.sh shipped with OCC (which expected libtool 1.4.3). Linking ./ros/make/ltmain.sh to /usr/share/libtool/ltmain.sh solved the problem. ./configure and make went smoothly and now all the libraries do contain .so.

This experience made me think that this step – ignoring shipped ltmain.sh and using pre-installed one should just always be done.

Any thoughts on this ?

P.S. Looking deeply into libtool which is autodenerated by ./configure, I still can’t figure out when ${shared_ext} is being defined to .so. So if anyone has mastered all this mechanism, I’d be interested to know ...

2010-03-31

Mixing run-times

A brief post not related to Surface modeling but still hopefully useful...

Last week-end I was testing a first version of the X3D converter to be added to CAD Exchanger. X3D stands for eXtensible 3D format (ISO19975) which could be seen as a successor of VRML format. Check http://www.web3d.org for more details.

The persistent representation uses XML as a file format, and I use Open CASCADE XML formatter for that. Once the XML document has been created, it is fed into LDOM_XmlWriter (part of Open CASCADE) which dumps contents to the file. LDOM_XmlWriter accepts FILE* as a file object.

Now beings the problem. When OCC is compiled with vc7.1 (Visual Studio 2003) and the X3D converter – with vc8 (VS2005), the application crashes during output to an XML file (inside the fwrite() function). I was trying to use VS2003 for OCC binaries as some CAD Exchanger SDK users preferred that. The same problem shows up if OCC binaries are compiled with VS2005 and the X3D converter is compiled with VS2008.

The root cause of the crash is that a FILE pointer is checked against pre-defined constant pointers to file objects (including stdout, stderr, etc) inside the run-time library. And when this pointer has been created in one run-time (vc8) and is checked inside the other (vc7.1), a different code branch is executed eventually leading to null pointer access and hence crash.

Thus, interoperability between run-times is really very limited and you should do as much as you can to avoid mixing different run-times in one application. In particular, if you are using open source software (Open CASCADE and others), make sure you rebuild them with the same run-time. If you are distributing closed source, make sure you prebuild binaries for multiple run-times.

Hope this helps if you ever encounter with such an issue, or better yet - *before* you encounter ;-).

2010-03-02

Surface modeling. Part6

(continued...)

Plating
This is one of the advanced techniques of surface modeling that Open CASCADE offers. Other names for it are hole filling, or constrained filling. ACIS calls it covering.

It involves creation of a surface that meets several constraints:
- point constraints – the surface must pass through given points;
- point constraints and tangency, and (optionally) curvature – the same as above + extra Gn requirements with respect to another surface;
- curve constrains – the surface must pass through given curves;
- curve with tangency constraint – the same + extra Gn requirements.

The latter type of constraint is used to produce class A surfaces, G1 tangent to adjacent surfaces. This is important, for instance, in auto industry when designing nice-looking car bodies, to ensure correct smooth refraction of light to avoid sharp edges.

Constraints can be mixed (e.g. 3 curve constraints, 1 curve with tangency constraint, 2 point constraints).

The main algorithm to construct plate surface is GeomPlate_BuildPlateSurface. I use it in CAD Exchanger to translate vertex_blend ACIS surfaces as well as net surfaces. The former is supposed to result from vertex blending and producing a surface G1-continous to neighbours. The image below shows one:



Net surface in ACIS is defined through two sets of curves going in approximately perpendicular directions and forming a grid-like structure. So the resulting surface is supposed to cover that grid as if a roof covered roof-timbers. The image below illustrates this:



As usual, here is an excerpt from the CAD Exchanger code:

/*! The objects in \a theBoundaries must be of the type Adaptor3d_HCurveOnSurface or
GeomAdaptor_HCurve indicating type of a constraint. Otherwise an exception
Standard_TypeMismatch is thrown.

If the \a theBoundaries list is empty then Standard_ConstructionError is thrown.

If the algorithm fails returns a null surface.
*/
Handle(Geom_Surface) ACISAlgo::MakeSurface (const TColStd_ListOfTransient& theBoundaries,
const Standard_Real theTol,
const Standard_Integer theNbPnts,
const Standard_Integer theNbIter,
const Standard_Integer theMaxDeg)
{
//constants for algorithm
const Standard_Integer aNbIter = theNbIter; //number of algorithm iterations
const Standard_Integer aNbPnts = theNbPnts; //sample points per each constraint
const Standard_Integer aDeg = 3; //requested surface degree ?
const Standard_Integer aMaxDeg = theMaxDeg;
const Standard_Integer aMaxSeg = 10000;
const Standard_Real aTol3d = 1.e-04;
const Standard_Real aTol2d = 1.e-05;
const Standard_Real anAngTol = 1.e-02; //angular
const Standard_Real aCurvTol = 1.e-01; //curvature

Handle(Geom_Surface) aRes;
GeomPlate_BuildPlateSurface aPlateBuilder (aDeg, aNbPnts, aNbIter, aTol2d, aTol3d,
anAngTol, aCurvTol);

TColStd_ListIteratorOfListOfTransient anIt (theBoundaries);
if (anIt.More()) {
int i = 1;
for (; anIt.More(); anIt.Next(), i++) {
const Handle(Standard_Transient)& aCur = anIt.Value();
if (aCur.IsNull()) {
assert (0);
Standard_ConstructionError::Raise ("ACISAlgo::MakeSurface()");
} else if (aCur->IsKind (STANDARD_TYPE (Adaptor3d_HCurveOnSurface))) {
//G1 constraint
const Handle(Adaptor3d_HCurveOnSurface)& aHCOS =
Handle(Adaptor3d_HCurveOnSurface)::DownCast (aCur);
Handle (GeomPlate_CurveConstraint) aConst =
new GeomPlate_CurveConstraint (aHCOS, 1 /*GeomAbs_G1*/,
aNbPnts, aTol3d, anAngTol, aCurvTol);
aPlateBuilder.Add (aConst);
} else if (aCur->IsKind (STANDARD_TYPE (GeomAdaptor_HCurve))) {
//G0 constraint
const Handle(GeomAdaptor_HCurve)& aHC =
Handle(GeomAdaptor_HCurve)::DownCast (aCur);
Handle (GeomPlate_CurveConstraint) aConst =
new GeomPlate_CurveConstraint (aHC, 0 /*GeomAbs_G0*/, aNbPnts, aTol3d);
aPlateBuilder.Add (aConst);
} else {
Standard_TypeMismatch::Raise ("ACISAlgo::MakeSurface()");
}
}
} else {
Standard_ConstructionError::Raise ("ACISAlgo::MakeSurface()");
}

//construct
aPlateBuilder.Perform();

if (!aPlateBuilder.IsDone()) {
return aRes;
}

const Handle(GeomPlate_Surface)& aPlate = aPlateBuilder.Surface();
//approximation (see BRepFill_Filling - when no initial surface was given)
Standard_Real aDMax = aPlateBuilder.G0Error();
TColgp_SequenceOfXY aS2d;
TColgp_SequenceOfXYZ aS3d;
aPlateBuilder.Disc2dContour (4, aS2d);
aPlateBuilder.Disc3dContour (4, 0, aS3d);
Standard_Real aMax = Max (aTol3d, 10. * aDMax);
GeomPlate_PlateG0Criterion aCriterion (aS2d, aS3d, aMax);
{
//data races in AdvApp2Var used by GeomApprox_Surface, use global mutex
Standard_Mutex::Sentry aSentry (theBSMutex);
GeomPlate_MakeApprox aMakeApprox (aPlate, aCriterion, aTol3d, aMaxSeg, aMaxDeg);
aRes = aMakeApprox.Surface();
}
return aRes;
}


The code above deals with curve or curve-with-tangency constraints only but you could add point constraints in the same way.

The algorithm starts with creation of initial approximation which you can either specify or it will create a plane otherwise. I have not come across a need of pre-specifying an initial surface but perhaps that would give some hints to the algorithm in complex cases. If there is anyone with such experience, it would be interesting to hear.

To be continued...

P.S. Writing this post in a hotel on the US west coast, Hillsboro, Oregon. The nature here is the greatest I have ever seen. Always enjoying when returning to Oregon...

2010-01-27

Surface modeling. Part5

(continued...)
Time remains the most scarce resource but I feel obliged to share some new materials with you as you may expect continuation of the series. As usual, many thanks for your patience and interest !

Skinning and lofting
This is a technique to create a model (surface or shell, or solid body) using curve constraints which the surface passes through. Below is an example of a surface built using skinning:



Skinning and lofting are actually the same technique and Open CASCADE does not make a difference between them. ACIS kernel does make a little difference (in terms of types of the inputs parameters and way of constraints definition) but also stresses that these are very similar.

Here is how you would create a surface using skinning technique:
Handle(Geom_Surface) ACISAlgo::MakeSkinSurface (
const NCollection_List<Handle(Geom_Curve)>& theSections)
{
//populate section generator
GeomFill_SectionGenerator aSecGenerator;
for (NCollection_List<Handle(Geom_Curve)>::Iterator anIt (theSections); anIt.More();
anIt.Next()) {
const Handle(Geom_Curve)& aCurve = anIt.Value();
aSecGenerator.AddCurve (aCurve);
}
aSecGenerator.Perform (Precision::PConfusion());

Handle(GeomFill_Line) aLine = new GeomFill_Line (theSections.Size());

//parameters
const Standard_Integer aMinDeg = 1, aMaxDeg = BSplCLib::MaxDegree(), aNbIt = 0;
Standard_Real aTol3d = 1e-4, aTol2d = Precision::Parametric (aTol3d);

//algorithm
GeomFill_AppSurf anAlgo (aMinDeg, aMaxDeg, aTol3d, aTol2d, aNbIt);
anAlgo.Perform (aLine, aSecGenerator);

Handle(Geom_Surface) aRes;
if (!anAlgo.IsDone()) {
return aRes;
}

aRes = new Geom_BSplineSurface(anAlgo.SurfPoles(), anAlgo.SurfWeights(),
anAlgo.SurfUKnots(), anAlgo.SurfVKnots(), anAlgo.SurfUMults(), anAlgo.SurfVMults(),
anAlgo.UDegree(), anAlgo.VDegree());

...

Curves must be bounded and consistently parameterized (so that parameter growth was in one direction). Final B-Spline will be parameterized [Umin, Umax; 0., 1.], where U parametrization is calculated depending on the curves parametrization. U parameter goes along the section curves, and V parameter goes across the curves.

When working at topological level you can use the following code:

Standard_Boolean anIsSolid = Standard_False;
Standard_Boolean anIsRuled = Standard_False;

BRepOffsetAPI_ThruSections aGenerator (anIsSolid,anIsRuled);

...
//add constraints
for (...) {
aGenerator.AddWire (TopoDS::Wire (aConstraint));
}


Standard_Boolean anIsCheck = ...;
aGenerator.CheckCompatibility (anIsCheck);

aGenerator.Build();

const TopoDS_Shape& aResult = Generator.Shape();


The topological algorithm may attempt to create a closed solid if the boundary constraints are planar.

Unlike ACIS, Open CASCADE only allows to specify curve constraints but does not give a way to specify tangential ones. So you will basically rely on underlying algorithms which try to smoothen the surface being built.

To be continued...

2010-01-10

CAD Exchanger Public Beta Update 2 is available

After individual Invitational Beta for ACIS-SAT support, public Beta Update2 is finally released.

Highlighted features: ACIS-SAT import and export (geometry and colors), Command Line mode and CAD Exchanger SDK. List of all changes is here. Go to download page to get the release.

As usual, feedback is very much appreciated and welcomed. Here in the blog, or on the forum, or directly at info@cadexchanger.com.

Thanks !



screenshot of the model imported from ACIS-SAT