App.ActiveContainer

Discussion about the development of the Assembly workbench.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

App.ActiveContainer

Post by DeepSOIC »

Hi!
I have done some serious change to FreeCAD, which affects everything. For that reason, I am asking for a bit of testing, just to know that workbenches I'm not familiar with are still working (i.e. Arch, FEM, and maybe some add-ons). Make sure you disable Part-o-Magic, if you have it.

https://github.com/DeepSOIC/FreeCAD-ell ... Container1


------------------------------------------------------------------------
Introducing: App.ActiveContainer, and Container interface class

== FOR USERS ==
ActiveContainer.png
ActiveContainer.png (259.44 KiB) Viewed 4223 times
From now on, body activation and part activation have FreeCAD-wide influence. If a Part is active, any new object is automatically added to it (including new Parts).

Same applies to PartDesign Body. As a result, attempting to create a Cube from Part workbench when PartDesign Body is active will fail with an error. Note that new sketches from Sketcher workbench will be accepted by Body, as well as most Draft primitives. That means you can create Draft ShapeString in a Body.

Also, you will have to deactivate a Body before creating a new Body (to deactivate a body, double-click it).

It used to be possible to activate a Part and a Body independently, which didn't really make sense. This was removed. There is only one active container now. Activating a Part will deactivate a Body.


== FOR PYTHON SCRIPTERS ==
ActiveContainer in console.png
ActiveContainer in console.png (29.96 KiB) Viewed 4223 times
From now on, the recommended way of creating new objects is:
App.ActiveContainer.newObject(...)
Doing so, you are specific about that you are creating an object into active container. If you for whatever reason want to create an object into any other container, use:
App.Containers.Container(App.ActiveDocument.Group).newObject(...)

All calls to App.ActiveDocument.addObject(...) are implicitly redirected to App.ActiveContainer.newObject(...). You can use App.ActiveDocument.newObject if you want to ignore active container.

You can create a standard interface around any object-containing thing like this: App.Containers.Container(thing). Thing can be a Document, a Group, a Part, Body, or Origin (Origin is there only for querying its children; adding objects to there is blocked).

The standard interface offers object creation, deletion, withdrawal, children enumeration, etc. It offers a canAccept() method, which can be used to test if an object of certain type can be created in the container. This is to be used in Gui Command code to gray out the button if object can't be created. I even think, it is worth adding another field to Command definition, which will make this test implicit.

Container() is an interface object. It has very small memory footprint, and operates on the underlying document/documentobject. It is OK to create it on the fly as needed.

To subscribe to changes of active container:

Code: Select all

class ACObserver(object):
    def slotDocActiveContainer(self, doc, newContainer, oldContainer):
        # called whenever active container in a document changes
        pass
    def slotAppActiveContainer(self, newContainer, oldContainer):
        # called whenever App.ActiveContainer changes. This can happen either because 
        # active container in active document changes, or because active document is switched.
        pass
observer = ACObserver()
App.addDocumentObserver(observer)
Active body can no longer be queried like this:
body = Gui.ActiveDocument.ActiveView.getActiveObject('pdbody')
Instead, use App.ActiveContainer.Object, or App.getDocument(name_of_document).ActiveContainer.
@shaise: you'll have to patch up sheetmetal again, sorry =)

== FOR C++ ==

In C++, Container object works very similarly.
App::GetApplication().getActiveContainer() returns a Container object. For everything else, Container interface should be constructed on the fly like so: Container(object)
It is intended to be handled in a by-value manner, simplifying memory management.

In C++, the container interface is even more useful, because it should eliminate a lot of type casting.

== IMPLEMENTATION DETAILS ==

Why interface class?
The options I was considering were:
1) just add ActiveContainer, assign object to it (either Group, or Document, or None) and do no more.
2) use None/nullptr for when document itself is active.
3) add common interface to Document, GroupExtension, and Origin. This could be by either:
3.1) make Document and Origin inherit from GroupExtension.
3.2) make GroupExtension inherit from Document, and Origin inherit GroupExtension
3.3) make Document, Group and Origin inherit from a standalone interface (abstract) object.
4) implement an independent interface object, which knows the three container types and translates calls.

Option 1 was the first thing I did. Then, when I started porting PartDesign and other things, it quickly became clear that dealing with three cases, and doing corresponding rather lengthy type casting was very annoying and inproductive. The final problem came up when I gave a try at porting object duplication. There I realized that Origin is causing trouble: first, it wasn't recognized as being in the Part/Body, second it was a kind of container itself. So I had to do something about it.

Option 2 really doesn't look very different from 1.

Option 3 is interesting, especially 3.2. Actually really interesting. It could allow to store Parts in files, and to have several objects with the same name in one project. But this means file format has to change, and .. the way viewproviders are organized, expressions and... well.. almost all FreeCAD core has to change, all at once. That will probably require forking FreeCAD and long development by a team. I'm definitely unable to do that.

