tag:blogger.com,1999:blog-32856779297774906562024-03-05T19:28:41.727+03:00Open CASCADE notesA blog about <a href="http://www.opencascade.org">the Open Source 3D modeling kernel</a>: notes from its former developer and project managerRoman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.comBlogger87125tag:blogger.com,1999:blog-3285677929777490656.post-4853470678183900952020-10-09T19:20:00.003+03:002021-10-23T09:49:03.418+03:00How to import SOLIDWORKS (or Parasolid, JT, NX, Creo) files into Open CASCADE<h1 style="text-align: left;">How to import SOLIDWORKS (or Parasolid, JT, NX, Creo) files into Open CASCADE</h1><div class="separator" style="clear: both; text-align: center;"><a href="https://cadexchanger.com/static/56d776055334d7420e679c93b15ed22e/cc785/model_cadex-and-draw.webp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="388" data-original-width="800" height="388" src="https://cadexchanger.com/static/56d776055334d7420e679c93b15ed22e/cc785/model_cadex-and-draw.webp" width="800" /></a></div><br /><div><img alt="Rotor Open CASCADE model" height="944" src="https://cadexchanger.com/images/model_cadex-and-draw.png" style="background-color: white; border-radius: 15px; border: none; box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px; color: #333333; display: block; font-family: Muli, sans-serif; font-size: 21px; height: auto; margin-bottom: 0px !important; margin-left: auto; margin-right: auto; margin-top: 10px; margin: 10px auto 0px; max-width: 700px; vertical-align: middle;" title="Rotor Open CASCADE model" width="2108" /></div><div><br /></div><div><br /></div><h2 style="text-align: left;">What is Open CASCADE</h2><div><div>If you are reading this blog post, you most likely already use Open CASCADE in your app and are well familiar with it. For those who are not, Open CASCADE is an open-source modeling kernel which can be used as a foundation to build 3D apps. In that sense, OCC competes with commercial kernels such as Parasolid, ACIS, CGM, or C3D.</div><div><br /></div><div>OCC has data structures to describe 3D models with precise B-Rep and various API to perform modeling operations (fillets and chamfers, Boolean operations, etc.). For the last 20+ years the OCC company has been focusing on rendering custom development services. So development of the kernel is mainly driven by those customer projects. Although some core modules are mainly in the maintenance mode, the kernel can still be a viable option for businesses evaluating the development platform for their apps.</div><div><br /></div><h2 style="text-align: left;">CAD file formats supported by Open CASCADE</h2><div>Out of the box, OCC supports a few neutral file formats, including IGES, STEP, STL, VRML. As paid add-ons, the OCC company offers interfaces with Parasolid, ACIS, DXF, JT. When I worked at the OCC company in the late 1990s and early 2000s, I led the engineering team which owned the development of many of those components, and I contributed lots of C++ code thereto myself. That’s when my passion for CAD data exchange started and over time drove me to start CAD Exchanger and founding the CADEX company in the early 2010s.</div><div><br /></div><h2 style="text-align: left;">CAD Exchanger components vs Open CASCADE’s</h2><div>In some sense, CAD Exchanger certainly overlaps with those ‘native’ offerings. However, our focus at CADEX is primarily CAD data conversion and doing it right and most efficiently, so we invested a lot into our own implementations. Whereas the OCC company switched to service-oriented business in the early 2000s and therefore has to be driven by on-demand customer projects, our primary focus is product development.</div><div><br /></div><div>We have to have a long-term predictable R&D roadmap, invest a far greater amount of human talents into product development, and be more responsive to numerous corner cases reported by our customers. Those focused efforts result in a broader amount of <a href="https://cadexchanger.com/formats" target="_blank">formats </a>(20+ at the time of this writing), a broader range of format versions, more efficient implementations (including <a href="https://cadexchanger.com/blog/207-cad-exchanger-receives-two-patents-on-parallel-computations-2" target="_blank">two hold patents</a> related to parallel computing), and eventually quality. With millions of users using CAD Exchanger-based applications, the stream of CAD files going through CAD Exchanger codes is in dozens of millions every year! That challenge does require us to provide world-class CAD data converters in order to be a successful sustainable business.</div><div><br /></div><div>We have multiple clients who decided to base their apps on Open CASCADE and they use some of the techniques described below to take advantage of the two worlds – OCC and CAD Exchanger.</div><div><br /></div><h2 style="text-align: left;">Integration of CAD Exchanger into an OpenCASCADE-based app</h2><div>With <a href="https://cadexchanger.com/products/sdk" target="_blank">CAD Exchanger SDK</a>, every developer using OCC to develop his/her application has an option to support a broader range of 3D file formats and thus to make the application more appealing to the end-users. And this integration is truly seamless and only takes a few lines of codes, and minutes.</div><div><br /></div><h3 style="text-align: left;">Let’s consider some technical details.</h3></div><div><div>How to read SOLIDWORKS file into Open CASCADE</div><div>Let’s take an example of how you could import a SOLIDWORKS file into your Open CASCADE-based app. (Instead of Solidworks, you could read NX, Creo, JT, IFC, or any other supported file format). It only takes a single line of code to replace the respective reader class (with NX_Reader, Creo_Reader, JT_Reader, and so on).</div></div><div><h4 style="text-align: left;"><br /></h4><h4 style="text-align: left;">Option 1. Topology and geometry only</h4><div>If you work with OCC at the level of geometry and topology (feel free to check out my older <a href="https://opencascade.blogspot.com/2009/02/">blog posts series</a> explaining this vital difference), then you must be using the TopoDS_Shape base class instance as a primary object holding your topological tree.</div><div><br /></div><div>In this case, your simplest code might look as follows:</div></div><div><br /></div>
<pre style="background-color: #303030; border-radius: 5px; line-height: 125%; margin: 0px;"><span style="color: #008073;">#</span><span style="color: #008073;">include </span><span style="color: #02d045;"><</span><span style="color: darkorchid;">cadex/SDL_Reader.hxx</span><span style="color: #02d045;">></span>
<span style="color: white;">cadex</span><span style="color: #b060b0;">::</span><span style="color: white;">SLD_Reader aReader</span><span style="color: #b060b0;">;</span>
<span style="color: white;">TopoDS_Shape aShape</span><span style="color: #b060b0;">;</span>
<span style="color: #e66170; font-weight: bold;">if</span> <span style="color: #d2cd86;">(</span><span style="color: #d2cd86;">!</span><span style="color: white;">aReader</span><span style="color: #d2cd86;">.</span><span style="color: #e66170; font-weight: bold;">ReadFile</span> <span style="color: #d2cd86;">(</span><span style="color: white;">“myfile</span><span style="color: #d2cd86;">.</span><span style="color: white;">ldprt”</span>s<span style="color: #d2cd86;">)</span> <span style="color: #d2cd86;">|</span><span style="color: #d2cd86;">|</span> <span style="color: #d2cd86;">!</span><span style="color: white;">aReader</span><span style="color: #d2cd86;">.</span><span style="color: white;">Transfer </span><span style="color: #d2cd86;">(</span><span style="color: white;">aShape</span><span style="color: #d2cd86;">)</span><span style="color: #d2cd86;">)</span>
<span style="color: #b060b0;">{</span>
<span style="color: #9999a9;">//some error happened</span>
<span style="color: #e66170; font-weight: bold;">return</span> <span style="color: #e66170; font-weight: bold;">false</span><span style="color: #b060b0;">;</span>
<span style="color: #b060b0;">}</span>
<span style="color: #9999a9;">//here aShape contains a full OCC shape</span>
</pre>
<h4 style="text-align: left;"><br /></h4><h4 style="text-align: left;">Option 2. Open CASCADE XDE</h4><div><div>If your OCC-based application uses XDE (eXtended Data Exchange), an OCAF (Open CASCADE Application Framework) extension, then you can import a SOLIDWORKS file into your XDE document very much like with the previous approach. The only difference is to convert an instance of cadex::ModelData_Data into OCC’s TDocStd_Document instance.</div><div><br /></div><div>In this case, the contents of the CAD Exchanger model will be appended to the contents of aDoc: the assembly structure, colors, names, layers.</div></div><div><br /></div>
<pre style="background-color: #303030; border-radius: 5px; line-height: 125%; margin: 0px;"><span style="color: #9999a9;">//convert CAD Exchanger document to OCC XDE document</span>
<span style="color: white;">Handle</span><span style="color: #d2cd86;">(</span><span style="color: white;">TDocStd_Document</span><span style="color: #d2cd86;">)</span><span style="color: white;"> aDoc </span><span style="color: #d2cd86;">=</span> <span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span><span style="color: #b060b0;">;</span> <span style="color: #9999a9;">//</span>
<span style="color: white;">cadex</span><span style="color: #b060b0;">::</span><span style="color: white;">ModelXDE_Converter</span><span style="color: #b060b0;">::</span><span style="color: white;">Convert </span><span style="color: #d2cd86;">(</span><span style="color: white;">aModel</span><span style="color: #d2cd86;">,</span><span style="color: white;"> aDoc</span><span style="color: #d2cd86;">)</span><span style="color: #b060b0;">;</span>
</pre>
<h4 style="text-align: left;"><br /></h4><h4 style="text-align: left;">Option 3. Open CASCADE topology and your own meta-data</h4><div><div>If your application works with meta-data (id’s, names, colors, materials, etc.) but you do not use XDE, then probably you have your own C++ classes that bind meta-data and OCC topology. In this case, you will just need to traverse the CAD Exchanger document and retrieve both the OCC topology and meta-data.</div><div><br /></div><div>The following code snippet demonstrates how this could be done.</div></div><div><br /></div>
<pre style="background-color: #303030; border-radius: 5px; line-height: 125%; margin: 0px;"><span style="color: white;">class</span><span style="color: white;"> SceneGraphVisitor </span><span style="color: #b060b0;">:</span> <span style="color: #e66170; font-weight: bold;">public</span><span style="color: white;"> ModelData_Model</span><span style="color: #b060b0;">::</span><span style="color: white;">VoidElementVisitor</span>
<span style="color: #b060b0;">{</span>
<span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span>
<span style="color: #e66170; font-weight: bold;">void</span> <span style="color: #e66170; font-weight: bold;">operator</span><span style="color: #d2cd86;">(</span><span style="color: #d2cd86;">)</span> <span style="color: #d2cd86;">(</span><span style="color: #e66170; font-weight: bold;">const</span><span style="color: white;"> ModelData_Part</span><span style="color: #d2cd86;">&</span><span style="color: white;"> thePart</span><span style="color: #d2cd86;">)</span><span style="color: white;"> override</span>
<span style="color: #b060b0;">{</span>
<span style="color: #e66170; font-weight: bold;">auto</span><span style="color: white;"> aBRep </span><span style="color: #d2cd86;">=</span><span style="color: white;"> thePart</span><span style="color: #d2cd86;">.</span><span style="color: white;">BRepRepresentation</span><span style="color: #d2cd86;">(</span><span style="color: #d2cd86;">)</span><span style="color: #b060b0;">;</span>
<span style="color: #e66170; font-weight: bold;">if</span> <span style="color: #d2cd86;">(</span><span style="color: white;">aBRep</span><span style="color: #d2cd86;">)</span> <span style="color: #b060b0;">{</span>
<span style="color: #e66170; font-weight: bold;">const</span> <span style="color: #e66170; font-weight: bold;">auto</span><span style="color: #d2cd86;">&</span><span style="color: white;"> aRootList </span><span style="color: #d2cd86;">=</span><span style="color: white;"> aBRep</span><span style="color: #d2cd86;">.</span><span style="color: white;">Get</span><span style="color: #d2cd86;">(</span><span style="color: #d2cd86;">)</span><span style="color: #b060b0;">;</span>
<span style="color: #e66170; font-weight: bold;">const</span><span style="color: white;"> TopoDS_Shape</span><span style="color: #d2cd86;">&</span><span style="color: white;"> aShape </span><span style="color: #d2cd86;">=</span> <span style="color: white;">aBRep</span><span style="color: #d2cd86;">.</span><span style="color: white;">ToOCC</span><span style="color: #d2cd86;">(</span><span style="color: #d2cd86;">)</span><span style="color: #b060b0;">;</span> <span style="color: #9999a9;">//convert to OCC shape</span>
<span style="color: white;">Base_UTF16String aName </span><span style="color: #d2cd86;">=</span><span style="color: white;"> thePart</span><span style="color: #d2cd86;">.</span><span style="color: white;">Name</span><span style="color: #d2cd86;">(</span><span style="color: #d2cd86;">)</span><span style="color: #b060b0;">;</span>
<span style="color: #9999a9;">//bind aShape and aName in your data structures...</span>
<span style="color: #b060b0;">}</span>
<span style="color: #b060b0;">}</span>
<span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span>
<span style="color: #b060b0;">}</span>
<span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span>
<span style="color: white;">SceneGraphVisitor aVisitor</span><span style="color: #b060b0;">;</span>
<span style="color: white;">aModel</span><span style="color: #d2cd86;">.</span><span style="color: white;">Accept </span><span style="color: #d2cd86;">(</span><span style="color: white;">aVisitor</span><span style="color: #d2cd86;">)</span><span style="color: #b060b0;">;</span> <span style="color: #9999a9;">//traverse model and retrieve data</span>
</pre>
<h4 style="text-align: left;"><br /></h4><h4 style="text-align: left;">Exporting Open CASCADE data into CAD files</h4><div><div>Writing Open CASCADE geometries into external CAD files (e.g., JT, or Parasolid, ACIS, etc.) is done very much like reading, considered above. Depending on your preferred usage of OCC API (TopoDS_Shape only, XDE or mix with your own API), you basically just need to perform the workflow opposite to reading – create and populate cadex::ModelData_Model object and feed it to the cadex::JT_Writer (or alike) instance:</div></div><div><br /></div>
<pre style="background-color: #303030; border-radius: 5px; line-height: 125%; margin: 0px;"><span style="color: white;">TopoDS_Shape aShape </span><span style="color: #d2cd86;">=</span> <span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span><span style="color: #d2cd86;">.</span><span style="color: #b060b0;">;</span>
<span style="color: white;">cadex</span><span style="color: #b060b0;">::</span><span style="color: white;">ModelData_Model aModel</span><span style="color: #b060b0;">;</span>
<span style="color: white;">cadex</span><span style="color: #b060b0;">::</span><span style="color: white;">ModelData_ShapeConverter</span><span style="color: #b060b0;">::</span><span style="color: white;">Add </span><span style="color: #d2cd86;">(</span><span style="color: white;">aShape</span><span style="color: #d2cd86;">,</span><span style="color: white;"> aModel</span><span style="color: #d2cd86;">)</span><span style="color: #b060b0;">;</span>
<span style="color: white;">cadex</span><span style="color: #b060b0;">::</span><span style="color: white;">JT_Writer aWriter</span><span style="color: #b060b0;">;</span>
<span style="color: #e66170; font-weight: bold;">if</span> <span style="color: #d2cd86;">(</span><span style="color: #d2cd86;">!</span><span style="color: white;">aWriter</span><span style="color: #d2cd86;">.</span><span style="color: white;">Transfer </span><span style="color: #d2cd86;">(</span><span style="color: white;">aModel</span><span style="color: #d2cd86;">)</span> <span style="color: #d2cd86;">|</span><span style="color: #d2cd86;">|</span> <span style="color: #d2cd86;">!</span><span style="color: white;">aReader</span><span style="color: #d2cd86;">.</span><span style="color: #e66170; font-weight: bold;">WriteFile</span> <span style="color: #d2cd86;">(</span><span style="color: white;">“myfile</span><span style="color: #d2cd86;">.</span><span style="color: white;">jt”</span><span style="color: #d2cd86;">)</span><span style="color: #d2cd86;">)</span> <span style="color: #b060b0;">{</span>
<span style="color: #9999a9;">//some error happened</span>
<span style="color: #e66170; font-weight: bold;">return</span> <span style="color: #e66170; font-weight: bold;">false</span><span style="color: #b060b0;">;</span>
<span style="color: #b060b0;">}</span>
</pre>
<h2 style="text-align: left;"><br /></h2><h2 style="text-align: left;">Benefits of CAD Exchanger</h2><div><div>If you decide to consider using CAD Exchanger SDK with Open CASCADE then here are a few key benefits you might expect to get:</div><div><br /></div><div><ul style="text-align: left;"><li>Truly seamless integration. It takes less than 5-10 lines of C++ code as explained above. Overall, perhaps sometimes it can be even more compact than using native OCC converters.</li><li>Single API. The conversion between CAD Exchanger SDK and OCC objects is totally format-agnostic. So if over time, you want to add new formats (e.g., NX after Solidworks) that will take a couple of extra lines (to add cadex::NX_Reader). We plan to achieve that extra amount to be zero (yes, zero!). In that case, even recompilation will not be required!</li><li>Single vendor. As I said, data conversion is our core business and our core architecture is shared across all formats. Although you can mix CAD Exchanger-based components with native OCC, preferring the former gives you consistent behavior, user experience, and benefits (such as delayed loading, CPU multi-threading, etc.). Price-wise, this can be even zero extra cost – e.g., when using our bundles, you get access to all formats.</li><li>Support. Again, due to very different business models, it is in our best interests to keep the quality of the converters as high as possible. So whenever you might encounter a very specific corner case, that report immediately goes into our tracker and will be picked up during upcoming development sprints.</li><li>Performance and Quality. Multi-core parallelism was my second professional passion (developed during ten years work in Intel software group), and multi-threading and high performance were in the DNA of CAD Exchanger from day one. We received two patents related to parallel computing, and this parallelism is part of the core architecture. Therefore even those overlapping formats (e.g., STEP, IGES, etc.) are implemented in a much more scalable way than legacy OCC codes. However, I remain fully open to counter-examples: if you come across any file where CAD Exchanger will demonstrate weaker results, drop me a line and our team will gladly review.</li></ul></div><h2 style="text-align: left;">Conclusion</h2><div>As you have seen, integrating CAD Exchanger SDK with your Open CASCADE based app is really seamless and does not require any significant effort. The SDK comes with numerous examples demonstrating the above OCC-focused scenarios as well as many others dedicated to exploring the model (assembly structure, geometries, meta-data), working with PMI (Product and Manufacturing), generating meshes, visualizing 3D models and many more.</div><div><br /></div><div>To evaluate CAD Exchanger SDK, please just fill out a brief <a href="https://cadexchanger.com/evaluation" target="_blank">evaluation form</a> and you will receive further instructions to guide you step by step. Should you have any questions on this topic or any other, feel free to drop me an email at roman dot lygin at cadexchanger dot com. Either myself or my support team will be happy to help.</div><div><br /></div><div>Thanks for reading!</div><div><br /></div></div>
Ramilhttp://www.blogger.com/profile/12796789982378835303noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-30315850160701949102015-09-25T10:16:00.000+03:002015-09-25T10:16:09.675+03:00CAD Exchanger is now on Android<div dir="ltr" style="text-align: left;" trbidi="on">
CAD Exchanger is now available in <a href="https://play.google.com/store/apps/details?id=com.cadexchanger.gui">Google Play</a>. Free, with in-app purchase (larger file size support, export to B-Rep formats, and more features in the future). OCC 6.9.0+ and Qt (QML) 5.5.0 based.<br />
<br />
I would like to thank again the OCC development team (Andrey B, Kirill G, Sergey A, Ivan and others) and management (Alexander T and Michael K) for their support in porting the OCC platform as a prerequisite. See more details in the official <a href="http://www.cadexchanger.com/cad-exchanger-mobile-app">press-release</a>.<br />
<br />
We met for the first time in August 2014 and the folks did their major part in a couple of months or so. Sometimes it was a bumpy road with some hot discussions but all is well that ends well. On our side we were doing a parallel port of a major version 3.0 to a totally new data model and UI, as a prerequisite to Android port. With parallel jumps between Windows, Linux and Android, with their different IDE's and toolchains - all pleasures of cross-platform development. That took us way too much time and we missed the schedule a few times. I regret we did this 6+ months later than originally planned but still would like to thank my team - Sergey, Denis and others for their extra efforts. There were multiple fruitful forum topics, bug reports and fixes - some are <a href="http://dev.opencascade.org/index.php?q=node/1079">here</a>, <a href="http://dev.opencascade.org/index.php?q=node/1019">here</a> or in Mantis, so good working relationship between the teams has been strengthened.<br />
<br />
Among technical highlights there are probably a few interesting items - for instance an asynchronous, non-blocking visualization workflow (see discussion preview <a href="http://dev.opencascade.org/index.php?q=node/1020">here</a>). When we have some free cycles, we will try to share more technical details.<br />
<br />
Meanwhile, if you have some spare time, please give a try to a brand new CAD Exchanger version and share your thoughts via Google Play, here or just email us at info@cadexhanger.com. We'd *really* be happy to hear from the community. Constructive critic, recommendations or positive comments are all valuable.<br />
<br />
And please do keep CAD Exchanger on your device, you now know some people behind it ;-).<br />
<br />
Thanks for your time and take care,<br />
Roman<br />
<br />
<br />
<br />
<br /></div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com17tag:blogger.com,1999:blog-3285677929777490656.post-87289731278695171172015-08-30T21:39:00.000+03:002015-08-30T21:39:14.771+03:00Arbitrary law-based curve and surface modeling. Part 2<div dir="ltr" style="text-align: left;" trbidi="on">
<i>(Continuation of <a href="http://opencascade.blogspot.ru/2015/08/arbitrary-law-based-curve-and-surface.html">part1</a>)</i><br />
Same approach works for surfaces. In this case GeomConvert_ApproxSurface and Adaptor3d_Surface classes should be used in exact same manner.<br />
<br />
Below are a few examples of creating law surfaces.<br />
<br />
The first case is an example of creating a variable offset surface, where a surface is defined as follows:<br />
S(u,v) = B(u,v) + Offset(u,v) * N (u,v), where<br />
B(u,v) is a basis surface,<br />
N (u,v) is a unit normal to the basis surface,<br />
Offset (u,v) is a function C + u ^ 2 + v ^ 2, where C is constant.<br />
<br />
The two below examples apply such offset laws to plane and sphere respectively. The basis surface B is in red, and the resulting surface S – in green.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3BNQZNMPuKOSQkQZ6xZH2cMK0T38R8dHWK9Q18ZkQcDFR_oJSn1o4EB-Ar8rAQwucaY6VkbAHgsuZ93LGyYCJ-N3RqlI7bvMU2PF4ivuVhyLb3qDEqhghZ7c0W7u492SEsr4YovQ6AM8/s1600/offset_plane.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3BNQZNMPuKOSQkQZ6xZH2cMK0T38R8dHWK9Q18ZkQcDFR_oJSn1o4EB-Ar8rAQwucaY6VkbAHgsuZ93LGyYCJ-N3RqlI7bvMU2PF4ivuVhyLb3qDEqhghZ7c0W7u492SEsr4YovQ6AM8/s320/offset_plane.png" width="304" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfq7F9IHNqx4DyadbKLNslpmnW-kiNMLv9gMElLyVuxHv3vXINWGGcGmsV7FaAUmJjkBX1ZEsVFgFoQzQ67R3hK1uHYnIu_KejKLC8z-FXdv3SiBcB0QJdi-NTzxvrjIk1xDi9gZj4bYw/s1600/offset_sphere.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfq7F9IHNqx4DyadbKLNslpmnW-kiNMLv9gMElLyVuxHv3vXINWGGcGmsV7FaAUmJjkBX1ZEsVFgFoQzQ67R3hK1uHYnIu_KejKLC8z-FXdv3SiBcB0QJdi-NTzxvrjIk1xDi9gZj4bYw/s320/offset_sphere.png" width="304" /></a></div>
<br />
<br />
<br />
The other example below demonstrates surface warping, where a planar face is twisted along one of its directions:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnYPTuT8NcspyIzQ3imSlim0qJ11BcWSND68GJRms7hrBfZWVU1JtPOD53OZIRimPCQ55ybv6Y01W5-_waQ729QW8d45xhl8c4KZg0q-J5la_MOwNbVoIbhEbBPjShjjxJC6z5WATjQ2o/s1600/warp_plane.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnYPTuT8NcspyIzQ3imSlim0qJ11BcWSND68GJRms7hrBfZWVU1JtPOD53OZIRimPCQ55ybv6Y01W5-_waQ729QW8d45xhl8c4KZg0q-J5la_MOwNbVoIbhEbBPjShjjxJC6z5WATjQ2o/s320/warp_plane.png" width="304" /></a></div>
<br />
Enjoy! :-)<br />
Roman
</div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-51695877251319333852015-08-30T17:13:00.000+03:002015-08-30T17:17:28.681+03:00Arbitrary law-based curve and surface modeling<div dir="ltr" style="text-align: left;" trbidi="on">
A recent case with enhancing <a href="http://www.cadexchanger.com/">CAD Exchanger</a> ACIS importer to broaden support of ACIS primitives inspired me to write this post. <br />
<br />
Like other modeling kernels, Open CASCADE comes with a finite set of supported types of curves and surfaces. For instance, for curves this includes lines and conic curves (circles, ellipses, parabolas and hyperbolas), B-Splines, Bezier curves, offset curves plus explicitly trimmed curves. OCC supports parametrics definition where 3D coordinate (x,y,z) is evaluated via parameter t, for surface it is calculated from a pair (u,v).<br />
<br />
For instance, for line it is:<br />
C(t) = O + Dt, where O is an origin and D is a unit vector.<br />
<br />
Thus, a line in OCC is parametrized by its length.<br />
<br />
ACIS and Parasolid additionally support so called procedural geometries (e.g. intersection curve or rolling ball surface) when there is no explicit formula but each point is still unambiguously calculated from parameters t or (u,v).<br />
<br />
However, a limited set of explicitly supported types does not allow to express other possible types of curves and surfaces which could be easily represented in parametric definition. For instance, there were a few questions on the OCC forum how to model a helix curve with the help of OCC.<br />
<br />
Let us consider how you could that indeed.<br />
<br />
Say, a helix is parametrized as follows:<br />
<br />
X(t) = O + Rcos(u) X<br />
Y(t) = O + Rsin(u) Y<br />
Z(t) = O + (pitch/(2*PI) * v Z<br />
<br />
Where {O ,X, Y, Z} is a local axis system (origin and 3 unit vectors). Pitch – is a helix step along the Z axis. See the below screenshot (taken from ACIS documentation):<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJbInVo0IYkU4qHB7oWBSWXpmy8bSQGK9vbpVZ1v6bHAy5arjukx9PMUAwL7QUlbiefYm2OR43Ju99NkbBsD5GnJ4zbW6DdyKDSM_m15xXqQTPwa5vV80Yet6l-fEJUl0CP1y6TybSH0Y/s1600/helix_acis.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJbInVo0IYkU4qHB7oWBSWXpmy8bSQGK9vbpVZ1v6bHAy5arjukx9PMUAwL7QUlbiefYm2OR43Ju99NkbBsD5GnJ4zbW6DdyKDSM_m15xXqQTPwa5vV80Yet6l-fEJUl0CP1y6TybSH0Y/s1600/helix_acis.jpg" /></a></div>
<br />
<br />
Now given this explicit definition (or law) how could you map this to Open CASCADE ?<br />
<br />
One option would be to subclass Geom_Curve and implement respective methods (D0(), D1(), ..., Continuity(), etc). That would be fine if you only need a limited time span of such an object and won’t feed it into various OCC modeling algorithms. The OCC classes ShapeExtend_ComplexCurve and _CompositeSurface follow this approach.<br />
<br />
However, although you could create an edge on such a curve, you would not be able to save it in a .brep file right away (you would need to enable a vehicle for saving/retrieving user-defined types). Some algorithms may throw an exception on an unrecognized type and so on. <br />
<br />
Another option is to do one time approximation with B-Spline and keep the B-Spline representation after that. You would certainly lose original definition and evaluation of a point and derivative would be times more expensive. However you could keep this representation in persistent representation and confidently use it anywhere. By the way ACIS does support helix type and combines both the original definition and its B-Spline approximation.<br />
<br />
To approximate with B-Spline, the GeomConvert_ApproxCurve class will do the job.<br />
<br />
GeomConvert_ApproxCurve accepts a curve adaptor which essentially implements an <a href="https://en.wikipedia.org/wiki/Adapter_pattern">Adaptor design pattern</a> and produces a B-Spline curve. You would essentially need to create a subclass of Adaptor3d_Curve and implement respective methods (D0(), D1(), Intervals(), etc). You could possibly mix this approach with the former and create GeomAdaptor_Curve which would accept your Geom_Curve subclass.<br />
<br />
Below is an excerpt from CAD Exchanger that approximates a helix with OCC B-Spline:<br />
<pre class="brush: cpp">
/*! Uses adaptor classes and invokes GeomConvert_ApproxCurve to approximate with a B-Spline.
Created B-Spline is polynomial and is of C2-continuity.
Returns true if the B-Spline has been successfully created and false otherwise.
*/
bool ACISAlgo_Helix::MakeHelix (const ACISGeom_HelixData& theSource,
Handle_Geom_BSplineCurve& theTarget)
{
Handle_ACISAlgo_HHelixCurveAdaptor anAdaptor = new ACISAlgo_HHelixCurveAdaptor (theSource);
Standard_Real aTol = Precision::Confusion();
GeomAbs_Shape aContinuity = GeomAbs_C2 /*highest supported continuity*/;
Standard_Integer aMaxSeg = 10000, /*max number of spans*/
aMaxDeg = 9; /*max degree, consistent with settings in Algo*/
GeomConvert_ApproxCurve anApprox (anAdaptor, aTol,
aContinuity,
aMaxSeg,
aMaxDeg);
if (anApprox.HasResult()) {
theTarget = anApprox.Curve();
Base_Debugger& aDebugger = Base_Debugger::GlobalInstance();
aDebugger.Save (theTarget, "curve");
}
return !theTarget.IsNull();
}
</pre>
<pre class="brush: cpp">
//! Defines data elements that can be reused for both common ACIS 'helix' and ASM 'helix_int_cur'.
struct ACISGeom_HelixData
{
ACISGeom_HelixData() :
myXRadius(0.),
myYRadius(0.),
myPitch(0.),
myTaper(0.),
myRangeMin (0.),
myRangeMax (2 * M_PI),
myScaleFactor (1.)
{}
__CADEX_DEFINE_PROPERTY(gp_Ax3,Position) //can be right- or left-handed
__CADEX_DEFINE_PRIMITIVE_PROPERTY(double,XRadius)
__CADEX_DEFINE_PRIMITIVE_PROPERTY(double,YRadius)
__CADEX_DEFINE_PRIMITIVE_PROPERTY(double,Pitch) //must be >= 0, if = 0 then is planar
__CADEX_DEFINE_PRIMITIVE_PROPERTY(double,Taper) //if > 0, helix widens along the Z-axis, if 0 - then lies on a cylinder
__CADEX_DEFINE_PRIMITIVE_PROPERTY(double,RangeMin)
__CADEX_DEFINE_PRIMITIVE_PROPERTY(double,RangeMax)
__CADEX_DEFINE_PRIMITIVE_PROPERTY(double,ScaleFactor)
};
</pre>
<pre class="brush: cpp">
/*! A few methods in OCC 6.9.0 have been made const.*/
#if OCC_VERSION_HEX < 0x060900
#define __CADEX_ADAPTOR3D_CURVE_CONST
#else
#define __CADEX_ADAPTOR3D_CURVE_CONST const
#endif
/* \class ACISAlgo_HelixCurveAdaptor
\brief Defines an adaptor to represent a helix curve.
Helix data is defined by ACISGeom_HelixData.
Evaluation is performed in the Evaluator subclass which can either represent a helix lying on a
cylinder (if taper is 0) or on a cone (if taper is not 0).
Helix can have distinct radii along X and Y axes, i.e. to have an elliptical section.
*/
class ACISAlgo_HelixCurveAdaptor : public Adaptor3d_Curve
{
public:
class Evaluator;
//! Constructor.
ACISAlgo_HelixCurveAdaptor (const ACISGeom_HelixData& theData);
//! Constructor
ACISAlgo_HelixCurveAdaptor (const std::shared_ptr<evaluator>& theEvaluator,
Standard_Real theMin,
Standard_Real theMax);
virtual Standard_Real FirstParameter()const __CADEX_OVERRIDE_ATTRIBUTE;
virtual Standard_Real LastParameter() const __CADEX_OVERRIDE_ATTRIBUTE;
virtual GeomAbs_Shape Continuity() const __CADEX_OVERRIDE_ATTRIBUTE;
virtual Standard_Integer NbIntervals (const GeomAbs_Shape S) __CADEX_ADAPTOR3D_CURVE_CONST
__CADEX_OVERRIDE_ATTRIBUTE;
virtual void Intervals (TColStd_Array1OfReal& T, const GeomAbs_Shape S) __CADEX_ADAPTOR3D_CURVE_CONST
__CADEX_OVERRIDE_ATTRIBUTE;
virtual Handle(Adaptor3d_HCurve) Trim (const Standard_Real First,
const Standard_Real Last,
const Standard_Real Tol) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual Standard_Boolean IsClosed() const __CADEX_OVERRIDE_ATTRIBUTE;
virtual Standard_Boolean IsPeriodic() const __CADEX_OVERRIDE_ATTRIBUTE;
virtual gp_Pnt Value (const Standard_Real U) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual void D0 (const Standard_Real U, gp_Pnt& P) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual void D1 (const Standard_Real U, gp_Pnt& P, gp_Vec& V) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual void D2 (const Standard_Real U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual void D3 (const Standard_Real U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2, gp_Vec& V3) const
__CADEX_OVERRIDE_ATTRIBUTE;
virtual gp_Vec DN (const Standard_Real U, const Standard_Integer N) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual Standard_Real Resolution (const Standard_Real R3d) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual GeomAbs_CurveType GetType() const __CADEX_OVERRIDE_ATTRIBUTE;
protected:
std::shared_ptr<evaluator> myEvaluator;
Standard_Real myMin;
Standard_Real myMax;
};
</pre>
<pre class="brush: cpp">
DEFINE_STANDARD_HANDLE(ACISAlgo_HHelixCurveAdaptor,Adaptor3d_HCurve)
class ACISAlgo_HHelixCurveAdaptor : public Adaptor3d_HCurve
{
public:
//! Constructor.
ACISAlgo_HHelixCurveAdaptor (const ACISGeom_HelixData& theData) : myAdaptor (theData) {}
//! Constructor.
ACISAlgo_HHelixCurveAdaptor (const std::shared_ptr<acisalgo_helixcurveadaptor::evaluator>& theEvaluator,
Standard_Real theMin,
Standard_Real theMax) : myAdaptor (theEvaluator, theMin, theMax) {}
//! Returns the adaptor as Adaptor3d_Curve.
/*! Return the internal ACISAlgo_HelixCurveAdaptor object.*/
virtual const Adaptor3d_Curve& Curve() const __CADEX_OVERRIDE_ATTRIBUTE { return myAdaptor; }
//! Returns the adaptor as Adaptor3d_Curve.
/*! Return the internal ACISAlgo_HelixCurveAdaptor object.*/
virtual Adaptor3d_Curve& GetCurve() __CADEX_OVERRIDE_ATTRIBUTE { return myAdaptor; }
public:
DEFINE_STANDARD_RTTI(ACISAlgo_HelixCurveAdaptor)
protected:
ACISAlgo_HelixCurveAdaptor myAdaptor;
};
__CADEX_IMPLEMENT_HANDLE(ACISAlgo_HHelixCurveAdaptor,Adaptor3d_HCurve)
</pre>
<pre class="brush: cpp">
/*********************************************************************************************/
/*! \class ACISAlgo_HelixCurveAdaptor::Evaluator
\brief Base abstract class to evaluate helix.
*/
class ACISAlgo_HelixCurveAdaptor::Evaluator
{
public:
__CADEX_DEFINE_MEMORY_MANAGEMENT
Evaluator (const ACISGeom_HelixData& theData) : myData (theData), myVCoef (1.) {}
virtual ~Evaluator() {}
const ACISGeom_HelixData& Data() const { return myData; }
double VParameter (Standard_Real U) const { return U * myVCoef; }
virtual void D0 (Standard_Real U, gp_Pnt& P) const = 0;
virtual void D1 (Standard_Real U, gp_Pnt& P, gp_Vec& V) const = 0;
virtual void D2 (Standard_Real U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2) const = 0;
virtual void D3 (Standard_Real U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2, gp_Vec& V3) const = 0;
virtual gp_Vec DN (Standard_Real U, Standard_Integer N) const = 0;
protected:
const gp_XYZ& Loc() const { return myData.Position().Location().XYZ(); }
const gp_XYZ& XDir() const { return myData.Position().XDirection().XYZ(); }
const gp_XYZ& YDir() const { return myData.Position().YDirection().XYZ(); }
const gp_XYZ& ZDir() const { return myData.Position().Direction().XYZ(); }
ACISGeom_HelixData myData;
double myVCoef; //coefficient to multiply U to get a V parameter on a respective surface
};
</pre>
<pre class="brush: cpp">
/*! \class ACISAlgo_HelixCurveAdaptor_CylinderEvaluator
\brief Evaluates a helix lying on a cylinder.
*/
class ACISAlgo_HelixCurveAdaptor_CylinderEvaluator : public ACISAlgo_HelixCurveAdaptor::Evaluator
{
public:
ACISAlgo_HelixCurveAdaptor_CylinderEvaluator (const ACISGeom_HelixData& theData);
virtual void D0 (Standard_Real U, gp_Pnt& P) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual void D1 (Standard_Real U, gp_Pnt& P, gp_Vec& V) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual void D2 (Standard_Real U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual void D3 (Standard_Real U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2, gp_Vec& V3) const __CADEX_OVERRIDE_ATTRIBUTE;
virtual gp_Vec DN (Standard_Real U, Standard_Integer N) const __CADEX_OVERRIDE_ATTRIBUTE;
};
ACISAlgo_HelixCurveAdaptor_CylinderEvaluator::ACISAlgo_HelixCurveAdaptor_CylinderEvaluator (
const ACISGeom_HelixData& theData) :
ACISAlgo_HelixCurveAdaptor::Evaluator (theData)
{
myVCoef = theData.Pitch() * theData.ScaleFactor() / (2 * M_PI);
}
void ACISAlgo_HelixCurveAdaptor_CylinderEvaluator::D0 (Standard_Real U,
gp_Pnt& P) const
{
Standard_Real v = VParameter (U);
Standard_Real Rx = myData.XRadius();
Standard_Real Ry = myData.YRadius();
Standard_Real sinU = sin (U);
Standard_Real cosU = cos (U);
P = Rx * cosU * XDir() + Ry * sinU * YDir() + v * ZDir() + Loc();
}
void ACISAlgo_HelixCurveAdaptor_CylinderEvaluator::D1 (
Standard_Real U,
gp_Pnt& P,
gp_Vec& V) const
{
Standard_Real v = VParameter (U);
Standard_Real Rx = myData.XRadius();
Standard_Real Ry = myData.YRadius();
Standard_Real sinU = sin (U);
Standard_Real cosU = cos (U);
P = Rx * cosU * XDir() + Ry * sinU * YDir() + v * ZDir() + Loc();
Standard_Real k = myVCoef;
V = -Rx * sinU * XDir() + Ry * cosU * YDir() + k * ZDir();
}
void ACISAlgo_HelixCurveAdaptor_CylinderEvaluator::D2 (
Standard_Real U,
gp_Pnt& P,
gp_Vec& V1,
gp_Vec& V2) const
{
Standard_Real v = VParameter (U);
Standard_Real Rx = myData.XRadius();
Standard_Real Ry = myData.YRadius();
Standard_Real sinU = sin (U);
Standard_Real cosU = cos (U);
P = Rx * cosU * XDir() + Ry * sinU * YDir() + v * ZDir() + Loc();
Standard_Real k = myVCoef;
V1 = -Rx * sinU * XDir() + Ry * cosU * YDir() + k * ZDir();
V2 = -Rx * cosU * XDir() - Ry * sinU * YDir();
}
</pre>
<pre class="brush: cpp">
/*********************************************************************************************/
ACISAlgo_HelixCurveAdaptor::ACISAlgo_HelixCurveAdaptor (const ACISGeom_HelixData& theData) :
myMin (theData.RangeMin()),
myMax (theData.RangeMax())
{
if (std::fabs (theData.Taper()) < Precision::Confusion()) { //cylinder
myEvaluator.reset (new ACISAlgo_HelixCurveAdaptor_CylinderEvaluator (theData));
} else { //cone
myEvaluator.reset (new ACISAlgo_HelixCurveAdaptor_ConeEvaluator (theData));
}
}
/*! Used when trimming*/
ACISAlgo_HelixCurveAdaptor::ACISAlgo_HelixCurveAdaptor (const std::shared_ptr<evaluator>& theEvaluator,
Standard_Real theMin,
Standard_Real theMax) :
myEvaluator (theEvaluator),
myMin (theMin),
myMax (theMax)
{
__CADEX_ASSERT_INVALID_VALUE(myEvaluator->Data().RangeMin() <= theMin);
__CADEX_ASSERT_INVALID_VALUE(myEvaluator->Data().RangeMax() >= theMax);
}
Standard_Real ACISAlgo_HelixCurveAdaptor::FirstParameter() const
{
return myMin;
}
Standard_Real ACISAlgo_HelixCurveAdaptor::LastParameter() const
{
return myMax;
}
GeomAbs_Shape ACISAlgo_HelixCurveAdaptor::Continuity() const
{
return GeomAbs_CN;
}
Standard_Integer ACISAlgo_HelixCurveAdaptor::NbIntervals (const GeomAbs_Shape /*S*/) __CADEX_ADAPTOR3D_CURVE_CONST
{
return 1;
}
void ACISAlgo_HelixCurveAdaptor::Intervals (TColStd_Array1OfReal& T, const GeomAbs_Shape /*S*/) __CADEX_ADAPTOR3D_CURVE_CONST
{
T (T.Lower()) = FirstParameter();
T (T.Upper()) = LastParameter();
}
Handle(Adaptor3d_HCurve) ACISAlgo_HelixCurveAdaptor::Trim (const Standard_Real First,
const Standard_Real Last,
const Standard_Real /*Tol*/) const
{
return new ACISAlgo_HHelixCurveAdaptor (myEvaluator, First, Last);
}
Standard_Boolean ACISAlgo_HelixCurveAdaptor::IsClosed() const
{
return Standard_False;
}
Standard_Boolean ACISAlgo_HelixCurveAdaptor::IsPeriodic() const
{
return Standard_False;
}
gp_Pnt ACISAlgo_HelixCurveAdaptor::Value (const Standard_Real U) const
{
gp_Pnt aP;
D0 (U, aP);
return aP;
}
void ACISAlgo_HelixCurveAdaptor::D0 (const Standard_Real U, gp_Pnt& P) const
{
myEvaluator->D0 (U, P);
}
void ACISAlgo_HelixCurveAdaptor::D1 (const Standard_Real U, gp_Pnt& P, gp_Vec& V) const
{
myEvaluator->D1 (U, P, V);
}
void ACISAlgo_HelixCurveAdaptor::D2 (const Standard_Real U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2) const
{
myEvaluator->D2 (U, P, V1, V2);
}
Standard_Real ACISAlgo_HelixCurveAdaptor::Resolution (const Standard_Real R3d) const
{
//see GeomAdaptor_Curve::Resolution()
const auto& aData = myEvaluator->Data();
Standard_Real R = std::max (aData.XRadius(), aData.YRadius());
if (R3d < 2 * R)
return 2 * ASin (R3d / (2 * R));
else
return 2 * M_PI;
}
GeomAbs_CurveType ACISAlgo_HelixCurveAdaptor::GetType() const
{
return GeomAbs_OtherCurve;
}
</pre>
<br />
<br />
Below are two screenshot of approximated helices in DRAW – the first one has different Rx and Ry radii and is lying on a cylindrical surface, the second is of equal Rx and Ry radii lying on a conical surface.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsKqGqxcgzwXrPoKX8UVqpjqooKcTL4LZ_aQh1-vmUBoUJox0x0KbcCGUFXJl2KlPSQ4-Waftob3mCmF3IcXHNjdbIQqiFuDiuBSaC3DjpBdPOCMieqcd62PoPe0sBwLploDdV5ucfMLI/s1600/helix_cyl.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsKqGqxcgzwXrPoKX8UVqpjqooKcTL4LZ_aQh1-vmUBoUJox0x0KbcCGUFXJl2KlPSQ4-Waftob3mCmF3IcXHNjdbIQqiFuDiuBSaC3DjpBdPOCMieqcd62PoPe0sBwLploDdV5ucfMLI/s320/helix_cyl.png" width="303" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrVUQdR2e7YvVflJvgoPk31vSGO9HsVUNP-ijduljhgiCknLkeY-7kFHW1H5sQMgBAeR7N_xZeB24TYEbR5rgA8NNw3LuA-oUnQI0D8xwJi0zvbS5_hoz7kQxlgJUDR4lk1iVnur5uCis/s1600/helix_cone.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrVUQdR2e7YvVflJvgoPk31vSGO9HsVUNP-ijduljhgiCknLkeY-7kFHW1H5sQMgBAeR7N_xZeB24TYEbR5rgA8NNw3LuA-oUnQI0D8xwJi0zvbS5_hoz7kQxlgJUDR4lk1iVnur5uCis/s320/helix_cone.png" width="303" /></a></div>
<br /><br />Hope this post will give you some hints on how to approximate arbitrary curves and surfaces using B-Spline approximation techniques.<br /><br />If you have any interesting examples to share that would certainly be good to know.<br /><br />Thanks,<br />Roman<br /><br /> </div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-56118200574558382162014-05-31T14:35:00.000+04:002014-05-31T14:35:02.431+04:00Applying Vectorization Techniques for B-Spline Surface Evaluation<div dir="ltr" style="text-align: left;" trbidi="on">
Last year I mentored an Intel Summer school project which was dedicated to demonstrating vectorization parallelism with the help of Intel C/C++ compiler. We chose Open CASCADE and its NURBS surface evaluation algorithms as a target.<br />
<br />
The outcome was quite nice - up to 16x speedup of the kernel computational functions. If you are interested, feel free to have a look at the paper published at Intel Developer Zone here - <a href="https://software.intel.com/en-us/articles/applying-vectorization-techniques-for-b-spline-surface-evaluation">https://software.intel.com/en-us/articles/applying-vectorization-techniques-for-b-spline-surface-evaluation</a>.<br />
<br /></div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com1tag:blogger.com,1999:blog-3285677929777490656.post-1095563298424779462013-11-08T02:06:00.000+04:002013-11-08T02:06:39.546+04:00AIS. Connecting objects.<div dir="ltr" style="text-align: left;" trbidi="on">
<i>(This post was written 2+ months ago but got stuck in my drafts folder. Sorry for that)</i> <br />
<br />
The Nokia's once famous motto 'Connecting people' has probably gone forever (especially given recent acquisition by Microsoft), but let us grab its idea. This post will be about a nice 'connecting' concept offered by the AIS (Application Interactive Services) package, part of the visualization mechanism of Open CASCADE. Unfortunately, like many other gems scattered across the product, this one sits virtually undocumented and therefore its powerful features are significantly underutilized in OCC-based apps.<br />
<br />
This is about AIS_ConnectedInteractive and AIS_MultipleConnected, which allows you to construct derived visual representations from already computed (or to be computed) ones.
Imagine an assembly consisting of various sub-assemblies, which in their turn may consist of further sub-assemblies, and so on. Eventually each sub-assembly is broken down into part(s). Each part or sub-assembly are 'instantiated' in a parent assembly, where an instance is a reference to a referred part or assembly plus an attached transformation. Here is an example of an assembly (from STEP test suite as1*.stp):
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAePBgWUn17zT6mnQ7pBcoIQHXU8UEEP2DM_vaDypbM-evVc54ZrZoI4M6d47hISeqcDKxcj_UChvE-mYEFGJGfdKexF4bSJt9zo3GfJWvMteDm5JvbTOOY6r0VOQ2Anof5VQC1uK-ZSY/s1600/as1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAePBgWUn17zT6mnQ7pBcoIQHXU8UEEP2DM_vaDypbM-evVc54ZrZoI4M6d47hISeqcDKxcj_UChvE-mYEFGJGfdKexF4bSJt9zo3GfJWvMteDm5JvbTOOY6r0VOQ2Anof5VQC1uK-ZSY/s1600/as1.png" /></a></div>
The next image is an assembly tree where you can see instances of various sub-assemblies.
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOZoxFGNzV7TVrcMmJlta99E8KDRHONps6rhAoaTB0_flkmcJhMDwvWgvfl1nwo18_BwBt_rlu6rHeaml7gSlFpLY8KlDWPBB4WKoAJMKKvpCzyWdEFk7YnzZ3ex6r-A25Jf5utc_SQy8/s1600/modbr-treeview.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOZoxFGNzV7TVrcMmJlta99E8KDRHONps6rhAoaTB0_flkmcJhMDwvWgvfl1nwo18_BwBt_rlu6rHeaml7gSlFpLY8KlDWPBB4WKoAJMKKvpCzyWdEFk7YnzZ3ex6r-A25Jf5utc_SQy8/s1600/modbr-treeview.png" /></a></div>
Then also goes a sub-assembly l-bracket-assembly consisting of instance of a part named l_bracket and 3 instances of nut-bolt-assembly (each with different transformation), where each is a combination of two instantiated parts (bolt and nut).
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4FJDBwbAbo2ilmYZYR9vhEgGKbJ2a5hNQICLAvm3ucpJSar41H9EtZXRblDZ4jHMBRxn0OqPaWVvc0wLJCrTXE6F8eqE2nZmi9K86sI8zKc35FFbBT-gRdkcpg0h6SNlx1Ch0XTNw0kE/s1600/modbr-subassembly.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4FJDBwbAbo2ilmYZYR9vhEgGKbJ2a5hNQICLAvm3ucpJSar41H9EtZXRblDZ4jHMBRxn0OqPaWVvc0wLJCrTXE6F8eqE2nZmi9K86sI8zKc35FFbBT-gRdkcpg0h6SNlx1Ch0XTNw0kE/s320/modbr-subassembly.png" /></a></div>
AIS_ConnectedInteractive and AIS_MultipleConnected offer efficient way to compute visual representations of such complex assemblies.<br />
<br />
<b>AIS_ConnectedInteractive</b><br />
AIS_ConnectedInteractive essentially implements the <a href="http://en.wikipedia.org/wiki/Proxy_pattern">proxy pattern</a> (or alike) and contains a reference to another AIS_InteractiveObject and transformation matrix.<br />
<br />
<pre class="prettyprint">Handle_AIS_InteractiveObject aRefObject =<br/> CreateRepresentation (...);
TopLoc_Location aLoc = ...;
Handle_AIS_ConnectedInteractive anInstance =
new AIS_ConnectedInteractive;
anInstance->Connect (theObject, aLoc);
</pre>
<br />
Note that you don't need to know details of aRefObject representation, you just attach it to anInstance.<br />
Thus, AIS_ConnectedInteractive is well suited to create visual representation of an instance in the assembly hierarchy.<br />
<br />
<b>AIS_MultipleConnected</b><br />
AIS_MultipleConnected follows the <a href="http://en.wikipedia.org/wiki/Composite_pattern">composite pattern</a> and allows to combine multiple representations into one. Therefore it is an efficient way to construct visual representation of the (sub-)assembly.<br />
<br />
<pre class="prettyprint">Handle_AIS_MultipleConnectedInteractive anAssebly =<br/>
new AIS_MultipleConnectedInteractive;
anAssebly->Connect (aChild1);
anAssebly->Connect (aChild2);
</pre>
...<br />
<br />
Again, this allows to abstract from internal details of each child object.<br />
<br />
I use the above approach to construct visual representations of objects in a new data model designed in CAD Exchanger (currently planned to be made part of public API in version 3.0), and so far it works great.<br />
<br />
To further abstract the creation of a final representation of the root object (or any interim one selected in a tree browser), there is a <a href="http://en.wikipedia.org/wiki/Factory_method_pattern">factory method</a> that returns a handle to an object of the base type AIS_InteractiveObject.<br />
<br />
<pre class="prettyprint">Handle_AIS_InteractiveObject anObject =<br/>
ModelPrs_InteractiveObjectFactory::Create (...);
</pre>
<br />
Thus, the callers do not know the real type of the object that will be created – it can be either AIS_MultipleConnected, or AIS_ ConnectedInteractive, or AIS_Shape, or any other subclass of AIS_InteractiveObject.<br />
<br />
The key benefits of *Connected* are in reusing representations of referred objects, instead of recomputing them. This allows to:<br />
<ul style="text-align: left;">
<li>Reduce computation time (as creation of part representations takes the greatest time)</li>
<li>Reduce memory footprint (you do not have to create extra objects supporting the representations)</li>
<li>Reduce code size (you do not have to design extra classes for composite objects, only parts representations are really necessary)</li>
<li>Achieve greater flexibility and abstract approach (internal implementation details of referred objects are hidden and can change independently from instances and assemblies) </li>
</ul>
<br />
To have a quick test, you might want to try DRAWEXE:<br />
pload ALL<br />
wedge w 1 2 3 0.5<br />
vinit<br />
vdisplay w<br />
vconnect iw -5 0 0 1 0 0 0 0 1 w #creates AIS_ConnectedInteractive of AIS_Shape<br />
box b 2 0 0 3 1 2<br />
vconnect a -5 0 0 1 0 0 0 0 1 w b #creates AIS_ConnectedInteractive of AIS_MultipleConnected<br />
<br />
At last, some limitations to be aware of:<br />
<ul style="text-align: left;">
<li>The referred objects must belong to the same AIS_InteractiveContext. This is unfortunate but is not specific to *Connected*.</li>
<li>Setting attributes to a referred object (e.g. part) affects the referring representation (which is sort of expected). Setting an attribute to a referring object seems to have no effect.</li>
<li>Some glitches with selection (as reproduced in DRAW). </li>
</ul>
<br />
Currently these are not affecting CAD Exchanger development, so I did not investigate these in greater details.<br />
<br />
Anyway, hopefully these hints will be helpful for those dealing with complex data structures and GUI. If you already worked with the *Connected* and have some experience to share, it would be great to hear your comments.<br />
<br />
Thanks!</div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com10tag:blogger.com,1999:blog-3285677929777490656.post-22523076901577974572013-06-02T12:04:00.000+04:002013-06-02T12:04:13.548+04:00Significant side effects in OCAF/XDE 6.6.0<div dir="ltr" style="text-align: left;" trbidi="on">
This is a heads-up for those who deal with OCAF documents and migrating to 6.6.0.<br />
<br /><u><b>Issue:</b></u> You may observe sporadic crashes especially when dealing with AIS_InteractiveContext and/or XDE documents.<br /><br /><u><b>Root-cause:</b></u> The
root-cause of the issues is side effects of the fix <a href="http://tracker.dev.opencascade.org/view.php?id=23523">#23523</a>. It has
changed the order of destruction of the document contents.<br />In 6.5.4 and earlier, the destructor of TDocStd_Document let the TDF_Data destructor be called what caused the following order:<br />-
the attributes got destructed in the reversed order in the tree (i.e.
first destructed were the attributes at the deepest label 0:x1:x2:...:xn, last - at
root label 0:)<br />- the attributes got destructed while still attached to their labels (i.e. Label() in destructor still returned a valid label)<br /><br />With 6.6.0 the TDocStd_Document destructor explicitly calls TDocStd_Document::Destroy() which does the opposite:<br />- it calls TDF_Label::ForgetAllAttributes() which removes the attributes in the direct order, from the root to the deepest label<br />- it first detaches the attributes' label prior to nullifying the attribute, so Label() in destructor will now always return 0.<br /><br />This new behavior has at least two side effects that affected my case:<br />1.
If the OCAF document has an attached AIS_InteractiveContext, then a
crash happens upon document destruction. The root-cause is that TPrsStd_AISViewer
attached to a root label contains a handle of AIS_InteractiveContext and
TPrsStd_AISPresentations (at sub-labels) contain AIS_InteractiveObject's. Each of the
latter has a *pointer* (not a handle, to avoid circular dependencies) to
that AIS_IC context. Now when the document is destroyed in a direct order, then
AIS_IC gets destroyed first (as part of TPrsStd_AISViewer destruction). So all
pointers in AIS_IO's become dangling. Then during destruction of
TPrsStd_AISPresentations, the method AISErase() access those dangling
pointers, leading to a crash.<br /><br />The same will happen in similar cases, when you have a resource on "top" labels pointed to from the "deeper" labels. <br />The
work-around for the AISViewer was to ensure another handle to AIS_IC
outside of the document which would keep the object (i.e. refcount >
0) while the document gets destroyed.<br /><br />2. In XDE,
XCAFDoc_DocumentTool maintains a global map storing labels containing
this attribute. This design already had a flaw and at some point I had
addressed it with fix <a href="http://tracker.dev.opencascade.org/view.php?id=23593">23593</a>. That time I made a note that a better fix
would get rid of a global map.<br />With 6.6.0 behavior
XCAFDoc_DocumentTool::Destroy() is no longer effective - as explained
above, upon destruction the label is already null, so the global map
keeps on containing orphan labels. Depending on memory allocation, a
similar label node address can be created and an access to a map would
incorrectly retrieve a mapped value for that label, leading to a crash.<br />The
new fix (<a href="http://tracker.dev.opencascade.org/view.php?id=24007">24007</a>) resolves this by getting rid of global map and keeping
self-contained XDE document. It now just stores a reference (tree-node
attribute) to uniquely find a document tool label inside the document.
The fix works fine for the documents stored with older versions of XDE.<br /><br /><u><b>Take-aways:</b></u><br />1. You currently cannot rely on the order in which the document gets destroyed.<br />2.
You should avoid accessing the resources stored at other labels during
attributes destruction. Perhaps for some cases, this would require
tweaks outside the OCAF doc (like in the case of AIS_IO described
above).<br />3. Deallocation of resources by attributes should not happen
in their destructors. Instead this should be done in a redefined method
TDF_Label::BeforeRemoval(). So, the first implementation of 23953 was suboptimial anyway.<br />
<br />
<u><b>Recommendations/requests to OCC:</b></u><br />
1. My personal point is that previous
"bottom-up" destruction order is more practical than current "top-down". Like in
C++, destruction order should be opposite to creation one, and in most
applications you populate the document from top to bottom.<br />2. I believe
OCAF must document and maintain a guarantee of the destruction policy,
so that the user's applications can rely on this guarantee. Is this
something OCC team can plan to address ?<br /><br />Thank you in advance,<br />Roman</div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com5tag:blogger.com,1999:blog-3285677929777490656.post-39088974049144561302013-05-10T17:20:00.000+04:002013-05-10T17:20:35.513+04:00Simple memory allocations<div dir="ltr" style="text-align: left;" trbidi="on">
The great convenience of standard collections for a developer is that they free him/her from extra efforts on managing underlying memory allocations.<br />
It’s so much convenient to just write<br />
<pre class="prettyprint">{
std::vector v(n);
for (int i = 0; i < n; ++i)
v[i] = i;
}</pre>
<br />
or likewise for OCC fixed size array:<br />
<pre class="prettyprint">{
TColStd_Array1OfReal a (0, n-1);
for (int i = 0; i < n; ++i)
v(i) = sqrt (i);
}</pre>
<br />
and be done. Who cares where the memory gets allocated for underlying elements ? Not a big deal, right ?<br />
<br />
Well, it can become a big deal if you have to work with multiple allocations/deallocations which become the hotspots.<br />
<br />
I was recently improving thread-safety of a few core Open CASCADE algorithms: approximation, intersection, etc (see #23952) and observed an interesting pattern that motivated posting this blog.<br />
<br />
Some OCC developer(s) realized in the past that memory allocations can be expensive in hot cycles and worked around this by using allocation in static memory. Excerpt from ApproxInt_ImpPrmSvSurfaces::Compute() (ApproxInt_ImpPrmSvSurfaces.gxx):<br />
<br />
<pre class="prettyprint">static math_Vector BornInf(1,2),BornSup(1,2),F(1,1),
X(1,2), Tolerance(1,2);
static math_Matrix D(1, 1, 1, 2);</pre>
<br />
<br />
This would result in a single allocation (upon first entry into this function) which would stay until the program termination.<br />
<br />
Obviously that does not work in multi-threaded environment. This would create data races – as two or more threads concurrently calling the same method – and results would be unpredictable (wrong and/or unstable results, sporadic crashes, etc).<br />
<br />
Substituting with local variables <br />
<pre class="prettyprint">math_Vector BornInf(1,2),BornSup(1,2),F(1,1),
X(1,2),Tolerance(1,2);
math_Matrix D(1, 1, 1, 2);</pre>
<br />
<br />
would resolve the reentrancy problem but would create another one – continuous memory allocation/deallocation would undermine performance as each constructor and destructor would call new[] and delete[] operators.<br />
<br />
The good news is that Open CASCADE provides constructors for math_* and TCollection_Array* classes that accept an extra parameter – an address of allocated memory. In this case they simply reuse that memory and only provide access to it. Neither do they attempt to free it upon own destruction. This provides a possibility to allocate data on stack (which has essentially zero performance cost) and make respective objects use it, preserving source compatibility with the rest of the code.<br />
<br />
Here is a fix that uses that:<br />
<pre class="prettyprint"> Standard_Real aBornInf[2],aBornSup[2],aF[1],aX[2],
aTolerance[2];
math_Vector BornInf(aBornInf,1,2),BornSup(aBornSup,1,2),
F(aF,1,1), X(aX,1,2),Tolerance(aTolerance,1,2);
Standard_Real aD[1][2];
math_Matrix D(aD,1, 1, 1, 2); </pre>
<br />
<br />
You might want to take advantage of this simple technique whenever dealing with small objects and knowing the required sizes in advance.</div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com1tag:blogger.com,1999:blog-3285677929777490656.post-10387886370950334252012-11-22T11:17:00.001+04:002012-11-22T11:17:13.138+04:00Standard allocator. Part 2<div dir="ltr" style="text-align: left;" trbidi="on">
<i>(continued)</i><br />
<br />
Having reviewed available <a href="http://opencascade.blogspot.ru/2012/11/ncollection-allocators.html">NCollection allocators</a>, we can create a C++ standard compliant allocator that would wrap them.<br /><br />Please have a look at the NCollection_StdAllocator.hxx which is available in the git repository, branch <a href="http://git.dev.opencascade.org/gitweb/?p=occt.git;a=shortlog;h=refs/heads/CR23569">CR23569</a>. Here is a corresponding <a href="http://tracker.dev.opencascade.org/view.php?id=23569">tracker report</a>.<br /><br />You can feed it with any instance of NCollection_BaseAllocator and then provide it to any STL container, for instance:<br /><br /><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> Handle(NCollection_IncAllocator) anIncAlloc = new NCollection_IncAllocator;<br /> NCollection_StdAllocator<topods_shape> aSAlloc (anIncAlloc);<br /> std::list<topods_shape ncollection_stdallocator="ncollection_stdallocator" opods_shape="opods_shape"> > aL (aSAlloc);<br /> TopoDS_Solid aSolid = BRepPrimAPI_MakeBox (10., 20., 30.);<br /> aL.push_back (aSolid);</topods_shape></topods_shape></span></span><br />Default constructor uses NCollection_BaseAllocator::CommonBaseAllocator() and thus redirects all allocation calls to Open CASCADE central allocator interface. The following will do exactly this:<br /><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> std::list<topods_shape ncollection_stdallocator="ncollection_stdallocator" opods_shape="opods_shape"> > aL2;</topods_shape></span></span><br /><br />This makes NCollection_StdAllocator a superset of Standard_StdAllocator suggested in <a href="http://opencascade.blogspot.ru/2011/11/standard-allocator-interface.html">Part1</a> and thus self-sufficient.<br /><br />Moreover, as allocators for one object type can be casted to another type (part of the C++ standard requirement), you might end up using a single type:<br /><br /><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">typedef NCollection_StdAllocator<char> AllocatorType;<br /><br />AllocatorType anAlloc (new NCollection_IncAllocator);<br />std::list<int> aL3 (anAlloc);<br />std::vector<topods_shape> aV (anAlloc);</topods_shape></int></char></span></span><br />and also:<br /><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">typedef std::basic_string<char char="char" std::char_traits="std::char_traits">, AllocatorType> StringType;<br />StringType aS1; //defaults to central allocator interface<br />StringType aS2 (anAlloc); //uses pool allocator<br />StringType aS3 (AllocatorType (NCollection_HeapAllocator::GlobalHeapAllocator())); //directly uses OS allocator and is effectively equivalent to std::string</char></span></span><br /><br />Now, with NCollection_StdAllocator you can take advantage of available Open CASCADE allocators and use STL, Boost or any other containers or template classes that accept standard allocator. By the way, this flexibility enabled me to start gradually moving from NCollection to Boost, but that's a subject for a separate post ;-)...<br /></div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com1tag:blogger.com,1999:blog-3285677929777490656.post-86343050957297642542012-11-22T11:11:00.000+04:002012-11-22T11:11:43.725+04:00NCollection allocators<div dir="ltr" style="text-align: left;" trbidi="on">
The <a href="http://opencascade.blogspot.ru/2011/11/standard-allocator-interface.html">previous post</a> has introduced a C++ standard compliant allocator that wraps Open CASCADE allocator interface (Standard::Allocate(), ::Free(), ::Reallocate()) and thus can be used in any containers or other template classes that accept such allocator.<br /><br />Before we move forward to consider another use case of standard allocator, let's make a step aside and review another allocator flavors offered by Open CASCADE. <br /><br />Those of you who happened to use templates from NCollection or just attentively read the source code employing it might notice NCollection_BaseAllocator base class. NCollection containers sort of tried to mimic interfaces of STL containers and as such introduced allocator in its inception in early 2000es. However, unlike STL where allocator is part of the type (e.g. std::list<int allocator1="allocator1"> is a different type than std::list<int allocator2="allocator2">) NCollection allows to dynamically supply an allocator, for instance:<br /><br /><span style="font-family: "Courier New",Courier,monospace;">NCollection_List<int> aVec1, aVec2 (new NCollection_IncAllocator);</int></span><br /><br />There are three types of allocator in NCollection:<br />- NCollection_BaseAllocator, which is a base class that also implements a wrapper over Standard::Allocate() and ::Free(). There is a singletone returned by NCollection_BaseAllocator::CommonBaseAllocator().<br />- NCollection_HeapAllocator, which wraps standard OS allocator (malloc/free). Similar to a base class there is a singletone returned by NCollection_HeapAllocator::GlobalHeapAllocator().<br />- NCollection_IncAllocator, which implements a pool allocator.<br /><br />The former two are straightforward and probably only NCollection_IncAllocator deserves some details.<br /><br />Pool allocators (see for instance <a href="http://en.wikipedia.org/wiki/Region-based_memory_management">Wikipedia</a>) are most helpful to manage temporary objects, especially when their destruction can be deferred to some common point in time, e.g. completion of an algorithm that created them. Implementation of a pool allocator is very straightforward – it preallocates a large chunk of memory (e.g. from a system heap or another allocator), and whenever there is a new temporary object to be allocated, just takes a required size from that large chunk's head pointer and shifts that pointer. Deallocation is void, i.e. no memory gets really freed after the object destructor has been called. Real deallocation of large chunk(s) happens upon own pool allocator destruction.<br /><br />This simple implementation allows to greatly improve performance when dealing with numerous temporary objects due to avoiding fragmentation and/or complicated techniques to manage individual smaller memory chunks.<br /><br />Here is a usage example which creates a temporary hash table:<br /><span style="font-family: "Courier New",Courier,monospace;">class PartnerShapeHasher<br />{<br />public:<br /> static Standard_Integer HashCode (const TopoDS_Shape& theKey, const Standard_Integer theUpper)<br /> {<br /> return ::HashCode (theKey.TShape(), theUpper);<br /> }<br /> static Standard_Boolean IsEqual(const TopoDS_Shape& theKey1, const TopoDS_Shape& theKey2)<br /> {<br /> return theKey1.IsPartner (theKey2);<br /> }<br />};<br /><br />//! Returns a number of partner subshapes of a given type<br />/*! A partner shape is a shared instance regardless of a location (transformation matrix).*/<br />static size_t NumberOfPartnerSubshapes (const TopoDS_Shape& theShape, const TopAbs_ShapeEnum theType)<br />{<br /> NCollection_Map<topods_edge partnershapehasher="partnershapehasher"> aMap (1 /*default number of buckets*/, new NCollection_IncAllocator);<br /> for (TopExp_Explorer anExp (theShape, theType); anExp.More(); anExp.Next()) {<br /> const TopoDS_Shape& aSubshape = anExp.Current();<br /> aMap.Add (aSubshape);<br /> }<br /> return aMap.Size();<br />}</topods_edge></span><br />If your temporary objects' life span is not necessarily aligned with the end of the algorithm scope (e.g. some objects are destroyed earlier) then you could either have a separate pool allocator for such objects or just accept a trade-off of temporary larger peak memory footprint.<br /><br />NCollection_IncAllocator's constructor accepts a parameter theBlockSize which specifies a size of large memory chunk(s) preallocated from the heap. I ended up with having a few predefined sizes via the following enumeration:<br /><br /><span style="font-family: "Courier New",Courier,monospace;">enum NCollection_IncAllocator_Size {<br /> NCollection_IncAllocator_Tiny = 1000,<br /> NCollection_IncAllocator_Small = 4000,<br /> NCollection_IncAllocator_Medium = 8000,<br /> NCollection_IncAllocator_Large = 16000,<br /> NCollection_IncAllocator_Huge = 32000<br />};</span><br />and use respective value depending on the most probable case, e.g.:<br />Handle(NCollection_BaseAllocator) anAlloc = new NCollection_IncAllocator (NCollection_IncAllocator_Tiny);<br />NCollection_List<topods_shape> aList (anAlloc);<br /><br />This should help avoid extra fragmentatoin in the OS allocator due to reuse of the same size blocks over and over again, yet providing a hint instead of default constructor parameter (around 26K).<br /><br />If the allocator is requested to allocate an object of a larger size than the constructor's theBlockSize parameter then a new larger chunk is allocated. Whenever a new large chunk is allocated then the remaining unused free bytes in a previous chunk are wasted. So some attention should be made to minimize unnecessary waste, if it can be substantial.<br /><br />Another point to remember is that NCollection_IncAllocator is not thread-safe (though reentrant), so access to the same instance should be synchronized outside, if it is used from multiple threads. For thread-safe scalable implementations you might want to check <a href="http://software.intel.com/en-us/blogs/2011/12/19/scalable-memory-pools-community-preview-feature">Intel Threading Building Blocks memory pools</a>.<br /><br />Now with the review of NCollection allocator completed, let's get back to the standard interface.<br /></topods_shape></int></int></div>
Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-51498752767064328762011-11-24T20:12:00.002+04:002011-11-24T20:32:13.803+04:00Standard allocator interface.As you know, Open CASCADE has its own memory allocation mechanism, which entry points are Standard::Allocate() and Standard::Free(). They forward (de-)allocation requests to a current memory manager, which can either be a). default system allocator (if environment variable MMGT_OPT=0), b). own Open CASCADE's (MMGT_OPT=1), or c). Intel TBB (MMGT_OPT=2).<br /><br />Most Open CASCADE classes (e.g. all which are defined in .cdl files) redefine operators new and delete to use Standard::Allocate() and Standard::Free() respectively – look at any .hxx file.<br /><br />Using common memory allocation mechanism allows to decrease memory footprint and/or increase performance, especially when using TBB allocator (in multi-threaded apps). However, you may easily miss this advantage in your application in the following typical cases:<br />1. You dynamically allocate objects of your classes and their new/delete operators do not use Standard::Allocate()/::Free().<br />2. You use standard containers (std::vector, std::map, std::list,...).<br />#1 is easily addressed by redefining new/delete operators in your base class(es) in a way similar to OCC.<br />#2 is more tough, as standard collections by default use standard allocator (std::allocator<T>). So all auxiliary elements (e.g. list nodes) are allocated outside of OCC-governed memory allocator.<br /><br />This was until now. Hereby I would like to share a code that implements the standard allocator interface as defined by the ISO C++ Standard 2003 (and also conforming to a recently published C++11). Download <a href="http://www.woofiles.com/dl-273606-xPSjnNTx-StandardStdAllocator.hxx">Standard_StdAllocator.hxx</a>.<br /><br />This is a pure header implementation, so no .cxx files and linking with libraries is needed; just copy to your project and start using. Implementation is derived from Intel tbb::tbb_allocator, which itself just follows the C++ standard requirements.<br /><br />The file is intentionally made copyright-free and put into a <a href="http://en.wikipedia.org/wiki/Public_domain">Public domain</a>, the most permissive way. I also hope that the OCC team will be willing to pick up this file and integrate into OCC.<br /><br />The simplest example can be as follows:<br /><br /><span style="font-family: courier new;">typedef Standard_StdAllocator<void> allocator_type;</span><br /><br /><span style="font-family: courier new;">std::vector<TopoDS_Shape, allocator_type> aSVec;</span><br /><span style="font-family: courier new;">TopoDS_Solid aSolid = BRepPrimAPI_MakeBox (10., 20., 30.);</span><br /><span style="font-family: courier new;">aSVec.push_back (aSolid);</span><br /><br /><span style="font-family: courier new;">std::list<int_Shape, allocator_type> aList;</span><br /><span style="font-family: courier new;">aList.push_back (1);</span><br /><br /><br />There is also a unit test (download <a href="http://www.woofiles.com/dl-273607-y7DktbN3-StandardStdAllocatorTest.cxx">Standard_StdAllocatorTest.cxx</a>). The code is based on Qt test framework and should be self-explaining to port to any other test framework.<br /><br />Those who are curious enough may offer similar standard allocator implementation for NCollection_BaseAllocator which is used in NCollection containers. It is as straightforward as this one.Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com12tag:blogger.com,1999:blog-3285677929777490656.post-80687853504570683342011-06-19T20:30:00.002+04:002011-06-19T20:33:41.509+04:00Is my memory leaking? Part3.<i>(continued)</i><br /><br />OK, now when you have some clues of possible root-causes, what's next ?<br /><br />#1.If you believe you found a memory leak, first of all, do not panic. Be patient to identify if this is a true leak or a false positive. For that:<br /><br />#2. Use efficient memory checking tools (Intel Parallel Inspector XE, Valgrind, etc).<br /><br />#3. If you want to exclude custom memory allocator, be sure to set environment variable MMGT_OPT=0 and experiment further.<br /><br />#4. If the issue has gone and you suspect it to be an allocator problem, then the chance is that it's a false positive, not an allocator issue. Though you may continue investigating, of course ;-).<br /><br />#5. When designing architecture of your application, make sure you understand how your memory is managed. Do consider consistent use of smart pointers – e.g. boost's or Open CASCADE handles. That will save you multiple hours of tedious debugging.<br /><br />#6. If you are paranoic about memory consumption then you might want to periodically call Standard::Purge() when using the OCC allocator (MMGT_OPT=1). It is supposed to free unused small memory blocks. You could do this when closing the MDI doc, for instance. (I never used myself though.)<br /><br />#7. Advanced developers may want to step further and use fine-tune optimization techniques like use of memory pools (or regions). See NCollection_IncAllocator as an example of such. NCollection containers can accept it as an extra argument. TBB is going provide support for thread-safe pools in future versions.<br /><br />#8. Black belts may also want to experiment with memory tracing routines that have been enabled in OCC allocator. See Standard_MMgrOpt::SetCallBackFunction() which is called after each alloc/free. This can be any user callback function that traces sizes of requested/freed chunks, addresses, etc.<br /><br />So, here is what I was able to recall on this subject, and hope it will be helpful.<br />As I said in the beginning, any extensions or other best practices are welcome.<br /><br />RomanRoman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-63722574640281304702011-06-09T20:45:00.002+04:002011-06-09T21:00:55.132+04:00Is my memory leaking? Part2.<i>(continued)</i><br /><br />Having discussed possible symptoms, now let's try to understand their possible root-causes.<br /><br /><span style="font-weight: bold;">1. True leaks.</span><br />When developing in native (C/C++) code you may just forget to free allocated memory. This can be for example:<br /><br />a. Something as simple as such:<br />{<br /> char* p = (char*)malloc (1 * 1024);<br /><br /> //do work...<br /><br /> //free (p); //will never forget to uncomment this later<br />}<br /><br />b. Architecture design deficiency. Unclear object ownership, management of their life-span leading to failure in proper destroying objects.<br />I found this shortage in Salome (the SMESH module in particular). There is proliferation of plain pointers (not smart pointers, like boost::shared_ptr) with complex dependencies between objects. I presume multiple developers maintaining the code just forgot some day which objects should destroy which. Here is the most recent work-around I had to make – to destroy sub-meshes in SMESH_Mesh you have to call SetShapeToMesh() with a null shape. This will destroy all objects stored in internal map which otherwise will be leaked (the SMESH_Mesh::~SMESH_Mesh() destructor does not destroy them):<br /><br /><br /><span style="font-family: courier new;">/*! Frees resources allocated in SMESH_Mesh which otherwise leak<br /> - bug in Salome.</span><br /><span style="font-family: courier new;">*/<br /></span><span style="font-family: courier new;">Mesh_MeshImpl::~Mesh_MeshImpl()</span><br /><span style="font-family: courier new;">{<br /></span><span style="font-family: courier new;"> TopoDS_Shape aNull;</span><br /><span style="font-family: courier new;"> mySMesh->ShapeToMesh (aNull);</span><br /><span style="font-family: courier new;">}<br /></span><br />where mySMesh is defined as follows:<br /><br /> <span style="font-family: courier new;">boost::shared_ptr</span><smesh_mesh><span style="font-family: courier new;"> mySMesh;</span><br /><br /><br />c. Cycles between smart pointers. If you have two smart pointers referring to each other, they won't get destroyed (as reference counter will never reach zero). I described this issue in the <a href="http://opencascade.blogspot.com/2008/11/open-cascade-handles-lets-handleem-part_20.html">very first post</a>.<br /><br />True leaks are usually well caught by memory checkers.<br /><br /><span style="font-weight: bold;">2. Memory caching by memory allocators.</span><br /><br />Many complex software comes with integrated memory allocators that are able to manage memory more efficiently (at least in terms of speed and/or footprint) than default allocators (part of OS or C run-time library). Open CASCADE comes with its own (activated by environment variable MMGT_OPT=1), with Intel TBB (MMGT_OPT=2), or default system allocator (MMGT_OPT=0).<br /><br />Though OSes provide better and better allocators, custom ones are likely to stay for foreseeable future due to efficient solving of particular problems (e.g. thread-safety and scalability as TBB). If you are curious, you might want to check some comparisons I conducted with default, OCC and TBB allocators <a href="http://software.intel.com/en-us/blogs/2011/04/04/tbb-adoption-in-cad-technical-insights/">here</a>.<br /><br />The central idea of allocators is caching and reuse of previously allocated memory chunks for further allocations. Thus, when your application object is destroyed, its memory is effectively retained by the allocator and is not returned to the system. That is why, in particular, you won't see in Task Manager the memory level returning to the previous level even if all your document objects got destroyed after closing the MDI document. Allocators may apply different policies to retain/return these memory blocks. For instance, both OCC and TBB have different approaches for small and large blocks; the latter are returned faster (as the chances of their reuse are smaller), while the former may never be returned until application terminates.<br /><br /><br /><span style="font-weight: bold;">3. Static objects residing in memory.</span><br /><br />It is a wide spread practice to create static objects which live throughout the application life-time and get destroyed only upon program termination. Consider this:<br /><br />myfile.cpp:<br /><br /><br />static boost::shared<myclass> theSingleton = new MyClass();<br /><br />MyClass* MyClass::Instance()<br />{<br />return theSingleton.get();<br />}<br /><br />theSingleton will be created during loading the library containing it, and will be destroyed when it is unloaded (effectively when the application terminates unless it is explicitly unloaded before that).<br /><br />There are multiple examples of such constructs in OCC code.<br /><br />Below is the Inspector screenshot of the (false positive) leak reported on the screenshot in Part1:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNtzf2ek8POX2ajRaAvDFSAkOr4AgVRZNDh_4a3Fm9LarMLqCTqPqeTN6SmOiEIP8tzCDbn6ZusdKB0zp2Rfjmmk6XZyiQCBbbgSx5Npdt7AGXVgFhCIwU6gc-Hp3UnfhSQVPnglIWWyY/s1600/insp2.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 182px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNtzf2ek8POX2ajRaAvDFSAkOr4AgVRZNDh_4a3Fm9LarMLqCTqPqeTN6SmOiEIP8tzCDbn6ZusdKB0zp2Rfjmmk6XZyiQCBbbgSx5Npdt7AGXVgFhCIwU6gc-Hp3UnfhSQVPnglIWWyY/s320/insp2.png" alt="" id="BLOGGER_PHOTO_ID_5616262343279564130" border="0" /></a><br /></myclass></smesh_mesh><div style="text-align: center;"><smesh_mesh><myclass><b>Static objects in TKTopAlgo</b></myclass></smesh_mesh><br /><smesh_mesh><myclass></myclass></smesh_mesh></div><smesh_mesh><myclass><br /><br /><span style="font-weight: bold;">4. Unused data residing in memory.</span><br /><br />Similar to above, there are cases when some data are stored with the help of static objects and used to pass between the algorithm calls. I gave some examples in an <a href="http://opencascade.blogspot.com/2009/03/unnoticeable-memory-leaks-part-1.html">earlier post</a>. I believe this is a bad design and should be avoided but it may happen in third-party code. It's not really a leak but essentially wasting memory, which again only gets freed upon program termination.<br /><br /><i>(to be continued)</i><br /></myclass></smesh_mesh>Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-13303829775292162112011-06-02T12:45:00.006+04:002011-06-02T12:59:02.373+04:00Is my memory leaking? Part1.There often appear posts on the Open CASCADE forum, either as questions or as blames that there are persistent memory leaks. Truth to be told, this happens on many forums of other software products I visited, so this is not something OCC-specific.<br />So I'd like to shed some light, which would hopefully help someone to understand the issue in the future. As always, extensions and any comments are welcome.<br /><br />So how one detects there is a memory leak? I would suggest the following possibilities (presumably in the order of decreasing frequency of use by developers):<br /><br /><b>Using Visual Studio built-in features</b><br />(I never use these though).<br />Put the following lines in the source file or your executable:<br /><br /><span style="font-family:courier new;">#define _CRTDBG_MAP_ALLOC</span><br /><span style="font-family:courier new;">#include <stdlib.h></span><br /><span style="font-family:courier new;">#include <crtdbg.h></span><br /><br />And into the main() function:<br /><span style="font-family:courier new;">_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );</span><br /><br />For more information you can read <a href="http://msdn.microsoft.com/en-us/library/x98tx3cf%28v=VS.80%29.aspx">this MSDN page</a> (select appropriate VS version).<br /><br />Under the debugger, in the Output window you will see something like:<br /><br /><span style="font-family:courier new;">Detected memory leaks!</span><br /><span style="font-family:courier new;">Dumping objects -></span><br /><span style="font-family:courier new;">{35171} normal block at 0x04CD0068, 260 bytes long.</span><br /><span style="font-family:courier new;">Data: < > 02 00 00 00 00 00 00 00 CD CD CD CD CD CD CD CD</span><br /><span style="font-family:courier new;">{349} normal block at 0x0330E350, 100 bytes long.</span><br /><span style="font-family:courier new;">Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD</span><br /><span style="font-family:courier new;">{246} normal block at 0x03309390, 108 bytes long.</span><br /><span style="font-family:courier new;">Data: < E ( > 01 00 00 00 1A 00 00 00 45 01 00 00 28 0A 00 00</span><br /><br /><br />Once you see this you might find yourself yelling – "how may this !@#$% product exist with such fundamental bugs ?!!". But as soon as you see this attributes to your code, you may go "hmm, is this really an error?" As you start digging deeper, things may go different ways. You may discover a bug in your product or a tool limitation (the latter is more likely though).<br /><br /><b>Windows Task Manager</b><br />You notice the level of consumed memory before you start some piece of your code (e.g. opening a document in an MDI application).<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOdQdlXONvpLXVl5alN9MtsSnZyO7niEtwA6hDoeuz9Ch4y0GBT1yV4xlqWGyHs0h2C3N-0qzEY6S394P-bvCQRSm8Te27E2Lkc-SV8Y6mBIsqdzmBgz2B2x1LEm0RYsPVzRpx9hY0fYQ/s1600/TM1.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 250px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOdQdlXONvpLXVl5alN9MtsSnZyO7niEtwA6hDoeuz9Ch4y0GBT1yV4xlqWGyHs0h2C3N-0qzEY6S394P-bvCQRSm8Te27E2Lkc-SV8Y6mBIsqdzmBgz2B2x1LEm0RYsPVzRpx9hY0fYQ/s320/TM1.png" alt="" id="BLOGGER_PHOTO_ID_5613541707820962706" border="0" /></a><br /><div style="text-align: center;"><b>MDI app memory level before opening a document</b><br /></div><br />Then do some actions and see the new level. For instance, after closing the document in the MDI app, you would expect the level returns to a previous one. If not (and as a rule, not!) you start asking why.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQPotwqD7ZeLOO5WZZYhe0ONg84dkJBbi6j4Wy7bfRA_oDBiJWvxfJS9_AbC2kfZCQVvGGlkFfS9zbzkl2BC0_xPBjGRLXqRkkWFbob6goaQu17XAx1LpiV3q4fKI_3Y2uCCQT-2zKaYc/s1600/TM2.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 90px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQPotwqD7ZeLOO5WZZYhe0ONg84dkJBbi6j4Wy7bfRA_oDBiJWvxfJS9_AbC2kfZCQVvGGlkFfS9zbzkl2BC0_xPBjGRLXqRkkWFbob6goaQu17XAx1LpiV3q4fKI_3Y2uCCQT-2zKaYc/s320/TM2.png" alt="" id="BLOGGER_PHOTO_ID_5613541945066232354" border="0" /></a><br /><div style="text-align: center;"><b>Same app memory level after closing an opened document</b><br /></div><br /><b>Specialized memory checking tools</b><br />This includes Valgrind, Bounds Checker (never used those two), Rational Purify, Intel Parallel Inspector XE, etc. I used Purify in late 1990es and as of 2008 sticked to Intel Inspector. Here is a sample report generated by Inspector:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl9PlRguDYErQIUVTRE-EagK0iFGU6BT2cgi-rTZHUstxDoFLy6eXJKHK2Sig6XV-lrwwFvbMyiHEAdwEQ-dgU2ijZqguFCnSMzQRkEkJ3FN9qBpYI9RroXMruAZGL6YQfqSd4Odsbytg/s1600/insp.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 190px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl9PlRguDYErQIUVTRE-EagK0iFGU6BT2cgi-rTZHUstxDoFLy6eXJKHK2Sig6XV-lrwwFvbMyiHEAdwEQ-dgU2ijZqguFCnSMzQRkEkJ3FN9qBpYI9RroXMruAZGL6YQfqSd4Odsbytg/s320/insp.png" alt="" id="BLOGGER_PHOTO_ID_5613542105204531362" border="0" /></a><br /><div style="text-align: center;"><b>Intel Parallel Inspector XE reporting memory leaks </b><br /></div><br />You can try an evaluation version on the Intel site <a href="http://software.intel.com/en-us/articles/intel-parallel-studio-xe-evaluation/">here</a>.<br /><br /><b>Debug print</b><br />Adding simple outputs in constructor and destructor is simple yet effective practice to quickly check if your object is destroyed whenever you expect.<br /><br /><b>Custom memory checkers/profilers</b><br />You might want to write some ad-hoc memory profiler – some hooks that trace allocation and deallocation routines (malloc/free, new/delete) – counting allocated and deallocated bytes. I myself created one when tracing memory in CAD Exchanger. If there is sufficient interest, I could publish it. The idea is to detect pieces of code where you expect that all allocated memory during that region will be deallocated upon its end.<br /><br /><br /><i>(to be continued...)</i>Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-6421682450960924242011-05-16T23:02:00.004+04:002011-05-16T23:18:21.866+04:006.5.0 Visualization Highlights. Part2Integrating <a href="http://opencascade.blogspot.com/2011/05/650-visualization-highlights-part1.html">new gradient support</a> I noticed that the Visual3d_Layer class has been extended to contain multiple items called Visual3d_LayerItem. Exploring this further I realized that this was a way to offer an extensible way to support multiple user-defined objects. One of these is a color scale in a 3D view defined as V3d_ColorScaleLayerItem subclass.<br /><br />Experimenting with this new concept I went on to redefine the way how the CAD Exchanger logo was implemented. It is a part of the over-layer, one which draws 'on top' of objects in a main scene. See the screenshot below:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAN1AWIP9JlzLLxMiaja1PMX8uZl89SzOxZgFhAkfvWmQfb9cD48gd380KCe1qahLsMVIsPeHjUsNMNOB-HVunii1JkKZtq9PmDzo_d-Ofe05IGT8VveQe_9Ng15sM7blXC8UB_PayNtI/s1600/logo.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 311px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAN1AWIP9JlzLLxMiaja1PMX8uZl89SzOxZgFhAkfvWmQfb9cD48gd380KCe1qahLsMVIsPeHjUsNMNOB-HVunii1JkKZtq9PmDzo_d-Ofe05IGT8VveQe_9Ng15sM7blXC8UB_PayNtI/s320/logo.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5607393661118775522" /></a><br /><b>Logo displayed in front of a 3D model</b><br /><br />So I subclassed Visual3d_LayerItem and redefined its RedrawLayerPrs() method to do the actual work. Check <a href="http://opencascade.wikidot.com/local--files/recipes/layeritem.zip">this archive</a> to see the full code.<br /><br />Here is how you can add this object into the layer:<br /> //create an overlayer<br /> Handle(V3d_View) aView = ...;<br /> Handle(Visual3d_Layer) anOverLayer = new Visual3d_Layer (aView->Viewer()->Viewer(), Aspect_TOL_OVERLAY, Standard_True /*aSizeDependant*/);<br /><br />...<br /> //create a texture layer item<br /> const Handle(Visual3d_Layer)& aLayer = aView->Viewer()->Viewer()->OverLayer();<br /> Handle(QOOcc_TextureLayerItem) aTexture = new QOOcc_TextureLayerItem (QImage (":/viewlogo.png"), aView, aLayer.operator->());<br /> aTexture->SetPosition (Aspect_TOC_BOTTOM_RIGHT);<br /> anOverLayer->AddLayerItem (aTexture);<br /><br />This way you may add as many items as you want. The only downside (or assumption if you will), which is likely in place is that all items have to be drawn with the same coordinate system that needs to be supported for the layer (using Visual3d_Layer::SetOrtho()). So RedrawLayerPrs() needs to be written with a single convention in mind, and you seem unable to work with individual settings. It would be great to hear whether this assumption is correct or not – from both the OCC folks and anyone having practical experience.Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com3tag:blogger.com,1999:blog-3285677929777490656.post-61208216688625078252011-05-16T22:41:00.003+04:002011-05-16T22:48:27.964+04:006.5.0 Visualization Highlights. Part1Open CASCADE 6.5.0 has introduced a few nice (undocumented, as often) features in its Visualization module and I'm going to highlight a couple of them today. Even if the version 6.5.0 made some mixed impressions (with regressions in BRepMesh being most unpleasant ones), visualization was really nice. So kudos to the involved folks!<br /><br />When migrating CAD Exchanger to 6.5.0 I wanted to rework the way a gradient background is displayed. The previous way was described in the <a href="http://opencascade.blogspot.com/2008/12/sexy-background.html">old blog post</a>. 6.5.0 has added a 'built-in' support for gradients offering various options (horizontal, vertical, diagonal, corners, etc). Below is a sample screenshot made in DRAW:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY6y_RYFpw70JUF94_E5t6dH4cJIJZRUFkQzTDqX-nPnyJqZPC_cKz7godgVT02zslyiwuT0C627lfDxLXCgBMAkQNoKubRkzj0qPqE5TVkbXcftVFG7DJdRvzUw-3UY2llARbp9mpAkk/s1600/grad-diag.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 149px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY6y_RYFpw70JUF94_E5t6dH4cJIJZRUFkQzTDqX-nPnyJqZPC_cKz7godgVT02zslyiwuT0C627lfDxLXCgBMAkQNoKubRkzj0qPqE5TVkbXcftVFG7DJdRvzUw-3UY2llARbp9mpAkk/s320/grad-diag.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5607386745068467090" /></a><br /><b>Built-in gradient background support in 6.5.0 (using diagonal1 option).</b><br /><br />You might want to experiment more using the vsetgradientbg command. The new API can be used as simply as follows:<br /><br />Handle(V3d_View) myView = ...;<br /> if (theEnable) {<br /> myView->SetBgGradientColors (myTopBackgroundColor, myBottomBackgroundColor, Aspect_GFM_HOR, Standard_False);<br /> } else {<br /> myView->SetBgGradientColors (myTopBackgroundColor, myBottomBackgroundColor, Aspect_GFM_NONE, Standard_False);<br /> }Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com3tag:blogger.com,1999:blog-3285677929777490656.post-42706650092777675182011-03-22T00:13:00.002+03:002011-03-22T00:20:17.362+03:00Thoughts on Git repository and communityThomas Paviot <a href="http://www.opencascade.org/org/forum/thread_20111/">announced </a>a <a href="https://github.com/tpaviot/oce">git repository</a> that is aimed to accumulate community produced patches in an effort to systematize and ease their production and use. That effort addresses very limited community support from the OCC company side which often begs a question of the company's commitment to Open Source model. That often provokes emotional debates on the forum and I suggest that we put this offline (we can continue in a separate topic though).<br /><br />I view Thomas' effort as much more constructive than typical claims, so it does deserve recognition, at least in my eyes. I appreciate it like other efforts, including a <a href="http://opencascade.wikidot.com/">Wiki site</a> initiated by Fabian Hachenberg, multiple fixes from Denis Barbier and Peter Dolby, numerous projects showcasing and leveraging OCC (pythonOCC by Thomas Paviot and Jelle Feringa, Salome ports by Fotios Sioutis, qtocc by Pete Dolby, etc) and even simple bug reports. As the old saying goes, "it's better to light a candle than to curse the darkness". So every constructive input is more valuable than an emotional claim.<br /><br />This post is to share some initial thoughts. I suggest that we continue discussing details of Thomas' proposal on some other place, not the org forum. This is just to avoid unnecessary potential sensitive issues. The blog format is likely not too convenient either and another forum (e.g. on wiki) could be preferred.<br /><br /><span style="font-weight: bold;">Repository structure</span><br />Let's start with something simple and clear. Arthur Magill has suggested a 3 level structure which I would simplified down to 2:<br />- One master (mainline / trunk) branch per each OCCT version. The branch would start with pristine version of OCCT (say 6.5.0) and incrementally accumulate fixes. There must be reasonably strict gatekeeping process to maintain it stable. Fixes must be code-reviewed and tested to make this branch.<br />- Set of experimental branches maintained by individuals, projects, etc. These are sandboxes where volunteers can maintain their own version and which can contain fixes they selectively pick up or produce. No gatekeeping, everything is up to an owner.<br /><br /><span style="font-weight: bold;">Master branch commit process</span><br />To make the master branch as stable as possible, some efforts must be applied. I would start with 3 most important:<br /><ul><li>Code completeness. For instance, if you modify the header file then modify both original .cdl file (if applies) and .hxx file. If you modify a .vcproj file for Visual Studio 2008 32 bit then modify files for other flavors of Visual Studio (e.g. 2005, 2010, 32 and 64 bits) and automake files.</li><li>Testing. Apply reasonable effort to test your modifications.</li><li>Code review. OCCT is complex and caution must be taken to analyze potential implications (side effects, performance, memory footprint, platform specificities, etc). Ultimately, the best would be to have single decision maker(s) to approve or veto the modification. Participation of OCC team lead engineers would be really helpful. Ideas are welcome.</li></ul>In the end of the day, the patch should make the official version of OCCT. To make it happen, the community should apply its efforts and due diligence. I could help in code reviewing testing as far as CAD Exchanger allows.<br /><br /><span style="font-weight: bold;">Consolidation of community resources</span><br />To avoid unnecessary proliferation of resources and thus confusion on what to use when, I suggest we define the tools and start sticking to them. Ideally, I would love to see all this hosted on opencascade.org but given continuous unwillingness to support that let's host them outside. If we find another single platform we might want to settle down there.<br /><ul><li>Git repository – to ease fixes sharing.</li><li>Forum. To discuss issues which OCC company does not appreciate on its forum (projects announcement, company policies, etc). More feature-rich forum (e.g. typical phpBB) would help overcome limitations of the ancient org forum.</li><li>Wiki – to document knowledge, maintain useful links, etc.</li><li>(?) Bug tracking. Org forum should probably suffice unless there are volunteers to track the bugs along the life cycle.</li><li>What else ?</li></ul>Sourceforge could be a good single place but my experience with it was far from smooth, so I gradually gave up. On the contrary, http://opencascade.wikidot.com is very nice to work with.<br /><br /><span style="font-weight: bold;">Next steps</span><br />Thus, the next steps that I see are:<br />1. Define a must have list of tools for efficient community functioning. Start with forum, and use this blog until that. Once the forum is defined, continue discussions there.<br />2. Agree on the git structure and basic policies.<br />3. Start using it.<br /><br />I would be thrilled to see OCC folks participating in the discussions and activities as much as they can. I agree with Thomas' statement that we all share same interests.<br /><br />Thanks !Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com8tag:blogger.com,1999:blog-3285677929777490656.post-25544416258357152062011-02-09T18:11:00.002+03:002011-02-09T18:17:02.940+03:003D view navigation: mouse wheel support and moreWith recently released <a href="http://www.cadexchanger.com">CAD Exchanger 2.1 Beta</a>, which adds some GUI improvements, I thought to share some experience about that.<br />Default OCC viewer suggests some conventions based on using the Ctrl button and mouse buttons:<br />- Ctrl + MB1 (left button) – zoom;<br />- Ctrl + MB2 (middle button) – pan;<br />- Ctrl + MB3 (right button) – rotate.<br />This seems to be not only inconsistent with typical conventions in CAD systems but also challenging for user experience. For instance, MB3 is normally expected to open a context menu.<br />So following CAD Exchanger users’ feedback, I had to implement different navigation based on Solidworks and other conventions. They are based on using MB2:<br />- MB2 – rotate<br />- Shift + MB2 – zoom<br />- Ctrl + MB2 – pan.<br /><br /><br />In addition, support for mouse wheel (to zoom in/out), which is a commonly used convention, has been added. For Qt-based viewer it appeared to be quite easy:<br /><br />void QOOcc_View3d::wheelEvent (QWheelEvent* theEvent)<br />{<br /> if (theEvent->orientation() == Qt::Vertical) {<br /> int numDegrees = theEvent->delta() / 8; //number of degrees the wheel rotated by<br /> //let 100 degrees be approximately 2x zoom (see V3d_View::Zoom())<br /> int numSteps = numDegrees;<br /><br /> myView->Zoom (0, 0, numSteps, 0);<br /> theEvent->accept();<br /> }<br />}<br /><br />Apparently, <a href="http://qtocc.sourceforge.net/">QtOCC project</a> by Peter Dolby already implemented that (though I did not notice). But Peter used V3d_View::SetScale(). Either should work anyway.<br /><br />Perhaps, wheel support could be added into default OCC viewers and default OCC conventions could be revisited to better align with industrial ones.Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com3tag:blogger.com,1999:blog-3285677929777490656.post-83066138259521381122010-11-24T17:45:00.004+03:002010-11-24T17:54:25.519+03:00Parasolid developer community ?By any lucky chance, does anyone reading my blog, know any community of Parasolid developers ?<br /><br />I tried to register at http://developer.hoops3d.com/forums but it seems dead - no forum threads, no approval/rejection replies, etc.<br /><br />Asked the same question on http://forums.spatial.com but people there did not know :-(.<br /><br />I even had a phone call with Siemens and their rep promised to help but no follow up after that :-(.<br /><br />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.<br /><br />Thanks in advance!Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-90660570492915802392010-11-08T21:37:00.001+03:002010-11-08T21:39:34.167+03:00Adapters. Part 1Those of you who have been developing software for quite some time likely came across the <a href="http://en.wikipedia.org/wiki/Adapter_pattern">adapter pattern</a>. 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.<br /><br />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. <br /><br />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 ?<br /><br />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.<br /><br />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.<br /><br />Here is a sample code:<br /><br />TopoDS_Wire aSpineWire = ...;<br />TopoDS_Edge aGuide1 = ..., aGuide2 = ...;<br />Handle(BRepAdaptor_HCompCurve) aSpineAdaptor = new BRepAdaptor_HCompCurve (aWire);<br />Handle(BRepAdaptor_HCurve) aGuideAdaptor1 = new BRepAdaptor_HCurve (aGuide1), aGuideAdaptor2=new BRepAdaptor_HCurve (aGuide2);<br />GeomFill_Pipe aPipe (aSpineAdaptor, aGuideAdaptor1, aGuideAdaptor2, aRadius);<br />aPipe.Perform (aTol, anIsPolynomial);<br /><br />In the code above, *_H* are just handle-based equivalents, subclasses of Adaptor3d_HCurve.<br /><br />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).<br /><br />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 <br />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.<br /><br />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.<br /><br /><span style="font-style:italic;">(to be continued...)</span>Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com1tag:blogger.com,1999:blog-3285677929777490656.post-89226275711015247032010-10-29T19:39:00.003+04:002010-10-29T19:47:34.257+04:00Data model highlights – Parasolid, ACIS, Open CASCADEWorking on the <a href="http://cadexchanger.com/xt_read.html">Parasolid importer</a> 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.<br /><br />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 <a href="http://opencascade.blogspot.com/2009/02/topology-and-geometry-in-open-cascade.html">remember</a>, Open CASCADE defines orientation as attached to the topology entity (face for surface, edge for curve).<br /><br />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.<br />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 <a href="http://opencascade.blogspot.com/2010/05/open-cascade-and-multi-threading-again.html">data race risks</a> in multi-threaded applications.<br /><br />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.<br />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 <a href="http://opencascade.blogspot.com/2009/11/surface-modeling-part3.html">described</a> in the past) does not offer such combination.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtHwPqGGYkjArj-lZgGZwzz_QaX9rGwfLTnj5KeFa0GVApHKD1ln18iE9WcgzEKIe8Ma8kYflCv8VS-qCBoWvwVjcWLd5fDx0tuQEK7BaTOWsKj6yYbvwGsNpjXwucE_EFQveYlPPoq48/s1600/para_rolling_ball_blend.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 169px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtHwPqGGYkjArj-lZgGZwzz_QaX9rGwfLTnj5KeFa0GVApHKD1ln18iE9WcgzEKIe8Ma8kYflCv8VS-qCBoWvwVjcWLd5fDx0tuQEK7BaTOWsKj6yYbvwGsNpjXwucE_EFQveYlPPoq48/s320/para_rolling_ball_blend.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5533494569797094946" /></a><br /><br />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.<br /><br />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.<br /><br />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.<br /><br />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.Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com3tag:blogger.com,1999:blog-3285677929777490656.post-89895507069624346272010-10-27T20:00:00.004+04:002010-10-27T20:13:47.798+04:00CAD Exchanger 2.0.2 Beta now available<span style="font-style: italic;">(Repost from <a href="http://www.cadexchanger.com/">www.cadexchanger.com</a>)</span><br /><em><br />October 26, 2010.</em> <strong>CAD Exchanger 2.0.2 Beta</strong> is available<strong> </strong><br />This version introduces <a href="http://cadexchanger.com/xt_read.html" title="Reading Parasolid XT files">Parasolid-XT</a> 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 <a href="mailto:info@cadexchanger.com">info@cadexchanger.com</a>. Public release should become available later this quarter or early 2011.<br /><br />GUI and CLI (Command Line Interface) versions are available immediately. SDK should be available shortly.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIX3udOkUTp9XC4FALuXAXsXYh_VjchFB2FBCTnK1IyU3bF7UR-Td51zrVxl7gy-RkQxijuIRs3J0Slwz6FtdotPjb_doE1Rd4f3c5uryTkcK4v3Gyza9ygDcLf2Mfw6GNb01qWnW9U6g/s1600/cadex_gui_parasolid.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 253px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIX3udOkUTp9XC4FALuXAXsXYh_VjchFB2FBCTnK1IyU3bF7UR-Td51zrVxl7gy-RkQxijuIRs3J0Slwz6FtdotPjb_doE1Rd4f3c5uryTkcK4v3Gyza9ygDcLf2Mfw6GNb01qWnW9U6g/s320/cadex_gui_parasolid.png" alt="" id="BLOGGER_PHOTO_ID_5532759106919143826" border="0" /></a>Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-16650101581324165262010-09-13T08:28:00.002+04:002010-09-13T08:30:58.494+04:00CAD 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 <a href="http://www.cadexchanger.com/download/CHANGES.txt" class="postlink">CHANGES</a> file for details and visit the <a href="http://www.cadexchanger.com/download.html" class="postlink">download page</a> to get the release. For SDK evaluation please contact us at <!-- e --><a href="mailto:info@cadexchanger.com">info@cadexchanger.com</a><!-- e -->.<br /><br />Feel free to share your feedback on the <a href="http://www.cadexchanger.com/forum">forum</a> or email directly.<br />Thanks!Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com3tag:blogger.com,1999:blog-3285677929777490656.post-21424743843429500652010-08-06T19:46:00.001+04:002010-08-06T19:48:02.228+04:00Handles and TemplatesSometimes 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.<br /><br />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:<br /><br />template<typename> class Base_FileModel : public Base_Transient<br />{<br />public:<br /><br /> //! Defines a type to index entities.<br /> /*! @todo It must be signed (e.g. to address -1 in ACIS. Should it be consistent with size_t (for 64bit port)?<br /> */<br /> typedef int Index_t;<br /><br /><br /> //! Defines an entity type.<br /> /*! Equals to the template parameter.*/<br /> typedef En Entity_t;<br /><br /><br /> //! Returns a number of entities.<br /> /*! An empty model returns 0.*/<br /> size_t NbEntities() const { return myRevEntMap.Size() - 1; }<br /><br /> //! Adds an entity.<br /> /*! Returns a rank number with which \a theEntity has been added into the model. By<br /> default, a new rank number is maximum rank number + 1.<br /> Null entities are ignored.<br /><br /> This method is not thread-safe.<br /><br /> \sa Bind().<br /> */<br /> Index_t Add (const Entity_t& theEntity)<br /> {<br /> Index_t aRank = myNullRank;<br /> if (!theEntity.IsNull()) {<br /> aRank = myEntVec.Size() + myNullRank;<br /> Bind (aRank, theEntity);<br /> }<br /> return aRank;<br /> }<br />...<br />protected:<br /><br /> //! Constructor.<br /> /*! \a theNullRank defines a null rank used to designate a null entity (e.g. 0 for<br /> Parasolid or -1 for ACIS).<br /> */<br /> Base_FileModel (const FileHeader_t& theHeader, const Index_t theNullRank) :<br /> myHeader (theHeader), myNullRank (theNullRank)<br /> {<br /> const Entity_t aNull;<br /> myEntVec.SetValue (Index (theNullRank), aNull);<br /> myRevEntMap.Bind (aNull, theNullRank);<br /> }<br /><br />...<br /><br /> NCollection_Vector<entity_t> myEntVec;<br /> NCollection_DataMap<entity_t,> myRevEntMap;<br /> const Index_t myNullRank;<br /><br /> <br />private:<br /> Base_FileModel (const Base_FileModel&);<br /> Base_FileModel& operator= (const Base_FileModel&);<br />};<br /><br /><br />Here is how a particular class (for ACIS) is defined:<br /><br />DEFINE_STANDARD_HANDLE(ACISBase_Model,Base_Transient)<br /><br />class ACISBase_Model : public Base_FileModel<handle_acisbase_entity,><br />{<br />public:<br /><br /> //! Constructor<br /> /*! Creates a model with a new ACISBase_FileHeader.*/<br /> ACISBase_Model() : Base_FileModel<handle_acisbase_entity,> (<br /> new ACISBase_FileHeader(), EntityNullRank) {}<br /><br />...<br />};<br /><br />Similarly in ACISBase_Model.cxx:<br /><br />IMPLEMENT_STANDARD_HANDLE(ACISBase_Model,Base_Transient)<br />IMPLEMENT_STANDARD_RTTIEXT(ACISBase_Model,Base_Transient)<br /><br />That's it !Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com0tag:blogger.com,1999:blog-3285677929777490656.post-46074291942494010062010-07-20T12:17:00.002+04:002010-07-20T12:20:48.758+04:00Side effects of the HandleLast week-end, working on refactoring existing <a href="http://www.cadexchanger.com/">CAD Exchanger</a> 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.<br /><br />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 <a href="http://opencascade.blogspot.com/2008/11/open-cascade-handles-lets-handleem-part.html">handle</a>).<br /><br />DEFINE_STANDARD_HANDLE(Base_Generator,Standard_Transient)<br />class Base_Generator : public Standard_Transient<br />{<br />...<br />};<br /><br /><br />class Base_GDriver<br />{<br />...<br /><br /> //! Translates an object into a result.<br /> virtual void Paste (const Source& theSource,<br /> Target& theTarget,<br /> Base_Generator* theGenerator) const = 0;<br />};<br /><br /><br />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.<br /><br />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:<br /> Handle(ACISGTopo_RGenerator) aRGenerator = new ACISGTopo_RGenerator();<br />I simply use:<br /> ACISGTopo_RGenerator aRGenerator;<br />This is fine as the object is not used outside the scope and no smart pointer is required.<br /><br />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:<br /><br />void ACISGGeom_BaseIntCurDriver::Paste (<br />const Handle(Standard_Transient)& theSource,<br />Handle(Standard_Transient)& theTarget,<br />Base_Generator* theGenerator) const<br /><br />{<br />...<br /><br /> const Standard_Real aTol =<br />Handle(ACISGTopo_RGenerator)::DownCast (theGenerator)->Model()->Header()->ResAbs();<br /><br />...<br />}<br /><br />Anyone sees the issue now ?<br />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!<br /><br />I ended up changing the ACISGGeom_BaseIntCurDriver::Paste() to use static cast:<br /> const Standard_Real aTol = static_cast<acisgtopo_rgenerator*>(theGenerator)->Model()->Header()->ResAbs();<br />as this is a guaranteed cast in this particular case.<br /><br />In general, the most reliable approach would be to wrap aRGenerator with a handle (instead of creating it on a stack).<br /><br />So, a few closing thoughts:<br />1. Test, test, test !<br />2. Even if you understand bolts and nuts of the handle, you can still be surprised;<br />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;<br />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 ?<br /><br />Take care!</acisgtopo_rgenerator*>Roman Lyginhttp://www.blogger.com/profile/18338419158437898791noreply@blogger.com2