realthunder / FreeCAD_assembly3

Experimental attempt for the next generation assembly workbench for FreeCAD
GNU General Public License v3.0
885 stars 74 forks source link

Performance issues #118

Open ceremcem opened 6 years ago

ceremcem commented 6 years ago

Currently my model uses lots of constraints. At the moment, lags are around 5-10 seconds, which leads - for example - rotating a screw to take 10-20 seconds.

Is there a known performance enhancement in plans, or am I misusing something?

Priority

This is a high priority issue because these lags causes very high distraction.

realthunder commented 6 years ago

Are you using release build of slvs? If not, you can try switch to release build. Same for FreeCAD, and pivy. Although, with this amount of constraint you are using, it is bound to get lag, especially the way you organize those assemblies. You can solve this by divide them into sub-assemblies whenever possible, so that already fixed part of the assembly does not take part in the recomputation.

ceremcem commented 6 years ago

Are you using release build of slvs?

I'm not sure, I just followed the build instructions [1] but I know I'm using debug build of FreeCAD. I'll build release versions and take a look at then.

with this amount of constraint you are using

I'll look what can I re-group into the same Assembly container.

Question: When I add one more constraint to the above example, would slvs re-compute whole constraints from the beginning? Or will it use a cache-like mechanism?

realthunder commented 6 years ago

It will recompute the whole constraints for that assembly. The solver will have to regenerate all equations involved in those constraints, which is usually not the bottleneck. The solver use some kind of gradient search algorithm, and if the new constraint does not change much of the position of the existing part, then the search terminates every fast.

The problem, however, is when you have redundant constraints, and it is very difficult to not have any, as illustrate in my tutorial. When you have redundant constraint, the solver slows down somewhat. And it will slow down a lot if you do a manual re-solve by click that solve button instead of relying on auto-solve. Because manual solve will try to identify the redundant constraints, and that is a slow process, especially if you have many constraints. I think I will add a new QuickSolve button to allow manual quick solve.

There is other problem that if you build your models based on the assembly, then a slight change of placement of a part may trigger lots of model recomputation, especially if expression is involved. BTW, I remember you asked in other post. The solver only moves the part if the movement exceeds some threshold, 1e-7 at the moment.

realthunder commented 6 years ago

To clarify a bit more about assembly recomputation, if you have a multiple level assemblies, and you changed some constraint in the sub-assembly, then the sub-assembly, and all its upper level assemblies will be re-solved.

ceremcem commented 6 years ago

redundant constraints

Sketcher also complains if there is a redundant constraint which I can't understand the reason. I must take some time to learn why redundancy is a problem in the first place.

Do we have an option to identify redundancies, like Sketcher has?

In the tutorial:

The default constraint solver can handle some level of redundancy, but if left uncheck and you keeps adding more redundancy, the solver sometimes may fail with no obvious reason.

That hit me once or twice. Which in turn requires removing the redundant constraints at the moment.

about recomputation

Think that we have a 1000 parts, assembled with 3000 constraints. "Solve constraints" command has to calculate all of them, this is natural. I would expect if we move the base part (that we locked, for example) every other part should be moved by that amount of displacement. In other words, computation shouldn't be required for the rest. I'm trying to understand the solver nature.

realthunder commented 6 years ago

Do we have an option to identify redundancies, like Sketcher has?

It is not always reliable. But if you turn off auto-solve, and do a manual solve, it will output redundant constraints as warning in the ReportView if it can find any.

I have just added QuickSolve button. For complex systems, you may want to turn off auto-solve, and do a manual quick solve if you've done placement adjustment, or added new constraints. Before hitting quick solve, you can select an sub-assembly, then it will only solve this and any lower touched sub-assemblies.

I would expect if we move the base part (that we locked, for example) every other part should be moved by that amount of displacement.

This is in fact the case if there is a higher level of assembly. And you are right, I should have special treatment of locked part move. I'll add that later. But still, to manage large assemblies, you have to divide and conquer.

ceremcem commented 6 years ago