So what I have done is option 4. It isn't as extensible and cool as 3.2, but it is a relatively small task that I can manage. Also, extensibility is provided by GroupExtension thanks to @ickby, so it is unlikely it will ever be needed to add more fundamental container types.

Why not 3.1 or 3.3? Because I remember the trouble @ickby had introducing extensions, and dealing with multiple inheritance. So I decided that option 4 is better just because it's dead simple. In addition, it allows to deploy another container-like system I plan to call Workspace, without much trouble.

-----------

Container remembers the object by taking a Python reference to the object. This is for delete-proofing (e.g. if the object the container interface is around gets deleted, the pointer will become invalid and crash may result). I originally made it use a raw pointer and subscribed to deletion signals to wipe out the pointer... but when I came up with the Py-like way of doing it, the benefits were obvious (no boost rubbish i.e. manual copy-constructor implementation, no performance penalty).

I've made a small experiment with conversion of C++ exceptions into python exeptions. Container defines quite a number of different exceptions, and I made these exceptions know their corresponding Py exception, which is used when translating the exceptions in PyImp. It is rather successful, so it may be worth adding this for all exceptions, not to just ContainerErrors. @abdullah: you said you want to propose some change to exception system. I'd be curious to know if it is the thing you wanted to do or not. Check out App/Containers/Exceptions.h-.cpp

I decided to not include my changes to Draft Autogroup, where I made it tied with active container. I'll leave that to Yorik to sort out, because I have poor understanding of Draft and Arch. I find it a bit unfortunate, because Draft AutoGroup became a rather nice display/set widget for active container.

I removed ActiveContainer management from Viewer's ActiveObject system. Even though I think it is a rather good idea to make it possible to have different active containers in differen views of the project, this needs the ability to have different states of viewproviders for each viewer. I think it's not coming any time soon. I haven't taken down the viewer activeobject system completely, and active container can be easily tied up to that system again if necessary.


== NEXT STEPS ==

This is only a beginning. I plan to create Workspace interface, which is similar to Container but gathers objects sharing a coordinate system transform. I also plan introducing ContainerPath and WorkspacePath objects. WorkspacePath's primary purpose will be to compute placement transforms, and ContainerPath is useful in Part-o-magic, hence will be useful in FreeCAD Gui as well. Before I spend time implementing those, I want the approval of the Container concept, the approval being a merge =) .
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

Re: Test request: App.ActiveContainer

Post by DeepSOIC »

just pushed a fix. Duplication should now work better.
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

Re: Test request: App.ActiveContainer

Post by DeepSOIC »

If Draft AutoGroup is enabled, a new object somehow ends up in both the group and active container. Strange, I can't figure out why.

EDIT: aha, Draft AutoGroup modifies Group property directly instead of using addObject, that's why.
User avatar
looo
Veteran
Posts: 3941
Joined: Mon Nov 11, 2013 5:29 pm

Re: Test request: App.ActiveContainer

Post by looo »

Maybe you can add some tests. This would make it easier for us to test you implementation. I ask because this branch has some conflicts with py3. And I want to make sure, not to have done anything wrong with resolving mergeconflicts.
ickby
Veteran
Posts: 3116
Joined: Wed Oct 05, 2011 7:36 am

Re: Test request: App.ActiveContainer

Post by ickby »

Hey deepsoic, this looks pretty cool. I give it a test ride over the weekends.
triplus
Veteran
Posts: 9471
Joined: Mon Dec 12, 2011 4:45 pm

Re: Test request: App.ActiveContainer

Post by triplus »

I did a few tests and i like the fact more general and FreeCAD wide approach is used. Some thoughts i had when testing.
  • When a feature is added developer needs to decide what kind of a feature it is. Therefore it doesn't make much sense to add Drawing Page or a Spreadsheet to such container. And therefore such features are always added to the active document instead.
  • When it comes to adding features in active container. I feel that any feature should by default be added to the active document. As for the active container. Document can have one or more of such containers and it should be possible to de-activate all of them. In this state all features are added to the active document. If there is an active container available in the document feature gets added in it (if not explicitly set to be always added to an active document).
  • There should be some indication or a mark in the tree view indicating what is the current active container.
  • If there are 2 containers and user attaches a sketch on a feature in a non-active container. Active container flag should i guess adapt accordingly.
I asked a question here:

https://forum.freecadweb.org/viewtopic.php?t=21559

I am starting to feel like there shouldn't be any Part/Body distinction. There would only be a Container. Single Container equals Body. If Container contains another Container that would equal to what we now call Part. Terms Part/Body/Container are all fine but i started to lean forward the (regular user) term Coordinate System (CS) or more technical term and to be more in alignment with other "datum" terminology. And that is a Reference Frame (Frame).
  • Therefore user could insert Part Box feature directly in active document.
  • Active Coordinate System/Reference Frame feature could be provided by the user. Part Box feature would go in it after. This is what we call Body ATM.
  • If Coordinate System/Reference Frame feature would have other child Coordinate System/Reference Frame feature(s). This is what we call Part ATM.
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

Re: Test request: App.ActiveContainer

Post by DeepSOIC »

