GSoC 2017 Dev Log: jnxd

On yorik’s suggesion (and due to the difficulty I’m facing while creating a blog of my own :mrgreen: ), I will be logging all my development activities on this thread. Hopefully it would be one post per week or more frequent. I’m parking this post as the header, and following posts will act as logs.

Project Plan: TODO

Update May 11th 2017

DeepSOIC and I have been exploring the possibilities with blended fusion, and it appears that there are bugs in OCC at multiple levels that prevent us from actually implementing this feature right away. I have written up the details about it elsewhere, but the here’s a tl;dr:

There are certain edge cases where the available OCC methods don’t work correctly, and thus either the edges are not properly identified or the fillet is not made. Due to these cases, we are restricting this functionality to macros, and are asking the community to test this macro and provide a feedback if we should refine and polish it.

Thanks for doing this jnxd!

You could go the easy route: create a blog on a platform like wordpress.com, everything is taken care of. The free plan offers 3GB of storage which is more than enough. There are others, like Google’s Blogger. If you already have a Google account, creating a blog can be very fast.

I really prefer to keep things Python, and Nikola seems like an interesting thing. Also, this thread itself is not really a bad idea, given that FreeCAD lives almost entirely on this forum. So I’ll wait till I have time and then set up ajinkyadahale.github.io, and maybe copy the log there. Thanks for the options, though.

I vote for keeping it on the forum, it will be way more interactive that way :wink:

Update Some musings, May 17

Finally I have submitted my thesis and now should have more time for the project. Unfortunately over almost the next whole week, I’ll be either traveling or otherwise without very active internet, but I should be able to do any offline stuff.

I believe that this project is going to be a as much research as programming, if not more. To this end, I want to know how this name “Topological Naming” came along. I have read a bit about topology (“a bit” is an overestimate, to be honest), and have an idea of what a topological space is. Topological spaces are defined in terms of open sets, and I would like to have an idea of what the open sets are here, and how it all connects.

I am hoping that understanding all this would lead to more robust algorithms in my implementations.

PS. DeepSOIC and I had this exact same discussion here.

I’m certainly not an expert but in BREP modeling we have two parts; topology and geometry.
Topology is faces, edges, and vertices. The stuff that OCC names.
Geometry is surfaces, curves, and points.
A face is a bounded surface defined by it’s edges. The edges are bounded curves defined by their vertices that exist at points.
If this is wrong please somebody explain my misunderstanding.

Maybe an idea to to check/ read https://forum.freecadweb.org/viewtopic.php?f=10&t=15847.
I think a part of all problems you encounter @ezzieyguywuf did too…

Sorry for my previos post. I’ve now read the full thread and see that DeepSOIC had already said the same.
This helped me https://en.wikipedia.org/wiki/Topology Reading the whole thing through before clicking on the links helped.
The first 10 pages of this http://www.ijcc.org/on-line2(pdf)/pdf/ijcc5-6.pdf helped me understand the task.
From the abstract this looks relevant http://link.springer.com/chapter/10.1007%2F978-0-387-35187-2_34
You may have already have seen all of this, maybe it’s of use for those following along.

OffTopic: DeepSOIC, you are very good at explaining, I learned a lot by reading the gitter thread.

Thanks the links. I can’t unfortunately access the last one, but reading the Wikipedia page helped a bit. Can’t say I am completely clear though.

It is probably a good idea to go with the definition of topology as used in CAD, viz. faces, edges, vertexes as opposed to surfaces, curves and points (I still don’t see the difference between points and vertexes). All connection with the classical definition of topology should become clearer in due course.

OffTopic: DeepSOIC, you are very good at explaining, I learned a lot by reading the gitter thread.

No doubt.

Update 26th May 2017

This was quite an inactive week with me visiting my native place and then my visa interview. I did however manage to read through OCC’s OCAF documentation and sgrogan’s links.

In a talk with ickby, it has become apparent that I should now be exploring ezzieyguywuf’s repository and thread on toponaming to see the merits and demerits of his approach.

Update 2nd June 2017

This week was spent getting a simple example up and running using OCC’s TNaming framework. The exercise was basically just following @ezzieyguywuf’s footsteps, and was made much easier thanks to his efforts. However, it still needed quite a bit of trial and error due to the poor documentation.

The model itself is the classic example of a box with a slot, such that if the slot is made longer to the full length of the box, one particular edge splits into two, making filleting it (them) difficult. Using TNaming, I was able to track this change. The file is attached.
fillet_with_cut_testing.zip (3.36 KB)
I also penned down a draft of how I plan to use TNaming in FreeCAD, but that’s a very recent development, and DeepSOIC and I are still discussing about it.
design_notes.txt (3.57 KB)

