├── fnwb_locator.py ├── TODO.md ├── Init.py ├── InitGui.py ├── DlgChangeParams.ui ├── Icons ├── IconMove.svg ├── IconCSHole.svg ├── IconChangeParam.svg ├── IconShape.svg ├── ISO7092.svg ├── ISO7093-1.svg ├── ISO7089.svg ├── ISO7094.svg ├── ISO7090.svg ├── IconMatchTypeInner.svg ├── IconMatchTypeOuter.svg ├── IconScrewCalc.svg ├── IconFlip.svg ├── PEMStud.svg ├── PEMTHStandoff.svg ├── PEMBLStandoff.svg ├── ISO7380-2.svg ├── IconBOM.svg └── ISO2010.svg ├── WidgetScrewCalc.ui ├── FSprefs.ui ├── README.md ├── DlgCountersunkHoles.ui ├── FSScrewCalc.py ├── ScrewMaker.py └── FSNuts.py /fnwb_locator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # fnwb_locator.py 5 | # 6 | # Copyright 2015 Shai Seger 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 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * ~~Add Press-Nuts~~ 2 | * ~~Add stand-offs~~ 3 | * ~~Add studs~~ 4 | * ~~Add standard nuts~~ 5 | * ~~Separate toolbars to different fastener types~~ 6 | * ~~Use drop-down buttons~~ 7 | * ~~show only available M diameters / lengths to each screw type~~ 8 | * ~~More decriptive names to fasteners~~ 9 | * ~~Changing screw type, will change icon as well~~ 10 | * ~~When generating a simple copy of a part, copy the name as well~~ 11 | * ~~Add more multi select options~~ 12 | * ~~Offsets can be negative~~ 13 | * ~~Support face selection~~ 14 | * ~~Add threads to nuts~~ 15 | * ~~Add "Make hole countersunk" function~~ 16 | * ~~Countersunk screws should be autofit by head diameter rather then by thread diameter~~ 17 | * Add option to insert screws in the countersunk hole generation dialog 18 | * ~~Add option to select auto diameter method - by inner or outer thread diameter~~ 19 | * ~~Select the type of countersunk holes to match the screws~~ 20 | * ~~Add option to batch-change fasteners properties~~ 21 | * ~~Add function to generate spreadsheet BOM~~ 22 | * Add various types of hole generation as proposed by pablogil 23 | -------------------------------------------------------------------------------- /Init.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # Init.py 5 | # 6 | # Copyright 2015 Shai Seger 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 "Fasteners workbench Loaded" 27 | -------------------------------------------------------------------------------- /InitGui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # InitGui.py 5 | # 6 | # Copyright 2015 Shai Seger 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 fnwb_locator 27 | fnWBpath = os.path.dirname(fnwb_locator.__file__) 28 | fnWB_icons_path = os.path.join( fnWBpath, 'Icons') 29 | 30 | global main_fnWB_Icon 31 | main_fnWB_Icon = os.path.join( fnWB_icons_path , 'FNLogo.svg') 32 | 33 | 34 | class FastenersWorkbench (Workbench): 35 | 36 | global main_fnWB_Icon 37 | 38 | MenuText = "Fasteners" 39 | ToolTip = "Create ISO Fasteners" 40 | Icon = main_fnWB_Icon 41 | 42 | def Initialize(self): 43 | "This function is executed when FreeCAD starts" 44 | import os 45 | import FastenerBase, FSScrewCalc, PEMInserts, FastenersCmd, FSNuts 46 | import CountersunkHoles, FSChangeParams 47 | self.list = [] 48 | cmdlist = FastenerBase.FSGetCommands("command") 49 | self.appendToolbar("FS Commands",cmdlist) 50 | self.list.extend(cmdlist) 51 | screwlist1 = FastenerBase.FSGetCommands("screws") 52 | screwlist = [] 53 | for cmd in screwlist1: 54 | if isinstance(cmd, tuple): # group in sub toolbars 55 | #FreeCAD.Console.PrintLog("Append toolbar " + str(cmd) + "\n") 56 | self.appendToolbar(cmd[0],cmd[1]) 57 | self.list.extend(cmd[1]) 58 | else: 59 | screwlist.append(cmd) 60 | if len(screwlist) > 0: 61 | self.appendToolbar("FS Screws",screwlist) # creates main screw toolbar 62 | self.list.extend(screwlist) 63 | FreeCADGui.addIconPath(FastenerBase.iconPath) 64 | FreeCADGui.addPreferencePage( os.path.join( FastenerBase.__dir__, 'FSprefs.ui'),'Fasteners' ) 65 | 66 | # self.appendMenu("My New Menu",self.list) # creates a new menu 67 | # self.appendMenu(["An existing Menu","My submenu"],self.list) # appends a submenu to an existing menu 68 | 69 | def Activated(self): 70 | "This function is executed when the workbench is activated" 71 | return 72 | 73 | def Deactivated(self): 74 | "This function is executed when the workbench is deactivated" 75 | return 76 | 77 | def ContextMenu(self, recipient): 78 | "This is executed whenever the user right-clicks on screen" 79 | # "recipient" will be either "view" or "tree" 80 | self.appendContextMenu("My commands",self.list) # add commands to the context menu 81 | 82 | def GetClassName(self): 83 | # this function is mandatory if this is a full python workbench 84 | return "Gui::PythonWorkbench" 85 | 86 | Gui.addWorkbench(FastenersWorkbench()) -------------------------------------------------------------------------------- /DlgChangeParams.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DlgChangeParams 4 | 5 | 6 | 7 | 0 8 | 0 9 | 451 10 | 216 11 | 12 | 13 | 14 | Change fastener parameters 15 | 16 | 17 | 18 | 19 | 20 | Fastener Parameters 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Fastener type: 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Auto set diameter 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Diameter: 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Length: 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Set length (mm): 85 | 86 | 87 | 88 | 89 | 90 | 91 | 2.000000000000000 92 | 93 | 94 | 9999.989999999999782 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Qt::Vertical 104 | 105 | 106 | 107 | 20 108 | 40 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /Icons/IconMove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 43 | 52 | 53 | 72 | 74 | 75 | 77 | image/svg+xml 78 | 80 | 81 | 82 | 83 | 84 | 89 | 95 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /WidgetScrewCalc.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DockWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 267 10 | 136 11 | 12 | 13 | 14 | true 15 | 16 | 17 | Screw hole calculator 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Fastener type: 29 | 30 | 31 | 32 | 33 | 34 | 35 | Qt::Horizontal 36 | 37 | 38 | 39 | 40 40 | 20 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Screw Diameter: 56 | 57 | 58 | 59 | 60 | 61 | 62 | Qt::Horizontal 63 | 64 | 65 | 66 | 40 67 | 20 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Suggested Hole diameter (mm): 83 | 84 | 85 | 86 | 87 | 88 | 89 | Qt::Horizontal 90 | 91 | 92 | 93 | 40 94 | 20 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | true 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | Qt::Vertical 112 | 113 | 114 | 115 | 20 116 | 40 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /FSprefs.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Gui::Dialog::DlgSettingsDraft 4 | 5 | 6 | 7 | 0 8 | 0 9 | 563 10 | 512 11 | 12 | 13 | 14 | General settings 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 1 23 | 24 | 25 | 26 | General 27 | 28 | 29 | 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | 37 | Toolbar screw icons grouping: 38 | 39 | 40 | 41 | 42 | 43 | 44 | Qt::Horizontal 45 | 46 | 47 | 48 | 40 49 | 20 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Method of icon grouping 58 | 59 | 60 | 0 61 | 62 | 63 | ScrewToolbarGroupMode 64 | 65 | 66 | Mod/Fasteners 67 | 68 | 69 | 70 | None 71 | 72 | 73 | 74 | 75 | Separate toolbars 76 | 77 | 78 | 79 | 80 | Drop down buttons 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Qt::Vertical 91 | 92 | 93 | 94 | 20 95 | 40 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 0 108 | 0 109 | 110 | 111 | 112 | 113 | 16777215 114 | 30 115 | 116 | 117 | 118 | 119 | 14 120 | 121 | 122 | 123 | Preferences for the Fasteners Workbench 124 | 125 | 126 | 127 | 128 | 129 | 130 | qPixmapFromMimeSource 131 | 132 | 133 | Gui::PrefComboBox 134 | QComboBox 135 |
Gui/PrefWidgets.h
136 |
137 |
138 | 139 | 140 |
141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FreeCAD Fasteners Workbench 2 | A FreeCAD workbench to add/attach various fasteners to parts 3 | 4 | ![Fasteners_toolbar](https://user-images.githubusercontent.com/4140247/32561138-815cc5f8-c479-11e7-988e-3be19d3e98c3.png) 5 | ![image](https://user-images.githubusercontent.com/4140247/32561849-4276249a-c47b-11e7-9977-110be802d624.png) 6 | ![image](https://user-images.githubusercontent.com/4140247/32561853-466e708e-c47b-11e7-9029-923256e50650.png)![image](https://user-images.githubusercontent.com/4140247/32561890-5a563096-c47b-11e7-9026-cf81bea25834.png)![image](https://user-images.githubusercontent.com/4140247/32562381-82b2e8d0-c47c-11e7-828d-1e1b361c6f12.png) 7 | 8 | 9 | 10 | ### Installation 11 | Starting from FreeCAD v0.17.9940 an Addons Installer has been built-in to FreeCAD and can be accessed from the Tools menu. 12 | You can use said Addon Installer to seamlessly install Fasteners Workbench. 13 | Versions before FreeCAD require manual installation, please see how via http://theseger.com/projects/2015/06/fasteners-workbench-for-freecad/ 14 | 15 | 16 | ### Usage 17 | Please see http://theseger.com/projects/2015/06/fasteners-workbench-for-freecad/ for a synopsis on how to use Fasteners Workbench. 18 | 19 | ### Note for FreeCAD 0.17 Part Design: 20 | To attach a fastener to a feature created with part design, it must be attached to the body, rather then one of its inner elements. To do so, first switch the "Display Mode" of the body from "Through" to "Tip". This can be found in the "View" tab of the Body's properties panel. To continue editing the Body, switch back to "Through" 21 | It is now done automatically 22 | 23 | #### Release notes 24 | * V0.3.02 14 Jan 2019: Fix hole matching bug 25 | * V0.3.01 28 Dec 2018: Fix ISO4762 M4x10 generation issue (occ7.2). 26 | * V0.3.00 03 Sep 2018: Make WB compatible with Python 3 and Qt 5 27 | * V0.2.19 22 Aug 2018: Add DIN 985 - Nyloc nuts. issue #27 28 | * V0.2.18 13 Aug 2018: Fix ISO10642 & ISO14584 issues on ver 0.18. issue #25 29 | * V0.2.17 13 Jul 2018: Fix Bug: selecting a face to select all holes did not work. 30 | * V0.2.16 01 May 2018: Attaching to Partdesign features redirect the atachment to the parent body 31 | * V0.2.15 25 Apr 2018: Add DIN 562 and DIN 557 square nuts 32 | * V0.2.14 19 Jun 2017: Fix countersunk function bug. Merge Maurice's fix for screw generation 33 | * V0.2.13 01 Oct 2015: Add generation of BOM 34 | * V0.2.11 24 Aug 2015: Add inner/outer match attribute to screws. Fix several bugs 35 | * V0.2.10 23 Aug 2015: Add new command: Batch change fasteners parameters 36 | * V0.2.09 23 Aug 2015: Fixed screw creation bug when not attached to geometry 37 | * V0.2.08 06 Aug 2015: Add threaded rod item. Fix loading issue 38 | * V0.2.07 05 Aug 2015: Add option to select auto crew diameter matching method: 39 | by inner or outer thread diameter 40 | * V0.2.06 02 Aug 2015: Added hole diameter calculator helper. 41 | * V0.2.05 01 Aug 2015: Option to select type of screw for countersunk holes. 42 | * V0.2.03 30 Jul 2015: Separate option for grouping icons as toolbars or as drop-down buttons 43 | * V0.2.01 28 Jul 2015: Update to Ulrich's V2.0 screw maker. many more screws, and nuts with threads! 44 | * V0.1.04 21 Jul 2015: Drop-down buttons can be enabled in Preferences unser Fasteners. 45 | * V0.1.03 15 Jul 2015: Disable drop-down buttons. It will be used only when screw items count will be too big. 46 | * V0.1.02 14 Jul 2015: Group screws in drop-down buttons (works for FreeCAD 0.16 and up) 47 | * V0.1.01 13 Jul 2015: Add a command to make recessed holes for countersunk screws. 48 | * V0.0.10 29 Jun 2015: Add PEM Metric Studs. 49 | * V0.0.09 28 Jun 2015: Selecting a face will put a fastener in all holes in that face. 50 | Caching of fasteners speed up generation of same shape ones 51 | * V0.0.08 27 Jun 2015: Edge selection over multiple objects when generating fasteners now works. 52 | * V0.0.07 26 Jun 2015: Add PEM Standoffs 53 | * V0.0.06 25 Jun 2015: Show only applicable M values and lengths, add descriptive name 54 | * V0.0.05 24 Jun 2015: Add simplify object function, Change icon colors 55 | * V0.0.04 23 Jun 2015: Add ISO 4032 Metric Hex Nut 56 | * V0.0.03 21 Jun 2015: Add PEM Metric Press-Nut (Self clinching nut) 57 | * V0.0.02 18 Jun 2015: Save/Load issue fixed 58 | * V0.0.01 18 Jun 2015: Initial version 59 | 60 | 61 | #### Developers 62 | * ScrewMaker: Ulrich Brammer [@ulrich1a](https://github.com/ulrich1a) 63 | * Workbench wrapper: Shai Seger [@shaise](https://github.com/shaise) 64 | 65 | ### Feedback 66 | For further discussion, feel free to open a forum thread on [FreeCAD Open Discussion subforum](https://forum.freecadweb.org/viewforum.php?f=8&sid=853eff68d2a09bfd39fb3508d038af97) 67 | and make sure to ping user 'shaise'. 68 | -------------------------------------------------------------------------------- /Icons/IconCSHole.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 44 | 54 | 55 | 74 | 76 | 77 | 79 | image/svg+xml 80 | 82 | 83 | 84 | 85 | 86 | 91 | 94 | 100 | 105 | 106 | 109 | 115 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /Icons/IconChangeParam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 44 | 45 | 64 | 66 | 67 | 69 | image/svg+xml 70 | 72 | 73 | 74 | 75 | 76 | 81 | 87 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /DlgCountersunkHoles.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DlgCountersunktHoles 4 | 5 | 6 | 7 | 0 8 | 0 9 | 424 10 | 426 11 | 12 | 13 | 14 | Countersunk screw holes 15 | 16 | 17 | 18 | 19 | 20 | Shape 21 | 22 | 23 | 24 | 6 25 | 26 | 27 | 9 28 | 29 | 30 | 31 | 32 | Base shape: 33 | 34 | 35 | 36 | 37 | 38 | 39 | Base 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | Chamfer Parameters 50 | 51 | 52 | 53 | 54 | 55 | 5 56 | 57 | 58 | 59 | 60 | Qt::Horizontal 61 | 62 | 63 | 64 | 221 65 | 20 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | All 74 | 75 | 76 | 77 | 78 | 79 | 80 | None 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 6 93 | 94 | 95 | 0 96 | 97 | 98 | 99 | 100 | Diameter: 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | No selection 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | Qt::Horizontal 117 | 118 | 119 | QSizePolicy::Minimum 120 | 121 | 122 | 123 | 40 124 | 20 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 0 135 | 136 | 137 | 138 | 139 | Screw type: 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | No Selection 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | Qt::Horizontal 156 | 157 | 158 | 159 | 40 160 | 20 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | treeView 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /Icons/IconShape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 28 | 29 | 48 | 50 | 51 | 53 | image/svg+xml 54 | 56 | 57 | 58 | 59 | 60 | 65 | 71 | 77 | 83 | 98 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Icons/ISO7092.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 67 | 72 | 77 | 82 | 87 | 92 | 97 | 102 | 107 | 112 | 117 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /Icons/ISO7093-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 67 | 72 | 77 | 82 | 87 | 92 | 97 | 102 | 107 | 112 | 117 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /Icons/ISO7089.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 67 | 69 | 74 | 79 | 84 | 89 | 94 | 99 | 104 | 109 | 114 | 119 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /Icons/ISO7094.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 67 | 72 | 77 | 82 | 87 | 92 | 97 | 102 | 107 | 112 | 117 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /Icons/ISO7090.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 67 | 72 | 77 | 82 | 87 | 92 | 96 | 103 | 104 | 109 | 114 | 119 | 124 | 129 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /Icons/IconMatchTypeInner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 44 | 45 | 64 | 66 | 67 | 69 | image/svg+xml 70 | 72 | 73 | 74 | 75 | 76 | 81 | 87 | 93 | 98 | 103 | 109 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Icons/IconMatchTypeOuter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 44 | 45 | 64 | 66 | 67 | 69 | image/svg+xml 70 | 72 | 73 | 74 | 75 | 76 | 81 | 87 | 93 | 98 | 103 | 109 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Icons/IconScrewCalc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 43 | 44 | 63 | 65 | 66 | 68 | image/svg+xml 69 | 71 | 72 | 73 | 74 | 75 | 80 | 82 | 91 | 99 | 102 | 110 | 118 | 119 | 122 | 130 | 138 | 139 | + 150 | - 161 | x 172 | 175 | 182 | 187 | 194 | 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /Icons/IconFlip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 37 | 41 | 45 | 46 | 49 | 53 | 57 | 58 | 61 | 65 | 69 | 70 | 79 | 88 | 97 | 106 | 115 | 124 | 133 | 142 | 143 | 162 | 164 | 165 | 167 | image/svg+xml 168 | 170 | 171 | 172 | 173 | 174 | 179 | 181 | 188 | 195 | 200 | 205 | 206 | 208 | 216 | 224 | 229 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /FSScrewCalc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # ScrewCalc.py 5 | # A calculator utility to calculate needed hole sizes for selected fasteners 6 | # 7 | # Copyright 2015 Shai Seger 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 22 | # MA 02110-1301, USA. 23 | # 24 | # 25 | ################################################################################### 26 | 27 | ################################################################################### 28 | # replace below with generated code from pyuic4 29 | ################################################################################### 30 | 31 | from PySide import QtCore, QtGui 32 | 33 | try: 34 | _fromUtf8 = QtCore.QString.fromUtf8 35 | except AttributeError: 36 | def _fromUtf8(s): 37 | return s 38 | 39 | try: 40 | _encoding = QtGui.QApplication.UnicodeUTF8 41 | def _translate(context, text, disambig): 42 | return QtGui.QApplication.translate(context, text, disambig, _encoding) 43 | except AttributeError: 44 | def _translate(context, text, disambig): 45 | return QtGui.QApplication.translate(context, text, disambig) 46 | 47 | class Ui_DockWidget(object): 48 | def setupUi(self, DockWidget): 49 | DockWidget.setObjectName(_fromUtf8("DockWidget")) 50 | DockWidget.resize(267, 136) 51 | DockWidget.setFloating(True) 52 | self.dockWidgetContents = QtGui.QWidget() 53 | self.dockWidgetContents.setObjectName(_fromUtf8("dockWidgetContents")) 54 | self.gridLayout = QtGui.QGridLayout(self.dockWidgetContents) 55 | self.gridLayout.setObjectName(_fromUtf8("gridLayout")) 56 | self.verticalLayout = QtGui.QVBoxLayout() 57 | self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) 58 | self.horizontalLayout = QtGui.QHBoxLayout() 59 | self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) 60 | self.label = QtGui.QLabel(self.dockWidgetContents) 61 | self.label.setObjectName(_fromUtf8("label")) 62 | self.horizontalLayout.addWidget(self.label) 63 | spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) 64 | self.horizontalLayout.addItem(spacerItem) 65 | self.comboFastenerType = QtGui.QComboBox(self.dockWidgetContents) 66 | self.comboFastenerType.setObjectName(_fromUtf8("comboFastenerType")) 67 | self.horizontalLayout.addWidget(self.comboFastenerType) 68 | self.verticalLayout.addLayout(self.horizontalLayout) 69 | self.horizontalLayout_2 = QtGui.QHBoxLayout() 70 | self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) 71 | self.label_2 = QtGui.QLabel(self.dockWidgetContents) 72 | self.label_2.setObjectName(_fromUtf8("label_2")) 73 | self.horizontalLayout_2.addWidget(self.label_2) 74 | spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) 75 | self.horizontalLayout_2.addItem(spacerItem1) 76 | self.comboDiameter = QtGui.QComboBox(self.dockWidgetContents) 77 | self.comboDiameter.setObjectName(_fromUtf8("comboDiameter")) 78 | self.horizontalLayout_2.addWidget(self.comboDiameter) 79 | self.verticalLayout.addLayout(self.horizontalLayout_2) 80 | self.horizontalLayout_3 = QtGui.QHBoxLayout() 81 | self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3")) 82 | self.labelHoleSize = QtGui.QLabel(self.dockWidgetContents) 83 | self.labelHoleSize.setObjectName(_fromUtf8("labelHoleSize")) 84 | self.horizontalLayout_3.addWidget(self.labelHoleSize) 85 | spacerItem2 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) 86 | self.horizontalLayout_3.addItem(spacerItem2) 87 | self.textHole = QtGui.QLineEdit(self.dockWidgetContents) 88 | self.textHole.setReadOnly(True) 89 | self.textHole.setObjectName(_fromUtf8("textHole")) 90 | self.horizontalLayout_3.addWidget(self.textHole) 91 | self.verticalLayout.addLayout(self.horizontalLayout_3) 92 | spacerItem3 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) 93 | self.verticalLayout.addItem(spacerItem3) 94 | self.gridLayout.addLayout(self.verticalLayout, 1, 0, 1, 1) 95 | DockWidget.setWidget(self.dockWidgetContents) 96 | 97 | self.retranslateUi(DockWidget) 98 | QtCore.QMetaObject.connectSlotsByName(DockWidget) 99 | 100 | def retranslateUi(self, DockWidget): 101 | DockWidget.setWindowTitle(_translate("DockWidget", "Screw hole calculator", None)) 102 | self.label.setText(_translate("DockWidget", "Fastener type:", None)) 103 | self.label_2.setText(_translate("DockWidget", "Screw Diameter:", None)) 104 | self.labelHoleSize.setText(_translate("DockWidget", "Suggested Hole diameter (mm):", None)) 105 | 106 | ################################################################################### 107 | # End position for generated code from pyuic4 108 | ################################################################################### 109 | 110 | def fillScrewTypes(self): 111 | self.comboFastenerType.currentIndexChanged.connect(self.onTypeChange) 112 | self.comboDiameter.currentIndexChanged.connect(self.onDiameterChange) 113 | self.comboFastenerType.clear() 114 | for type in FSCScrewTypes: 115 | icon, name, table = type 116 | self.comboFastenerType.addItem(QtGui.QIcon(os.path.join(iconPath , icon)), name) 117 | 118 | def fillDiameters(self): 119 | self.comboDiameter.clear() 120 | idx = self.comboFastenerType.currentIndex() 121 | table = FSCScrewTypes[idx][2] 122 | for diam in table: 123 | self.comboDiameter.addItem(diam[0]) 124 | 125 | def onDiameterChange(self, diamindex): 126 | idx = self.comboFastenerType.currentIndex() 127 | table = FSCScrewTypes[idx][2] 128 | self.textHole.setText(str(table[diamindex][1])) 129 | 130 | def onTypeChange(self, typeindex): 131 | self.fillDiameters() 132 | 133 | from FreeCAD import Gui 134 | from FreeCAD import Base 135 | import FreeCAD, FreeCADGui, Part, os, math 136 | __dir__ = os.path.dirname(__file__) 137 | iconPath = os.path.join( __dir__, 'Icons' ) 138 | 139 | import FastenerBase 140 | from FastenerBase import FSBaseObject 141 | import ScrewMaker 142 | screwMaker = ScrewMaker.Instance() 143 | 144 | FSCPEMPressNutHoleChart = ( 145 | ("M2", 4.22), 146 | ("M2.5", 4.22), 147 | ("M3", 4.22), 148 | ("M3.5", 4.75), 149 | ("M4", 5.41), 150 | ("M5", 6.35), 151 | ("M6", 8.75), 152 | ("M8", 10.5), 153 | ("M10", 14), 154 | ("M12", 17) 155 | ) 156 | 157 | # hole size +0.08 158 | FSCPEMStandOffHoleChart = ( 159 | ("M3", 4.22), 160 | ("3.5M3", 5.41), 161 | ("M3.5", 5.41), 162 | ("M4", 7.14), 163 | ("M5", 7.14) 164 | ) 165 | 166 | FSCPEMStudHoleChart = ( 167 | ("M2.5", 2.5), 168 | ("M3", 3), 169 | ("M3.5", 3.5), 170 | ("M4", 4), 171 | ("M5", 5), 172 | ("M6", 6), 173 | ("M8", 8) 174 | ) 175 | 176 | FSCScrewTypes = ( 177 | ("ISO7045.svg", "Metric Screw", ScrewMaker.FSCScrewHoleChart), 178 | ("PEMPressNut.svg", "PEM Press-nut", FSCPEMPressNutHoleChart), 179 | ("PEMBLStandoff.svg", "PEM Stand-off", FSCPEMStudHoleChart), 180 | ("PEMStud.svg", "PEM Stud", FSCPEMStudHoleChart) 181 | ) 182 | 183 | FSScrewCalcDlg = QtGui.QDockWidget() 184 | FSScrewCalcDlg.ui = Ui_DockWidget() 185 | FSScrewCalcDlg.ui.setupUi(FSScrewCalcDlg) 186 | FSScrewCalcDlg.ui.fillScrewTypes() 187 | Gui.getMainWindow().addDockWidget(QtCore.Qt.RightDockWidgetArea, FSScrewCalcDlg) 188 | FSScrewCalcDlg.setFloating(True) 189 | FSScrewCalcDlg.hide() 190 | 191 | 192 | class FSScrewCalcCommand: 193 | """Display a calculator for needed screw holes""" 194 | 195 | def GetResources(self): 196 | FreeCAD.Console.PrintLog("Getting resources\n") 197 | icon = os.path.join( iconPath , 'IconScrewCalc.svg') 198 | return {'Pixmap' : icon , # the name of a svg file available in the resources 199 | 'MenuText': "Screw calculator" , 200 | 'ToolTip' : "Show a screw hole calculator"} 201 | 202 | def Activated(self): 203 | if FSScrewCalcDlg.isHidden(): 204 | FSScrewCalcDlg.show() 205 | else: 206 | FSScrewCalcDlg.hide() 207 | return 208 | 209 | def IsActive(self): 210 | return True 211 | 212 | Gui.addCommand("FSScrewCalc", FSScrewCalcCommand()) 213 | FastenerBase.FSCommands.append("FSScrewCalc", "command") 214 | 215 | -------------------------------------------------------------------------------- /Icons/PEMStud.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 52 | 53 | 55 | 60 | 62 | 67 | 72 | 77 | 82 | 88 | 94 | 99 | 104 | 109 | 114 | 119 | 124 | 129 | 134 | 139 | 144 | 149 | 153 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /ScrewMaker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # A Wrapper to Ulrich's screw_maker macro 4 | 5 | import FreeCAD, FreeCADGui, Part, math 6 | from FreeCAD import Base 7 | import DraftVecUtils 8 | import FastenerBase 9 | 10 | from PySide import QtCore, QtGui 11 | from screw_maker import * 12 | import FSNuts 13 | from FSNuts import din557def, din562def, din985def 14 | 15 | 16 | FSCScrewHoleChart = ( 17 | ("M1", 0.75), 18 | ("M1.1", 0.85), 19 | ("M1.2", 0.95), 20 | ("M1.4", 1.10), 21 | ("M1.6", 1.25), 22 | ("M1.8", 1.45), 23 | ("M2", 1.60), 24 | ("M2.2", 1.75), 25 | ("M2.5", 2.05), 26 | ("M3", 2.50), 27 | ("M3.5", 2.90), 28 | ("M4", 3.30), 29 | ("M4.5", 3.70), 30 | ("M5", 4.20), 31 | ("M6", 5.00), 32 | ("M7", 6.00), 33 | ("M8", 6.80), 34 | ("M9", 7.80), 35 | ("M10", 8.50), 36 | ("M11", 9.50), 37 | ("M12", 10.20), 38 | ("M14", 12.00), 39 | ("M16", 14.00), 40 | ("M18", 15.50), 41 | ("M20", 17.50), 42 | ("M22", 19.50), 43 | ("M24", 21.00), 44 | ("M27", 24.00), 45 | ("M30", 26.50), 46 | ("M33", 29.50), 47 | ("M36", 32.00), 48 | ("M39", 35.00), 49 | ("M42", 37.50), 50 | ("M45", 40.50), 51 | ("M48", 43.00), 52 | ("M52", 47.00), 53 | ("M56", 50.50), 54 | ("M60", 54.50), 55 | ("M64", 58.00), 56 | ("M68", 62.00) 57 | ) 58 | 59 | # prepare a dictionary for fast search of FSCGetInnerThread 60 | FSCScrewHoleChartDict = {} 61 | for s in FSCScrewHoleChart: 62 | FSCScrewHoleChartDict[s[0]] = s[1] 63 | 64 | def FSCGetInnerThread(diam): 65 | diam = diam.lstrip('(') 66 | diam = diam.rstrip(')') 67 | return FSCScrewHoleChartDict[diam] 68 | 69 | 70 | screwTables = { 71 | # name, def table, length table, range table, diam pos*, K pos** 72 | 'ISO4017': ("Screw", iso4017head, iso4017length, iso4017range, -1, 0), 73 | 'ISO4014': ("Screw", iso4014head, iso4014length, iso4014range, -1, 0), 74 | 'EN1662': ("Screw", en1662def, en1662length, en1662range, -1, 0), 75 | 'EN1665': ("Screw", en1665def, en1665length, en1665range, -1, 0), 76 | 'ISO2009': ("Screw", iso2009def, iso2009length, iso2009range, 4, 5), 77 | 'ISO2010': ("Screw", iso2009def, iso2009length, iso2009range, 4, 5), 78 | 'ISO4762': ("Screw", iso4762def, iso4762length, iso4762range, -1, 0), 79 | 'ISO10642': ("Screw", iso10642def, iso10642length, iso10642range, 3, 7), 80 | 'ISO1207': ("Screw", iso1207def, iso1207length, iso1207range, -1, 0), 81 | 'ISO1580': ("Screw", iso1580def, iso2009length, iso2009range, -1, 0), 82 | 'ISO7045': ("Screw", iso7045def, iso7045length, iso7045range, -1, 0), 83 | 'ISO7046': ("Screw", iso2009def, iso7045length, iso7046range, 4, 5), 84 | 'ISO7047': ("Screw", iso2009def, iso7045length, iso7046range, 4, 5), 85 | 'ISO7048': ("Screw", iso7048def, iso7048length, iso7048range, -1, 0), 86 | 'DIN967': ("Screw", din967def, din967length, din967range, -1, 0), 87 | #'ISO7380': ("Screw", iso7380def, iso7380length, iso7380range, -1), 88 | 'ISO7380-1':("Screw", iso7380def, iso7380length, iso7380range, -1, 0), 89 | 'ISO7380-2':("Screw", iso7380_2def, iso7380length, iso7380range, -1, 0), 90 | 'ISO14579': ("Screw", iso14579def, iso14579length, iso14579range, -1, 0), 91 | 'ISO14580': ("Screw", iso14580def, iso14580length, iso1207range, -1, 0), 92 | 'ISO14582': ("Screw", iso14582def, iso14582length, iso14582range, 4, 5), 93 | 'ISO14583': ("Screw", iso14583def, iso7045length, iso7046range, -1, 0), 94 | 'ISO14584': ("Screw", iso14584def, iso7045length, iso14584range, 3, 5), 95 | 'ISO7089': ("Washer", iso7089def, None, None, -1, 0), 96 | 'ISO7090': ("Washer", iso7090def, None, None, -1, 0), 97 | #'ISO7091': ("Washer", iso7091def, None, None, -1, 0), # same as 7089 ?? 98 | 'ISO7092': ("Washer", iso7092def, None, None, -1, 0), 99 | 'ISO7093-1':("Washer", iso7093def, None, None, -1, 0), 100 | 'ISO7094': ("Washer", iso7094def, None, None, -1, 0), 101 | 'ISO4032': ("Nut", iso4032def, None, None, -1, 0), 102 | 'ISO4033': ("Nut", iso4033def, None, None, -1, 0), 103 | 'ISO4035': ("Nut", iso4035def, None, None, -1, 0), 104 | #'ISO4036': ("Nut", iso4036def, None, None, -1), 105 | 'EN1661': ("Nut", en1661def, None, None, -1, 0), 106 | 'DIN557': ("Nut", din557def, None, None, -1, 0), 107 | 'DIN562': ("Nut", din562def, None, None, -1, 0), 108 | 'DIN985': ("Nut", din985def, None, None, -1, 0), 109 | 'ScrewTap': ("ScrewTap", tuningTable, None, None, -1, 0), 110 | 111 | # * diam pos = the position within the def table to be used for auto diameter selection, -1 = get size from Mxx 112 | # * K Pos = the position within the def table to be used for countersunk holes creation 113 | } 114 | 115 | FSNutsList = ['DIN562', 'DIN557', 'DIN985'] 116 | 117 | class FSScrewMaker(Screw): 118 | def FindClosest(self, type, diam, len): 119 | ''' Find closest standard screw to given parameters ''' 120 | if not (type in screwTables): 121 | return (diam, len) 122 | name, diam_table, len_table, range_table, table_pos, k_pos = screwTables[type] 123 | 124 | # auto find diameter 125 | if not (diam in diam_table): 126 | origdia = FastenerBase.MToFloat(diam) 127 | mindif = 100.0 128 | for m in diam_table: 129 | diff = abs(FastenerBase.MToFloat(m) - origdia) 130 | if (diff < mindif): 131 | mindif = diff 132 | diam = m 133 | 134 | # auto find length 135 | if (len_table != None) and not (len in len_table): 136 | origlen = float(len) 137 | mindif = 100.0 138 | for l in len_table: 139 | diff = abs(float(l) - origlen) 140 | if (diff < mindif): 141 | mindif = diff 142 | len = l 143 | 144 | # make sure length in range 145 | if range_table != None: 146 | minl , maxl = range_table[diam] 147 | if float(len) < float(minl): 148 | len = minl 149 | if float(len) > float(maxl): 150 | len = maxl 151 | 152 | return (diam, len) 153 | 154 | 155 | def AutoDiameter(self, type, holeObj, baseobj = None, matchOuter = FastenerBase.FSMatchOuter): 156 | ''' Calculate screw diameter automatically based on given hole ''' 157 | res = 'M6' 158 | #matchOuter = FastenerBase.FSMatchOuter 159 | if baseobj != None and baseobj.Name.startswith("Washer"): 160 | matchOuter = True 161 | if holeObj != None and hasattr(holeObj, 'Curve') and hasattr(holeObj.Curve, 'Radius') and (type in screwTables): 162 | d = holeObj.Curve.Radius * 2 163 | table = screwTables[type][1] 164 | tablepos = screwTables[type][4] 165 | mindif = 10.0 166 | dif = mindif 167 | for m in table: 168 | #FreeCAD.Console.PrintLog("Test M:" + m + "\n") 169 | if (tablepos == -1): 170 | if matchOuter: 171 | dia = FastenerBase.MToFloat(m) - 0.01 172 | if (d > dia): 173 | dif = d - dia 174 | else: 175 | dia = FSCGetInnerThread(m) 176 | dif = math.fabs(dia - d) 177 | 178 | else: 179 | dia = table[m][tablepos] 180 | if (d > dia): 181 | dif = d - dia 182 | if dif < mindif: 183 | mindif = dif 184 | res = m 185 | return res 186 | 187 | def GetAllTypes(self, typeName): 188 | list = [] 189 | for key in screwTables: 190 | if screwTables[key][0] == typeName: 191 | list.append(key) 192 | list.sort() 193 | return list 194 | 195 | def GetTypeName(self, type): 196 | if not(type in screwTables): 197 | return "None" 198 | return screwTables[type][0] 199 | 200 | def GetAllDiams(self, type): 201 | FreeCAD.Console.PrintLog("Get diams for type:" + str(type) + "\n") 202 | return sorted(screwTables[type][1], key = FastenerBase.MToFloat) 203 | 204 | def GetAllLengths(self, type, diam): 205 | lens = screwTables[type][2] 206 | range = screwTables[type][3][diam] 207 | list = [] 208 | min = float(range[0]) 209 | max = float(range[1]) 210 | for len in lens: 211 | l = float(len) 212 | if l >= min and l <= max: 213 | list.append(len) 214 | list.sort(key = FastenerBase.MToFloat) 215 | return list 216 | 217 | def GetAllCountersunkTypes(self): 218 | list = [] 219 | for key in screwTables: 220 | if screwTables[key][0] == "Screw" and screwTables[key][4] >= 0: 221 | list.append(key) 222 | list.sort() 223 | return list 224 | 225 | def GetCountersunkDiams(self, type): 226 | dpos = screwTables[type][4] 227 | if dpos < 0: 228 | return None 229 | kpos = screwTables[type][5] 230 | table = screwTables[type][1] 231 | res = {} 232 | for diam in table: 233 | res[diam] = (table[diam][dpos], table[diam][kpos]) 234 | return res 235 | 236 | def GetCountersunkDims(self, type, diam): 237 | dpos = screwTables[type][4] 238 | if dpos < 0: 239 | return (0,0) 240 | kpos = screwTables[type][5] 241 | table = screwTables[type][1] 242 | if not(diam in table): 243 | return (0,0) 244 | return (table[diam][dpos], table[diam][kpos]) 245 | 246 | def createFastener(self, type, diam, len, threadType, shapeOnly = False): 247 | if type in FSNutsList : 248 | return FSNuts.createNut(type, diam) 249 | return self.createScrew(type, diam, len, threadType, shapeOnly) 250 | 251 | 252 | ScrewMakerInstance = None 253 | def Instance(): 254 | global ScrewMakerInstance 255 | if ScrewMakerInstance == None: 256 | ScrewMakerInstance = FSScrewMaker() 257 | return ScrewMakerInstance 258 | -------------------------------------------------------------------------------- /Icons/PEMTHStandoff.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 16 | 18 | image/svg+xml 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 30 | 34 | 38 | 42 | 46 | 50 | 54 | 55 | 59 | 63 | 67 | 71 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 107 | 111 | 115 | 119 | 123 | 127 | 131 | 135 | 139 | 143 | 147 | 151 | 155 | 159 | 163 | 167 | 171 | 175 | 179 | 183 | 187 | 191 | 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /Icons/PEMBLStandoff.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 52 | 53 | 55 | 60 | 64 | 69 | 74 | 79 | 84 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 175 | 180 | 185 | 190 | 195 | 200 | 205 | 210 | 215 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /Icons/ISO7380-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 66 | 73 | 77 | 82 | 87 | 91 | 98 | 99 | 104 | 109 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /Icons/IconBOM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 43 | 53 | 63 | 72 | 82 | 92 | 101 | 111 | 121 | 130 | 140 | 141 | 160 | 162 | 163 | 165 | image/svg+xml 166 | 168 | 169 | 170 | 171 | 172 | 177 | 180 | 186 | 191 | 192 | 195 | 201 | 206 | 207 | 210 | 216 | 221 | 222 | 225 | 231 | 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /FSNuts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ################################################################################### 3 | # 4 | # FSNuts.py 5 | # 6 | # Copyright 2015 Shai Seger 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 | 27 | # NOTE!! this command is left for backward compatibility, Screw_Maker.py nuts are now used. 28 | 29 | 30 | from FreeCAD import Gui 31 | from FreeCAD import Base 32 | import FreeCAD, FreeCADGui, Part, os, math 33 | __dir__ = os.path.dirname(__file__) 34 | iconPath = os.path.join( __dir__, 'Icons' ) 35 | 36 | import FastenerBase 37 | from FastenerBase import FSBaseObject 38 | import ScrewMaker 39 | #screwMaker = ScrewMaker.Instance() 40 | 41 | 42 | ################################################################################### 43 | # Standard Metric Hex nuts 44 | NutDiamCodes = ['Auto', 'M1.6', 'M2', 'M2.5', 'M3', 'M3.5', 'M4', 'M5', 'M6', 'M8', 'M10', 'M12', 'M14', 'M16', 'M20', 'M24', 'M30', 'M36'] 45 | MHexNutTable = { 46 | # S, M, d 47 | 'M1.6':(3.2, 1.3, 1.25), 48 | 'M2': (4.0, 1.6, 1.6), 49 | 'M2.5':(5.0, 2.0, 2.05), 50 | 'M3': (5.5, 2.4, 2.5), 51 | 'M3.5':(6.0, 2.8, 2.9), 52 | 'M4': (7.0, 3.2, 3.3), 53 | 'M5': (8.0, 4.7, 4.2), 54 | 'M6': (10.0, 5.2, 5.0), 55 | 'M8': (13.0, 6.8, 6.8), 56 | 'M10': (16.0, 8.4, 8.5), 57 | 'M12': (18.0, 10.8, 10.2), 58 | 'M14': (21.0, 12.8, 12.0), 59 | 'M16': (24.0, 14.8, 14.0), 60 | 'M20': (30.0, 18.0, 15.0), 61 | 'M24': (36.0, 21.5, 21.0), 62 | 'M30': (46.0, 25.6, 26.5), 63 | 'M36': (55.0, 31.0, 32.0) 64 | } 65 | 66 | # 2D lines on the X, Z Plane 67 | def nutMakeLine2D(x1, z1, x2, z2): 68 | return Part.makeLine(FreeCAD.Base.Vector(x1,0,z1),FreeCAD.Base.Vector(x2,0,z2)) 69 | 70 | cos15 = math.cos(math.radians(15.0)) 71 | cos30 = math.cos(math.radians(30.0)) 72 | tan15 = math.tan(math.radians(15.0)) 73 | tan30 = math.tan(math.radians(30.0)) 74 | 75 | def nutMakeFace(do, di, s, m): 76 | do = do / 2 77 | di = di / 2 78 | s = s / 2.01 79 | e = s * 1.02 / cos30 80 | ch1 = do - di 81 | ch2 = (e - s) / cos15 82 | fm = FastenerBase.FSFaceMaker() 83 | fm.AddPoint(di, ch1) 84 | fm.AddPoint(do, 0) 85 | fm.AddPoint(s, 0) 86 | fm.AddPoint(e, ch2) 87 | fm.AddPoint(e, m - ch2) 88 | fm.AddPoint(s, m) 89 | fm.AddPoint(do, m) 90 | fm.AddPoint(di, m - ch1) 91 | return fm.GetFace() 92 | 93 | 94 | def nutMakeSolid(diam): 95 | if not(diam in MHexNutTable): 96 | return None 97 | (key, shape) = FastenerBase.FSGetKey('Nut', diam) 98 | if shape != None: 99 | return shape 100 | 101 | s, m, di = MHexNutTable[diam] 102 | do = FastenerBase.MToFloat(diam) 103 | f = nutMakeFace(do, di, s, m) 104 | p = f.revolve(Base.Vector(0.0,0.0,0.0),Base.Vector(0.0,0.0,1.0),360) 105 | screwMaker = ScrewMaker.Instance() 106 | htool = screwMaker.makeHextool(s, m, s * 2) 107 | shape = p.cut(htool) 108 | FastenerBase.FSCache[key] = shape 109 | return shape 110 | 111 | # h = clMakePressNut('M5','1') 112 | 113 | class FSHexNutObject(FSBaseObject): 114 | def __init__(self, obj, attachTo): 115 | '''"Add Metric Hex nut" ''' 116 | FSBaseObject.__init__(self, obj, attachTo) 117 | self.itemText = "Nut" 118 | 119 | obj.addProperty("App::PropertyEnumeration","diameter","Parameters","Press nut thread diameter").diameter = NutDiamCodes 120 | obj.Proxy = self 121 | 122 | def execute(self, fp): 123 | '''"perform object creation" ''' 124 | 125 | try: 126 | baseobj = fp.baseObject[0] 127 | shape = baseobj.Shape.getElement(fp.baseObject[1][0]) 128 | except: 129 | baseobj = None 130 | shape = None 131 | 132 | if (not (hasattr(self,'diameter')) or self.diameter != fp.diameter): 133 | if fp.diameter == 'Auto': 134 | d = FastenerBase.FSAutoDiameterM(shape, MHexNutTable, -1) 135 | else: 136 | d = fp.diameter 137 | 138 | if d != fp.diameter: 139 | fp.diameter = d 140 | s = nutMakeSolid(d) 141 | self.diameter = fp.diameter 142 | fp.Shape = s 143 | fp.Label = fp.diameter + '-Nut' 144 | else: 145 | FreeCAD.Console.PrintLog("Using cached object\n") 146 | if shape != None: 147 | #fp.Placement = FreeCAD.Placement() # reset placement 148 | FastenerBase.FSMoveToObject(fp, shape, fp.invert, fp.offset.Value) 149 | 150 | FastenerBase.FSClassIcons[FSHexNutObject] = 'HexNut.svg' 151 | 152 | class FSHexNutCommand: 153 | """Add Preass-nut command""" 154 | 155 | def GetResources(self): 156 | icon = os.path.join( iconPath , 'HexNut.svg') 157 | return {'Pixmap' : icon , # the name of a svg file available in the resources 158 | 'MenuText': "Add Hex Nut" , 159 | 'ToolTip' : "Add Metric Hexagon Nut - ISO 4032, Style 3"} 160 | 161 | def Activated(self): 162 | FastenerBase.FSGenerateObjects(FSHexNutObject, "Nut") 163 | return 164 | 165 | def IsActive(self): 166 | return Gui.ActiveDocument != None 167 | 168 | Gui.addCommand("FSHexNut", FSHexNutCommand()) 169 | #FastenerBase.FSCommands.append("FSHexNut", "screws", "Nut") 170 | 171 | ################################################################################### 172 | # Square Metric Hex nuts DIN562 173 | din562def = { 174 | # s, m, d 175 | 'M1.6':(3.2, 1, 1.25), 176 | 'M2': (4.0, 1.2, 1.6), 177 | 'M2.5':(5.0, 1.6, 2.05), 178 | 'M3': (5.5, 1.8, 2.5), 179 | 'M4': (7.0, 2.2, 3.3), 180 | 'M5': (8.0, 2.7, 4.2), 181 | 'M6': (10.0, 3.2, 5.0), 182 | 'M8': (13.0, 4, 6.8), 183 | 'M10': (17.0, 5, 8.5) 184 | } 185 | 186 | def makeSquareTool(s, m): 187 | # makes a cylinder with an inner square hole, used as cutting tool 188 | # create square face 189 | msq = Base.Matrix() 190 | msq.rotateZ(math.radians(90.0)) 191 | polygon = [] 192 | vsq = Base.Vector(s / 2.0, s / 2.0, -m * 0.1) 193 | for i in range(4): 194 | polygon.append(vsq) 195 | vsq = msq.multiply(vsq) 196 | polygon.append(vsq) 197 | square = Part.makePolygon(polygon) 198 | square = Part.Face(square) 199 | 200 | # create circle face 201 | circ = Part.makeCircle(s * 3.0, Base.Vector(0.0, 0.0, -m * 0.1)) 202 | circ = Part.Face(Part.Wire(circ)) 203 | 204 | # Create the face with the circle as outline and the square as hole 205 | face=circ.cut(square) 206 | 207 | # Extrude in z to create the final cutting tool 208 | exSquare = face.extrude(Base.Vector(0.0, 0.0, m * 1.2)) 209 | # Part.show(exHex) 210 | return exSquare 211 | 212 | 213 | def sqnutMakeFace(do, di, dw, s, m): 214 | do = do / 2 215 | dw = dw / 2 216 | di = di / 2 217 | ch1 = do - di 218 | ch2 = (s - dw) * tan30 219 | 220 | fm = FastenerBase.FSFaceMaker() 221 | fm.AddPoint(di, ch1) 222 | fm.AddPoint(do, 0) 223 | fm.AddPoint(s, 0) 224 | if dw > 0: 225 | fm.AddPoint(s, m - ch2) 226 | fm.AddPoint(dw, m) 227 | else : 228 | fm.AddPoint(s, m) 229 | fm.AddPoint(do, m) 230 | fm.AddPoint(di, m - ch1) 231 | return fm.GetFace() 232 | 233 | def nut562MakeSolid(diam): 234 | if not(diam in din562def): 235 | return None 236 | (key, shape) = FastenerBase.FSGetKey('Nut562', diam) 237 | if shape != None: 238 | return shape 239 | 240 | s, m, di = din562def[diam] 241 | do = FastenerBase.MToFloat(diam) 242 | f = sqnutMakeFace(do, di, 0, s, m) 243 | p = f.revolve(Base.Vector(0.0,0.0,0.0),Base.Vector(0.0,0.0,1.0),360) 244 | htool = makeSquareTool(s, m) 245 | shape = p.cut(htool) 246 | FastenerBase.FSCache[key] = shape 247 | return shape 248 | 249 | ################################################################################### 250 | # Square Metric Hex nuts DIN 557 251 | din557def = { 252 | # s, m, d dw 253 | 'M4': (7.0, 3.2, 3.3, 5.7), 254 | 'M5': (8.0, 4, 4.2, 6.7), 255 | 'M6': (10.0, 5, 5.0, 8.7), 256 | 'M8': (13.0, 6.5, 6.8, 11.5), 257 | 'M10': (17.0, 8, 8.5, 15.5), 258 | 'M12': (19.0, 10, 10.2, 17.2), 259 | 'M16': (24.0, 13, 14.0, 22) 260 | } 261 | 262 | def nut557MakeSolid(diam): 263 | if not(diam in din557def): 264 | return None 265 | (key, shape) = FastenerBase.FSGetKey('Nut557', diam) 266 | if shape != None: 267 | return shape 268 | 269 | s, m, di, dw = din557def[diam] 270 | do = FastenerBase.MToFloat(diam) 271 | f = sqnutMakeFace(do, di, dw, s, m) 272 | p = f.revolve(Base.Vector(0.0,0.0,0.0),Base.Vector(0.0,0.0,1.0),360) 273 | htool = makeSquareTool(s, m) 274 | shape = p.cut(htool) 275 | FastenerBase.FSCache[key] = shape 276 | return shape 277 | 278 | ################################################################################### 279 | # Nyloc Hex nuts DIN 985 280 | din985def = { 281 | # P, damax, dw, e, m, h, s_nom 282 | 'M3': (0.5, 3.45, 4.6, 6.1, 2.4, 4.0, 5.5), 283 | 'M4': (0.7, 4.6, 5.9, 7.7, 2.9, 5.0, 7.0), 284 | 'M5': (0.8, 5.75, 6.9, 8.9, 3.2, 5.0, 8.0), 285 | 'M6': (1.0, 6.75, 8.9, 11.05, 4.0, 6.0, 10.0), 286 | 'M7': (1.0, 6.75, 9.6, 12.12, 4.7, 7.5, 11.0), 287 | 'M8': (1.25, 8.75, 11.6, 14.5, 5.5, 8.0, 13.0), 288 | 'M10': (1.50, 10.8, 15.6, 17.9, 6.5, 10.0, 16.0), 289 | 'M12': (1.75, 13.0, 17.4, 20.1, 8.0, 12.0, 18.0), 290 | 'M14': (2.00, 15.1, 20.5, 24.5, 9.5, 14.0, 22.0), 291 | 'M16': (2.00, 17.3, 22.5, 26.9, 10.5, 16.0, 24.0), 292 | 'M18': (2.50, 19.5, 24.9, 29.6, 13.0, 18.5, 27.0), 293 | 'M20': (2.50, 21.6, 27.7, 33.7, 14.0, 20.0, 30.0), 294 | 'M22': (2.50, 23.7, 29.5, 37.3, 15.0, 22.0, 34.0), 295 | 'M24': (3.00, 25.9, 33.2, 40.1, 15.0, 24.0, 36.0), 296 | 'M27': (3.00, 29.1, 38.0, 45.2, 17.0, 27.0, 41.0), 297 | 'M30': (3.50, 32.4, 42.7, 50.9, 19.0, 30.0, 46.0), 298 | 'M33': (3.50, 35.6, 46.6, 55.4, 22.0, 33.0, 50.0), 299 | 'M36': (4.00, 38.9, 51.1, 61.0, 25.0, 36.0, 55.0), 300 | 'M39': (4.00, 42.1, 55.9, 66.5, 17.0, 39.0, 60.0), 301 | 'M42': (4.50, 45.4, 60.6, 71.3, 29.0, 42.0, 65.0), 302 | 'M45': (4.50, 48.6, 64.7, 77.0, 32.0, 45.0, 70.0), 303 | 'M48': (5.00, 51.8, 69.4, 82.6, 36.0, 48.0, 75.0), 304 | } 305 | 306 | 307 | def nylocMakeFace(do, p, da, dw, e, m, h, s): 308 | di = (do - p) / 2 309 | do = do / 2 310 | dw = dw / 2 311 | da = da / 2 312 | e = e / 2 313 | s = s / 2 314 | s1 = s * 0.999 315 | ch1 = do - di 316 | ch2 = (e - dw) * tan30 317 | ch3 = m - (e - s) * tan30 318 | h1 = h * 0.9 319 | r = (s - di) / 3 320 | 321 | fm = FastenerBase.FSFaceMaker() 322 | fm.AddPoint(di, ch1) 323 | fm.AddPoint(da, 0) 324 | fm.AddPoint(dw, 0) 325 | fm.AddPoint(e, ch2) 326 | fm.AddPoint(e, ch3) 327 | fm.AddPoint(s1, m) 328 | fm.AddPoint(s1, h - r) 329 | fm.AddArc2(-r, 0, 90) 330 | fm.AddPoint(di + r, h) 331 | fm.AddPoint(di + r, h1) 332 | fm.AddPoint(di, h1) 333 | return fm.GetFace() 334 | 335 | def nut985MakeSolid(diam): 336 | if not(diam in din985def): 337 | return None 338 | (key, shape) = FastenerBase.FSGetKey('Nut985', diam) 339 | if shape != None: 340 | return shape 341 | 342 | p, da, dw, e, m, h, s = din985def[diam] 343 | do = FastenerBase.MToFloat(diam) 344 | f = nylocMakeFace(do, p, da, dw, e, m, h, s) 345 | p = f.revolve(Base.Vector(0.0,0.0,0.0),Base.Vector(0.0,0.0,1.0),360) 346 | screwMaker = ScrewMaker.Instance() 347 | htool = htool = screwMaker.makeHextool(s, m, s * 2) 348 | shape = p.cut(htool) 349 | FastenerBase.FSCache[key] = shape 350 | return shape 351 | 352 | 353 | def createNut(type, diam): 354 | if (type == 'DIN557'): 355 | return nut557MakeSolid(diam) 356 | if (type == 'DIN562'): 357 | return nut562MakeSolid(diam) 358 | if (type == 'DIN985'): 359 | return nut985MakeSolid(diam) 360 | return None -------------------------------------------------------------------------------- /Icons/ISO2010.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 60 | 66 | 73 | 78 | 84 | 86 | 92 | 98 | 103 | 108 | 114 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 175 | 180 | 185 | 190 | 195 | 200 | 205 | 206 | 207 | 208 | 209 | --------------------------------------------------------------------------------