assembly without solver

Discussion about the development of the Assembly workbench.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
realthunder
Veteran
Posts: 2190
Joined: Tue Jan 03, 2017 10:55 am

Re: assembly without solver

Post by realthunder »

Zolko wrote: Wed Jan 09, 2019 11:49 am BTW: what's the difference between .Placement and ._pla ? Both seemed to do the same
_pla is a pseudo property that gives you the accumulated placement of the sub object along the path, while "Placement" is the tail object's Placement. With your expression, you actually missed Body.Placement.

Zolko wrote: Wed Jan 09, 2019 12:28 am So what are the next steps ?
Have you read my BOM demo in the expression document? You can build a spreadsheet to let user attach LCS step by step. First, select document, then an assembly, then a pair of parts, then available LCS of each parts. You can use the following expression to create the placement binding.

Code: Select all

Part_1._self.setExpression('Placement', expression_string)
The 'expression_string' above is a string of the generated binding expression.
Try Assembly3 with my custom build of FreeCAD at here.
And if you'd like to show your support, you can donate through patreon, liberapay, or paypal
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: assembly without solver

Post by Zolko »

realthunder wrote: Wed Jan 09, 2019 10:17 am You are reading the wrong document. These syntaxes are only available in my branch, which is documented here.
in your explanation, I read:

My Link branch FreeCAD went through major upgrade to bring you full object hierarchy information through this SubElementName field, as a . separated string
is this "." separator a good choice ? I have often used "." in assembly names, especially for multiple instances:
  • Part_1.1
  • Part_1.2
  • Part_1.3
  • Part_1.4
Would that still work ? Is this the reason your branch transforms "." in names to "_" ? (as I've read in another thread). Did-you consider other separators, like "::" or double-underscore ... ? I've actually been hit by this.
try the Assembly4 workbench for FreCAD — tutorials here and here
triplus
Veteran
Posts: 9471
Joined: Mon Dec 12, 2011 4:45 pm

Re: assembly without solver

Post by triplus »

I see use cases:

https://forum.freecadweb.org/viewtopic. ... 00#p277800

But IMHO you won't be able to sell this as a general and a direct replacement for a robust assembly solver. For one bolts in an assembly usually don't stick in all directions. ;) Usually end user does need more control, over such geometry in an assembly. Don't read this as i am opposing your suggestions. I am fine with them and i am sure they will complement other assembly capabilities in FreeCAD nicely. In addition i do feel that such use cases are likely already possible. For example if you want to create an empty assembly structure up front:
EmptyAssemblyStructure.png
EmptyAssemblyStructure.png (42.72 KiB) Viewed 3849 times
Part feature provides LCS. You could for example add 6 of them and constrain their placement to form a hexagon. Using expressions, attachment capabilities or by adding assembly solver relations (LCS used for reference). No geometry added yet. After both @saso and @Zolko can start working on an individual part. If they prefer the top-down design approach. About creating an Airbus, somebody, a lot of them, needs to try it out and report back, i guess! ;)

All in all i feel we are on the same boat. That is we want to assemble with FreeCAD and would like our preferred workflow to be supported. Luckily that usually does happen as FreeCAD does provide more ways to tackle some use case.
realthunder
Veteran
Posts: 2190
Joined: Tue Jan 03, 2017 10:55 am

Re: assembly without solver

Post by realthunder »

Zolko wrote: Wed Jan 09, 2019 3:27 pm is this "." separator a good choice ? I have often used "." in assembly names, especially for multiple instances:
There are two types of names for each object in FC. An internal name which is given at object creation time, the one you passed to "addObject()" if you use Python to create the object. That internal name can only contain alphanumerics and '_'. This rule is enforced by FC core from upstream.

The second name is a user changeable label (e.g. when you rename the object in treeview), which has no restriction. However, my subname referencing scheme allows to reference sub-object using a label. Therefore, the limitation (of no '.') is only applicable if you choose to reference by label. For example, A3 uses label reference for elements, so my code enforces no '.' policy in element name only. I used enforce this policy on all labels, but I have changed it since that thread.

On a separate issue, when you reference an object in expression by label, you use << and >> to quote it, so there are no restriction there. The limitation only happens in sub object label reference.
Try Assembly3 with my custom build of FreeCAD at here.
And if you'd like to show your support, you can donate through patreon, liberapay, or paypal
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: assembly without solver

Post by Zolko »

