├── Mod_Asm4
├── VERSION
├── icons
│ ├── Assembly4.png
│ ├── PartDesign_Point.svg
│ ├── New_Axis.svg
│ ├── AxisCross.svg
│ ├── Point.svg
│ ├── CoordinateSystem.svg
│ ├── FreeCad.svg
│ ├── Model_NewSketch.svg
│ ├── PartDesign_Body.svg
│ ├── ImportDatum.svg
│ ├── LinkModel.svg
│ ├── Assembly4.svg
│ ├── Model.svg
│ └── Place_AxisCross_small.svg
├── README.md
├── asm4wb_locator.py
├── Init.py
├── newBodyCmd.py
├── newModelCmd.py
├── newSketchCmd.py
├── newPointCmd.py
├── newLCSCmd.py
├── updateAssemblyCmd.py
├── InitGui.py
├── libAsm4.py
├── insertLinkCmd.py
└── importDatumCmd.py
├── Resources
└── media
│ ├── asm_EE.png
│ ├── Asm4_V4.gif
│ ├── Asm4_wb0.png
│ ├── Asm4_wb1.png
│ ├── Asm4_wb2.png
│ ├── Asm4_wb3.png
│ ├── Toolbar.png
│ ├── LCS_MapMode.png
│ ├── LCS_Attachment.png
│ ├── asm_Bielle_demo.png
│ ├── asm_EE_showall.png
│ ├── asm_V4_2pistons.gif
│ ├── Lego_House+Garden.png
│ ├── asm_Bielle_tree_arrows.png
│ └── asm_Bielle_constr_Offset.png
├── Examples
├── Asm4_Example1
│ ├── Cuve.fcstd
│ ├── Bague.fcstd
│ ├── Bielle.fcstd
│ ├── Screw_CHC.fcstd
│ └── asm_Bielle.fcstd
├── Asm4_Example2
│ ├── asm_V4.FCStd
│ ├── Cylindre.FCStd
│ ├── Bielle
│ │ ├── Bague.fcstd
│ │ ├── Cuve.fcstd
│ │ ├── Bielle.fcstd
│ │ ├── Screw_CHC.fcstd
│ │ └── asm_Bielle.fcstd
│ ├── Crankshaft.FCStd
│ ├── Piston
│ │ └── Piston.FCStd
│ └── ReadMe.txt
└── Asm4_Example3
│ ├── Support.fcstd
│ ├── Wheel.fcstd
│ ├── Triangle.fcstd
│ ├── asm_Hypnotic.fcstd
│ ├── kunda1-thingy-sketcher.FCStd
│ └── ReadMe.txt
└── README.md
/Mod_Asm4/VERSION:
--------------------------------------------------------------------------------
1 | Assembly 4 Workbench for FreeCAD-v0.19
2 | v0.6 2019.10.05
3 |
4 |
--------------------------------------------------------------------------------
/Resources/media/asm_EE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/asm_EE.png
--------------------------------------------------------------------------------
/Mod_Asm4/icons/Assembly4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Mod_Asm4/icons/Assembly4.png
--------------------------------------------------------------------------------
/Resources/media/Asm4_V4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/Asm4_V4.gif
--------------------------------------------------------------------------------
/Resources/media/Asm4_wb0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/Asm4_wb0.png
--------------------------------------------------------------------------------
/Resources/media/Asm4_wb1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/Asm4_wb1.png
--------------------------------------------------------------------------------
/Resources/media/Asm4_wb2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/Asm4_wb2.png
--------------------------------------------------------------------------------
/Resources/media/Asm4_wb3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/Asm4_wb3.png
--------------------------------------------------------------------------------
/Resources/media/Toolbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/Toolbar.png
--------------------------------------------------------------------------------
/Resources/media/LCS_MapMode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/LCS_MapMode.png
--------------------------------------------------------------------------------
/Examples/Asm4_Example1/Cuve.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example1/Cuve.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example1/Bague.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example1/Bague.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example1/Bielle.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example1/Bielle.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/asm_V4.FCStd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/asm_V4.FCStd
--------------------------------------------------------------------------------
/Examples/Asm4_Example3/Support.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example3/Support.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example3/Wheel.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example3/Wheel.fcstd
--------------------------------------------------------------------------------
/Resources/media/LCS_Attachment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/LCS_Attachment.png
--------------------------------------------------------------------------------
/Resources/media/asm_Bielle_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/asm_Bielle_demo.png
--------------------------------------------------------------------------------
/Resources/media/asm_EE_showall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/asm_EE_showall.png
--------------------------------------------------------------------------------
/Resources/media/asm_V4_2pistons.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/asm_V4_2pistons.gif
--------------------------------------------------------------------------------
/Examples/Asm4_Example1/Screw_CHC.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example1/Screw_CHC.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/Cylindre.FCStd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/Cylindre.FCStd
--------------------------------------------------------------------------------
/Examples/Asm4_Example3/Triangle.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example3/Triangle.fcstd
--------------------------------------------------------------------------------
/Resources/media/Lego_House+Garden.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/Lego_House+Garden.png
--------------------------------------------------------------------------------
/Examples/Asm4_Example1/asm_Bielle.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example1/asm_Bielle.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/Bielle/Bague.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/Bielle/Bague.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/Bielle/Cuve.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/Bielle/Cuve.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/Crankshaft.FCStd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/Crankshaft.FCStd
--------------------------------------------------------------------------------
/Examples/Asm4_Example3/asm_Hypnotic.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example3/asm_Hypnotic.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/Bielle/Bielle.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/Bielle/Bielle.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/Piston/Piston.FCStd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/Piston/Piston.FCStd
--------------------------------------------------------------------------------
/Resources/media/asm_Bielle_tree_arrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/asm_Bielle_tree_arrows.png
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/Bielle/Screw_CHC.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/Bielle/Screw_CHC.fcstd
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/Bielle/asm_Bielle.fcstd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example2/Bielle/asm_Bielle.fcstd
--------------------------------------------------------------------------------
/Resources/media/asm_Bielle_constr_Offset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Resources/media/asm_Bielle_constr_Offset.png
--------------------------------------------------------------------------------
/Examples/Asm4_Example3/kunda1-thingy-sketcher.FCStd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_Assembly4/master/Examples/Asm4_Example3/kunda1-thingy-sketcher.FCStd
--------------------------------------------------------------------------------
/Mod_Asm4/README.md:
--------------------------------------------------------------------------------
1 | # FreeCAD Assembly Without Solver / Assembly 4
2 |
3 | This directory is to be copied (or linked) to :
4 | * for Linux: ~/.FreeCAD/Mod/Mod_Asm4
5 | * for Windows: C:\Users\\*******\AppData\Roaming\FreeCAD\Mod\Mod_Asm4
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/Asm4_Example2/ReadMe.txt:
--------------------------------------------------------------------------------
1 | Assembly of a V4 engine in Assembly-4:
2 |
3 | * open the top-level assembly asm_V4.fcstd
4 |
5 | * to animate the rotation of the crankshaft, activate document asm_V4.fcstd,
6 | and copy-and-paste the following lines into the python console:
7 |
8 | import time
9 | step = 10
10 | for angle in range( 30, 750+step, step ):
11 | App.getDocument("asm_V4").LCS_crankshaft.AttachmentOffset = App.Placement( App.Vector(0,0,0), App.Rotation( App.Vector(0,1,0), angle ) )
12 | App.ActiveDocument.recompute()
13 | Gui.updateGui()
14 | time.sleep(0.025)
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Examples/Asm4_Example3/ReadMe.txt:
--------------------------------------------------------------------------------
1 | Assembly of a hypnotic thingy: https://forum.freecadweb.org/viewtopic.php?f=20&t=34530
2 |
3 |
4 | * open the top-level assembly asm_Hypnotic.fcstd
5 |
6 | * to animate the rotation of the wheel, activate document asm_Hypnotic.fcstd,
7 | and copy-and-paste the following lines into the python console:
8 |
9 | step = 1
10 | for angle in range( 15, 735+step, step ):
11 | App.getDocument("asm_Hypnotic").LCS_rot.AttachmentOffset = App.Placement( App.Vector(0,0,0), App.Rotation( App.Vector(0,0,1), angle ) )
12 | App.activeDocument().recompute()
13 | Gui.updateGui()
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Mod_Asm4/asm4wb_locator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###################################################################################
3 | #
4 | # dynamicdatawb_locator.py
5 | #
6 | # Copyright 2018 Mark Ganson mwganson at gmail
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 | ###################################################################################
25 |
--------------------------------------------------------------------------------
/Mod_Asm4/Init.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###################################################################################
3 | #
4 | # Init.py
5 | #
6 | # Copyright 2018 Mark Ganson
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 | ###################################################################################
25 |
26 | print("Loading Assembly 4 WorkBench")
27 |
--------------------------------------------------------------------------------
/Mod_Asm4/newBodyCmd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | #
4 | # newBodyCmd.py
5 |
6 |
7 | from PySide import QtGui, QtCore
8 | import FreeCADGui as Gui
9 | import FreeCAD as App
10 | import Part, math, re
11 |
12 | from libAsm4 import *
13 |
14 |
15 |
16 | class newBody:
17 | "My tool object"
18 |
19 | def GetResources(self):
20 | return {"MenuText": "Create a new Body",
21 | "Accel": "Ctrl+B",
22 | "ToolTip": "Create a new Body in the Model",
23 | "Pixmap" : os.path.join( iconPath , 'PartDesign_Body.svg')
24 | }
25 |
26 | def IsActive(self):
27 | if App.ActiveDocument:
28 | # is something selected ?
29 | if Gui.Selection.getSelection():
30 | return(False)
31 | else:
32 | return(True)
33 | else:
34 | return(False)
35 |
36 | def Activated(self):
37 | # do something here...
38 | bodyName = 'Body'
39 | text,ok = QtGui.QInputDialog.getText(None,'Create new Body in Model','Enter new Body name : ', text = bodyName)
40 | if ok and text:
41 | App.activeDocument().getObject('Model').newObject( 'PartDesign::Body', text )
42 |
43 |
44 | # add the command to the workbench
45 | Gui.addCommand( 'newBodyCmd', newBody() )
46 |
--------------------------------------------------------------------------------
/Mod_Asm4/newModelCmd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | #
4 | # newModelCmd.py
5 |
6 |
7 | from PySide import QtGui, QtCore
8 | import FreeCADGui as Gui
9 | import FreeCAD as App
10 | import Part, math, re
11 |
12 | from libAsm4 import *
13 |
14 |
15 |
16 | class newModel:
17 | "My tool object"
18 |
19 | def GetResources(self):
20 | return {"MenuText": "Create a new Model",
21 | "Accel": "Ctrl+M",
22 | "ToolTip": "Create a new Model App::Part",
23 | "Pixmap" : os.path.join( iconPath , 'Model.svg')
24 | }
25 |
26 | def IsActive(self):
27 | if App.ActiveDocument:
28 | # is something selected ?
29 | if Gui.Selection.getSelection():
30 | return(False)
31 | else:
32 | return(True)
33 | else:
34 | return(False)
35 |
36 | def Activated(self):
37 | # create a new App::Part called 'Model'
38 | App.activeDocument().Tip = App.activeDocument().addObject('App::Part','Model')
39 | App.activeDocument().getObject('Model').newObject('App::DocumentObjectGroup','Constraints')
40 | App.activeDocument().getObject('Model').newObject('PartDesign::CoordinateSystem','LCS_0')
41 |
42 |
43 | # add the command to the workbench
44 | Gui.addCommand( 'newModelCmd', newModel() )
45 |
--------------------------------------------------------------------------------
/Mod_Asm4/newSketchCmd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | #
4 | # newSketchCmd.py
5 |
6 |
7 | from PySide import QtGui, QtCore
8 | import FreeCADGui as Gui
9 | import FreeCAD as App
10 | import Part, math, re
11 |
12 | from libAsm4 import *
13 |
14 |
15 |
16 | class newSketch:
17 | "My tool object"
18 |
19 | def GetResources(self):
20 | return {"MenuText": "Create a new Sketch",
21 | "ToolTip": "Create a new Sketch in the Model",
22 | "Pixmap" : os.path.join( iconPath , 'Model_NewSketch.svg')
23 | }
24 |
25 | def IsActive(self):
26 | if App.ActiveDocument:
27 | # is something selected ?
28 | if Gui.Selection.getSelection():
29 | return(False)
30 | else:
31 | return(True)
32 | else:
33 | return(False)
34 |
35 | def Activated(self):
36 | # input dialog to ask the user the name of the Sketch:
37 | sketchName = 'Sketch_1'
38 | text,ok = QtGui.QInputDialog.getText(None,'Create new Sketch in Model','Enter Sketch name : ', text = sketchName)
39 | if ok and text:
40 | App.activeDocument().getObject('Model').newObject( 'Sketcher::SketchObject', text )
41 |
42 |
43 | # add the command to the workbench
44 | Gui.addCommand( 'newSketchCmd', newSketch() )
45 |
46 |
--------------------------------------------------------------------------------
/Mod_Asm4/newPointCmd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | #
4 | # newPointCmd.py
5 |
6 |
7 | from PySide import QtGui, QtCore
8 | import FreeCADGui as Gui
9 | import FreeCAD as App
10 | import Part, math, re
11 |
12 | from libAsm4 import *
13 |
14 |
15 |
16 | class newPoint:
17 | "My tool object"
18 |
19 | def GetResources(self):
20 | return {"MenuText": "Create a new Point",
21 | "ToolTip": "Create a new Datum Point in the Model",
22 | "Pixmap" : os.path.join( iconPath , 'Point.svg')
23 | }
24 |
25 | def IsActive(self):
26 | if App.ActiveDocument:
27 | # is something selected ?
28 | if Gui.Selection.getSelection():
29 | return(False)
30 | else:
31 | return(True)
32 | else:
33 | return(False)
34 |
35 | def Activated(self):
36 | # do something here...
37 | # input dialog to ask the user the name of the LCS:
38 | pointName = 'Point_1'
39 | text,ok = QtGui.QInputDialog.getText(None,'Create new Datum Point','Enter Datum Point name : ', text = pointName)
40 | # if everything went well:
41 | if ok and text:
42 | App.activeDocument().getObject('Model').newObject( 'PartDesign::Point', text )
43 |
44 |
45 | # add the command to the workbench
46 | Gui.addCommand( 'newPointCmd', newPoint() )
47 |
--------------------------------------------------------------------------------
/Mod_Asm4/newLCSCmd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | #
4 | # newLCSCmd.py
5 |
6 |
7 | from PySide import QtGui, QtCore
8 | import FreeCADGui as Gui
9 | import FreeCAD as App
10 | import Part, math, re
11 |
12 | from libAsm4 import *
13 |
14 |
15 |
16 | class newLCS:
17 | "My tool object"
18 |
19 | def GetResources(self):
20 | return {"MenuText": "Create a new LCS",
21 | "ToolTip": "Create a new Coordinate System in the Model",
22 | "Pixmap" : os.path.join( iconPath , 'AxisCross.svg')
23 | }
24 |
25 | def IsActive(self):
26 | if App.ActiveDocument:
27 | # is something selected ?
28 | if Gui.Selection.getSelection():
29 | return(False)
30 | else:
31 | return(True)
32 | else:
33 | return(False)
34 |
35 | def Activated(self):
36 | # do something here...
37 | # input dialog to ask the user the name of the LCS:
38 | lcsName = 'LCS_1'
39 | text,ok = QtGui.QInputDialog.getText(None,'Create new coordinate system','Enter Local Coordinate System name : ', text = lcsName)
40 | # if everything went well:
41 | if ok and text:
42 | App.activeDocument().getObject('Model').newObject( 'PartDesign::CoordinateSystem', text )
43 |
44 |
45 | # add the command to the workbench
46 | Gui.addCommand( 'newLCSCmd', newLCS() )
47 |
--------------------------------------------------------------------------------
/Mod_Asm4/updateAssemblyCmd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | #
4 | # newModelCmd.py
5 |
6 |
7 | from PySide import QtGui, QtCore
8 | import FreeCADGui as Gui
9 | import FreeCAD as App
10 | import Part, math, re
11 |
12 | from libAsm4 import *
13 |
14 |
15 |
16 | class updateAssembly:
17 | "My tool object"
18 |
19 | def GetResources(self):
20 | return {"MenuText": "Solve and Update Assembly",
21 | "ToolTip": "Solve all constraints and update Assembly",
22 | "Pixmap" : os.path.join( iconPath , 'Solver.svg')
23 | }
24 |
25 | def IsActive(self):
26 | if App.ActiveDocument:
27 | return(True)
28 | else:
29 | return(False)
30 |
31 |
32 |
33 | """
34 | +-----------------------------------------------+
35 | | the real stuff |
36 | +-----------------------------------------------+
37 | """
38 | def Activated(self):
39 |
40 | # get the current active document to avoid errors if user changes tab
41 | self.activeDoc = App.activeDocument()
42 |
43 | # find all the linked parts in the assembly...
44 | for obj in self.activeDoc.findObjects():
45 | # ... and update it
46 | obj.recompute()
47 | # finally uodate the parent assembly
48 | self.activeDoc.Model.recompute()
49 |
50 |
51 | # add the command to the workbench
52 | Gui.addCommand( 'updateAssemblyCmd', updateAssembly() )
53 |
--------------------------------------------------------------------------------
/Mod_Asm4/InitGui.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###################################################################################
3 | #
4 | # InitGui.py
5 | #
6 | # Copyright 2018 Mark Ganson mwganson at gmail
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 | ###################################################################################
25 |
26 | import asm4wb_locator
27 | asm4wbPath = os.path.dirname( asm4wb_locator.__file__ )
28 | asm4wb_icons_path = os.path.join( asm4wbPath, 'icons')
29 |
30 | global main_Assembly4WB_Icon
31 | main_Assembly4WB_Icon = os.path.join( asm4wb_icons_path , 'Assembly4.svg' )
32 |
33 |
34 | #def myFunc(string):
35 | # print (string)
36 | # global act
37 | # act.setVisible(True)
38 |
39 | #mw=Gui.getMainWindow()
40 | #bar=mw.menuBar()
41 | #act=bar.addAction("MyCmd")
42 | #mw.workbenchActivated.connect(myFunc)
43 |
44 |
45 |
46 | """
47 | +-----------------------------------------------+
48 | | Initialize the workbench |
49 | +-----------------------------------------------+
50 | """
51 | class Assembly4_WorkBench(Workbench):
52 |
53 | global main_Assembly4WB_Icon
54 |
55 | MenuText = "Assembly 4"
56 | ToolTip = "Assembly 4 workbench"
57 | Icon = main_Assembly4WB_Icon
58 |
59 |
60 | def __init__(self):
61 | "This function is executed when FreeCAD starts"
62 | pass
63 |
64 |
65 | def Initialize(self):
66 | import newModelCmd # creates a new App::Part container called 'Model'
67 | import newSketchCmd # creates a new Sketch in 'Model'
68 | import newLCSCmd # creates a new LCS in 'Model'
69 | import newPointCmd # creates a new LCS in 'Model'
70 | import newBodyCmd # creates a new Body in 'Model
71 | import insertLinkCmd # inserts an App::Link to a 'Model' in another file
72 | import placeLinkCmd # places a linked part by snapping LCS (in the Part and in the Assembly)
73 | import placeDatumCmd # places an LCS relative to an external file (creates a local attached copy)
74 | import importDatumCmd # creates an LCS in assembly and attaches it to an LCS relative to an external file
75 | import updateAssemblyCmd # updates all parts and constraints in the assembly
76 | self.listCmd = [ "newModelCmd", "insertLinkCmd", "placeLinkCmd", "newLCSCmd", "importDatumCmd", "placeDatumCmd", "newSketchCmd", "newPointCmd", "newBodyCmd", "updateAssemblyCmd" ] # A list of command names created in the line above
77 | self.itemsMenu = [ "newModelCmd", "insertLinkCmd", "placeLinkCmd", "newLCSCmd", "importDatumCmd", "placeDatumCmd", "newSketchCmd", "newPointCmd", "newBodyCmd", "updateAssemblyCmd" ] # A list of command names created in the line above
78 | self.itemsToolbar = [ "newModelCmd", "insertLinkCmd", "placeLinkCmd", "newLCSCmd", "importDatumCmd", "placeDatumCmd", "newSketchCmd", "newPointCmd", "newBodyCmd", "updateAssemblyCmd" ] # A list of command names created in the line above
79 | self.itemsContextMenu = [ "placeLinkCmd", "placeDatumCmd" ] # A list of command names created in the line above
80 | self.appendToolbar("Assembly 4",self.itemsToolbar) # leave settings off toolbar
81 | self.appendMenu("&Assembly",self.itemsMenu) # creates a new menu
82 | #self.appendMenu(["&Edit","DynamicData"],self.list) # appends a submenu to an existing menu
83 |
84 |
85 | """
86 | +-----------------------------------------------+
87 | | Standard necessary functions |
88 | +-----------------------------------------------+
89 | """
90 | def Activated(self):
91 | "This function is executed when the workbench is activated"
92 | return
93 |
94 |
95 | def Deactivated(self):
96 | "This function is executed when the workbench is deactivated"
97 | return
98 |
99 |
100 | def ContextMenu(self, recipient):
101 | "This is executed whenever the user right-clicks on screen"
102 | # "recipient" will be either "view" or "tree"
103 | self.appendContextMenu( "Assembly", self.itemsContextMenu ) # add commands to the context menu
104 |
105 |
106 | def GetClassName(self):
107 | # this function is mandatory if this is a full python workbench
108 | return "Gui::PythonWorkbench"
109 |
110 |
111 |
112 | """
113 | +-----------------------------------------------+
114 | | actually make the workbench |
115 | +-----------------------------------------------+
116 | """
117 | wb = Assembly4_WorkBench()
118 | Gui.addWorkbench(wb)
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/Mod_Asm4/libAsm4.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 | #
4 | # libraries for FreeCAD's Assembly Without Solver
5 |
6 |
7 | """
8 | +-----------------------------------------------+
9 | | shouldn't these be DEFINE's ? |
10 | +-----------------------------------------------+
11 | """
12 | constraintPrefix = 'constr_'
13 |
14 | import os
15 | __dir__ = os.path.dirname(__file__)
16 | iconPath = os.path.join( __dir__, 'icons' )
17 |
18 |
19 | """
20 | +-----------------------------------------------+
21 | | populate the ExpressionEngine |
22 | | for a linked App::Part |
23 | +-----------------------------------------------+
24 | """
25 | def makeExpressionPart( attLink, attPart, attLCS, constrName, linkedPart, linkLCS ):
26 | # if everything is defined
27 | if attLink and attLCS and constrName and linkedPart and linkLCS:
28 | # this is where all the magic is, see:
29 | #
30 | # https://forum.freecadweb.org/viewtopic.php?p=278124#p278124
31 | #
32 | # as of FreeCAD v0.19 the syntax is different:
33 | # https://forum.freecadweb.org/viewtopic.php?f=17&t=38974&p=337784#p337784
34 | # expr = ParentLink.Placement * ParentPart#LCS.Placement * constr_LinkName.AttachmentOffset * LinkedPart#LCS.Placement ^ -1
35 | # expr = LCS_in_the_assembly.Placement * constr_LinkName.AttachmentOffset * LinkedPart#LCS.Placement ^ -1
36 | expr = attLCS+'.Placement * '+constrName+'.AttachmentOffset * '+linkedPart+'#'+linkLCS+'.Placement ^ -1'
37 | # if we're attached to another sister part (and not the Parent Assembly)
38 | # we need to take into account the Placement of that Part.
39 | if attPart:
40 | expr = attLink+'.Placement * '+attPart+'#'+expr
41 | else:
42 | expr = False
43 | return expr
44 |
45 |
46 |
47 |
48 | """
49 | +-----------------------------------------------+
50 | | split the ExpressionEngine of a linked part |
51 | | to find the old attachment LCS |
52 | | (in the parent assembly or a sister part) |
53 | | and the old target LCS in the linked Part |
54 | +-----------------------------------------------+
55 | """
56 | def splitExpressionPart( expr, parent ):
57 | # expr = ParentLink.Placement * ParentPart#LCS.Placement * constr_LinkName.AttachmentOffset * LinkedPart#LCS.Placement ^ -1'
58 | bad_EE = ( '', 'None', 'None', 'None', 'None', 'None')
59 | if not expr:
60 | return ( 'Empty expression x1', 'None', 'None', 'None', 'None', 'None')
61 | if parent == 'Parent Assembly':
62 | # we're attached to an LCS in the parent assembly
63 | # expr = LCS_in_the_assembly.Placement * constr_Name.AttachmentOffset * LinkedPart#LCS.Placement ^ -1'
64 | ( attLCS, separator, rest1 ) = expr.partition('.Placement * ')
65 | ( constrName, separator, rest2 ) = rest1.partition('.AttachmentOffset * ')
66 | ( linkedPart, separator, rest3 ) = rest2.partition('#')
67 | ( linkLCS, separator, rest4 ) = rest3.partition('.Placement ^ ')
68 | restFinal = rest4[0:2]
69 | attLink = parent
70 | attPart = 'None'
71 | #return ( restFinal, 'None', 'None', 'None', 'None', 'None')
72 | else:
73 | # we're attached to an LCS in a sister part
74 | # expr = ParentLink.Placement * ParentPart#LCS.Placement * constr_Name.AttachmentOffset * LinkedPart#LCS.Placement ^ -1'
75 | ( attLink, separator, rest1 ) = expr.partition('.Placement * ')
76 | ( attPart, separator, rest2 ) = rest1.partition('#')
77 | ( attLCS, separator, rest3 ) = rest2.partition('.Placement * ')
78 | ( constrName, separator, rest4 ) = rest3.partition('.AttachmentOffset * ')
79 | ( linkedPart, separator, rest5 ) = rest4.partition('#')
80 | ( linkLCS, separator, rest6 ) = rest5.partition('.Placement ^ ')
81 | restFinal = rest6[0:2]
82 | #return ( restFinal, 'None', 'None', 'None', 'None', 'None')
83 | if restFinal=='-1':
84 | # wow, everything went according to plan
85 | # retval = ( expr, attPart, attLCS, constrLink, partLCS )
86 | retval = ( attLink, attPart, attLCS, constrName, linkedPart, linkLCS)
87 | else:
88 | # rats ! Didn't succeed in decoding the ExpressionEngine.
89 | # But still, if the decode is unsuccessful, put some text
90 | retval = bad_EE
91 | return retval
92 |
93 |
94 |
95 | """
96 | +-----------------------------------------------+
97 | | populate the ExpressionEngine |
98 | | for a Datum object |
99 | | linked to an LCS in a sister part |
100 | +-----------------------------------------------+
101 | """
102 | def makeExpressionDatum( attLink, attPart, attLCS ):
103 | # check that everything is defined
104 | if attLink and attPart and attLCS:
105 | # expr = Link.Placement * LinkedPart#LCS.Placement
106 | expr = attLink +'.Placement * '+ attPart +'#'+ attLCS +'.Placement'
107 | else:
108 | expr = False
109 | return expr
110 |
111 |
112 |
113 | """
114 | +-----------------------------------------------+
115 | | split the ExpressionEngine |
116 | | of a linked Datum object to find |
117 | | the old attachment Part and LCS |
118 | +-----------------------------------------------+
119 | """
120 | def splitExpressionDatum( expr ):
121 | # expr = Link.Placement * LinkedPart#LCS.Placement
122 | ( attLink, separator, rest1 ) = expr.partition('.Placement * ')
123 | ( attPart, separator, rest2 ) = rest1.partition('#')
124 | ( attLCS, separator, rest3 ) = rest2.partition('.')
125 | restFinal = rest3[0:9]
126 | if restFinal=='Placement':
127 | # wow, everything went according to plan
128 | retval = ( attLink, attPart, attLCS )
129 | #self.expression.setText( attPart +'***'+ attLCS )
130 | else:
131 | # rats ! But still, if the decode is unsuccessful, put some text
132 | retval = ( restFinal, 'None', 'None' )
133 | return retval
134 |
135 |
136 |
--------------------------------------------------------------------------------
/Mod_Asm4/insertLinkCmd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | #
4 | # insertLinkCmd.py
5 |
6 |
7 | from PySide import QtGui, QtCore
8 | import FreeCADGui as Gui
9 | import FreeCAD as App
10 | import Part, math, re
11 |
12 | from libAsm4 import *
13 |
14 |
15 | """
16 | +-----------------------------------------------+
17 | | main class |
18 | +-----------------------------------------------+
19 | """
20 | class insertLink( QtGui.QDialog ):
21 | "My tool object"
22 |
23 | def __init__(self):
24 | super(insertLink,self).__init__()
25 |
26 |
27 | def GetResources(self):
28 | return {"MenuText": "Insert an external Part",
29 | "Accel": "Ctrl+L",
30 | "ToolTip": "Insert an external Part from another open document",
31 | "Pixmap" : os.path.join( iconPath , 'LinkModel.svg')
32 | }
33 |
34 |
35 | def IsActive(self):
36 | if App.ActiveDocument:
37 | # is something selected ?
38 | if Gui.Selection.getSelection():
39 | return False
40 | else:
41 | return True
42 | else:
43 | return(False)
44 |
45 |
46 | def Activated(self):
47 | # This function is executed when the command is activated
48 |
49 | # get the current active document to avoid errors if user changes tab
50 | self.activeDoc = App.activeDocument()
51 |
52 | # the GUI objects are defined later down
53 | self.drawUI()
54 |
55 | # Search for all App::Parts in all open documents
56 | self.getAllParts()
57 |
58 | # build the list
59 | for part in self.allParts:
60 | newItem = QtGui.QListWidgetItem()
61 | newItem.setText( part.Document.Name +" -> "+ part.Name )
62 | newItem.setIcon(part.ViewObject.Icon)
63 | self.partList.addItem(newItem)
64 |
65 | # show the UI
66 | self.show()
67 |
68 |
69 |
70 | """
71 | +-----------------------------------------------+
72 | | the real stuff happens here |
73 | +-----------------------------------------------+
74 | """
75 | def onCreateLink(self):
76 | # parse the selected items
77 | # TODO : there should only be 1
78 | model = []
79 | for selected in self.partList.selectedIndexes():
80 | # get the selected part
81 | model = self.allParts[ selected.row() ]
82 | # get the name of the link (as it should appear in the tree)
83 | linkName = self.linkNameInput.text()
84 | # only create link if there is a Part object and a name
85 | if model and linkName:
86 | # create the App::Link with the user-provided name
87 | createdLink = self.activeDoc.getObject('Model').newObject( 'App::Link', linkName )
88 | # assigne the user-selected model to it
89 | createdLink.LinkedObject = model
90 | # update the link
91 | createdLink.recompute()
92 |
93 | # close the dialog UI...
94 | self.close()
95 |
96 | # ... and launch the placement of the inserted part
97 | Gui.Selection.clearSelection()
98 | Gui.Selection.addSelection( self.activeDoc.Name, 'Model', createdLink.Name+'.' )
99 | Gui.runCommand( 'placeLinkCmd' )
100 |
101 | # if still open, close the dialog UI
102 | self.close()
103 |
104 |
105 |
106 | """
107 | +-----------------------------------------------+
108 | | some functions |
109 | +-----------------------------------------------+
110 | """
111 | def getAllParts(self):
112 | # get all App::Part from all open documents
113 | self.allParts = []
114 | for doc in App.listDocuments().values():
115 | # except this document: we don't want to link to itself
116 | if doc != self.activeDoc:
117 | parts = doc.findObjects("App::Part")
118 | # there might be more than 1 App::Part per document
119 | for obj in parts:
120 | self.allParts.append( obj )
121 |
122 |
123 | def onItemClicked( self, item ):
124 | for selected in self.partList.selectedIndexes():
125 | # get the selected part
126 | model = self.allParts[ selected.row() ]
127 | # set the text of the link to be made to the document where the part is in
128 | self.linkNameInput.setText(model.Document.Name)
129 |
130 |
131 | def onCancel(self):
132 | self.close()
133 |
134 |
135 |
136 | """
137 | +-----------------------------------------------+
138 | | defines the UI, only static elements |
139 | +-----------------------------------------------+
140 | """
141 | def drawUI(self):
142 |
143 | # Our main window is a QDialog
144 | self.setModal(False)
145 | # make this dialog stay above the others, always visible
146 | self.setWindowFlags( QtCore.Qt.WindowStaysOnTopHint )
147 | self.setWindowTitle('Insert a Model')
148 | self.setWindowIcon( QtGui.QIcon( os.path.join( iconPath , 'FreeCad.svg' ) ) )
149 | self.setMinimumSize(400, 500)
150 | self.resize(400,500)
151 | #self.Layout.addWidget(self.GUIwindow)
152 |
153 | # label
154 | self.labelMain = QtGui.QLabel(self)
155 | self.labelMain.setText("Select Part to be inserted :")
156 | self.labelMain.move(10,20)
157 | #self.Layout.addWidget(self.labelMain)
158 |
159 | # label
160 | self.labelLink = QtGui.QLabel(self)
161 | self.labelLink.setText("Enter a Name for the link :\n(Must be unique in the Model tree)")
162 | self.labelLink.move(10,350)
163 |
164 | # Create a line that will contain the name of the link (in the tree)
165 | self.linkNameInput = QtGui.QLineEdit(self)
166 | self.linkNameInput.setMinimumSize(380, 0)
167 | self.linkNameInput.move(10, 400)
168 |
169 | # The part list is a QListWidget
170 | self.partList = QtGui.QListWidget(self)
171 | self.partList.move(10,50)
172 | self.partList.setMinimumSize(380, 280)
173 |
174 | # Cancel button
175 | self.CancelButton = QtGui.QPushButton('Cancel', self)
176 | self.CancelButton.setAutoDefault(False)
177 | self.CancelButton.move(10, 460)
178 |
179 | # create Link button
180 | self.createLinkButton = QtGui.QPushButton('Insert part', self)
181 | self.createLinkButton.move(285, 460)
182 | self.createLinkButton.setDefault(True)
183 |
184 | # Actions
185 | self.CancelButton.clicked.connect(self.onCancel)
186 | self.createLinkButton.clicked.connect(self.onCreateLink)
187 | self.partList.itemClicked.connect( self.onItemClicked)
188 |
189 |
190 | """
191 | +-----------------------------------------------+
192 | | add the command to the workbench |
193 | +-----------------------------------------------+
194 | """
195 | Gui.addCommand( 'insertLinkCmd', insertLink() )
196 |
197 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/PartDesign_Point.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
81 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/New_Axis.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
226 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/AxisCross.svg:
--------------------------------------------------------------------------------
1 |
2 |
202 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/Point.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
259 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/CoordinateSystem.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
229 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FreeCAD Assembly Without Solver / Assembly 4
2 | FreeCAD add-on for a bare-bone assembly structure, using App::Link
3 |
4 | Welcome to the FreeCAD_aws wiki! This page tries to explain how to make assemblies using these tools. This project the result of [discussions on the FreeCAD forum](https://forum.freecadweb.org/viewtopic.php?f=20&t=32843).
5 |
6 | ## Installation
7 |
8 | Download and extract the archive, and then move (or link) the sub-directory `Mod_Asm4` (which contains all the actual code) into the `~/.FreeCDA/Mod` folder, containing all additional modules.
9 |
10 | Please bear in mind that Assembly 4 is not compatible with the stable FreeCAD v0.18, it needs at least v0.19. You can find [pre-built binaries here](https://github.com/FreeCAD/FreeCAD/releases/tag/0.19_pre)
11 |
12 | You can use the [example assemblies](https://github.com/Zolko-123/FreeCAD_Assembly4/tree/master/Examples) to experiment with this workbench's features. Open one _asm_something.fcstd_ file and try out the functions. There are ReadMe.txt files in each durectory with some explanations.
13 |
14 |
15 | ## Principle
16 |
17 | The point is that each part is a mostly standard FreeCAD `App::Part` object, and these are assembled using the `App::Link` framework found in the fork of FreeCAD (https://github.com/realthunder/FreeCAD/tree/LinkStage3, pre-built binaries on the [realthunder's FreeCAD_assembly3 release page](https://github.com/realthunder/FreeCAD_assembly3/releases)
18 |
19 | The particularities of the `App::Part` used here are the following:
20 |
21 | * they are called 'Model' at creation time
22 | * they contain a group called 'Constraints' at the root
23 | * they contain a Datum Coordinate System called LCS_0 at the root
24 |
25 | The purpose of this is to avoid burdensome error checking: it is supposed that no other usecase would create an `App::Part` called 'Model', _i.e._ that if an `App::Part` is called 'Model' it will conform to these characteristics.
26 |
27 | Any Model can contain (by `App::Link`) any other Model, and they are placed to each-other by matchings their Datum Coordinate Systems (`PartDesign::CoordinateSystem`, called here-after LCS (Local Coordinate System)). There is no need for any geometry to be present to place and constrain parts relative to each other. LCS are used because they are both mechanical objects, since they fix all 6 degrees of freedom in an isostatic way, and mathematical objects that can be easily manipulated by rigorous methods (mainly combination and inversion).
28 |
29 | To actually include some geometry, a body needs to be created, and designed using the PartDesign workbench. To be linked with the previously created model, this body needs to be inside the `App::Part container` called 'Model'.
30 |
31 | The result is the following:
32 | 
33 |
34 | * the part _Bielle_ is placed in the assembly by attaching it's _LCS_0_ to the _LCS_0_ of the parent assembly.
35 | * the part _Cuve_ is placed in the assembly by placing its _LCS_0_ on the _LCS_1_ of the part _Bielle_
36 | * the part _Bague_ is placed in the assembly by placing its _LCS_0_ on the _LCS_0_ of the part _Bielle_
37 | * the parts _Screw_CHC_1_ and _Screw_CHC_2_ are placed in the assembly by placing their _LCS_0_ on the _LCS_1_ and _LCS_2_ of the part _Cuve_
38 |
39 |
40 | ## ExpressionEngine
41 |
42 | Assembly4 uses a special and very useful feature of FreeCAD: the ExpressionEngine. Some parameters can be entered through mathematical furmalae, that are evaluated by this ExpressionEngine. For Assembly4, it's the parameter _`Placement`_ of the inserted _`App::Link`_ object that is calculated, such that 2 LCS - one in the linked part and the one in the assembly - are superimposed.
43 |
44 | In normal use, the ExpressionEngine of an _`App::Link`_ object is hidden, it must be shown as in the following screenshot:
45 |
46 | 
47 |
48 |
49 | The syntax of the ExpressionEngine is the following:
50 |
51 |
52 | * **If the LCS belongs to the parent assembly:**
53 |
54 | `LCS_parent.Placement * constr_LinkName.AttachmentOffset * LinkedPart#LCS_link.Placement ^ -1`
55 |
56 | * **If the LCS belongs to a sister part:**
57 |
58 | `ParentLink.Placement * ParentPart#LCS_parent.Placement * constr_LinkName.AttachmentOffset * LinkedPart#LCS.Placement ^ -1`
59 |
60 | * _ParentLink_ is the name of the App::Link of the sister part in the assembly
61 | * _ParentPart_ is the name of the App::Part that the previous ParentLink refers-to
62 | * _LCS_parent_ is the LCS in the parent part (can be either the assembly itself or a sister part in the assembly)
63 | * _constr_LinkName_ is a FeaturePython object with a conventional name
64 | * _LinkedPart_ is the App::Part's name that the inserted App::Link refers-to
65 | * _LCS_link_ is the LCS in the linked part
66 |
67 | **Constraints**:
68 |
69 | To each part inserted into an assembly is associated an `App::FeaturePython` object, placed in the 'Constraints' group. This object contains information about the placement of the linked object in the assembly. It also contains an `App::Placement`, called 'AttachmentOffset', which introduces an offset between the LCS in the part and the LCS in the assembly. The main purpose of this offset is to correct bad orientations between the 2 matching LCS.
70 |
71 |
72 | These constraints are not really constraints in the traditional CAD sense, but since `App::FeaturePython` objects are very versatile, they could be expanded to contain real constraints in some (distant) future.
73 |
74 | 
75 |
76 | _Taking a closer look at the fields contained in an `App::FeaturePython` object associated with the part 'Bague'. The small button under the cursor opens the dialog that allows to edit the parameters of the Attachment Offset_
77 |
78 | 
79 |
80 | _Dialog that opens when clicking the previous small button, and permitting to edit the parameters of the_ `App::Placement` _called_ 'AttachmentOffset' _in the constraint associated with a link, and allowing relative placement of the link -vs- the attachment LCS_
81 |
82 | ## Workflow
83 |
84 | The toolbar for the Assembly4 workbench holds the following buttons:
85 |
86 |
87 | 
88 |
89 |
90 | * New Model
91 | * Insert link
92 | * Place linked Part
93 | * New LCS in the Model
94 | * Import Datum object
95 | * Place Datum object
96 | * New Sketch in the Model
97 | * New Datum Point in the Model
98 | * New Body in the Model
99 |
100 |
101 |
102 | ### Part
103 |
104 | The basic workflow for creating a part is the following:
105 |
106 | * create a new document, create a new 'Model' with the macro 'new_Model'. This will create a new App::Part called 'Model' with the default structure
107 | * create or import geometries in bodies (`PartDesign::Body`)
108 | * create LCS with the macro 'new_ModelLCS', and place them wherever you feel they're useful. Use the MapMode to attach the LCS
109 | * save part (only saved parts can be used currently). If necessary, close and re-open the file
110 | * repeat
111 |
112 | ### Assembly
113 |
114 | The basic workflow for creating a part is the following:
115 |
116 | * create a new document, create a new 'Model'
117 | * create a new sketch with the 'new_ModelSketch' macro. Attach it to whatever is useful, and draw the skeleton of the assembly, placing vertices and lines where useful
118 | * create new LCS (with the new_ModelLCS macro) and place them on the correct vertices of the sketch (using MapMode)
119 | * save document
120 | * import a part using the 'link_Model' macro. It will place your part with its LCS_0 to the LCS_0 of the assembly. You should give it a recognisable name.
121 | * select the link in the tree, and execute the macro 'place_Link'. Select the LCS of the part that you want to use as attachment point, select the part that you want it to be attached, and select the LCS in that part where you want the part to be attached. Click 'Apply'.
122 | * if the placement corresponds to your needs, click 'Ok', else select other LCS / Part until satisfied
123 | * if the part is correctly paced but badly oriented, select the corresponding constraint in the 'Constraints' folder, select the property 'Offset', and modify its parameters. Click 'Apply'. Repeat until satisfied
124 |
125 | ### Nested assemblies
126 |
127 | The previous method allows to assemble parts within a single level.
128 | But this workbench also allows the assembly of assemblies: since there is no difference between
129 | parts and assemblies, the 'Insert External Part' allows to chose a part that has other parts linked to it.
130 | The only difference will be for the coordinate systems in the inserted assemblies: in order to be used
131 | with Assembly 4, a coordinate system must be directly in the root 'Model' container, meaning that a
132 | coordinate system inside a linked part cannot be used to attach the assembly to a higher-level assembly.
133 |
134 | Therefore, in order to re-use a coordinate system of a part in an assembly, a coordinate system must be created at the root of the 'Model', and the placement of this coordinate system must be 'copied' over from the coordinate system that the user wants to use. This is done by inserting a coordinate system and using the 'Place LCS' command, which allows to select a linked part in the assembly and one of it's coordinate systems: the 2 coordinate systems — the one at the root of 'Model' and the one in the linked part — will always be superimposed, even if the linked part is modified, allowing the placement of the assembly in a higher level assembly using a linked part as reference. It sounds more complicated than it actually is.
135 |
136 | 
137 |
138 | 
139 |
140 |
141 |
142 | #### Release notes
143 |
144 |
145 |
146 | #### Release notes
147 |
148 | * 2019.10.05 (**version 0.6**) :
149 | Ported to FreeCAD-v0.19-pre, with new syntax for the ExpressionEngine
150 |
151 | * 2019.07.23 (**version 0.5.5**) :
152 | Fixed a bug in partLCSlist.findItems
153 |
154 | * 2019.07.18 (**version 0.5.4**) :
155 | A cosmetic update to fix a 25 year old Windows bug:
156 | some UTF-8 characters in the comments were not accepted on some Windows 10 machines
157 |
158 | * 2019.06.15 (**version 0.5.3**) :
159 | Now the LCS can be renamed, and they show up in the LCS list in the command placeLink as such.
160 | It's only visual, the ExpressionEngine still uses the LCS.Name though
161 |
162 | * 2019.05.07 (**version 0.5.2**) :
163 | added insertDatumCmd
164 |
165 | * 2019.03.18 (**version 0.5.1**) :
166 | Part can now be linked without being placed: this is then a raw interface with App::Link
167 | The instance can be moved manually with the 'Transform' dragger
168 |
169 | * 2019.03.12 (**version 0.5**) :
170 | moved the actual code to Mod_Asm4
171 |
172 | * 2019.03.11 (**version 0.4.1**) :
173 | Added placement of Datum Point
174 |
175 | * 2019.03.09 (**version 0.4**) :
176 | FreeCAD now imports as App
177 | insert_Link launches place_Link
178 |
179 | * 2019.03.05 (**version 0.3.1**) :
180 | added the RotX-Y-Z buttons
181 |
182 | * 2019.02.20 (**version 0.3**)
183 | mostly working version
184 |
185 | * 2019.02.18 (**version 0.1**) :
186 | initial release of Assembly 4 WB
187 |
188 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/FreeCad.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
257 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/Model_NewSketch.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
300 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/PartDesign_Body.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
323 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/ImportDatum.svg:
--------------------------------------------------------------------------------
1 |
2 |
257 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/LinkModel.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
297 |
--------------------------------------------------------------------------------
/Mod_Asm4/importDatumCmd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | #
4 | # placeDatumCmd.py
5 |
6 |
7 | from PySide import QtGui, QtCore
8 | import FreeCADGui as Gui
9 | import FreeCAD as App
10 | import Part, math, re
11 |
12 | from libAsm4 import *
13 |
14 |
15 |
16 | """
17 | +-----------------------------------------------+
18 | | main class |
19 | +-----------------------------------------------+
20 | """
21 | class importDatum( QtGui.QDialog ):
22 | "My tool object"
23 |
24 |
25 | def __init__(self):
26 | super(importDatum,self).__init__()
27 | self.datumTable = [ ]
28 |
29 |
30 | def GetResources(self):
31 | return {"MenuText": "Import Datum Point or Coordinate System",
32 | "ToolTip": "Import a Datum Point or Coordinate System from a linked Part",
33 | "Pixmap" : os.path.join( iconPath , 'ImportDatum.svg')
34 | }
35 |
36 | def IsActive(self):
37 | if App.ActiveDocument:
38 | return True
39 | else:
40 | return False
41 |
42 |
43 | """
44 | +-----------------------------------------------+
45 | | the real stuff |
46 | +-----------------------------------------------+
47 | """
48 | def Activated(self):
49 |
50 | # get the current active document to avoid errors if user changes tab
51 | self.activeDoc = App.activeDocument()
52 |
53 | # Now we can draw the UI
54 | self.drawUI()
55 |
56 |
57 | # We get all the App::Link parts in the assembly
58 | self.asmParts = []
59 | # the first item is "Select linked Part" therefore we add an empty object
60 | self.asmParts.append( [] )
61 | # find all the linked parts in the assembly
62 | for obj in self.activeDoc.findObjects("App::Link"):
63 | if obj.LinkedObject.isDerivedFrom('App::Part'):
64 | # add it to our tree table if it's a link to an App::Part ...
65 | self.asmParts.append( obj )
66 | # ... and add to the drop-down combo box with the assembly tree's parts
67 | objIcon = obj.LinkedObject.ViewObject.Icon
68 | self.parentList.addItem( objIcon, obj.Name, obj)
69 | # Set the list to the first element
70 | self.parentList.setCurrentIndex( 0 )
71 |
72 |
73 | # Now we can show the UI
74 | self.show()
75 |
76 |
77 |
78 | """
79 | +-----------------------------------------------+
80 | | check that all necessary things are selected, |
81 | | populate the expression with the selected |
82 | | elements, put them into the constraint |
83 | | and trigger the recomputation of the part |
84 | +-----------------------------------------------+
85 | """
86 | def onApply(self):
87 | # get the name of the part where the datum to be copied is:
88 | #linkedPartName = self.parentList.currentText()
89 |
90 |
91 | # get the name of the part to attach to:
92 | # it's either the top level part name ('Model')
93 | # or the provided link's name.
94 | if self.parentList.currentIndex() > 0:
95 | parent = self.asmParts[ self.parentList.currentIndex() ]
96 | linkName = parent.Name
97 | linkedPart = parent.LinkedObject.Document.Name
98 | else:
99 | linkName = None
100 | linkedPart = None
101 |
102 | # check that something is selected in the datum list
103 | if self.datumList.selectedItems():
104 | # this is in the QWidgetList...
105 | listDatumName = self.datumList.currentItem().text()
106 | # ... and this is the table with the actual datum objects
107 | datumType = self.datumTable[ self.datumList.currentRow() ].TypeId
108 | else:
109 | listDatumName = None
110 |
111 | # the name of the datum in the assembly, as per the dialog box
112 | datumAsmName = self.datumName.text()
113 |
114 | # check that all of them have something in
115 | if not (linkName and linkedPart and listDatumName and datumType and datumAsmName):
116 | self.datumName.setText( 'Please select a Datum object' )
117 | self.importDatumName()
118 | else:
119 | # create the Datum
120 | if datumType == 'PartDesign::CoordinateSystem':
121 | #createdLink = self.activeDoc.getObject('Model').newObject( 'App::Link', linkName )
122 | createdDatum = App.activeDocument().getObject('Model').newObject( 'PartDesign::CoordinateSystem', datumAsmName )
123 | self.datumName.setText( '=> ' +createdDatum.Name )
124 | self.importSucceeded()
125 | elif datumType == 'PartDesign::Point':
126 | createdDatum = App.activeDocument().getObject('Model').newObject( 'PartDesign::Point', datumAsmName )
127 | self.datumName.setText( '=> ' +createdDatum.Name )
128 | self.importSucceeded()
129 | else:
130 | self.datumName.setText( 'unsupported Datum::Type' )
131 | return
132 | # build the expression to the linked datum (not the datumName in the assembly !)
133 | expr = makeExpressionDatum( linkName, linkedPart, listDatumName )
134 | # load the built expression into the Expression field of the datum created in the assembly
135 | self.activeDoc.getObject( createdDatum.Name ).setExpression( 'Placement', expr )
136 | # recompute the object to apply the placement:
137 | createdDatum.recompute()
138 | # highlight the selected LCS in its new position
139 | Gui.Selection.clearSelection()
140 | Gui.Selection.addSelection( self.activeDoc.Name, 'Model', createdDatum.Name +'.')
141 | # clear the selection in the datum list
142 | #self.datumList.setCurrentRow( -1, QtGui.QItemSelectionModel.Clear )
143 | self.datumList.setCurrentRow( -1 )
144 | return
145 |
146 |
147 |
148 |
149 | """
150 | +-----------------------------------------------+
151 | | find all the linked parts in the assembly |
152 | +-----------------------------------------------+
153 |
154 | def getAllLinkedParts(self):
155 | allLinkedParts = [ ]
156 | for obj in self.activeDoc.findObjects("App::Link"):
157 | # add it to our list if it's a link to an App::Part
158 | if obj.LinkedObject.isDerivedFrom('App::Part'):
159 | allLinkedParts.append( obj )
160 | return allLinkedParts
161 | """
162 |
163 | """
164 | +-----------------------------------------------+
165 | | get all the Datums in a Link |
166 | +-----------------------------------------------+
167 | """
168 | def getLinkDatums( self, link ):
169 | linkDatums = [ ]
170 | # parse all objects in the part (they return strings)
171 | for objName in link.getSubObjects():
172 | # get the proper objects
173 | # all object names end with a "." , this needs to be removed
174 | obj = link.getObject( objName[0:-1] )
175 | if obj.TypeId == 'PartDesign::CoordinateSystem' or obj.TypeId == 'PartDesign::Point':
176 | linkDatums.append( obj )
177 | return linkDatums
178 |
179 |
180 | """
181 | +------------------------------------------------+
182 | | fill the LCS list when changing the parent |
183 | +------------------------------------------------+
184 | """
185 | def onParentList(self):
186 | self.importDatumName()
187 | # clear the LCS list
188 | self.datumList.clear()
189 | self.datumTable = [ ]
190 | self.datumName.setText( '' )
191 | # clear the selection in the GUI window
192 | Gui.Selection.clearSelection()
193 | # the current text in the combo-box is the link's name...
194 | parentName = self.parentList.currentText()
195 | # ... or it's 'Parent Assembly' then the parent is the 'Model' root App::Part
196 | if parentName =='Parent Assembly':
197 | Parent = self.activeDoc.getObject( 'Model' )
198 | # we get the LCS directly in the root App::Part 'Model'
199 | partDatums = self.getLinkDatums( Parent )
200 | self.parentDoc.setText( Parent.Document.Name )
201 | # a sister object is an App::Link
202 | # the .LinkedObject is an App::Part
203 | else:
204 | Parent = self.activeDoc.getObject( parentName )
205 | # we get the LCS from the linked part
206 | partDatums = self.getLinkDatums( Parent.LinkedObject )
207 | self.parentDoc.setText( Parent.LinkedObject.Document.Name )
208 | # highlight the selected part:
209 | Gui.Selection.addSelection( Parent.Document.Name, 'Model', Parent.Name+'.' )
210 | # build the list
211 | for datum in partDatums:
212 | newItem = QtGui.QListWidgetItem()
213 | newItem.setText( datum.Name )
214 | newItem.setIcon( datum.ViewObject.Icon )
215 | self.datumList.addItem( newItem )
216 | self.datumTable.append(datum)
217 | return
218 |
219 |
220 | """
221 | +-----------------------------------------------+
222 | | An LCS has been clicked in 1 of the 2 lists |
223 | | We highlight both LCS |
224 | +-----------------------------------------------+
225 | """
226 | def onDatumClicked( self ):
227 | self.importDatumName()
228 | # clear the selection in the GUI window
229 | Gui.Selection.clearSelection()
230 | # get the part where the selected LCS is
231 | a_Part = self.parentList.currentText()
232 | # LCS in the parent
233 | a_LCS = self.datumList.selectedItems()[0].text()
234 | # pre-fill the Datum name with the one selected
235 | self.datumName.setText( a_LCS )
236 | # parent assembly and sister part need a different treatment
237 | if a_Part == 'Parent Assembly':
238 | linkDot = ''
239 | else:
240 | linkDot = a_Part+'.'
241 | # Highlight the selected datum object
242 | Gui.Selection.addSelection( self.activeDoc.Name, 'Model', linkDot+a_LCS+'.')
243 | return
244 |
245 |
246 |
247 | """
248 | +-----------------------------------------------+
249 | | success |
250 | +-----------------------------------------------+
251 | """
252 | def importSucceeded(self):
253 | self.datumLabel.setText("Datum object successfully imported:")
254 | return
255 |
256 |
257 | """
258 | +-----------------------------------------------+
259 | | success |
260 | +-----------------------------------------------+
261 | """
262 | def importDatumName(self):
263 | self.datumLabel.setText("The imported Datum objects's name:")
264 | return
265 |
266 |
267 |
268 | """
269 | +-----------------------------------------------+
270 | | Cancel |
271 | | restores the previous values |
272 | +-----------------------------------------------+
273 | """
274 | def onCancel(self):
275 | self.close()
276 |
277 |
278 | """
279 | +-----------------------------------------------+
280 | | OK |
281 | | accept and close |
282 | +-----------------------------------------------+
283 | """
284 | def onOK(self):
285 | self.onApply()
286 | self.close()
287 |
288 |
289 | """
290 | +-----------------------------------------------+
291 | | defines the UI, only static elements |
292 | +-----------------------------------------------+
293 | """
294 | def drawUI(self):
295 | # Our main window will be a QDialog
296 | self.setWindowTitle('Import a Datum object')
297 | self.setWindowIcon( QtGui.QIcon( os.path.join( iconPath , 'FreeCad.svg' ) ) )
298 | self.setMinimumSize(400, 590)
299 | self.resize(400,590)
300 | self.setModal(False)
301 | # make this dialog stay above the others, always visible
302 | self.setWindowFlags( QtCore.Qt.WindowStaysOnTopHint )
303 |
304 | # label
305 | self.linkLabel = QtGui.QLabel(self)
306 | self.linkLabel.setText("Select a linked Part:")
307 | self.linkLabel.move(10,20)
308 | # combobox showing all available App::Link
309 | self.parentList = QtGui.QComboBox(self)
310 | self.parentList.move(10,55)
311 | self.parentList.setMinimumSize(380, 1)
312 | # initialize with an explanation
313 | self.parentList.addItem( 'Please select ...' )
314 |
315 | # label
316 | self.parentLabel = QtGui.QLabel(self)
317 | self.parentLabel.setText("Parent Document (for info):")
318 | self.parentLabel.move(10,100)
319 | # the document containing the linked object
320 | self.parentDoc = QtGui.QLineEdit(self)
321 | self.parentDoc.setReadOnly(True)
322 | self.parentDoc.setMinimumSize(330, 1)
323 | self.parentDoc.move(30,130)
324 | # label
325 | self.labelRight = QtGui.QLabel(self)
326 | self.labelRight.setText("Select Datum object to import :")
327 | self.labelRight.move(10,180)
328 | # The list of all attachment LCS in the assembly is a QListWidget
329 | # it is populated only when the parent combo-box is activated
330 | self.datumList = QtGui.QListWidget(self)
331 | self.datumList.move(10,220)
332 | self.datumList.setMinimumSize(380, 220)
333 |
334 | # imported Link name
335 | self.datumLabel = QtGui.QLabel(self)
336 | #self.datumLabel.setText("The imported Datum objects's name:")
337 | self.importDatumName()
338 | self.datumLabel.move(10,460)
339 | # the name as seen in the tree of the selected link
340 | self.datumName = QtGui.QLineEdit(self)
341 | self.datumName.setMinimumSize(380, 1)
342 | self.datumName.move(10,495)
343 |
344 | # Buttons
345 | #
346 | # Cancel button
347 | self.CancelButton = QtGui.QPushButton('Close', self)
348 | self.CancelButton.setAutoDefault(False)
349 | self.CancelButton.move(10, 550)
350 |
351 | # Apply button
352 | self.ApplyButton = QtGui.QPushButton('Import', self)
353 | self.ApplyButton.setAutoDefault(False)
354 | self.ApplyButton.move(310, 550)
355 | self.ApplyButton.setDefault(True)
356 |
357 | # OK button
358 | #self.OKButton = QtGui.QPushButton('OK', self)
359 | #self.OKButton.setAutoDefault(False)
360 | #self.OKButton.move(310, 550)
361 | #self.OKButton.setDefault(True)
362 |
363 | # Actions
364 | self.CancelButton.clicked.connect(self.onCancel)
365 | self.ApplyButton.clicked.connect(self.onApply)
366 | #self.OKButton.clicked.connect(self.onOK)
367 | self.parentList.currentIndexChanged.connect( self.onParentList )
368 | self.datumList.itemClicked.connect( self.onDatumClicked )
369 |
370 |
371 |
372 | """
373 | +-----------------------------------------------+
374 | | add the command to the workbench |
375 | +-----------------------------------------------+
376 | """
377 | Gui.addCommand( 'importDatumCmd', importDatum() )
378 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/Assembly4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
412 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/Model.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
412 |
--------------------------------------------------------------------------------
/Mod_Asm4/icons/Place_AxisCross_small.svg:
--------------------------------------------------------------------------------
1 |
2 |
367 |
--------------------------------------------------------------------------------