Python Topological Namer/Tracker

Here's the place for discussion related to coding in FreeCAD, C++ or Python. Design, interfaces and structures.
ezzieyguywuf
Posts: 557
Joined: Tue May 19, 2015 1:11 am

Python Topological Namer/Tracker

Postby ezzieyguywuf » Wed Jul 12, 2017 8:32 pm

Hello everyone. As some of you are aware, I did a bit of work last year looking into OpenCascade's built-in topological naming library, i.e. TNaming. Since then, I've been a bit busy getting married and stuff, but I've come back to this problem and decided to try and approach it from a different angle. Let me explain why.

The main issue I have with OpenCascade's TNaming solution is that it is a 'black box'. I've spent time looking at the source code, but it is poorly documented and rather cryptic in its approach. The more I dived into it, the more confused I got. This became a problem mostly during debugging/development of the FreeCAD Topological Naming solution. It was so difficult to obtain sane, sensible error messages from the OCC TNaming module that it ended up being not very fun at all to continue development using this library. At the end of the day, if we HAVE to use OCC TNaming, I suppose we could make it work. But I wanted to take a break and try something else.

So, that's what I did. Based on some papers I have read (suggested by some of you), it seems that a sensible way to address the Topological Naming issue is to keep track of all the faces in your Shape. If you have a history of all your Faces, any given Edge can be identified by the two Faces that make it up. The hard part is keeping track of changes to your Faces: has a Face been modified? Has it been deleted? Has a new face been added?

Fortunately, this is an area in which OpenCascade can help: most (all?) off the classes in OpenCascade that modify a solid provide methods that detail the changes to the Faces in that solid: in fact, that is the bases with which the TNaming module is built. In order to use the TNaming module, I was forced to create a list of Modified/Deleted/Created Faces and pass that information along to the TNaming module.

So, what I decided to do was take the TNaming module out of the equation and develop my own Topological Naming tracker that uses the same OpenCascade faculties (i.e. the list of Modified/Deleted/Created faces) to keep track of the topology. I decided to create this tracker in python because I am most familiar and comfortable with coding in python, therefore it would be more fun (for me) as well as quicker to Develop->Test->Modify my code.

You can see the current state of this code base here on github. The code itself consists of 5 classes, but the majority of the work is done by the TopoTracker class. I created some Mock classes to mock the FreeCAD and OpenCascade functionality that this tracker relies on: this makes development faster because I don't have to compile FreeCAD every time that I make a change or develop a new feature.

If you'd like to get an idea of how the tracker works at the very high level, take a look at the testTopoNamer.py test case. I have documented this thoroughly in order to (hopefully) make it pretty easy to understand how it works.

The next step that I have is to figure a way of incorporating this tracker into FreeCAD for an implementation test. This will necessitate (I think) adding some functionality to the TopoShape class in FreeCAD. Namely, I'll need to expose the Modified/Deleted/Created faces from any given OpenCascade operation via the TopoShapePy implementation. I have some limited experience with exposing OCC things to the FreeCAD python framework, so I think I should be able to figure this out.

Anyway, I was inspired by jnxd's work on the GSoC stuff to clean up the current state of this PyTopoNamer enough to get to a point where it can be tested within FreeCAD. Any comments, criticisms, concerns, whatever are welcome.
User avatar
Kunda1
Posts: 5764
Joined: Thu Jan 05, 2017 9:03 pm

Re: Python Topological Namer/Tracker

Postby Kunda1 » Wed Jul 12, 2017 9:31 pm

jnxd wrote:...
CC @jnxd
Want to contribute back to FC? Checkout:
#lowhangingfruit | Use the Source, Luke. | How to Help FreeCAD | How to report FC bugs and features
User avatar
microelly2
Posts: 4407
Joined: Tue Nov 12, 2013 4:06 pm
Contact:

Re: Python Topological Namer/Tracker

Postby microelly2 » Wed Jul 12, 2017 9:47 pm

Thank you for sharing this. Having a python framework to check ideas is a good start point.
triplus
Posts: 8729
Joined: Mon Dec 12, 2011 4:45 pm

Re: Python Topological Namer/Tracker

Postby triplus » Wed Jul 12, 2017 10:07 pm

