Topological Naming (another take)

First of all, for those that have been trying to build this, I have updated my cmake stuff as well as the second post. The build should be a lot smoother now, as I’ve level-upped my cMake-fu and incorporated some “find_package” stuff. I’m considering creating one monolithic project with OccWrapper, TopoManagers, and my FreeCAD branch as submodules to reduce the “git clone” and build down to a single step, but I haven’t decided if I want to do that yet or not.

Also, I’ve actually tested the build using fresh git clones. FreeCAD is currently still compiling, but so far no issues!

Thank you for taking the time to look through this! I’ve glanced over your code (I hesitate to say “browsed” since you’ve done a more thorough job looking through my code than I have yours :blush:). While I agree that our approaches are different, I think the implementations have some commonalities. i.e. it seems that we both rely on the Modified/Generated/Deleted methods of the various occ generator classes.

I don’t disagree with this. I would ultimately like to do as you’ve suggested and move the manager into TopoShape. I used the current approach to achieve a short-term “proof of concept” to show how these managers can work.

In keeping with the encapsulation principle, I’m of the opinion that TopoShape should be the only one that ever modifies its own resources, namely its TopoDS_Shape. That’s not really the case right now in FreeCAD. As a step towards “protecting” the TopoDS_Shape, I submitted a pull request (that I think went through) a while back making it a private member variable, but I also added (with wmayer’s help) a getter/setter, so the net impact was zero. But, in the longer term, it’d be great to find every piece of code in freecad that uses the setShape method in TopoShape and eliminate that call.

I did mention this briefley on this post. I have considered it. Essentially, my idea in this regard is to extend the concept of “two faces can be used to identify an edge” in the 3D world to “two edges can be used to define a vertex” in the 2D world.

As such, a “PlanarSurfaceManager” if you will would provide the same function as the “PrimitiveSolidManager”, except with edges and vertices. Now, any time this planar surface is modified, the “PlanarSurfaceManager” would have an “updateSurface” method which would need to know how all the edges have changed.

This “PlanarSurfaceManager” can than be the basis for an “SweptSolidManager”. The “SweptSolidManager” would figure out which faces are generated from which faces and then in essence function like the “PrimitiveSolidManager”. Please note that an extrusion is essentially a swept solid.

We would, of course, need a specific “SolidManager” for any method that could create or modify a solid. It sounds like we’d need an “OffsetSolidManager” as well.

I think you’re talking specificially about CompoundSolidManager. You are correct about how I have encoded each face. However, this three-tuple is only used to initially identify each face in the CompoundSolid, i.e. when the CompoundSolidManager is constructed. This is so that every face in the solid can be uniquely identified by tying it back to whatever face it originated from.

Can you specify an example of when a face originates “from multiple faces from multiple base solids”? I’ve been dealing solely with boolean fuse and cut so far.

I don’t think I follow along here: what complex data structure are you suggesting?


This is by design. During any given operation, a face or edge could be split into two or more constituent faces/edges. Take as an example the second gif I posted in this update. You can see that originally, a single edge was filleted. When that edge is split, the original edge no longer exists. We do know, though, that it was split into two. Therefore, any reference to a particular Edge must return a list rather than a single edge. The same holds true for a reference to a particular face.

I agree with your point that this does not allow a user to unambiguously reference any given edge or face. That was not my intent, though. Rather, I wanted to provide a mechanism by which a topological entity (for now, only edges and faces) could be referenced in such a way that despite geometric changes, the topological reference remains in tact.

Put another way, the intended user of any ISolidManager derived class is not the end-user of the FreeCAD program. Rather, ISolidManager and its derived classes are intended to be used within the FreeCAD code base as a means of managing references to topological entities.

As an example, the naming mechanism that you have focused your efforts on, which provides a unique string ID that uniquely identifies various topological entities would be an ideal “user” of an ISolidManager. This naming mechanism could keep track of whether an edge/face was split (by tracking the length of the vector returned by getEdgeByIndex and getFaceByIndex, respectively) and, say, take an edge that was originally named “Edge1” and rename it to “Edge1a”, “Edge1b”.

I greatly appreciate you taking the time to look through my code and provide your thoughts. I’m looking forward to continuing to work together on this! It’s my turn to take some time to read through your approach :slight_smile:

See the fusion example. The top middle face comes from both the cube and cylinder. And after refine, the top merged face comes from all three top faces before the refinement. Note that we can’t really rely on OCCT to provide a stable order of its returned modified shapes. I sort the shapes by their tags and names.

I don’t think I follow along here: what complex data structure are you suggesting?

I saw you code comment at CompoundSolidManager::mappedFaces, you are thinking of change the map → array of three indices into map of map. So I guess you may have considered the possibility of multiple origins. You can see that my naming scheme is full of combinations and recursives. Using string is kind of a cheat to avoid the need of a complex data structure, because I don’t have to provide a way to parse the string and reverse the modeling steps, unless of course, there is a demand for that function. This is in fact a shortcoming of my approach, as it is difficult to trace back the changes at the moment, especially when hashing is involved.