triplus wrote: Wed Jan 09, 2019 10:15 pm Part feature provides LCS. You could for example add 6 of them and constrain their placement to form a hexagon.
I haven't been able to do that with a pure App::Part. Are you sure it's possible ? Could you provide an example file demonstrating this ?
try the Assembly4 workbench for FreCAD — tutorials here and here
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: assembly without solver

Post by Zolko »

Now that this essentially works, I've been toying with this assembly without solver somehow, and assembled a small tutorial, to have all information in one place. I have thus made a "real-world example", and not just some cubes and cylinders. There remain some glitches, more to that later. Done in v0.18 LinkStage3 with Qt5 and Py3 compiled on my computer (Ubuntu 18.04), works quite well. Tested on Asm3-AppImage, doesn't always work.

asm_All.png
asm_All.png (46.75 KiB) Viewed 3670 times

There are 4 parts:
  • Bielle, which is an App::Part but contains also the linked (assembled) parts, therefore it's functionally an assembly. It has 2 LCS:
    • one at the origin
    • one at the lower end, at a distance that is parametric, where the Cuve part attaches
  • Cuve, which has 3 LCS:
    • one at the origin, which is used to attach Cuve to Bielle
    • 2 at each hole, for attachment of 2 Screws
  • Bague, which has 1 LCS where it is attached to Bielle
  • Screw_CHC, which has 1 LCS, used to attach to one of the LCS from Cuve (but the App::Link Screw is in the App::Part Bielle, not Cuve !). Inserted twice at 2 locations.
When changing the length of Bielle, everything follows nicely, as it should:

asm_Bielle_300mm.png
asm_Bielle_300mm.png (172.39 KiB) Viewed 3670 times