Sounds interesting but likely way over my head to do some tests and provide some valuable feedback. ;)

P.S. Once implemented in FreeCAD for sure the fun can start when working with real geometry.
chrisb
Posts: 18834
Joined: Tue Mar 17, 2015 9:14 am

Re: Python Topological Namer/Tracker

Postby chrisb » Thu Jul 13, 2017 12:07 am

Thank you for tackling this last big Problem-Rucksack FreeCAD carries around. All other things are only issues.
User avatar
tanderson69
Posts: 1500
Joined: Thu Feb 18, 2010 1:07 am

Re: Python Topological Namer/Tracker

Postby tanderson69 » Thu Jul 13, 2017 2:29 am

ezzieyguywuf wrote:
Wed Jul 12, 2017 8:32 pm
The main issue I have with OpenCascade's TNaming solution is that it is a 'black box'. I've spent time looking at the source code, but it is poorly documented and rather cryptic in its approach. The more I dived into it, the more confused I got. This became a problem mostly during debugging/development of the FreeCAD Topological Naming solution. It was so difficult to obtain sane, sensible error messages from the OCC TNaming module that it ended up being not very fun at all to continue development using this library. At the end of the day, if we HAVE to use OCC TNaming, I suppose we could make it work. But I wanted to take a break and try something else.
congratulations! you have earned your OCCT 'wings' ;)
ezzieyguywuf
Posts: 557
Joined: Tue May 19, 2015 1:11 am

Re: Python Topological Namer/Tracker

Postby ezzieyguywuf » Thu Jul 13, 2017 3:46 am

Update: I did a bit of preliminary testing this evening. Unfortunately, it didn't go so well.

The PyTopoNamer I wrote relies on the OpenCascade `isEqual` method to determine of two Edges are the same. This is done in order to figure out which Edge two Faces have in common on a Solid.

My assumption was that in, say, a Cube, despite there being 6 faces each with their own 4 Edges that the Cube itself would only have a total of 12 Edges. In other words, each Face would have an Edge that `isEqual` to an Edge on another Face.

My preliminary testing shows that this may not be the case. Using the Part workbench in FreeCAD v0.16 I have created a Cube primitive. Then, I do something like the following:

Code: Select all

box = App.ActiveDocument.getObjectFromLabel('Cube')
anEdge = box.Shape.Edges[9]

for i,face in enumerate(box.Shape.Faces):
    for j,edge in enumerate(face.Edges):
        if edge.isEqual(anEdge):
            print('face {} has an equal edge at Index {}'.format(i, j))
I expect this code to print two Edge Indices on two separate Face indices. However, I only get one hit.

Does anyone know what I might be missing here? Is it not true that any given Edge on a cube should be shared by two different Faces?

Edit : actually I took a look at the occ documentation for TopoDS_Shape and I believe the method I'm looking for is `isSame` as this checks for equality in tshape and location but disregards orientation. I will test this tomorrow.
ezzieyguywuf
Posts: 557
Joined: Tue May 19, 2015 1:11 am

Re: Python Topological Namer/Tracker

Postby ezzieyguywuf » Thu Jul 13, 2017 2:43 pm

Alright, so I have completed a very basic integration test within FreeCAD and I'm happy to say that I've had some success! If you take a look at my github repo you'll notice I've added a 'FreeCAD_scripts' folder. In here, you can see a very basic test case that shows how my python topological namer should work. Let me explain a bit.

My current benchmark for dealing with the Topological Naming issue is the following issue in vanilla FreeCAD:
  1. Create New Document
  2. Open 'Part' workbench
  3. Create Box primitive
  4. Create Cylinder primitive
  5. Fuse the two primitives together
  6. Chamfer one of the Edges (I usually choose 'Edge10')
  7. Make cylinder height smaller
When the cylinder is resized, the chamfered Edge moves when FreeCAD executes its recompute algorithm. This is because 'Edge10', i.e. the 10th Edge in the 'Fusion' shape (i.e. App.ActiveDocument.getObject('Fusion').Shape.Edges[9]) is no longer the same Edge that we are interested in. It has moved due to various OCC internals (I think because the total number of Faces changes?)

What I've done in the test-case that you see in the 'FreeCAD_scripts' folder is recreated this failure up to fusion of the two primitives. If you have the 'Report View' visible, you can see why this is still a good test:
  1. Create New Document
  2. Open 'Part' workbench
  3. Create Box primitive
  4. Create Cylinder primitive
  5. Fuse the two primitives together
  6. Select an Edge and make note of its name (i.e. 'Edge10') in the Report View
  7. Make cylinder height smaller
  8. Select 'same' Edge and make note of its name in the Report View. It should be different
FreeCAD's `recompute` algorithm relies on the position of the Edge in the list of the 'Fusion' shape's edges in order to determine which Edge to fillet, but as we can see (and have known for a while) this method is not reliable.

When you run the test-case, you'll notice three messages:

Code: Select all

The chamfered Edge is named Edge009
RecoveredEdge.isEqual(originalEdge) == True
RecoveredEdge.isEqual(originalEdge) == True
If you follow along in the macro itself, you'll realize what these messages are telling you. The first is telling you about the Topological Name of the Edge that has been selected for chamfer - in this case, the Edge is named 'Edge009'. This name will always refer to this Edge as long as this Edge exists.

The second message is showing how this string can be used to retrieve the Edge. In other words, if we were going to chamfer the Edge, we'd want to use the string 'Edge009' to retrieve an instance of an OpenCascade Edge to chamfer.

Finally, the last message proves that even after the cylinder is resized, 'Edge009' still returns the Edge that we are looking for.

I'm pretty excited about this! For my next step in testing, I want to hack the FreeCAD `recompute` algorithm a bit so that it uses my namer - hopefully this is a quick hack just to finish proving out the namer. After that, it will likely be time to take a step back and determine the best path forward: whether to implement more OCC methods in Python or to re-implement the namer in CPP.
ickby
Posts: 2922
Joined: Wed Oct 05, 2011 7:36 am

Re: Python Topological Namer/Tracker

Postby ickby » Sat Jul 15, 2017 6:28 pm

Hello,

nice that you are going forward with this. I have two small remarks: When going for the identification by faces only make sure you also have a way to handle sketches. These are wires only, but the user can easily add or remove edges in the wire, and the created faces by extrusion must be named somehow.

The second remark is to give you a hint on work that was done over the last year about that topic. There is a basic test implementation of a custom aproach. Maybe it can give a small inspiration.
Description: https://docs.google.com/document/d/1-d2 ... 2R9Ug/edit#
Code: https://github.com/ickby/FreeCAD_sf_master/tree/Naming
ezzieyguywuf
Posts: 557
Joined: Tue May 19, 2015 1:11 am

Re: Python Topological Namer/Tracker

Postby ezzieyguywuf » Sun Jul 16, 2017 2:37 am

Here is another update: I've added a second script to my git repo that does a better job showing how the PyTopoNamer works. I tried to document the Macro very well, but if there are any questions just let me know.

This example does a much better job than the last example of showing how the PyTopoNamer can help us with our topological naming issue. In fact, this example shows how my PyTopoNamer fixes this bug

Please, take a moment to review this and let me know your thoughts!
ickby wrote:
Sat Jul 15, 2017 6:28 pm
When going for the identification by faces only make sure you also have a way to handle sketches. These are wires only, but the user can easily add or remove edges in the wire, and the created faces by extrusion must be named somehow.
This is a good point. As far as I'm aware, OCC does not provide the same methods (i.e. modified, deleted, etc...) for an extruded solid. I haven't spent a lot of time considering this particular problem, but I have a feeling that I could add another PyTopoNamer layer specific to the 2-D wire used to extrude the solid. Rather than defining a particular Edge by the two Faces that constitute it (as in my current 3-D implementation) we could define vertices (which become Edges) by the two Edges (which become Faces) that constitute them.

Anyway, one step at a time.
ickby wrote:
Sat Jul 15, 2017 6:28 pm
The second remark is to give you a hint on work that was done over the last year about that topic. There is a basic test implementation of a custom aproach. Maybe it can give a small inspiration.
Description: https://docs.google.com/document/d/1-d2 ... 2R9Ug/edit#
Code: https://github.com/ickby/FreeCAD_sf_master/tree/Naming
Thank you for that, I will take a look when I have a moment. For now, I'm rather excited about the progress I've made on PyTopoNamer and I want to see how far I can get that.