Ah, I see what you’re talking about now. The getConstituentFace method handles this by designating the originating base shape as the first in the myBaseSolids vector. I could probably improve this such that getConstituentFace returns all baseShapes that generate a given face, however, I don’t know if this is necessary.

I didn’t put too much thought into this problem because, in my opinion, the solid generated by this fuse example is invalid. That “captured face” that you’re talking about serves no functional purpose and should be (in my opinion) cleaned up by the OpenCasCade Fuse algorithm, similar to how our refine algorithm does.

That being said, I was hoping to delve into the refine code a bit and incorporate that into my Occ::SolidMaker::makeBoolean method, such that the fuse operation will implicitly return a solid without these “captured faces”

And here’s where even the approach I’ve listed above would have a problem, because despite “eliminating” the “captured face” in question, to your point the final single top face will be derived from both the cylinder and the cube. As such, I can see a reason to extend the definition of a “constituentFace” to include all generating solids.

Ah, got it. In fact, I’m actually now considering creating another helper class, so that I can use that as a return value of getConstituentFace, and I can also use it as a member variable (in a vector) of CompoundSolidManager.

Either way, I actually consider these data structures complex. I’m utilizing standard library containers, and storing exclusively unsigned integers. This is also by design. I wanted to use data structures that were a simple as possible such that they can be serialized/deserialized as painlessly as possible. Also, I wanted to make them as easy as possible to debug for development purposes.

Contrast this with opencascade’s built-in OCAF and TNaming, which uses data structures that I don’t understand at all and was a nightmare to debug.

WARNING: don’t spend too much time reading that TNaming documentation, it’s pretty confusing and not that helpful :stuck_out_tongue: I would link the TNaming example program provided with the source code, but as far as I can tell OCC doesn’t have a github anywhere. Here’s a link to an old version 6.9 example from OCE which should give you a rough idea of how TNaming is used.