Here is what I've done, I don't go into details on how to use Part::Design (>>> means it's typed in the Python console)
  • Bielle:
    - new document → new Part → new Body → save-as: Bielle.fcstd
    >>> App.activeDocument().Body.newObject('PartDesign::CoordinateSystem','LCS_0')
    - make a sketch (called "Length" here) to materialise the part's 2nd hole
    >>> App.activeDocument().Body.newObject('PartDesign::CoordinateSystem','LCS_1')
    → place on edge of previous sketch
    - do design ...
  • Cuve:
    - new document → new Part → new Body → save-as: Cuve.fcstd
    >>> App.activeDocument().Body.newObject('PartDesign::CoordinateSystem','LCS_0')
    - do design ...
    >>> App.activeDocument().Body.newObject('PartDesign::CoordinateSystem','LCS_1')
    >>> App.activeDocument().Body.newObject('PartDesign::CoordinateSystem','LCS_2')
    → place on holes
  • Bague (bronze autolubrifiant):
    - new document → new Part → new Body → save-as: Bague.fcstd
    >>> App.activeDocument().Body.newObject('PartDesign::CoordinateSystem','LCS_0')
    - do design ...
  • Screw_CHC:
    - new document → new Part → new Body → save-as: Screw_CHC.fcstd
    >>> App.activeDocument().Body.newObject('PartDesign::CoordinateSystem','LCS_0')
    - do design ...
once you have your parts, go into Bielle, and insert the linked parts (Bielle App::Part thus becomes an assembly):
  • App.getDocument('Bielle').addObject('App::Link','Cuve')
    → select Part@Cuve
    → in the Bielle tree, move (drag'n-drop) Cuve (the App::Link) to Part@Bielle
    → select Cuve and in Placement → select Expression, and enter:
    LCS_1.Placement.multiply(.<<Body.LCS_0.>>.Placement.inverse())
  • App.getDocument('Bielle').addObject('App::Link','Bague')
    → select Part@Bague
    → in the Bielle tree, move Bague (the App::Link) to Part@Bielle
    → select Bague and in Placement → select Expression, and enter:
    LCS_0.Placement.multiply( .<<Body.LCS_0.>>.Placement.inverse() )
  • App.getDocument('Bielle').addObject('App::Link','Screw_1')
    → select Part@Screw_1
    → in the Bielle tree, move Screw_1 (the App::Link) to Part@Bielle
    → select Screw_1 and in Placement → select Expression, and enter:
    <<Cuve>>.Placement.multiply( <<Cuve>>.<<Body.LCS_1.>>.Placement ).multiply( .<<Body.LCS_0.>>.Placement.inverse() )
  • App.getDocument('Bielle').addObject('App::Link','Screw_2')
    → select Part@Screw_2
    → in the Bielle tree, move Screw_2 (the App::Link) to Part@Bielle
    → select Screw_2 and in Placement → select Expression, and enter:
    <<Cuve>>.Placement.multiply( <<Cuve>>.<<Body.LCS_2.>>.Placement ).multiply( .<<Body.LCS_0.>>.Placement.inverse() )
The main thing to rememner is: all parts are inserted into the Bielle assembly, but the parts Cuve and Bague are attached to a target LCS in the parent assembly, whereas the Screw links are attached to a target LCS in a sister part. The << >> refer to labels: since we have added the LCS by name, they are not strictly necessary, therefore for Cuve we could have also entered: <<LCS_1>>.Placement.multiply(.<<Body.LCS_0.>>.Placement.inverse()) , it works also. Here realthunder's explanation:

realthunder wrote: Sat Jan 05, 2019 10:32 am enhanced expression engine. Select Part_1 in the assembly, right click in the property view, and select "Show all". Then, right click in Placement field, and select "Expression...". Finally, enter the following expression:

Code: Select all

LCS_1.Placement.multiply(.<<LCS_1.>>.Placement.inverse())
The first "LCS_1" refers to the LCS_1 object in the assembly document. The second LCS_1 in that special syntax is refer the sub object of this current object (i.e. Part_1). Now you can move LCS_1 in the assembly, and the part will automatically follow it. You can find more details here.

So, what's missing: apart from the user experience, i.e. workflow which is all manual, you can see that some parts are not correctly placed: Bague and Screw_2:

asm_Bielle_LCS.png
asm_Bielle_LCS.png (224.99 KiB) Viewed 3670 times

That's because the attachment LCS in the part and the target LCS in the assembly don't perfectly match: either they have different orientation (as for Screw_2) or different offset (target LCS_0 in Bielle is centred while attachment LCS_0 in Bague is at the edge). We can imagine to fix the target LCS for each part, but if it were possible to insert an additional Placement (Base::Placement ?) between the target LCS and the attachment LCS, we could easily correct that at assembly time. I'm sure it's possible, I tried many things, but didn't succeed in finding the correct syntax. realthunder, any suggestions ?

There is another problem: it's all buggy as hell. It took me an entire day to make those parts. Also, I had to remove nice features like fillets and chamfers, because the edges in different FreeCAD versions get confused (TopoNaming ?). Also, when changing a part, it can get all confused in the assembly, closing and re-opening the assembly fixes that.

Files are attached for testing.
Attachments
Asm_Part.zip
(207.83 KiB) Downloaded 79 times
try the Assembly4 workbench for FreCAD — tutorials here and here
User avatar
saso
Veteran
Posts: 1920
Joined: Fri May 16, 2014 1:14 pm
Contact:

Re: assembly without solver

Post by saso »

Thanks for both of the examples Zolko, they are obviously very nice. What I would like that others take note about with this examples is that if instead of the LCS placements one would use the 3d constrains to put this different Parts together, the tree structure of both of this example assemblies would still be very much the same and there should be no other way to do this, this structure is fundamental. And no matter how big or small the assembly is, it is always build like this.

The two pictures below show how Zolko's current assembly with LCS looks and how it would look if he would use constraints for it... It is the same thing.
Attachments
constraints.png
constraints.png (33.66 KiB) Viewed 3638 times
lcs.png
lcs.png (26.1 KiB) Viewed 3645 times
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: assembly without solver

Post by Zolko »

saso wrote: Sun Jan 13, 2019 7:40 pm The two pictures below show how Zolko's current assembly with LCS looks and how it would look if he would use constraints for it... It is the same thing.
You're a genius:

asm_Constraints.png
asm_Constraints.png (124.58 KiB) Viewed 3572 times

I also added a group called Constraints in Bielle
>>> App.activeDocument().Part = App.activeDocument().addObject('App::DocumentObjectGroup','Constraints') ,
moved it to the Part,

For Bague, I added a Placement
>>> App.ActiveDocument.addObject('App::Placement','PLA_1')
moved to Constraints
defined this Placement as "Translation Z=-13" in the GUI, and entered as Expression in the Placement for Bague:
LCS_0.Placement.multiply(PLA_1.Placement).multiply(.<<Body.LCS_0.>>.Placement.inverse())

For Screw_2, I added a Placement
>>> App.ActiveDocument.addObject('App::Placement','PLA_2')
moved to Constraints
defined this Placement as "Roll 180°" in the GUI, and entered as Expression in the Placement for Screw_2:
<<Cuve>>.Placement.multiply( <<Cuve>>.<<Body.LCS_2.>>.Placement ).multiply(PLA_2.Placement).multiply( .<<Body.LCS_0.>>.Placement.inverse() )

And now, EVERYTHING WORKS AS EXPECTED !!! With this group "Constraints" there is no need for complex syntax, it becomes trivial. Actually, adding a relative Placement for all parts by default would be easy, and if unnecessary it can be left as Identity (nulll translation and null rotation). In this case, the syntax for any expression would always be the same:

For a part attached to a target LCS in the parent assembly:

Code: Select all

<<target_LCS>>.Placement.multiply( <<relative_PLA>>.Placement ).multiply( .<<Body.attachment_LCS.>>.Placement.inverse() )
For a part attached to a target LCS in a sister part:

Code: Select all

<<sister_PART>>.Placement.multiply( <<sister_PART>>.<<Body.target_LCS.>>.Placement ).multiply( <<relative_PLA>>.Placement ).multiply( .<<Body.attachment_LCS.>>.Placement.inverse() )
Attached the new Bielle.fcstd for testing, the other parts are unchanged.

EDIT: typo in expression for target LCS in sister part, all "." are important. Replaced with new (better) Bielle.fcstd file
Attachments
Bielle.FCStd
(95.07 KiB) Downloaded 91 times
try the Assembly4 workbench for FreCAD — tutorials here and here
User avatar
Zolko
Veteran
Posts: 2213
Joined: Mon Dec 17, 2018 10:02 am

Re: assembly without solver

Post by Zolko »

Hello,

I have made a more complex assembly with this method, and this includes this time:
asm_directory.png
asm_directory.png (201.39 KiB) Viewed 3413 times

and with a small macro, the crankshaft angle can be adjusted, and everything rotates with it. The clocking of the 4 pistons is defined in the crankshaft, as it should, LCS are defined on the points, and the positions are "copied" into the main assembly by expressions:

Code: Select all

<<Crankshaft>>.Placement.multiply(<<Crankshaft>>.<<Body.LCS_C1.>>.Placement)

These are then used in 4 sketches that form the skeleton for each piston's and bielle's position. to which LCS are attached, to which in turn linked parts are attached. It all works as expected. There are no geometries that are defined, only datum objects, therefore a "traditional" assembly approach with geometrical constraints would NOT (edit) work here. Once such an assembly is made, the sub-assemblies in their respective sub-directories can be handed out to different designers, and they can develop their model in parallel, and as long as they don't delete or rename the interface LCS-s, the assembly will always be consistent. What one would want to do is give read-access to everybody to all the assembly's directories, and give write-access to specific subdirectories only to the responsibles of the sub-assembly in those sub-directories.

asm_V4_890Kb.gif
asm_V4_890Kb.gif (890.73 KiB) Viewed 3413 times

This means that with this approach, complex, nested, animated assemblies can be designed in parallel, provided that all constraints can be reduced to 2D constraints. Now, this only works on the LinkStage3 fork compiled on my computer, it doesn't work with realthunder's Assembly3 AppImage, the <<expressions>> part is lost, don't know why. Files are included for testing .... Do you plan to release a new AppImage with these features ?

realthunder wrote: Sat Jan 05, 2019 10:32 am If you'd like to explore more about my branch, you can try the enhanced expression engine...
For me, essentially, the assembly problem is solved for FreeCAD : 99% of all problems can be handled this way (an Airbus landing gear will be next step, but then the PartDesign part becomes limiting and time-consuming). What needs to be done now is to integrate realthunder's App::Link into mainline, and then adjust the GUI for easier workflow. Later, the placements in the Constraint folder can be extended for the remaining 1% of problems that cannot be simplified to 2D constraints.
Attachments
Asm_V4.tar.gz
(538.83 KiB) Downloaded 75 times
try the Assembly4 workbench for FreCAD — tutorials here and here
User avatar
fosselius
Posts: 381
Joined: Sat Apr 23, 2016 10:03 am
Contact:

Re: assembly without solver

Post by fosselius »

Now, this is awesome :)
Combine this with the git-freecad file effort and FreeCAD will be great for larger teams :)
Post Reply