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 (46.75 KiB) Viewed 2694 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 (172.39 KiB) Viewed 2694 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 (224.99 KiB) Viewed 2694 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.