triplus wrote: Therefore it doesn't make much sense to add Drawing Page or a Spreadsheet to such container.
IMO it does make sense to add a spreadsheet to active container, because the spreadsheet is likely to control stuff that is in the container. Otherwise, deactivate it, or grag it out explicitly (when drag-drop gets finally fixed). Same for drawing page, I might be making a drawing of a particular part, and it might make sense to add the part to active container.

I'd love if you prepare a list of objects that shouldn't obey active container. So that once we agree, I can shoot them one-by-one.
Like:
* Spreadsheet: to root
* Drawing: to parent container (and change active one accordingly)
...
triplus wrote:Terms Part/Body/Container are all fine but i started to lean forward the (regular user) term Coordinate System (CS) or more technical term and to be more in alignment with other "datum" terminology. And that is a Reference Frame (Frame).
I am about to call that "Workspace"
"Container" is any object that can be activated to receive new objects.
"Workspace" is a set of objects sharing a coordinate system
For example:

Code: Select all

Document
    Object1
    Group
        Object2
    Part
    	Object3
    	Group
            Object4
Document, as a container, contains Object1, Group, and Part.
Document, as a workspace, contains Object1, Group, Object2, Part
Group is a container, but not a workspace. All objects it contains are in Document workspace.
Part, as a container, contains Object3, and Group
Part, as a workspace, contains Object3, Group and Object4

Workspace concept is important, because within a workspace, objects can freely geometrically link to each other, with no need to look for extra placements caused by moved containers. Yet geometric links across workspaces generally needs to account for the difference in workspace placement in one way or another.

-----------------

To all:
I am doing a slight rework of the code (to use Container class more extensively). Please wait a bit with testing :oops: .
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

Re: Test request: App.ActiveContainer

Post by DeepSOIC »

triplus wrote:[*]There should be some indication or a mark in the tree view indicating what is the current active container.
It should be highlighted in light blue. Does it work for you?
I am not a fan of this blue highlight, it is too easily confused with selection. I can try a different highlight, or even expose it as a setting in preferences.
The enum lists these possible variants:

Code: Select all

/// highlight modes for the tree items
enum HighlightMode {    Underlined,
                        Italic    ,
                        Overlined ,
                        Bold      ,
                        Blue      ,
                        LightBlue
};
I haven't looked if they are all functional, and if it's possible to add more.
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

Re: Test request: App.ActiveContainer

Post by DeepSOIC »

DeepSOIC wrote:To all:I am doing a slight rework of the code (to use Container class more extensively). Please wait a bit with testing.
Rework completed!

I was changing App.ActiveDocument.ActiveContainer to return and accept a Container object instead of plain object. This also made me change arguments in signals/observer to use Container objects as well, and patch up a lot of stuff that went broken...
triplus
Veteran
Posts: 9471
Joined: Mon Dec 12, 2011 4:45 pm

Re: Test request: App.ActiveContainer

Post by triplus »

DeepSOIC wrote:IMO it does make sense to add a spreadsheet to active container, because the spreadsheet is likely to control stuff that is in the container. Otherwise, deactivate it, or grag it out explicitly (when drag-drop gets finally fixed). Same for drawing page, I might be making a drawing of a particular part, and it might make sense to add the part to active container.
Could be. ATM i perceive it more as a place where geometry should go. And not as a feature used for grouping/organizing everything. This perception i guess could change in the future.
I'd love if you prepare a list of objects that shouldn't obey active container. So that once we agree, I can shoot them one-by-one.
Like:
* Spreadsheet: to root
* Drawing: to parent container (and change active one accordingly)
...
I doubt creating and maintaining such list makes much sense. There i guess should be a mechanism and developers need to decide by using that mechanism. If the feature should go in active container (in active Coordinate System) or not. If not active document (therefore global Coordinate System if we are talking about something that ends up in 3D View) is used.
"Container" is any object that can be activated to receive new objects.
"Workspace" is a set of objects sharing a coordinate system
Will this scale to other workbenches if and when they adapt? And doesn't it complicate things? As by saying here it is. A Coordinate System feature you can use. That isn't hard to understand? And can scale easily to all workbenches?

We don't have the need to call global coordinate system a workspace? As it's more understandable when you say Coordinate System? And basically what you are describing is a Body? With i am guessing only one thing changed. And that is a single solid isn't a requirement?
Workspace concept is important, because within a workspace, objects can freely geometrically link to each other, with no need to look for extra placements caused by moved containers. Yet geometric links across workspaces generally needs to account for the difference in workspace placement in one way or another.
Or in more understandable language one could say geometrical links inside single Coordinate System work good. Cross Coordinate Systems geometry links can be troublesome? And that is more or less it. Any FreeCAD user can understand at least up to a point what is it about. Without looking in documentation first.
It should be highlighted in light blue. Does it work for you?
Now i see it. I guess i did mistaken it for "selection".

P.S. What could be a bit troublesome (too me) is all this double click action needed to change containers. It's slowing down the workflow.
Post Reply