The solver only moves the part if the movement exceeds some threshold, 1e-7 at the moment

Is that mm? I've faced with this case recently. Could it be because of that tolerance?

ceremcem commented 6 years ago

You can solve this by divide them into sub-assemblies whenever possible

There are cases this may or may not possible. For example, if a part is used more than one place with distinct constraints (for example, bolt in my example), they can not be put in a separate assembly. On the other hand, if some parts are logically grouped into one part (an already manufactured component, ie. RaspberryPi, or bolt) it is perfectly suitable for handling in a sub-assembly (even in another document). This point is highly related with #86 . If we could create "external constraints" within the assembly containers, we could be able to - i.e. - use only 2 parts (2 assembly containers) in a fully working space craft design document. I think, otherwise, there are nearly no cases that we could put the parts we produce in a separate assembly container.


Question: Are there any differences between:

  1. grouping some parts into a sub-assembly container
  2. grouping those parts in another document and creating link into the parent container

In the first case, we would experience some difficulties if we need to modify relative placements of the parts. When we use a separate document, we could freely move parts within the "sub-assembly container" (as it's not a sub-assembly container, it's an assembly container). Are there any differences apart from this detail?

ceremcem commented 6 years ago

Also, it seems that moving a part that has no constraints at all with the move tool also triggers recalculation. Might that be true? Isn't that unnecessary?

Edit

Scaling an independent dxf file also triggers something (maybe Refresh?) which takes ~15 seconds to finish while auto-solve constraints is off. This is also FreeCAD's Release build. There might be an obvious inefficiency in somewhere. From now on, it seems it's very hard to keep focused on the work.

realthunder commented 6 years ago

If auto-solve is off, then it is most likely not the solver's fault. Can I have the file with the dxf. I'll try to find out the bottleneck. PS. as a last resort, you can also right click a document icon in tree view, and select Skip recompute to disable auto recompute, which might be trigger by lots of FreeCAD features.

ceremcem commented 6 years ago

Reproduction

  1. Move the logo 1 mm in any direction
  2. See the cpu usage and the duration.

case-v3-20180919T0256.zip

If auto-solve is off, then it is most likely not the solver's fault

I agree.

select Skip recompute to disable auto recompute

I'm not sure if this would bite me in another aspect.

realthunder commented 6 years ago

I have pushed a few optimizations, but nothing really dramatic. The way you are modeling determines the amount of computation required for each change, such as that logo. To see recomputation time of each object, you can increase log level by type in python console,

App.setLogLevel('App',3)

After you recompute, there will be timing information of each object in the ReportView such as below. This log setting is persistent if you close FreeCAD normally.

353.032 <App> Document.cpp(3058): Recomputing enclosure#Group002
353.032 <App> Document.cpp(3058): Recomputing enclosure#Binder
353.034 <App> Document.cpp(3058): Recomputing enclosure#Cut003

The first number is the start time, so checkout next message for the time difference as this object's recompute time.

ceremcem commented 6 years ago

I have pushed a few optimizations, but nothing really dramatic.

There is an absolutely huge difference (I think amount of lag effect is exponential on user experience). I can use move tool with 1-2 seconds lag.

Currently auto-solve constraints makes a difference (~which is natural, of course~ (see below post)). Using the move tool on a newly linked object causes 1-2 seconds of lag while rotating the object while auto-solve is off and 5-6 seconds when auto-solve is on.

ceremcem commented 6 years ago
  1. Currently moving an object that originally belongs to the model (auto-solve is off) has 1-2 seconds lag. Clicking "ok" in the task window to close the move tool takes ~22 seconds with a massive amount of CPU usage.

  2. I'm unable to understand why an object without any constraint has an impact on computation while auto-solve is on.

  3. App.setLogLevel('App',3) doesn't work as intended. Is it only applicable to Debug builds?

realthunder commented 6 years ago

In ReportView, right click, select options->Logging.