I just went through your design notes and have a question.
When you reach this state:

  (a)--(b)--(c)--(d)--(e)...
        |    |    |
        |    |    |
       (B)--(C)--(D)

Will you save this structure indefinitely or will you then remove b,c, d an keep a – B – C – D – e ?

I am mostly concerned about the build up of part complexity vs preserving the “undo capability”/history .

I would not like to have structure that expands like this after N changes:

(a1)  -- (b1)  - (..) - (N1)
          |       |      |
         (b2) - (..)  - (N2)
          |       |      |
         (..) -  (..) - (..)
          |       |      |        
         (Bn) - (..)  - (Nn)

I had planned on removing the old ones as we progress. However, I had not considered undo-redo functionality. Without that, I believe I could go so far as even deleting (c) when generating (E).

So, we were trying to expose the history framework of OCC, i.e. the methods that tell which elements of the old shape generated/were modified to which elements of the new shape, or which were deleted. However, we have hit a roadblock since w e have to make some potentially radical changes. Thus the call to @wmayer.

We plan to expose the methods by storing within TopoShape an instance of BRepBuilderAPI_MakeShape, which has the relevant methods (viz. Modified, IsDeleted and Generated). Now the problem with that comes up that the methods that use BRepBuilderAPI_MakeShape to make shapes just return the resultant TopoDS_Shape, and so not give us the opportunity to store the BRepBuilderAPI_MakeShape. See, for example, TopoShapePy::fuse to see why this would be a problem. So now, we might have to modify TopoShape::fuse to return TopoShape rather than TopoDS_Shape. However, @DeepSOIC is worried this might disturb third party libraries.

Any inputs?

I prefer (as user not as programmer) that if things need te be broken to make it better it is fine…
at least it is better than having 4 or more generations of software in a single package like Pro-E Creo have at this moment :frowning: .

Update 21st June 2017

I created a tnaming branch in my own fork of FreeCAD for this project’s purposes (long time coming :mrgreen:).

As for my previous post, I went with a “ask for forgiveness rather than permission” policy and created an overloaded function TopoShape fuse(TopoShape) const to support the history methods. With that, two proof of concept implementations are created.

First, Part.Shape.fuse() in python is modified such that it returns a shape injected with the BRepBuilderAPI_MakeShape. So you could try something like:

import FreeCAD
import Part

App.newDocument("Unnamed")
App.setActiveDocument("Unnamed")
App.ActiveDocument=App.getDocument("Unnamed")
Gui.ActiveDocument=Gui.getDocument("Unnamed")
App.ActiveDocument.addObject("Part::Box","Box")
App.ActiveDocument.ActiveObject.Label = "Cube"
App.ActiveDocument.recompute()
#Gui.SendMsgToActiveView("ViewFit")
App.ActiveDocument.addObject("Part::Cone","Cone")
App.ActiveDocument.ActiveObject.Label = "Cone"
App.ActiveDocument.recompute()

fusion = App.ActiveDocument.Box.Shape.fuse(App.ActiveDocument.Cone.Shape)
fusion.modified(App.ActiveDocument.Box.Shape.Edges[3])

Note that the last two lines will have to be entered manually (as opposed to using a macro) to see any results.

Secondly, Part::Boolean is modified such that execute() command always creates a TopoShape with injected BRepBuilderAPI_MakeShape. So, you could also try:

import FreeCAD
import Part

App.newDocument("Unnamed")
App.setActiveDocument("Unnamed")
App.ActiveDocument=App.getDocument("Unnamed")
Gui.ActiveDocument=Gui.getDocument("Unnamed")
App.ActiveDocument.addObject("Part::Box","Box")
App.ActiveDocument.ActiveObject.Label = "Cube"
App.ActiveDocument.recompute()
#Gui.SendMsgToActiveView("ViewFit")
App.ActiveDocument.addObject("Part::Cone","Cone")
App.ActiveDocument.ActiveObject.Label = "Cone"
App.ActiveDocument.recompute()

App.activeDocument().addObject("Part::Cut","Cut")
App.activeDocument().Cut.Base = App.activeDocument().Box
App.activeDocument().Cut.Tool = App.activeDocument().Cone
Gui.activeDocument().Box.Visibility=False
Gui.activeDocument().Cone.Visibility=False
Gui.ActiveDocument.Cut.ShapeColor=Gui.ActiveDocument.Box.ShapeColor
Gui.ActiveDocument.Cut.DisplayMode=Gui.ActiveDocument.Box.DisplayMode
App.ActiveDocument.recompute()