Should the topological naming algorithm be responsible for keeping track of the history of the modeling steps? In my opinion it should not. Indeed, while you seem to imply that my approach may have an easier time doing thing (and I think you’re right, it would), I purposefully avoid this. In fact, the OCC TNaming solution seems to track the entire modeling history, but I found that this was extremely cumbersome and very wasteful. At most, we should only need to concern ourselves with the most recent modification (which I do to an extent by storing, say, the CompoundSolidManager from the last boolean operation. I need this in order to create the appropriate mapping for the updateSolid method. Anything beyond this, though, is (in my opinion) unnecessary, and should be created as a part of a dedicated undo/redo mechanism.

I wanted to make sure you guys were aware of another thread.
https://forum.freecadweb.org/viewtopic.php?f=8&t=21471

I know this thread from early last year, but didn’t follow it as I wasn’t interested in this topic then. It’s a refreshing read, I can see lots of similarities in ickby’s approach and mine. I have already solved some problems raised in that thread by construct the element naming string in segments, and hash the segments separately, so that the hash ID can be used to identify related elements without deep parsing. I am currently refining the naming algo to allow hash ID survive throughout modeling history, to some degree.

Phew! What a read. Honestly, I didn’t understand most of it :stuck_out_tongue:

I like the example with the reorder and split, it provides a good stress test of the topo naming algorithm.

Update: I can now confirm that the instructions in my second post actually work, as I have done a fresh download from git and compiled everything.

Sorry to those that were attempting to build my code and ran into problems. This should hopefully be much smoother now. Please let me know if you run into any problems.

I’ve been reading through OCC 7.2.0 release notes, and picked this:

Summary: Create a mechanism to serve shape history in a common way for algorithms accepting and producing shapes.
Shape history mechanism has been implemented in the new class BRepTools_History. It supports history for shapes with types ‘vertex’, ‘edge’, ‘face’ and ‘solid’ and allows defining relations ‘generated’, ‘modified’ and ‘removed’ between the accepted and the produced shapes. It provides algorithm to merge two histories of sequentially applied algorithms. It is positioned as a replacement of widely used history methods Generated, Modified, IsDeleted.
Type BRepTools_ReShape has been extended to support the BRepTools_History and correctly merge history of several shapes merged to a single one.
BRepTools_History history has been implemented for algorithm ShapeUpgrade_UnifySameDomain. The history of changes in the initial shape now considers all shapes created by the algorithm as modified instead of generated shapes.
:black_small_square: to get the modified shapes, use History()->Modified()
:black_small_square: to check whether the shape has been deleted, use History()>IsRemoved().

I thought it might be interesting for you, one of the two who messes with toponaming. If you already know this, then sorry for the noise :wink:

I glanced through the release notes and remember dismissing this, due to my poor luck with their TNaming stuff. That being said, perhaps I was too quick to dismiss it. I’ve seen occ improve things pretty well when they level-upped their boolean op stuff, perhaps this topo history update is not so bad. I’ll have to spend some time playing with it, but right now I’m working on a python interface for my TopoManager proof-of-concept.

Heres a small update to my OccWrapper library. I’ve added some python bindings to most (all?) of the c++ classes/methods, as well as the static factory classes. I’ve done this mostly to educate myself about creating python bindings, but also because I want to create a standalone workbench in FreeCAD in order to “sandbox” the topological naming stuff I’m trying, and python bindings are a core aspect of how FreeCAD works.

Enough gabbing, here is how to install it (I recommend using a python virtualenv - also, I’ve tested this build with a fresh git download so I know it works! lol):

-> mkvirtualenv occwrapper # this is optional
-> git clone --recursive https://github.com/ezzieyguywuf/OccWrapper.git
-> cd OccWrapper
-> mkdir build
-> cd build
-> cmake ..
-> make -j8 install

Once you’ve compiled and installed it, you’ll have a new python module available to you called OccWrapper. Here’s a short script that shows you some of what it will do:

# test.py
import OccWrapper as occ

myBox = occ.Box(10, 10, 10)
faces = myBox.getFaces()
edges = faces[0].getEdges()
myBox.getNamedFace(occ.FaceName.front)

myCyl = occ.Cylinder(2.5, 10)
faces = myCyl.getFaces()
edges = faces[0].getEdges()

myBox.writeFile("box.brep")
myCyl.writeFile("cyl.brep")

myFus = myBox.fuse(myCyl)

myFus.writeFile("fus.brep")

print("success! Wrote shapes to files.")

I’ve done a decent job of documenting, so using the python help utility should give you more info.

I know it’s not that impressive (essentially a very lame TopoShape replacement), but again, this was mostly educational for me and is also serving as a building block towards a standalone workbench in which I can do some more exploring/testing with toponaming.

Next on the list is a similar treatment for my TopoManagers library, but then the real tricky part is going to be figuring out how to make my OccWrapper::Shape (whether it’s in cpp or python) to interact wit FreeCAD. I think I’m going to need to learn a bit about Coin or w/e. I guess I could cheat and just use OccWrapper::Shape::getShape() (which returns a TopoDS_Shape) and use that to create a Part::TopoShape, and then sort of ‘backdoor’ my way into the FreeCAD environment that way…

Update: I’ve created one of those nifty appimage’s of my TopoManager branch. You can grab it here on github, it’s the “testFreeCAD” file. I’m not sure if I did it right, so if someone can download and try it to let me know I’d appreciate it (works on my system…) I didn’t include the qt libraries, and I’ve build it with qt5, so you’ll probably need qt5 installed.

Thank you so much. 630mb, a big mama. Realthunders appimage is about 230mb.
I downloaded it, set the executable bit, but it didn’t start.

./testFreeCAD
APP_DIR=/tmp/.mount_testFr4yIiDL
/tmp/.mount_testFr4yIiDL/usr/bin/FreeCAD: error while loading shared libraries: libmpi_cxx.so.20: cannot open shared object file: No such file or directory

My computer:

 inxi -Fz
System:    Host: xubu-wr Kernel: 4.4.0-121-generic x86_64 (64 bit)
           Desktop: Xfce 4.12.3 Distro: Ubuntu 16.04 xenial
Machine:   Mobo: ASRock model: 990FX Extreme3
           Bios: American Megatrends v: P1.50 date: 10/12/2012
CPU:       Octa core AMD FX-8350 Eight-Core (-MCP-) cache: 16384 KB 
           clock speeds: max: 4000 MHz 1: 1400 MHz 2: 1400 MHz 3: 1400 MHz
           4: 3400 MHz 5: 1400 MHz 6: 1400 MHz 7: 1400 MHz 8: 1400 MHz
Graphics:  Card: Advanced Micro Devices [AMD/ATI] Whistler LE [Radeon HD 6610M/7610M]
           Display Server: X.Org 1.18.4 drivers: ati,radeon (unloaded: fbdev,vesa)
           Resolution: 1920x1080@60.00hz
           GLX Renderer: AMD TURKS (DRM 2.43.0 / 4.4.0-121-generic, LLVM 5.0.0)
           GLX Version: 3.0 Mesa 17.2.8
Audio:     Card-1 Advanced Micro Devices [AMD/ATI] Turks/Whistler HDMI Audio [Radeon HD 6000 Series]
           driver: snd_hda_intel
           Card-2 Advanced Micro Devices [AMD/ATI] SBx00 Azalia (Intel HDA)
           driver: snd_hda_intel
           Card-3 C-Media driver: USB Audio
           Card-4 Logitech Webcam C270 driver: USB Audio
           Sound: Advanced Linux Sound Architecture v: k4.4.0-121-generic
Network:   Card: Broadcom NetLink BCM57781 Gigabit Ethernet PCIe driver: tg3
           IF: eth0 state: up speed: 1000 Mbps duplex: full mac: <filter>
Drives:    HDD Total Size: 2360.5GB (13.0% used)
           ID-1: /dev/sda model: KINGSTON_SV300S3 size: 120.0GB
           ID-2: /dev/sdb model: Crucial_CT240M50 size: 240.1GB
           ID-3: USB /dev/sdc model: External_USB_3.0 size: 2000.4GB
Partition: ID-1: / size: 213G used: 178G (89%) fs: ext4 dev: /dev/sdb1
           ID-2: swap-1 size: 8.54GB used: 0.04GB (1%) fs: swap dev: /dev/sdb5
RAID:      No RAID devices: /proc/mdstat, md_mod kernel module present
Sensors:   System Temperatures: cpu: 37.5C mobo: 48.0C gpu: 39.0
           Fan Speeds (in rpm): cpu: N/A fan-1: 0 fan-2: 2556 fan-3: 0 fan-4: 0 fan-5: 0
Info:      Processes: 308 Uptime: 7:40 Memory: 3346.1/7941.2MB
           Client: Shell (bash) inxi: 2.2.35

If I start realthunders appimage it’s like this:

./FreeCAD-asm3-20180423-c59a9b24-61466dc.glibc2.17-x86_64.AppImage
FreeCAD 0.17, Libs: 0.17R5235 (Git shallow)
© Juergen Riegel, Werner Mayer, Yorik van Havre 2001-2018
  #####                 ####  ###   ####  
  #                    #      # #   #   # 
  #     ##  #### ####  #     #   #  #   # 
  ####  # # #  # #  #  #     #####  #   # 
  #     #   #### ####  #    #     # #   # 
  #     #   #    #     #    #     # #   #  ##  ##  ##
  #     #   #### ####   ### #     # ####   ##  ##  ##

loading Base
Loading Parameters
Loading Compatibility
Part-o-magic is disabled.

I can add the mpi library. I don’t know why mine is so much bigger, but I didn’t use realthunder’s script b/c I didn’t understand how it worked :blush: instead, I used the appimagekit tool directly. Give me a bit, I’ll re-upload.

Try the testFreeCAD2.appimage file here. I run it by simply:

chmod +x testFreeCAD2.appimage
./testFreeCAD2

I made it a tad smaller by removing a bunch of python libraries that your system should (hopefully) have. Hopefully I didn’t break it in some other way… The file is still large because I include the opencascade libraries in it. I’m working on an appimage that doesn’t include the occ libraries, as I guess your system probably already has those installed.

Update: I uploaded the smaller version without the OCC libs. I was not able to make this work on my system, but then again I don’t have OCC installed on my system (I keep it in a build folder). I figured I’d upload it anyway in case you wanted to give it a whirl.

Thank’s for your help, but it didn’t work.

chmod +x testFreeCAD2.appimage



./testFreeCAD2.appimage 
APP_DIR=/tmp/.mount_testFrfRXeaY
/tmp/.mount_testFrfRXeaY/usr/bin/FreeCAD: error while loading shared libraries: libmpi_cxx.so.20: cannot open shared object file: No such file or directory

Thanks for testing! I know why it’s still failing. Let me work on creating an appimage with the libmpi_cxx (right now I just have other mpi libraries in there) and I’ll upload again.

I have a new version up ready for test here. Unfortunately, I can’t easily test this on my system, as it would require uninstalling my mpi libraries, which I think other programs on my system depend on. I could set up a “sandbox” virtualbox or something, but that seems like it’d take a while.

I wonder if I linked freecad against static libraries instead of shared libraries if these sorts of issues would go away…

Are you sure that your upload did work?

./testFreeCAD3.appimage 
APP_DIR=/tmp/.mount_testFrOhCvpR
/tmp/.mount_testFrOhCvpR/usr/bin/FreeCAD: error while loading shared libraries: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory

I deleted that about two builds ago, maybe I messed up the path or something. if you run the following does it work?

LD_LIBRARY_PATH=$PATH ./testFreeCAD3.appimage

Do you have python3.6 installed on your comp? If not I guess I need to include that in the appimage.

Update: you know what, I bet this was an issue all along it just never showed up b/c it was trying and failing to load the mpi thing. I’ll rebuild with the python library. Also, I’ll check realthunder’s build to see all the libraries he includes, and include the same.

No panic, keep cool. It doesn’t matter if I can toy around with your topo-naming improvement tomorrow or next week. The best thing is, that two nice an talented people are sharing their time and effort for to find a solution for that damn topo-naming issue. :sunglasses: