Steps forward to assembly infrastructure

Thanks for the detailed explanation DeepSOIC… Understanding better now. This is a more specific issue than I thought, sorry about the noise… I don’t have a very strong or useful opinion… Seems to me someone could need both ways, depending on the situation…

DeepSoic, I think I start seeing where your idea is heading. So basically you still build the graph structure, but with the introduction of a Instance you try to achieve the following two points:

  1. Hide that complexity from the user by showing him a tree only
  2. Making a clear object hierarchy with the master object and its instances

In regards to your picture with the Instance (Clone) I see the graph in the following situation: Part and Instance are again in a another Part. What you mean when talking from a tree is that from every Instance there is only one path to the graph top, not many possible.

[quote=“ickby”]

  1. … Getting the global position of a shape? Not possible.
    [/quote]

I already said how it is to be done: Selection object should include the list of instances, through which the object was selected. Nothing impossible.

Having this information in selection only is not enough. One needs an API to query a objects positions in the document on App level, even if it forms a graph in reality. Initially I thought you try to prevent that with a tree structure, but I think I misunderstood you there, as you only hide the graph from the user. So such an API is easily possible, one would add a few functions to DocumentObject like “getAllInstances()” or “getAllPathsToObject()” which allow to handle the graph, with instances or without doesn’t matter much.
This also means that you still would do a lot of “bottom-up” searching of the graph (e.g. from object to all objects linking to that object) where the current document object model is not well suited for. Hence jriegels adoptions are still valuable for speeding this up.


DeepSOIC: there is a master Part, that is editable, and instances of the Part, that aren’t editable. Deleting master part will delete actual geometry, and break all its instances.
vs.
ickby: there is no master part. Any “instance” of part is just another part re-using some objects. Any “instance” can be deleted with no consequences; only when the very last instance that uses the objects is deleted, the actual objects (geometry) will get lost

I thought about this today too, this is a clear advantage of your approach. No need for reference counting.

A advantage of mine is that we need no control if a object already belongs to another Coordinate system, and things like “instance of instance” must also be forbidden. But that is nothing impossible.

So honestly after I think I fully got your idea it does look pretty good. I would still like to think a few days about it, you know, I’m a marked child regarding failed assembly structures and want to be sure there is not a show stopper we miss.

Ok a few more thoughts, as always open for discussion and for proving me wrong :slight_smile:

  1. My assumption " from every Instance there is only one path to the graph top" is wrong. As one can stack assemblies and parts wildly the following is easily achived. A simplified example, where Instance1 is at 2 places, but many more complex things are possible (e.g. nut is 10 times in motor which is 5 times in machine):
     CS2           CS3
       |              |
Instance2       Instance3
        \           /
          \       /
            CS1
              |
          Instance1
              |
            CS0

This means even with instances full graph handling is needed. The only valid identifier is a full Path from toplevel to object. Hence the implementation of the CS structure cannot be simplyfied with instances. DeepSOICs and my approach would lead to nearly the same graph handling code.

  1. A objects location cannot be determined by its instances only. There may be no instance of a object and still it may be 100 times in the document. That happens if an assembly it is in has instances. If one wants to know all occurences one must hence not search the objects instances but all paths from this object to the graph toplevel. Each occurance can only be described by a path of stacked CS’s. This means the DocumentObject API for handling this must allow retreiving all Paths to this object. Hence it is exactly the same for DeepSOICs and Ickbys approach.

  2. Instance must be of type GeoFeatureGroup. Considering the two points above I think the Instance object must be of this type to allow building CS paths as simple lists of GeoFeatureGroups. It would than seamlessly integrate into other types of GeoFeatureGroups like the current Part or coming Assemblies. If it would be of any other type a tremeandous amount of extra type checking and handling would need to be introduced.

  3. Placement considerations. In ickbys approach once a CS or object is added two times to anything both would have always the same placement. This would be unintuitive: If the user reproduces an assembly he expects to give it different placements. In ickby approach he must first introduce annother CS where he puts in the copied one and move this. This is strange. This step of an additional CS is automated by DeepSOICs approach, as the Instance object would be that extra CS by default. This is IMHO more intuitive.


    So I think code wise both approaches are pretty equal, all the hard stuff definitely is. The differences come down to:

  4. Tree: Showing all childs vs. Showing instance object

  5. Master object vs one objeckt multiple times: difference in deletion and editing behaviour

  6. Placement: Automated possibility of changed placement with Instances vs. user must add additional CS.

I think in all 3 of those points DeepSOICs idea wins, even in point 1 I changed my opinion after thinking more about it.

Hi Normand,
I have used for some years that software (what it add a colon) and I like the philosophy to mark the various files with different extensions. But “that” software uses for every environment (draft, asm, part, sheet metal…) only specific workbenches. In my opinion it is useful because prevents to add a wrong feature to a specific and delicate object (sheet metal with different thickness, directly edit an assembly instead of a his part…) but I think this is “less freecadized” (against his philosophy) and more “solidedgeized” and we should rewrite many things to separate workbenches for each enviroment…

Editing the part would open the FCPart document separately. It could be done from any instance shown in the tree, not only from the “original” one.

Now that I think about it, it is exactly what the Assembly2 module does.

I confirm and I like that.


Marco_T

And what about performance. Having for example 1000 instances of the part (@DeepSOIC) vs. 1000 times reusing objects form the part (@ickby)?

I think neither approach has advantage, as with so many objects, rendering/selection will be the bottleneck. Or.. maybe ickby’s approach is at win here, because one could select an object in tree. With instances, assuming they don’t allow to expand their content in tree view, only picking in 3d view will be possible.

If we really would want to achieve that it could happen with your approach couldn’t it? Anyway if there isn’t any fundamental difference good. But if one approach would have a clear advantage (performance) that would need to be acknowledged.

And what about using the assembly geometry after directly. To add additional feature to it (only to one “instance” and not all of them). Would any of the proposed approaches provide that?

In ickby approach - yes, it is trivial. Just activate the “instance” (which is a Part container most likely), and add extra features to it. With my approach (instance objects) - no you can’t. You must either edit the original Part, which will automatically propagate to all instances of the Part, or create another Part with the new features, and replace the instance.

This is probably the main big advantage for ickby approach. Yet at the same time it is a disadvantage, because updating all same parts at once becomes a problem.

I see therefore in your proposal instead of using the instance of the part 1000 times one would need to use it 999 times. And for that one instance that needs to have additional feature added. Basically a new part would be created with the added feature and that would be used instead.

There is one thing i don’t understand when it comes to @ickby proposal. In tree view there would be only one entry? Similar to using the Sketch multiple times and only one Sketch is listed in the tree view. How would that work? What is the actual geometry in the end? A copy/duplicate/clone or something else? As there are multiple solids of the same part in 3D View and only one Part feature tree in the Assembly tree view?

Actually, your assumption is almost right. You should replace “graph top” with “top of the document”, or “global CS”, and “one path” with “container path”.

The word “path” is also ambiguous. Let’s define some terminology again.
PATH: sequence of objects to consider when resolving an object
RELATIVE PATH: similar to path, but we must clearly express what objects to leave and what objects to enter. Let’s notate them with + and - signs: “+” means go into container, and “-” means leave container. + and - correspond to whether we take placement as inverse or not when computing transformation.
FULL PATH: includes plain containers, moveable containers, and instance objects
CS PATH: take full path, and keep only things that affect coordinate systems.
INSTANCE PATH: take full path and throw away everything but instance objects.
CONTAINER PATH: full path that has no instance objects involved (if full path has instances, it cannot be converted to container path).

With my approach:

  • container path to top of the document is unique for each object. (document is also a kind of container, just a little bit of a special one)
  • full path can be completely recovered from instance path and the context to where we are resolving it.
    Since full path can be recovered from instance path, I propose to remember instance paths only, and recover full path on the fly.

Let’s consider a reasonably complex example. This is how tree view will look like:

Document
    Part
        Box
    Part001
        Cylinder
        SubPart
            Wedge
    SubAssembly
        instance(Part)
        instance001(Part)
        instance002(Part001)
        Part002
            sphere
        Torus
    FinalAssembly
        instance003(SubAssembly)
        instance004(Part002)
        Constraint

Container path of Wedge (unique): +Document +Part001 +SubPart

All possible full paths to Wedge (i.e. count how many times to render Wedge):

  1. +Document +Part001 +SubPart +Wedge
  2. +Document +SubAssembly +instance002 +SubPart (note: Part001 is not included on purpose, it is replaced by Instance)
    3 +Document +FinalAssembly +instance003 +instance002 +SubPart
    How to gather all these paths by code? 1 is container path, that always exists for any object and is unique. Next, find all instances of any object from the container path. Instances of Wedge? no. Instances of SubPart? no. Instances of Part001? yes, instance002. Now repeat this for finding all paths to instance002.

Let’s recover path 3 given only instance path and target object: +instance003 +instance002 Wedge

  • prepend container path to instance003: +FinalAssembly +instance003 +instance002
  • instance003 is instance of SubAssembly. Insert container path from within SubSassembly to instance002. This path is trivial, so nothing to do (instance002 is already within SubAssembly). → +FinalAssembly +instance003 +instance002
  • instance002 is instance of Part001. Insert container path from within Part001 to Wedge. → +Document +FinalAssembly +instance003 +instance002 +SubPart
    Done.

Container path from Wedge to Box: -SubPart -Part001 +Part
Container path from Wedge to Sphere: -SubPart -Part001 +SubAssembly +Part002
There are other paths possible from Wedge to Sphere, but these other paths involve instances, so they are not container paths.

Let’s try to recover broken path given only instance path and target object: +instance002 +instance003 Wedge

  • prepend container path to instance002: +SubAssembly +instance002 +instance003
  • instance002 is instance of Part001. Insert container path from within Part001 to instance003. This path is -Part001 +FinalAssembly. It contains something with minus sign → ERROR!



    Logic to test if direct linking from object A to object B is possible: Figure out relative container path to get from A to B. If there are containers with placement in that path <=> linking is impossible.

See point 4 of the very first post:

I see. Therefore tree view itself would need to be adapted for this change. And after having a representation of the same part multiple times would be possible. In addition each part could i guess be expanded to see all the features in it. To be honest that doesn’t sound all that bad.

And what about the actual geometry in 3D View? How would that be done? For each “copy” of the part i am guessing each feature could be accessed directly? Wouldn’t that introduce huge performance penalty? Or would there be some smart mechanism involved and performance penalty would be basically the same as when using single part?

Whatever you need to do. please don’t hide a DAG structure behind a tree view.


Have you guys considered drawing something up to describe your document/assembly structures? A picture is worth a thousand words! Here is a screen dumped example. You can find the file in ‘src/Gui/DAGView/DAGViewerDesign.svg’(inkscape)
Screenshot_20161011_101800.png

I’d be curious to know what was done. I thought of that too, and the best I’ve come up with is that as soon as InList of anything is queried, document graph is built and cached, so that calls to InList of this and other objects that will follow will be super fast. Then, if document changes, the cached graph is either updated, or simply thrown away. The bad thing about that is that getInList will become non-const and non-reentrant.

This is your advantage in this case, IMO. At least you know something that lead to trouble in past. I don’t know, so I can fall into that trap too.

And one more thing that probably must be taken into consideration upfront. Array support.
Array of instances, I mean. Arrays can be dynamic in object count. So, during recomputing, instances may pop up and vanish.

I see two approaches:

  • generate and delete instance objects during recompute
    or
  • collapse the array into one instance object

I think the easiest way is the second one - “array instance”. It will be a single document object, but it will render the linked Part in multiple placements. Consequently, paths must include array item number when an array instance is in path!

In general it is rather straight forward. Every DocumentObject has a std::vector InList that holds all objects that link to it. Than all PropertyLink properties are adopted to keep this list up-to-date. Every time a link is made or dropped the objects Inlists are updated. I actually like it very much, keeps the updating-work localized and the effort minimal compared to current way of doing it. The only downside is the need to really find all link codes.

Sounds good to me.
In addition to PropertyLink-derived properties, expression binding should be tracked too. Or, alternatively, add a specialized path-tracking variant of InList, that is only updated by containers/instances.

I don’t quite understand what you mean. But. That made me think of tree view, and ickby’s proposal to make tree view show an object multiple times.

First, I thought, OK! great, I’ve been wanting this long before I saw Part/Body containers! But then, after a bit of thinking, I realize it’s not as great as it was at first glance.
First, imagine this situation.
cross-container-link.png
The tree view might show this:

Document
    Part
        Extrude
            Sketch
    Part001
        Sketch

Now this is misleading, because Sketch is not in Part, but it appears like it is. This is easily revealed by hiding Part001 - Sketch suddenly gets hidden, even though Part is visible.
So what I would like to see, is that tree filling algorithm must be redesigned to have containers hard-coded into it. It should take children of container, and compute a subtree made from only those children. So that the tree will look like this:

Document
    Part
        Extrude
    Part001
        Sketch

Then, I realized there is another interesting problem. If we are to show every claimed child, the size of the tree can grow exponentially with the number of objects. A specific example that demonstrates the possibility is this:
exponential-tree.png
This will generate ~160 items in tree, and adding another layer will roughly triple that number. So, adding about 14 more layers like that will make the computer run out of memory trying to build the whole tree.
Of course, it’s a worst-case scenario, but it’s just a proof that this can be such a problem. It can be avoided of course by building the tree on-demand, at the moment user expands the subtree.

Currently tree view does behave in a way when adding more and more items to it selection operation in the tree view starts to get slower and slower. Therefore i do imagine adding the whole part with its history to it (for each part “copy”) could potentially influence performance. And i do imagine the situation in 3D view would become similar.

Comparing it to “Draft Clone” like assembly solid feature where each part “copy” is just another (standalone) feature (made on top of the “Part Tip”).

I feel that 2 concepts are competing here.

1.) More “Draft Clone” alike and things can be figured out sooner. As after all Assembly2 module figured out a lot of it already. Generic assembly solver would likely work on all sorts of geometry not just on “Part Tip”. Users would likely grasp the concept quickly.

2.) More complex and likely idealised vision. That is you can move the whole parts with all it features in 3D View easily and edit them in place when assembled… This in my opinion is more PartDesign centric solution and a lot of things are currently only a theory. I feel that nobody really knows the potential caveats ahead. Workflow involved would likely be more powerful but at the same time more hardware intensive and likely harder to grasp due to the fact adding complexity in the back-end by developers usually results in complex interaction in the front-end. Likely due to the fact resources are extremely limited and it is impossible to think about front-end interaction more in depth.

Now if things don’t change there are 3 potential developers capable of progressing this. It looks like only 1 will work on it in foreseeable future.

P.S. Potential possible strategy:

Assembly2 → Results (focused on concept 1 and Python based) → FreeCAD 0.17
Assembly1 → Long term FreeCAD assembly infrastructure related work (focused on concept 2 and beyond) → It’s done when it is done.