1 & 2 both because you are using lots of binder, which links to the the binding object though assembly. So whenever any part inside the assembly is moved, the assembly has to be recomputed, then the binder, and then the fusion and cut, which takes up majority of the recompute time.

ceremcem commented 6 years ago

In ReportView, right click, select options->Logging.

That worked.

1 & 2 both because you are using lots of binder,

Yes, I use them a lot. If recomputation is really needed, that's okay. But adding a brand new object has nothing to do with an unconnected binder. So I would expect no recomputation for the binders at all.

realthunder commented 6 years ago

The dependency calculate cannot be that fine grained. It can only track object to object relationship, not property to property, which I am afraid may not be computationally feasible because of too many possibilities. So that means any thing changed inside assembly will affect binder, and in turn your models.

The solution, do not link the binder through assembly. It is only useful for cross coordinate linking. However, your top level assembly is always stationary, with no Placement. You directly link to the part object. Unfortunately, there is no easy UI for doing that at the moment. Say for example, type in python

App.ActiveDocument.Binder037.Support
(<Part::PartFeature>, ['Parts.Link036.', 'Parts.Link037.'])

To link to Link036 and Link037 without go through assembly, make a link group

Gui.Selection.clearSelection()
Gui.Selection.addSelection(App.ActiveDocument.Assembly,'Parts.Link036.')
Gui.Selection.addSelection(App.ActiveDocument.Assembly,'Parts.Link037.')
App.runCommand('Std_LinkMakeGroup')

And drag that group to the binder, or with code, assuming the new group's internal name is LinkGroup

App.ActiveDocument.Binder037.ViewObject.dropObject(App.ActiveDocument.LinkGroup)

To make it easy to see what the binder binds to, set its ClaimChildren

App.ActiveDocument.ClaimChildren = True

Now the link group will be group under the binder.

ceremcem commented 6 years ago

The dependency calculate cannot be that fine grained. It can only track object to object relationship, not property to property, which I am afraid may not be computationally feasible because of too many possibilities.

I want to say something about this but first I should think on it throughly and at least understand the underlying problem.

The solution, do not link the binder through assembly.

I read it over and over again, but I didn't understand what path to follow and why.

realthunder commented 6 years ago

I read it over and over again, but I didn't understand what path to follow and why.

Refer to the Binder037.Support, which is the property stores what this binder binds to

(<Part::PartFeature>, ['Parts.Link036.', 'Parts.Link037.'])

The first entry of the tuple gives you the object that this binder directly binds to, and it is the top level assembly. The second entry, which is a list, shows the sub-elements. In this case, the sub-elements are two sub-objects. They may not be objects, they can be faces or edges, too. So, when FreeCAD sees this Support of Binder037, it only knows that it depends on the top level assembly. FreeCAD does not care what sub-element/objects are used, because it has no knowledge of how they are are going to be used.

Same logic applies to the Assembly object. It has a property Group similar to Support of binder, that links to every part object. FreeCAD sees that the Assembly depends on those part, so whenever any part object changes, the assembly has to be recomputed, after which the binder will be recomputed, after which the fusion/cut object using the binder will be recomputed. FreeCAD will perform a topological sort using object's dependency information to make sure the objects are recomputed in the correct order.

Now back to your model. Do you really need to bind through Assembly? If it is sub-assembly, which means it is being assembled with some placement, then the binder will provide the correct placement information if you bind through that sub-assembly. In your case, you don't. You are binding through a top-level assembly, with no placement.

If you follow the PartDesign approach, then the desired workflow is to create the body, add the body into the top level assembly, and then use the binder to bind to other sibling parts. The binder creation command sees that the body and the binding parts are inside the same container, and thus binds them directly. BTW, binder can only bind to one object directly. So you may need multiple binders.

However, you have chosen the Part approach, where the binder command cannot determine its current container before creation, and thus have to bind through the top level container, and cause this dreadful over-depending problem.

Apart from the solution I mentioned above, remember you suggested to implement the auto link correction on drag and drop? If that can work, then the problem can be solved by simply move the fusion/cut object into the assembly. I will try it later.