Find Label by ID OCAF

Jojain

New member
Hello,

I'm trying to get started with OCAF and I'm not finding a way to get a label from it's ID.
I'm talking about ID but it doesn't have an ID method like TDF_Attribute has.

Is there a class that acts like a mapping of TDF_Labels with some identifier ?

I've looked a bit into TDF classes and didn't really found anything that would do what I want. It leads me to think that I might be conceptually wrong on my design and that maybe I should change the way I'm designing my app.

My logic is as follow :
  • I create a document
  • The document can have several parts, parts are stored in the document with a part label
  • Several parts are populated in the document, a GUI action triggers a Qt Signal that convey a Command object
  • Several slots are connected to this signal and modify the document depending on the kind of command
  • Let's say the command is a part color change. The command object would hold some ID identifying the part aswell as data for the color.
  • The document slots receive the Command object and needs to identify which label it must attach the color attributes. (At this point I have some troubles)
Without OCAF I would have a dictionnary (I'm using Python) mapping each Label to and ID so I could retrieve it easily.

So I would like to know if there is a way to do something like this with OCAF ? Or maybe I should think about it differently ? (But I don't see how)

Any help will be appreciated !

Cheers,

Jojain
 

Jonathan

Member
Jojain,

what ID are you referring to ? I think of the label as being the 'id' ..unless you are talking about attributes which do have a GetID() method

I store my shapes using shapetool, and color them with colortool
 

Jojain

New member
@Quaoar Reading again about this I think it could.
Tell me if I wrong but from what I understand :
  • TDF_Tools::Label takes as input argument TDF_Data and an entry (a string representing the label)
  • An entry can be obtained with TDF_Tools::Entry
  • TDF_Data by aLabel.GetData()
  • So I would need to first create entries for each label in the document map them to what it needs to be mapped (others unrelated objects) (in a dict) and then retrieve the Label object associated with a specific object in the dict by calling TDF_Tools::Label ?
It seems viable but sounds a bit complicated for something I would judge as trivial, that's why I wasn't sure if my architecture design was good.
Anyway I'll taste this when I have time and update if it works the way I intend ! Thanks
 

Quaoar

Administrator
Staff member
It seems viable but sounds a bit complicated for something I would judge as trivial, that's why I wasn't sure if my architecture design was good.
I think, what you have to decide on is where to keep entires of your labels. There are many ways of doing that, and this is largely your architectural decision. For example, in XDE, as @Jonathan mentioned, there is such a thing as a "Tool" attribute that gives you all the interfaces for your document. You may go your own way here, keeping in mind that OCAF is a sort of in-memory database, which makes applicable some design patterns from Enterprise apps. Let me share my own approach if you will. Back in 2012, I came up with a data framework for that purpose which I still use (https://gitlab.com/ssv/AnalysisSitus/-/tree/master/src/asiActiveData): the idea is that you stop speaking labels and attributes directly and rather design your data model in a more object-oriented fashion. Each object is actually an interface to your data, so it does not store the real data but only contains a label. Each object also has an ID, which is just the same entry of the label it contains.

Once you have designed your interfaces like this, you can use them to read and populate OCAF data. All objects are accessible through the Data Model facade, which is a singleton object representing your whole document in the application. You can find the source by the link above, and here's a bit of documentation on the approach: https://www.researchgate.net/public...E_software_powered_by_Open_CASCADE_Technology

It can probably give you some ideas for organizing your data. Again, just keep in mind that OCAF is a low-level thing, like a database. You'll need to add some object-relational mapping to it by yourself to get it working the best.
 

Jojain

New member
@Jonathan
Well I was actually expecting Labels to have an ID like Attributes have an GUID.

@Quaoar

With your info and youtube video I think I more or less understood your point which is that one should build a convenient object oriented library on top of OCAF to fit the application needs the best.
I actually started doing this especially to have a more Pythonic interface to the Data and to keep an abstraction level on top of OCAF since it's not that simple to use.

Still I will need to link my data framework to the underlying OCAF data and that's where I am right now and I am a bit going forward in the jungle so that can explain some of my confusion.

PS : While writing the example below I actually found an answer, I'm leaving it for other people that would have the same question

To get back to the first question, if we have a OCAF document looking like this :

[0]
|
[1:0] - [1:1] - [1:2]
| |
[2:1:1] [2:1:2]
|
[3:2:1:1]
(I'm not sure OCAF labels it's TDF_Label like that but that's not important for the example)

With these labels having some attributes attached to them like a shape or a name like :
[1:0] -> Shape = myBox
[1:0] -> Name= "myBox"

[2:1:1] -> Shape = myCyl
[2:1:1] -> Name= "the cylinder"
etc.

If the user through the GUI triggers a command that would say something like : Color myCyl in red
Then I would need to find the Label associated with myCyl in my document, to do this I had two ideas :
  1. Looping over all the labels and checking if the label has an attribute myCyl -> not very efficient
  2. Mapping each label "ID" (i.e [2:1:1] ) with the associated Shape in another data structure (like a dict) and retrieving the label from there
I think the first one is feasible but not really great and the second one would be feasible if there were a way to get the Label ID which was my initial question.

Anyway while writing this example I realize that a better solution would be to retrieve the TDF_Attribute holding myCyl and from there reaching the Label with the appropriate method. It would look like something like this (in Python):

Python:
myCyl_guid = 154
my_attribute = shapes_attributes_by_guid[154]
my_label = my_attribute.Label()

It seems obvious now but It wasn't when I created the thread. This post might be a little confuse, I am sorry for that, I'm learning as I go so...

In any case thank you for the ActiveData paper, the more I read about it the more I realize that I don't know anything of what I'm doing 😄

Cheers
 

Quaoar

Administrator
Staff member
Anyway while writing this example I realize that a better solution would be to retrieve the TDF_Attribute holding myCyl and from there reaching the Label
It seems like the source of your confusion was how to identify a certain object in the OCAF tree. Well, the ID of a label (object) is its entry, i.e., the string like "0:1:2" as you perfectly reflected in your data schema. As I understand, you were searching for a label/attribute that contains myCyl, and I guess this is the object of TopoDS_Shape type, right? If so, you were essentially looking for the object by its value (which is actually a pointer in that case, but that does not matter).

What you could have done alternatively (just a couple of things that could be useful) if you had UI:
  1. For an object browser tree (something like QTreeWidget for Qt), you might have stored the entry "0:1:2" right in the UI element (that is allowed in Qt and should be a common thing for other UI frameworks).
  2. For a visualization engine (VTK, AIS), you could have associated the entry "0:1:2" right with the presented object (actor for VTK and AIS shape for OCCT visualization).
For both scenarios, whenever the user selects an object interactively (either in the project tree or in the 3D viewer), you get the associated label ID "0:1:2" as a string. Once you're there, you could have called this TDF_Tool::Label() thingy passing there the entry and getting the TDF_Label out of it. Then, you could settle down your interface object on that label and continue working in the object-oriented fashion with your data.

That approach is something standard for desktop apps based on OCAF, but I'm sure you could elaborate something more tailored to your needs, and it seems you have done exactly this. Thank you for sharing your recipe, I'm sure we're getting a good practical guide for OCAF here :) Maybe we should cover TFunctions as well.
 

Jojain

New member
If so, you were essentially looking for the object by its value (which is actually a pointer in that case, but that does not matter).
As I am actually writing the code in python with bindings to OCCT and I have no idea of how things are handled in that case. And I am not used to think about those kind of things, so here might come the confusion, i.e mostly because of poor knowledge of C++ rather than knowledge of OCAF (I am actually not a software developer so that doesn't help either lol)

What you described in your points is actually what I would like to do, correct me if I'm wrong but the goal of this approach is to avoid transmit data by value and just pass references of the underlying data ? (As you said working with AIS_presentation of a 3D object rather than directly with the TopoDS_Shape ?) It's more efficient performance wise right ?

So I'm back again at the first question (kindof) how do you get a Label entry ? TDF_Label doesn't have a method that returns an Entry.
The only way I see is :

C++:
TCollection_AsciiString anEntry;
TDF_Tool::Entry(myLabel, anEntry);

What I find surprising is that one would have to use another library to retrieve the entry of a TDF_Label, to me that would be totally obvious that a Label knows about it's entry and could return it easily ?

Yes there is still a lot of things that seems nice like TFunctions, the Reference mechanism (the OCAF one), Transaction.
Going step by step here
 

Jonathan

Member
@Jojain

TDF_Tool::Entry(const TDF_Label& aLabel, TCollection_AsciiString& anEntry)

would be used like this :

C++:
TCollection_AsciiString anEntry;
TDF_Tool::Entry(aLabel,anEntry);

I believe the string value would be stored in the variable anEntry.

TDF_Tool:Label(const Handle(TDF_Data)& aDF, const TCollection_AsciiString& anEntry, TDF_Label& aLabel, const Standard_Boolean create = Standard_False)

C++:
    TCollection_AsciiString anEntry = "0:1:1";
    TDF_Label aLabel;
    TDF_Tool::Label(myDoc->GetData(), anEntry, aLabel);

aLabel will be set to the 0:1:1 label, in thise case, if it exists.. since create=Standard_False by default..
 

Jonathan

Member
@Quaoar ,

I do use the pattern you presented on your Youtube channel..

I create class which have a m_root tdf_label, everything goes down from there..

I also use enum to give children note names.. I find it easier than trying to remember the 3rd children is supposed to be some vertex ..

It works pretty good.
 

Quaoar

Administrator
Staff member
What you described in your points is actually what I would like to do, correct me if I'm wrong but the goal of this approach is to avoid transmit data by value and just pass references of the underlying data ?
What I wanted to emphasize is that these UI elements (an object browser and a visualized actor) give you a source of ID. If I understood you well, UI elements substitute your dictionary; why would you need a dictionary if you can grab the object ID right from a visible object? The dichotomy "by value / by reference" is actually not that important in this context. In the end, I think, it should all come down to this TDF_Tool::Label() invocation.

What I find surprising is that one would have to use another library to retrieve the entry of a TDF_Label, to me that would be totally obvious that a Label knows about it's entry and could return it easily ?
Welcome to OpenCascade architecture 🥴 It's Ok. Kind of. I mean, take TopoDS_Edge as an example: you cannot grab a curve from it directly, you have to use BRep_Tool for that, which is also an extra class. There are just some things in the library which you have to get used to. Or, alternatively, introduce your level of indirection to make it handier for your app.

I also use enum to give children note names.. I find it easier than trying to remember the 3rd children is supposed to be some vertex ..
That's a clever idea. This whole architecture of interface objects is the as-designed way of OCAF usage. People at OCC use it like that.
 

Quaoar

Administrator
Staff member
I moved this thread to OpenCascade Q&A as it seems a bit more relevant for this kind of "best practice."
 

Jojain

New member
@Quaoar
I agree with all your point, I'm glad we had this discussion because it's now way more clear to me how I'll be able to handle things.

About the occt architecture, at least it seems to be coherent across the framework ? When something obvious is not where you think it is, it would be in XXX_Tool ? I can live with that.

The need for the dictionary was to retrieve the label / Node ( or anything we call it) when typing a command. ( I aim to use cadquery and so writing cadquery code should populate the OCAf document)
It's clear now that from UI (either 3D view or tree view) i can retrieve the label easily. Thanks for your englightment !

About what said @Jonathan , I don't see yet how this would be a trouble but I'll keep in mind to use something similar if i come across the same difficulty !
 
Last edited:

Quaoar

Administrator
Staff member
About the occt architecture, at least it seems to be coherent across the framework ? When some obvious is now where you think it is, it would be in XXX_Tool ? I can live with that.
Let me share with you a little bit of insight here. One of the old community members of OpenCascade, who is now running a very successful CAD business (his product is based on Parasolid now), once said:

"Sometimes when I was digging into OCCT codes, I felt like someone wrote it without reading the necessary papers before. In terms of API it is also very very bad, probably OCCT has one of the worst code base I have ever worked with..."
I do not want to pick on the devs (after all, I was one of them for quite a while), and the last thing I wanna do is spreading a negative word around the library (it was, is, and is going to be just awesome), but there are things which you have to admit to grow up something better (at least your own API) on that understanding. There is no common architecture throughout the library and each module tends to have its own way of doing things. It's more like you just have to practice it for a while and get used to how things are done.
 

Jojain

New member
Lol thanks for the insight. As you said we are probably better off writing a better API on top of it so once it's done one doesn't have to struggle with the unlogical OCCT API.
While i don't know the history detail, given the age of the library, something that seems like bad architectural choice today was maybe a technical problem back then. (Or maybe it is not but what can we do about it after all !)
Anyway since I'm not going to rewrite OCCT, I'm fine with learning how it works 😄
 

Quaoar

Administrator
Staff member
I think you're right, and there was some motivation behind the scenes which we are simply not aware of. For example, the design of the TopoDS package (and the entire B-rep data structure) was a product of careful thinking. As far as I know, pioneers of CAS.CADE met with people from ACIS to come up with something clever at the end. Like this, for example, we do not have any attributes in B-rep (unlike in ACIS where we do have attributes attached to edges, vertices, and faces), and we use OCAF instead (B-rep is kept pure geometric and minimalistic). That was an intentional architectural decision. For the implementation details (I mean, things like ShapeTool, ColorTool, TDF_Tool, etc.), it does not matter that much if the whole idea is consistent, and I think it is.
 

Jojain

New member
Little update :

As I went a bit deeper in the project, It's getting more clear how I will handle my data.
By the force of things since I wanted to use a QTreeView, I had to subclass a QAbstractItemModel which helped me think about how to handle everything.

So what I have right now is :
  • A Model : QAbstractItemModel that is used to interface the GUI with the internal data
  • A Node top class that is used to construct trees. Each new Node is attached to it's parent, the tree of Nodes is responsible to hold the actual data.
  • A Node can be subclassed to be something more specific (A Part, a Feature, a Parameter, etc)
It's not done yet but I plan to attach to the Nodes the underlying OCAF data (i.e labels)

I think it can work not too bad but I feel a bit strange since my architecture will be in reality 3 hierarchical structures (QAbstractItemModel has it's own which needs to be connected to the Nodes that is another one which stores the OCCT ones)

It's like this for now, I might change it as I face new problems that make me rethink the design.

Right now it is looking like this : (there is so much to do and so little time to work on lol)


nales_alpha.gif
 

JSlyadne

New member
Hello @Jojain!

It's a typical data organization when dealing with OCAF and Qt - you can consider OCAF as a data source while the Qt Tree widget is one of many possible representations of these data. So you're on the right way here I would say.

I only have a concern about this your statement:
A Node top class that is used to construct trees. Each new Node is attached to it's parent, the tree of Nodes is responsible to hold the actual data.

Why not store the actual data directly in OCAF and keep a Node class as just an interface wrapping labels-related routine? In this case, you can be sure that data will be in order and won't be de-synchronized one day, and also, as a nice bonus, you can utilize Undo/Redo mechanism of OCAF. However, all of this makes sense if you plan to provide a possibility to save a session to the disk.
 

Jojain

New member
Why not store the actual data directly in OCAF and keep a Node class as just an interface wrapping labels-related routine?
You are right that's what I am going to do as @Quaoar also said this will probably ease the process. Node class will then allow me to interact with it with my logic leave the burden of dealing with OCAf to the class internals.

Thanks for the tips, since I'm mostly figuring things as I go it's good to have feedback :)
 
Top