and then App.ActiveDocument.Cut.Shape.modified(App.ActiveDocument.Box.Shape.Edges[3]). The results of this command will change accordingly if either the cube or the cone are moved using the appropriate tool.

Moving forward, there is a need to replace the use of BRepBuilderAPI_MakeShape with a more general implementation, since many features use more than one BRepBuilderAPI_MakeShape objects to build their final shapes. One likely contender is the Part::ShapeHistory Struct, but it may have to be extended into a full class before using it.

Hello all. I am late to the party and have yet to read through this thread. I did receive a PM from jnxd asking for some input regarding the Topo Naming work I did last year, so I imagine this year’s GSoC is relating to Topo Naming? That is great! I have PMed jnxd my personal email so that he can access me more readily, as I’m not on these boards as often as I was last year.

I will say I’ve been working ‘offline’ (from here) a bit on a prototype Topological Naming solution written purely in python. The idea was that it is easier and quicker to develop-test-refine using python and its built-in testing framework rather than trying to do all the development within the larger FreeCAD project. If I could develop a solution using just python, the idea was to later write the FreeCAD->python code that is necessary to use that solution in the FreeCAD project.

I was hesitant to post anything about this work on the boards here due to the strong opinions that any Topo Naming solution must by definition be written in C++ at the lower level - I wanted to wait until I had a better working solution before floating it out for further discussion. But, all that being said, I’m also open to discussing and sharing the work I’ve done in that arena as well. It hasn’t come all that far, but I do think it may be a more sustainable solution rather than using the OCC built-in topo naming stuff.

Edit: after reading through the thread a bit I have an additional comment.

Moving forward, there is a need to replace the use of BRepBuilderAPI_MakeShape with a more general implementation

For what it’s worth, if you look at my tnaming_ezziey_01 branch, you’ll notice I’ve created a TopoNamingHelper class. This class is subsequently used in various helper methods that I’ve added to the TopoShape class. One of these methods is trackFuse.

This trackFuse method is then used in FeaturePartFuse, which is the existing legacy FreeCAD code that gets called whenever two shapes are fused together.

So why did I go with this approach? Well, there are various analogs to FeaturePartFuse already that do many of the common tasks that we want in FreeCAD: there’s FeatureFillet, FeaturePartBox, FeaturePartBoolean, etc… Traditionally, each of these creates the requisite OCC objects needed to perform the needed operation, i.e. BrepAlgoAPI_Fuse for the fuse operation. You’ll notice in my FeaturePartFuse implementation, I’ve removed that code and moved it instead to the trackFuse method in TopoShape. The reason for this was in order to retain access to the BrepAlgoAPI_Fuse instance without having to pass it around - also, philosophically, I don’t believe that FeaturePartFuse should itself modify the TopoShape, but rather call access methods located in TopoShape that themselves modify the stored TopoDS _Shape variable.

Finally, if you look at the inherited methods that BrepAlgoAPI_Fuse has from BrepAlgoAPI_BooleanOperation, you’ll notice there are methods such as IsDeleted, HasModified, etc…

So that was the strategy - move all the actual “change an OCC TopoDS_Shape” logic out of periphery classes such as FeaturePartFuse and move it into TopoShape. Subsequently, tap into the *API_whatever OCC classes to track the modified/deleted/created topology using OCC’s builtin tnaming classes.

Any updates?

Oh! So sorry for not posting for so long.

Update: 3rd July 2017

We’re working on making a pull request that stores the history algorithm for others to experiment with. When complete, methods in Part.Shape shall have an optional parameter withHistory that will lead to the shape being returned have a History attribute attached that can be used to query the developments that went into making this shape. Some technical details follow.

First of all, why are we making it optional? In some very basic experiments, we found that the memory footprint of history is really high:
as much as 367 MB for 1000 shapes as compared to 41.5 MB without. We’re working on using better implementations to bring this difference down, but I think it’ll still make substantial difference with and without history.

Secondly, why are we going for a new class rather than the current implementation? That is simply so that we can encapsulate any alternative implementation neatly away from TopoShape. Apart from the previously mentioned memory footprint issue, we plan to make the new implementation such that it can support features that use more than one BRepBuilderAPI_MakeShapes from OCC.

Finally, DeepSOIC wanted to have the return such with withHistory, the python methods return a tuple (shape, history). While that is possible, I went for having history as an attribute to TopoShape, since I felt that kept the shape generation methods cleaner.

It’ll take about 7-10 days to implement this for all the methods in TopoShape, after which I’ll make the PR. As such TopoNaming is too much work if targeted for the already delayed 0.17 release, but these optional parameters should not be too much of a hassle.