├── .gitignore
├── Resources
├── smvideo.jpg
├── SheetMetal4.gif
├── sheetmetal_terms.png
├── SheetMetal.qrc
└── icons
│ ├── SheetMetal_AddCornerRelief.svg
│ ├── SheetMetal_AddRelief.svg
│ ├── SheetMetal_AddJunction.svg
│ ├── SheetMetal_AddBend.svg
│ ├── SheetMetal_Update.svg
│ └── SheetMetal_Forming.svg
├── tools
├── terminology.png
├── calc-unfold.py
├── README.md
└── Press_brake_schematic.svg
├── updating-ui.md
├── package.xml
├── smwb_locator.py
├── Init.py
├── engineering_mode.py
├── lookup.py
├── InitGui.py
├── SMprefs.ui
├── UnfoldOptions.ui
├── SheetMetalBendSolid.py
├── README.md
├── SheetMetalBaseCmd.py
├── Macros
└── SheetMetalUnfoldUpdater.FCMacro
├── SheetMetalJunction.py
├── SheetMetalBend.py
├── SheetMetalRelief.py
├── SheetMetalFoldCmd.py
├── SketchOnSheetMetalCmd.py
└── SheetMetalFormingCmd.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | /SheetMetal2.py
--------------------------------------------------------------------------------
/Resources/smvideo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_SheetMetal/master/Resources/smvideo.jpg
--------------------------------------------------------------------------------
/tools/terminology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_SheetMetal/master/tools/terminology.png
--------------------------------------------------------------------------------
/Resources/SheetMetal4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_SheetMetal/master/Resources/SheetMetal4.gif
--------------------------------------------------------------------------------
/Resources/sheetmetal_terms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/easyw/FreeCAD_SheetMetal/master/Resources/sheetmetal_terms.png
--------------------------------------------------------------------------------
/updating-ui.md:
--------------------------------------------------------------------------------
1 | # Updating UnfoldOptions.ui
2 |
3 | 1. Generate the Python code from UnfoldOptions.ui by:
4 |
5 | ```
6 | pyuic4 UnfoldOptions.ui -o myui.py
7 | ```
8 |
9 | 2. Copy and paste relevant codes into the `SMUnfoldTaskPanel()` class in `SheetMetalUnfolder.py`.
10 |
11 |
12 | # TODO
13 |
14 | * Use `UnfoldOptions.ui` file directly.
15 |
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SheetMetal Workbench
4 | A simple sheet metal tools workbench for FreeCAD.
5 | 0.2.50
6 | 2022-07-09
7 | Shai Seger
8 | GPLv3
9 | https://github.com/shaise/FreeCAD_SheetMetal
10 | https://github.com/shaise/FreeCAD_SheetMetal/blob/master/README.md
11 | Resources/icons/SMLogo.svg
12 |
13 |
14 |
15 | SMWorkbench
16 | ./
17 | sheetmetal
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tools/calc-unfold.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # Tool for manually calculating the unfolded geometry
3 | # See terminology.png for terminology.
4 |
5 | import math
6 |
7 | r = inner_radius = 1.64
8 | T = thickness = 2.0
9 | ML = mold_line_distance = 50
10 | K = k_factor = 0.38
11 | bend_angle = 90.0
12 |
13 | t = thickness * k_factor
14 | BA = bend_allowance = 2.0 * math.pi * (r + t) * (bend_angle / 360.0)
15 | leg_length = ML - BA / 2.0
16 | ossb = r + T
17 | flange_diff = ossb - BA / 2.0
18 | flange_length = ossb + leg_length
19 |
20 | print "Inputs: r, T, Kf, ML, angle"
21 | print "---------------------------------"
22 | print "Effective inner radius: %.2f mm" % inner_radius
23 | print "Effective outer radius: %.2f mm" % (r + T)
24 | print "Flange length: %.2f mm" % flange_length
25 | print "* Flange diff (FD = FL - ML): %.2f mm" % flange_diff
26 | print "Bend allowance: %.2f mm" % BA
27 | print "Leg length: %.2f mm" % leg_length
28 |
--------------------------------------------------------------------------------
/Resources/SheetMetal.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | icons/SheetMetal_AddBase.svg
4 | icons/SheetMetal_AddBend.svg
5 | icons/SheetMetal_AddCornerRelief.svg
6 | icons/SheetMetal_AddFoldWall.svg
7 | icons/SheetMetal_AddJunction.svg
8 | icons/SheetMetal_AddRelief.svg
9 | icons/SheetMetal_AddWall.svg
10 | icons/SheetMetal_Extrude.svg
11 | icons/SheetMetal_Forming.svg
12 | icons/SheetMetal_SketchOnSheet.svg
13 | icons/SheetMetal_Unfold.svg
14 | icons/SheetMetal_UnfoldUnattended.svg
15 | icons/SheetMetal_UnfoldUpdate.svg
16 | icons/SheetMetal_Update.svg
17 | icons/preferences-sheetmetal.svg
18 | icons/SMLogo.svg
19 |
20 |
--------------------------------------------------------------------------------
/smwb_locator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ##############################################################################
3 | #
4 | # smwb_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 |
--------------------------------------------------------------------------------
/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("Sheet Metal workbench loaded")
--------------------------------------------------------------------------------
/engineering_mode.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###################################################################################
3 | #
4 | # engineering_mode.py
5 | #
6 | # Copyright 2019 Cerem Cem Aslan
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 FreeCAD
27 |
28 |
29 | def engineering_mode_enabled():
30 | FSParam = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/SheetMetal")
31 | return FSParam.GetInt("EngineeringUXMode", 0) # 0 = disabled, 1 = enabled
32 |
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | # Air Bending
2 |
3 | 
4 |
5 |
6 | ```
7 | <- b -> <- a ->
8 | ----+------+-------|
9 | B A
10 | ```
11 |
12 | ## First bend (A)
13 |
14 | x is equal to the "ML" distance, which is "a" in this case.
15 |
16 | ## After first bend (B, ...)
17 |
18 | x is equal to "b + FD" distance. Calculate FD value with [calc-unfold](./calc-unfold.py) tool.
19 |
20 | ## K-factor table
21 |
22 | Taken from https://en.wikipedia.org/wiki/Bending_(metalworking):
23 |
24 | | Generic K-factors (ANSI) | Aluminum | Aluminum | Steel |
25 | |----------------------------|----------------|------------------|----------------|
26 | | Radius | Soft materials | Medium materials | Hard materials |
27 | | **Air bending** | | | |
28 | | 0 to thickness | 0.33 | 0.38 | 0.40 |
29 | | Thickness to 3 × thickness | 0.40 | 0.43 | 0.45 |
30 | | Greater than 3 × thickness | 0.50 | 0.50 | 0.50 |
31 | | **Bottoming** | | | |
32 | | 0 to thickness | 0.42 | 0.44 | 0.46 |
33 | | Thickness to 3 × thickness | 0.46 | 0.47 | 0.48 |
34 | | Greater than 3 × thickness | 0.50 | 0.50 | 0.50 |
35 | | **Coining** | | | |
36 | | 0 to thickness | 0.38 | 0.41 | 0.44 |
37 | | Thickness to 3 × thickness | 0.44 | 0.46 | 0.47 |
38 | | Greater than 3 × thickness | 0.50 | 0.50 | 0.50 |
39 |
--------------------------------------------------------------------------------
/lookup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###################################################################################
3 | #
4 | # lookup.py
5 | #
6 | # Copyright 2019 Cerem Cem Aslan
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 collections
27 |
28 |
29 | def get_val_from_range(lookup, input, interpolate=False):
30 | '''
31 | lookup: dictionary
32 | input: float
33 |
34 | For working principle, see below tests
35 | '''
36 | lookup_sorted = collections.OrderedDict(sorted(lookup.items(), key=lambda t: float(t[0])))
37 | val = None
38 | prev_val = None
39 | prev_key = None
40 | input = float(input)
41 | for _range in lookup_sorted:
42 | val = float(lookup_sorted[_range])
43 | if input > float(_range):
44 | prev_val = val
45 | prev_key = float(_range)
46 | continue
47 |
48 | if interpolate:
49 | # Do the interpolation here
50 | if prev_key is not None:
51 | key = float(_range)
52 | #print "interpolate for input: ", input, ": ", prev_key, "to ", key, "->", prev_val, val
53 | input_offset_percentage = (input - prev_key) / (key - prev_key)
54 | val_diff = val - prev_val
55 | val_offset = val_diff * input_offset_percentage
56 | interpolated_val = prev_val + val_offset
57 | round_2 = lambda a: int((a * 100) + 0.5) / 100.0
58 | val = round_2(interpolated_val)
59 | #print "...interpolated to: ", val, interpolated_val
60 | break
61 | return val
62 |
63 | mytable = {
64 | 1: 0.25,
65 | 1.1: 0.28,
66 | 3: 0.33,
67 | 5: 0.42,
68 | 7: 0.5
69 | }
70 |
71 | # Interpolation disabled
72 | assert get_val_from_range(mytable, 0.1) == 0.25
73 | assert get_val_from_range(mytable, 0.99) == 0.25
74 | assert get_val_from_range(mytable, 1) == 0.25
75 | assert get_val_from_range(mytable, 1.01) == 0.28
76 | assert get_val_from_range(mytable, 1.09) == 0.28
77 | assert get_val_from_range(mytable, 1.2) == 0.33
78 | assert get_val_from_range(mytable, 2.5) == 0.33
79 | assert get_val_from_range(mytable, 4) == 0.42
80 | assert get_val_from_range(mytable, 40) == 0.5
81 | assert get_val_from_range(mytable, 1000) == 0.5
82 |
83 | # Interpolation enabled
84 | assert get_val_from_range(mytable, 0.1, True) == 0.25
85 | assert get_val_from_range(mytable, 0.99, True) == 0.25
86 | assert get_val_from_range(mytable, 1, True) == 0.25
87 | assert get_val_from_range(mytable, 1.01, True) == 0.25
88 | assert get_val_from_range(mytable, 1.09, True) == 0.28
89 | assert get_val_from_range(mytable, 2.05, True) == 0.31
90 | assert get_val_from_range(mytable, 2.5, True) == 0.32
91 | assert get_val_from_range(mytable, 4, True) == 0.38
92 | assert get_val_from_range(mytable, 6, True) == 0.46
93 | assert get_val_from_range(mytable, 40, True) == 0.5
94 | assert get_val_from_range(mytable, 1000, True) == 0.5
95 |
--------------------------------------------------------------------------------
/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 | from PySide import QtCore
27 | import smwb_locator
28 | import os
29 | from engineering_mode import engineering_mode_enabled
30 |
31 | smWBpath = os.path.dirname(smwb_locator.__file__)
32 | smWB_icons_path = os.path.join( smWBpath, 'Resources', 'icons')
33 |
34 | global main_smWB_Icon
35 | main_smWB_Icon = os.path.join( smWB_icons_path , 'SMLogo.svg')
36 |
37 | class SMWorkbench (Workbench):
38 |
39 | global main_smWB_Icon
40 | global SHEETMETALWB_VERSION
41 | global QtCore
42 | global engineering_mode_enabled
43 | global smWBpath
44 | global smWB_icons_path
45 |
46 | MenuText = 'Sheet Metal'
47 | ToolTip = QtCore.QT_TRANSLATE_NOOP('SheetMetal','Sheet Metal workbench allows for designing and unfolding sheet metal parts')
48 | Icon = main_smWB_Icon
49 |
50 | def Initialize(self):
51 | "This function is executed when FreeCAD starts"
52 | import SheetMetalCmd # import here all the needed files that create your FreeCAD commands
53 | import SheetMetalExtendCmd
54 | import SheetMetalUnfolder
55 | import SheetMetalBaseCmd
56 | import SheetMetalFoldCmd
57 | import SheetMetalRelief
58 | import SheetMetalJunction
59 | import SheetMetalBend
60 | import SketchOnSheetMetalCmd
61 | import SheetMetalCornerReliefCmd
62 | import SheetMetalFormingCmd
63 | import os.path
64 | self.list = ["SMBase", "SMMakeWall", "SMExtrudeFace", "SMFoldWall", "SMUnfold", "SMCornerRelief", "SMMakeRelief", "SMMakeJunction",
65 | "SMMakeBend", "SMSketchOnSheet", "SMFormingWall"] # A list of command names created in the line above
66 | if engineering_mode_enabled():
67 | self.list.insert(self.list.index("SMUnfold") + 1,"SMUnfoldUnattended")
68 | self.appendToolbar("Sheet Metal",self.list) # creates a new toolbar with your commands
69 | self.appendMenu("Sheet Metal",self.list) # creates a new menu
70 | # self.appendMenu(["An existing Menu","My submenu"],self.list) # appends a submenu to an existing menu
71 | FreeCADGui.addPreferencePage(os.path.join(smWBpath, 'SMprefs.ui'),'SheetMetal')
72 | FreeCADGui.addIconPath(smWB_icons_path)
73 |
74 | def Activated(self):
75 | "This function is executed when the workbench is activated"
76 | return
77 |
78 | def Deactivated(self):
79 | "This function is executed when the workbench is deactivated"
80 | return
81 |
82 | def ContextMenu(self, recipient):
83 | "This is executed whenever the user right-clicks on screen"
84 | # "recipient" will be either "view" or "tree"
85 | self.appendContextMenu("Sheet Metal",self.list) # add commands to the context menu
86 |
87 | def GetClassName(self):
88 | # this function is mandatory if this is a full python workbench
89 | return "Gui::PythonWorkbench"
90 |
91 | Gui.addWorkbench(SMWorkbench())
92 |
--------------------------------------------------------------------------------
/SMprefs.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Gui::Dialog::DlgSettingsDraft
4 |
5 |
6 |
7 | 0
8 | 0
9 | 563
10 | 401
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 | Engineering UX Mode
38 |
39 |
40 |
41 | -
42 |
43 |
44 | Qt::Horizontal
45 |
46 |
47 |
48 | 40
49 | 20
50 |
51 |
52 |
53 |
54 | -
55 |
56 |
57 | true
58 |
59 |
60 | Method of icon grouping
61 |
62 |
63 | 0
64 |
65 |
66 | EngineeringUXMode
67 |
68 |
69 | Mod/SheetMetal
70 |
71 |
-
72 |
73 | Disabled
74 |
75 |
76 | -
77 |
78 | Enabled
79 |
80 |
81 |
82 |
83 |
84 |
85 | -
86 |
87 |
88 | Qt::Vertical
89 |
90 |
91 |
92 | 20
93 | 40
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | -
102 |
103 |
104 |
105 | 0
106 | 0
107 |
108 |
109 |
110 |
111 | 16777215
112 | 30
113 |
114 |
115 |
116 |
117 | 14
118 |
119 |
120 |
121 | Preferences for the SheetMetal Workbench
122 |
123 |
124 |
125 |
126 |
127 |
128 | qPixmapFromMimeSource
129 |
130 |
131 | Gui::PrefComboBox
132 | QComboBox
133 |
134 |
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/Resources/icons/SheetMetal_AddCornerRelief.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Resources/icons/SheetMetal_AddRelief.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Resources/icons/SheetMetal_AddJunction.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Resources/icons/SheetMetal_AddBend.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/UnfoldOptions.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | SMUnfoldTaskPanel
4 |
5 |
6 |
7 | 0
8 | 0
9 | 462
10 | 415
11 |
12 |
13 |
14 | Unfold sheet metal object
15 |
16 |
17 | -
18 |
19 |
20 | QLayout::SetMaximumSize
21 |
22 |
-
23 |
24 |
25 | Generate projection sketch
26 |
27 |
28 |
29 | -
30 |
31 |
-
32 |
33 |
34 | Use Material Definition Sheet
35 |
36 |
37 |
38 | -
39 |
40 |
41 | -
42 |
43 |
44 |
45 | 50
46 | 16777215
47 |
48 |
49 |
50 | Apply
51 |
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 | QLayout::SetDefaultConstraint
60 |
61 |
-
62 |
63 |
64 |
65 | 0
66 | 0
67 |
68 |
69 |
70 | Manual K-factor
71 |
72 |
73 |
74 | -
75 |
76 |
77 | Qt::Horizontal
78 |
79 |
80 |
81 | 40
82 | 20
83 |
84 |
85 |
86 |
87 | -
88 |
89 |
90 | false
91 |
92 |
93 |
94 | 0
95 | 0
96 |
97 |
98 |
99 |
100 | 70
101 | 16777215
102 |
103 |
104 |
105 | 3
106 |
107 |
108 | 2.000000000000000
109 |
110 |
111 | 0.100000000000000
112 |
113 |
114 | 0.500000000000000
115 |
116 |
117 |
118 | -
119 |
120 |
121 | ANSI
122 |
123 |
124 |
125 | -
126 |
127 |
128 | DIN
129 |
130 |
131 |
132 |
133 |
134 | -
135 |
136 |
137 | QLayout::SetMinimumSize
138 |
139 |
140 | 0
141 |
142 |
-
143 |
144 |
145 | Unfold object transparency
146 |
147 |
148 |
149 | -
150 |
151 |
152 |
153 | 0
154 | 0
155 |
156 |
157 |
158 |
159 | 70
160 | 16777215
161 |
162 |
163 |
164 | %
165 |
166 |
167 | 100
168 |
169 |
170 | 70
171 |
172 |
173 |
174 |
175 |
176 | -
177 |
178 |
179 | Qt::Vertical
180 |
181 |
182 |
183 | 20
184 | 40
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/SheetMetalBendSolid.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ##############################################################################
3 | #
4 | # SheetMetalBendSolid.py
5 | #
6 | # Copyright 2020 Jaise James
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 | import Part, math
26 |
27 | def getPointOnCylinder(zeroVert, poi, Radius, circent, axis, zeroVertNormal):
28 | dist = zeroVert.distanceToPlane(poi, zeroVertNormal)
29 | poi1 = poi.projectToPlane(zeroVert, zeroVertNormal)
30 | angle = dist / Radius
31 | axis = axis * -1
32 | #print([dist, angle])
33 | vec = poi1 - circent
34 | rVec = vec * math.cos(angle) + axis * ( axis.dot(vec)) * (1 - math.cos(angle)) + vec.cross(axis) * math.sin(angle)
35 | Point = circent + rVec
36 | #print([rVec,Point])
37 | return Point
38 |
39 | def WrapBSpline(bspline, Radius, zeroVert, cent, axis, zeroVertNormal):
40 | poles = bspline.getPoles()
41 | newpoles = []
42 | for poi in poles:
43 | bPoint = getPointOnCylinder(zeroVert, poi, Radius, cent, axis, zeroVertNormal)
44 | newpoles.append(bPoint)
45 | newbspline = bspline
46 | newbspline.buildFromPolesMultsKnots(newpoles, bspline.getMultiplicities(),
47 | bspline.getKnots(), bspline.isPeriodic(), bspline.Degree, bspline.getWeights())
48 | return newbspline.toShape()
49 |
50 | def WrapFace(Face, Radius, axis, normal, zeroVert, cent, zeroVertNormal):
51 | # circent = cent
52 | Edges = []
53 | for e in Face.Edges:
54 | #print(type(e.Curve))
55 | if isinstance(e.Curve, (Part.Circle, Part.ArcOfCircle)) :
56 | #bspline = gg.Curve.toBSpline()
57 | poles = e.discretize(Number=50)
58 | bspline=Part.BSplineCurve()
59 | bspline.interpolate(poles)
60 | #bs = bspline.toShape()
61 | #Part.show(bs,"bspline")
62 | Edges.append(WrapBSpline(bspline, Radius, zeroVert, cent, axis, zeroVertNormal))
63 |
64 | elif isinstance(e.Curve, Part.BSplineCurve) :
65 | Edges.append(WrapBSpline(e.Curve, Radius, zeroVert, cent, axis, zeroVertNormal))
66 |
67 | elif isinstance(e.Curve, Part.Line) :
68 | sp = e.valueAt(e.FirstParameter)
69 | ep = e.valueAt(e.LastParameter)
70 | dist1 = abs(sp.distanceToPlane(cent, normal))
71 | dist2 = abs(ep.distanceToPlane(cent, normal))
72 | #print(dist1,dist2)
73 | linenormal = ep - sp
74 | mp = sp + linenormal / 2.0
75 | linenormal.normalize()
76 | #print(linenormal.dot(axis))
77 | #print(linenormal.dot(normal))
78 | if linenormal.dot(axis) == 0.0 and (dist2 - dist1) == 0.0:
79 | Point1 = getPointOnCylinder(zeroVert, sp, Radius, cent, axis, zeroVertNormal)
80 | Point2 = getPointOnCylinder(zeroVert, mp, Radius, cent, axis, zeroVertNormal)
81 | Point3 = getPointOnCylinder(zeroVert, ep, Radius, cent, axis, zeroVertNormal)
82 | arc = Part.Arc(Point1,Point2,Point3)
83 | Edges.append(arc.toShape())
84 | elif linenormal.dot(axis) == 1.0 or linenormal.dot(axis) == -1.0 :
85 | Point1 = getPointOnCylinder(zeroVert, sp, Radius, cent, axis, zeroVertNormal)
86 | Point2 = getPointOnCylinder(zeroVert, ep, Radius, cent, axis, zeroVertNormal)
87 | #print([Point1,Point2])
88 | Edges.append(Part.makeLine(Point1, Point2))
89 | elif linenormal.dot(normal) == 1.0 or linenormal.dot(normal) == -1.0 :
90 | Point1 = getPointOnCylinder(zeroVert, sp, Radius, cent, axis, zeroVertNormal)
91 | Point2 = getPointOnCylinder(zeroVert, ep, Radius, cent, axis, zeroVertNormal)
92 | #print([Point1,Point2])
93 | Edges.append(Part.makeLine(Point1, Point2))
94 | else :
95 | poles = e.discretize(Number=50)
96 | #print(poles)
97 | bspline=Part.BSplineCurve()
98 | bspline.interpolate(poles, PeriodicFlag=False)
99 | #bs = bspline.toShape()
100 | #Part.show(bs,"bspline")
101 | #bspline = disgg.toBSpline()
102 | Edges.append(WrapBSpline(bspline, Radius, zeroVert, cent, axis, zeroVertNormal))
103 | return Edges
104 |
105 | def BendSolid(SelFace, SelEdge, BendR, thk, neutralRadius, Axis, flipped):
106 | normal = SelFace.normalAt(0,0)
107 | zeroVert = SelEdge.Vertexes[0].Point
108 | if not(flipped) :
109 | cent = zeroVert + normal * BendR
110 | zeroVertNormal = normal.cross(Axis) * -1
111 | shp = Part.makeCylinder(BendR, 100, cent, Axis, 360)
112 | else:
113 | cent = zeroVert - normal * (BendR + thk)
114 | zeroVertNormal = normal.cross(Axis)
115 | shp = Part.makeCylinder(BendR+thk, 100, cent, Axis, 360)
116 | elt = shp.Face1
117 | #Part.show(elt)
118 | #Part.show(SelFace)
119 | #print([cent,zeroVertNormal])
120 |
121 | # Wirelist = []
122 | w = WrapFace(SelFace, neutralRadius, Axis, normal, zeroVert, cent, zeroVertNormal)
123 | eList = Part.__sortEdges__(w)
124 | myWire = Part.Wire(eList)
125 | #Part.show(myWire, "myWire")
126 | nextFace = Part.Face(elt.Surface, myWire)
127 | #f.check(True)
128 | nextFace.validate()
129 | #Part.show(nextFace, "nextFace")
130 | nextFace.check(True) # No output = good
131 | #Part.show(nextFace, "nextFace")
132 | if not(flipped) :
133 | bendsolid = nextFace.makeOffsetShape(thk, 0.0, fill = True)
134 | else:
135 | bendsolid = nextFace.makeOffsetShape(-thk, 0.0, fill = True)
136 | #Part.show(bendsolid, "bendsolid")
137 | return bendsolid
138 |
139 |
140 |
--------------------------------------------------------------------------------
/tools/Press_brake_schematic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
191 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FreeCAD SheetMetal Workbench
2 | [](https://lgtm.com/projects/g/shaise/FreeCAD_SheetMetal/alerts/) [](https://lgtm.com/projects/g/shaise/FreeCAD_SheetMetal/context:python)
3 |
4 | A simple sheet metal tools workbench for FreeCAD
5 |
6 | 
7 |
8 | ### Tutorial by Joko Engineering:
9 |
10 | [](https://youtu.be/xidvQYkC4so "FreeCAD - The Elegant Sheet Metal Workbench")
11 |
12 | #### Developers:
13 | * Folding tools:
14 | > [@shaise](https://github.com/shaise) Shai Seger
15 | > [@jaisekjames](https://github.com/jaisekjames)
16 | > [@ceremcem](https://github.com/ceremcem) Cerem Cem ASLAN
17 | > ([@JMG1](https://github.com/JMG1)) Based and inspired by Javier Martínez's code
18 | * Unfolding tool:
19 | > Copyright 2014 by Ulrich Brammer AKA [@ulrich1a](https://github.com/ulrich1a)
20 |
21 | # Terminology
22 | ## Sheetmetal Workbench definitions
23 | 
24 |
25 | ## Physical material definitions
26 | 
27 |
28 | # Test case
29 |
30 | As a simple test case, consider the following example:
31 |
32 | * Inputs:
33 | - Thickness: 2mm
34 | - K-factor: 0.38 (ANSI)
35 | - Leg length: 48.12mm
36 | - Inner effective radius: 1.64mm
37 | - Flange length: 51.76mm
38 | * Output:
39 | - End to mold-line distance: 50mm
40 |
41 | You can find a simple calculator in [`tools/calc-unfold.py`](tools/calc-unfold.py).
42 |
43 | # Material Definition Sheet
44 |
45 | ### Description
46 |
47 | You can use a Spreadsheet object to declare K-factor values inside the project file permanently. This will allow:
48 |
49 | * Different K-factor values to be used for each bend in your model
50 | * Sharing the same material definition for multiple objects
51 |
52 | ### Usage
53 |
54 | 1. Create a spreadsheet with the name of `material_foo` with the following content (see [this table](https://user-images.githubusercontent.com/6639874/56498031-b017bc00-6508-11e9-8b14-6076513d8488.png)):
55 |
56 | | Radius / Thickness | K-factor (ANSI) |
57 | | ---| ---|
58 | | 1 | 0.38 |
59 | | 3 | 0.43 |
60 | | 99 | 0.5 |
61 |
62 | Notes:
63 |
64 | 1. The cell names are case/space sensitive.
65 | 2. Possible values for `K-factor` is `K-factor (ANSI)` or `K-factor (DIN)`.
66 | 3. `Radius / Thickness` means `Radius over Thickness`. Eg. if inner radius is `1.64mm` and material thickness is `2mm` then `Radius / Thickness == 1.64/2 = 0.82` so `0.38` will be used as the K-factor. See [lookup.py](https://github.com/ceremcem/FreeCAD_SheetMetal/blob/k-factor-from-lookup/lookup.py#L46-L68) for more examples.
67 |
68 | 2. Use "Unfold Task Panel" to assign the material sheet.
69 | 3. Unfold as usual.
70 |
71 | ### Screencast
72 | 
73 |
74 | # Engineering Mode
75 |
76 | ### Description
77 |
78 | Some sort of parameters effect the fabrication process but are impossible to inspect visually, such as K-factor, which makes them susceptible to go unnoticed until the actual erroneous production took place.
79 |
80 | In engineering mode, such "non-visually-inspectable" values are not assigned with default values and explicit user input is required. "Engineering mode" is a safer UX mode for production environments.
81 |
82 | ### Activating
83 |
84 | 1. Switch to SheetMetal WB at least once.
85 | 2. Edit -> Preferences -> SheetMetal
86 | 3. Select `enabled` in `Engineering UX Mode` field.
87 |
88 | # Installation
89 | For installation and how to use, please visit:
90 | http://theseger.com/projects/2015/06/sheet-metal-addon-for-freecad/
91 | Starting from FreeCAD 0.17 it can be installed via the [Addon Manager](https://github.com/FreeCAD/FreeCAD-addons) (from Tools menu)
92 |
93 | #### References
94 | * Development repo: https://github.com/shaise/FreeCAD_SheetMetal
95 | * FreeCAD wiki page: https://www.freecadweb.org/wiki/SheetMetal_Workbench
96 | * Authors webpage: http://theseger.com/projects/2015/06/sheet-metal-addon-for-freecad/
97 | * FreeCAD Forum announcement/discussion [thread](https://forum.freecadweb.org/viewtopic.php?f=3&t=60818)
98 |
99 | #### Release notes:
100 | * V0.2.50 09 Jul 2022: Moved 'Drawing' to 'TechDraw' for FC0.21 compatibility. Thank you!
101 | * V0.2.49 03 Jul 2021: Add SubShapeBinder as source by [@s-light][s-light]. Thank you!
102 | * V0.2.48 02 May 2021: Add context menu [@jaisejames][jaisejames]. Thank you!
103 | * V0.2.47 24 Feb 2021: Add translation support by [@jaisejames][jaisejames]. Thank you!
104 | * V0.2.46 31 Jan 2021: Small bug fixes and code clean by [@jaisejames][jaisejames]. Thank you!
105 | * V0.2.45 24 Dec 2020: Added punch tool feature by [@jaisejames][jaisejames]. Thank you!
106 | * V0.2.44 19 Dec 2020: Added extend feature by [@jaisejames][jaisejames]. Thank you!
107 | * V0.2.43 01 Dec 2020: Added corner feature and map sketch to cut openings by [@jaisejames][jaisejames]. Thank you!
108 | * V0.2.42 09 Jun 2020: Added Engineering UX Mode by [@ceremcem][ceremcem]. Thank you!
109 | * V0.2.41 01 Jun 2020: Added Drop down Menu
110 | * V0.2.40 24 May 2020: Added added tools for conversion of solid corners to sheetmetal by [@jaisejames][jaisejames]. Thank you!
111 | * V0.2.34 09 Mar 2020: Rename "my commands" context menu to sheet metal
112 | * V0.2.33 09 Mar 2020: Fix bend radius bug on sketch bends. Thank you Léo Flaventin!
113 | * V0.2.32 02 Jan 2020: Python 3.8 update by [@looooo][lorenz]. Thank you!
114 | * V0.2.31 24 Apr 2019: Added better K factor control by [@ceremcem][ceremcem]. Thank you!
115 | * V0.2.30 30 Mar 2019: Added Fold-on-sketch-line tool by [@jaisejames][jaisejames]. Thank you!
116 | * V0.2.22 24 Jan 2019: Fix some typos, Issue [#54][54]
117 | * V0.2.21 20 Jan 2019: Fix some typos, Issue [#52][52]
118 | * V0.2.20 10 Jan 2019: Added sheetmetal generation from base wire by [@jaisejames][jaisejames]. Thank you!
119 | * V0.2.10 01 Nov 2018: Merge new features by [@jaisejames][jaisejames]. Thank you!
120 | * Added Edge based selection
121 | * Added Auto-mitering
122 | * Added Sketch based Wall
123 | * Added Sketch based Guided wall
124 | * Added Relief factor
125 | * Added Material Inside, thk inside, Offset options
126 | * V0.2.04 21 Sep 2018: Fix K-Factor bug
127 | * V0.2.03 20 Sep 2018: Merge [@easyw][easyw] PR: Add separate color for inner sketch lines. (issue [#46][46]). Change Gui layout
128 | * V0.2.02 15 Sep 2018: Add color selection for unfold sketches (issue [#41][41])
129 | * V0.2.01 15 Sep 2018:
130 | * Fix bug when not generating sketch (issue [#42][42])
131 | * Support separate color for bend lines (issue [#41][41])
132 | * V0.2.00 04 Sep 2018: Make SheetMetal compatible with Python 3 and QT 5
133 | * V0.1.40 20 Aug 2018: Merge [Ulrich][ulrich]'s V20 unfolder script - supports many more sheet metal cases and more robust
134 | * V0.1.32 25 Jun 2018: New feature: Option to separately unfold bends. Thank you [@jaisejames][jaisejames]!
135 | * V0.1.31 25 Jun 2018: Support ellipses and parabolas, Try standard sketch conversion first
136 | * V0.1.30 25 Jun 2018:
137 | * New feature: Generate unfold sketch with folding marks. Issue [#33][33]. Thank you [@easyw][easyw]!
138 | * New feature: K-Factor foe unfolding is now editable. Issue [#30][30]
139 | * V0.1.21 19 Jun 2018: Fixed back negative bend angles, restrict miter to +/- 80 degrees
140 | * V0.1.20 19 Jun 2018: (Thank you [@jaisejames][jaisejames] for all these new features!!)
141 | * Add bend extension to make the bended wall wider
142 | * Add relief shape selection (rounded or flat)
143 | * Double clicking on a bent in the tree view, brings a dialog to select different faces (good when editing the base object breaks the bend, and new faces need to be selected)
144 | * Setting miter angle now works with unfold command
145 | * V0.1.13 10 May 2018: Change unbending method so shape refinement can work.
146 | * V0.1.12 25 Mar 2018: Allow negative bend angles. Change XPM icons to SVG
147 | * V0.1.11 01 Feb 2018: Fix Issue [#23][23]: when there is a gap only on one side, an extra face is added to the other
148 | * V0.1.10 11 Nov 2017: Add miter option to bends. By [@jaisejames][jaisejames]
149 | * V0.1.02 22 Jun 2017: Fix nesting bug, when saving and loading file
150 | * V0.1.01 03 Mar 2017: Support version 0.17 (starting from build 10423)
151 | * V0.0.13 07 Sep 2015: Add negative gaps for extrude function. (per deveee request)
152 | * V0.012 07 Sep 2015: Fix issue submitted by deveee
153 | * V0.010 13 Jun 2015: Add [Ulrich][ulrich]'s great unfolding tool. Thanks!!!
154 | * V0.002 12 Jun 2015: Fix Save/Load issues
155 | * V0.001 11 Jun 2015: Initial version
156 |
157 | [lorenz]: https://github.com/looooo
158 | [ulrich]: https://github.com/ulrich1a
159 | [ceremcem]: https://github.com/ceremcem
160 | [jaisejames]: https://github.com/jaisekjames
161 | [easyw]: https://github.com/easyw
162 | [s-light]: https://github.com/s-light
163 | [30]: https://github.com/shaise/FreeCAD_SheetMetal/issues/30
164 | [33]: https://github.com/shaise/FreeCAD_SheetMetal/issues/33
165 | [41]: https://github.com/shaise/FreeCAD_SheetMetal/issues/41
166 | [42]: https://github.com/shaise/FreeCAD_SheetMetal/issues/42
167 | [46]: https://github.com/shaise/FreeCAD_SheetMetal/issues/46
168 | [52]: https://github.com/shaise/FreeCAD_SheetMetal/issues/52
169 | [54]: https://github.com/shaise/FreeCAD_SheetMetal/issues/54
170 |
171 | ## License
172 | GPLv3 (see [LICENSE](LICENSE))
173 |
--------------------------------------------------------------------------------
/SheetMetalBaseCmd.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###################################################################################
3 | #
4 | # SheetMetalBaseCmd.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 | from FreeCAD import Gui
27 | from PySide import QtCore, QtGui
28 |
29 | import FreeCAD, Part, os
30 | __dir__ = os.path.dirname(__file__)
31 | iconPath = os.path.join( __dir__, 'Resources', 'icons' )
32 |
33 | def smWarnDialog(msg):
34 | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error in macro MessageBox', msg)
35 | diag.setWindowModality(QtCore.Qt.ApplicationModal)
36 | diag.exec_()
37 |
38 | def smBelongToBody(item, body):
39 | if (body is None):
40 | return False
41 | for obj in body.Group:
42 | if obj.Name == item.Name:
43 | return True
44 | return False
45 |
46 | def smIsSketchObject(obj):
47 | return str(obj).find(" 1:
107 | wallSolid = solidlist[0].multiFuse(solidlist[1:])
108 | else :
109 | wallSolid = solidlist[0]
110 | #Part.show(wallSolid,"wallSolid")
111 |
112 | #Part.show(wallSolid,"wallSolid")
113 | return wallSolid
114 |
115 | class SMBaseBend:
116 | def __init__(self, obj):
117 | '''"Add wall or Wall with radius bend" '''
118 | selobj = Gui.Selection.getSelectionEx()[0]
119 |
120 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Bend Radius")
121 | obj.addProperty("App::PropertyLength","radius","Parameters",_tip_).radius = 1.0
122 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Thickness of sheetmetal")
123 | obj.addProperty("App::PropertyLength","thickness","Parameters",_tip_).thickness = 1.0
124 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Relief Type")
125 | obj.addProperty("App::PropertyEnumeration", "BendSide", "Parameters",_tip_).BendSide = ["Outside", "Inside", "Middle"]
126 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Length of wall")
127 | obj.addProperty("App::PropertyLength","length","Parameters",_tip_).length = 100.0
128 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Wall Sketch object")
129 | obj.addProperty("App::PropertyLink", "BendSketch", "Parameters",_tip_).BendSketch = selobj.Object
130 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Extrude Symmetric to Plane")
131 | obj.addProperty("App::PropertyBool","MidPlane","Parameters",_tip_).MidPlane = False
132 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Reverse Extrusion Direction")
133 | obj.addProperty("App::PropertyBool","Reverse","Parameters",_tip_).Reverse = False
134 | obj.Proxy = self
135 |
136 | def execute(self, fp):
137 | '''"Print a short message when doing a recomputation, this method is mandatory" '''
138 | if (not hasattr(fp,"MidPlane")):
139 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Extrude Symmetric to Plane")
140 | fp.addProperty("App::PropertyBool","MidPlane","Parameters",_tip_).MidPlane = False
141 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Reverse Extrusion Direction")
142 | fp.addProperty("App::PropertyBool","Reverse","Parameters",_tip_).Reverse = False
143 |
144 | s = smBase(thk = fp.thickness.Value, length = fp.length.Value, radius = fp.radius.Value, Side = fp.BendSide,
145 | midplane = fp.MidPlane, reverse = fp.Reverse, MainObject = fp.BendSketch)
146 |
147 | fp.Shape = s
148 | obj = Gui.ActiveDocument.getObject(fp.BendSketch.Name)
149 | if obj:
150 | obj.Visibility = False
151 |
152 | class SMBaseViewProvider:
153 | "A View provider that nests children objects under the created one"
154 |
155 | def __init__(self, obj):
156 | obj.Proxy = self
157 | self.Object = obj.Object
158 |
159 | def attach(self, obj):
160 | self.Object = obj.Object
161 | return
162 |
163 | def updateData(self, fp, prop):
164 | return
165 |
166 | def getDisplayModes(self,obj):
167 | modes=[]
168 | return modes
169 |
170 | def setDisplayMode(self,mode):
171 | return mode
172 |
173 | def onChanged(self, vp, prop):
174 | return
175 |
176 | def __getstate__(self):
177 | # return {'ObjectName' : self.Object.Name}
178 | return None
179 |
180 | def __setstate__(self,state):
181 | if state is not None:
182 | import FreeCAD
183 | doc = FreeCAD.ActiveDocument #crap
184 | self.Object = doc.getObject(state['ObjectName'])
185 |
186 | def claimChildren(self):
187 | objs = []
188 | if hasattr(self.Object,"BendSketch"):
189 | objs.append(self.Object.BendSketch)
190 | return objs
191 |
192 | def getIcon(self):
193 | return os.path.join( iconPath , 'SheetMetal_AddBase.svg')
194 |
195 | class AddBaseCommandClass():
196 | """Add Base Wall command"""
197 |
198 | def GetResources(self):
199 | return {'Pixmap' : os.path.join( iconPath , 'SheetMetal_AddBase.svg'), # the name of a svg file available in the resources
200 | 'MenuText': QtCore.QT_TRANSLATE_NOOP('SheetMetal','Make Base Wall'),
201 | 'Accel': "C, B",
202 | 'ToolTip' : QtCore.QT_TRANSLATE_NOOP('SheetMetal','Create a sheetmetal wall from a sketch\n'
203 | '1. Select a Skech to create bends with walls.\n'
204 | '2. Use Property editor to modify other parameters')}
205 |
206 | def Activated(self):
207 | doc = FreeCAD.ActiveDocument
208 | view = Gui.ActiveDocument.ActiveView
209 | activeBody = None
210 | # selobj = Gui.Selection.getSelectionEx()[0].Object
211 | if hasattr(view,'getActiveObject'):
212 | activeBody = view.getActiveObject('pdbody')
213 | # if not smIsOperationLegal(activeBody, selobj):
214 | # return
215 | doc.openTransaction("BaseBend")
216 | if activeBody is None :
217 | a = doc.addObject("Part::FeaturePython","BaseBend")
218 | SMBaseBend(a)
219 | SMBaseViewProvider(a.ViewObject)
220 | else:
221 | #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name)
222 | a = doc.addObject("PartDesign::FeaturePython","BaseBend")
223 | SMBaseBend(a)
224 | SMBaseViewProvider(a.ViewObject)
225 | activeBody.addObject(a)
226 | doc.recompute()
227 | doc.commitTransaction()
228 | return
229 |
230 | def IsActive(self):
231 | if len(Gui.Selection.getSelection()) != 1 :
232 | return False
233 | selobj = Gui.Selection.getSelection()[0]
234 | if not(
235 | selobj.isDerivedFrom("Sketcher::SketchObject")
236 | or selobj.isDerivedFrom("PartDesign::ShapeBinder")
237 | or selobj.isDerivedFrom("PartDesign::SubShapeBinder")
238 | ):
239 | return False
240 | return True
241 |
242 | Gui.addCommand('SMBase',AddBaseCommandClass())
243 |
--------------------------------------------------------------------------------
/Resources/icons/SheetMetal_Update.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
258 |
--------------------------------------------------------------------------------
/Macros/SheetMetalUnfoldUpdater.FCMacro:
--------------------------------------------------------------------------------
1 | __author__ = "ceremcem"
2 | """
3 | URL : https://github.com/shaise/FreeCAD_SheetMetal/blob/master/Macros/SheetMetalUnfoldUpdater.FCMacro
4 | URL2 : https://github.com/ceremcem/FreeCAD_SheetMetal/blob/master/Macros/SheetMetalUnfoldUpdater.FCMacro
5 |
6 | Description:
7 | -----------------------------------------------------------------------------------
8 | This macro automatically updates all unfold operations as well as their related
9 | TechDraw views.
10 |
11 | Dependencies:
12 | -----------------------------------------------------------------------------------
13 | It depends on LinkStage3 toponaming feature for storing "unfold face" information
14 | within the project file.
15 |
16 | Usage:
17 | -----------------------------------------------------------------------------------
18 |
19 | 1. Store the "unfold face" information within the project:
20 | 1. Put your processed (final) object into an assembly container.
21 | 2. Create an "Element" for the unfold face
22 | 3. Rename the element to "foo_unfold_face"
23 | 2. Run this macro.
24 | 3. See "foo_unfold" and "foo_unfold_solid" is created properly.
25 | 4. Create TechDraw views by using those two objects
26 | 5. (Re-)run this script. If everything (unfold, TechDraw source updates) went successfully,
27 | then those two objects should be marked as invisible.
28 |
29 | Verification:
30 | ------------------------
31 | If everything went successful, you SHOULD NOT see any visible objects in the drawing area
32 | or any `_TMP` postfixed objects in the treeview.
33 |
34 | KNOWN ISSUE:
35 | --------------------
36 | Problem: Following error might be received:
37 |
38 | No graphical interface
39 | discretizing Sketch
40 | Running the Python command 'SMUnfoldUnattended' failed:
41 | Traceback (most recent call last):
42 | File "/home/aea/.FreeCAD/Mod/sheetmetal/SheetMetalUnfolder.py", line 3102, in Activated
43 | taskd.accept()
44 | File "/home/aea/.FreeCAD/Mod/sheetmetal/SheetMetalUnfolder.py", line 2968, in accept
45 | docG.getObject(a.Name).Transparency = genObjTransparency
46 |
47 | Workaround: Please double click the documents that contains the bent parts at least one time.
48 |
49 |
50 |
51 | """
52 |
53 | import FreeCAD
54 | import re
55 |
56 | echo = FreeCAD.Console.PrintMessage
57 | warn = FreeCAD.Console.PrintWarning
58 | error = FreeCAD.Console.PrintError
59 |
60 | prev_workbench = Gui.activeWorkbench().name()
61 | # TODO: Actually there is no need to switch to SMWorkbench after the first one.
62 | Gui.activateWorkbench("SMWorkbench")
63 | if prev_workbench:
64 | Gui.activateWorkbench(prev_workbench)
65 |
66 | unfold_sketch_regex = re.compile('.+_unfold_face')
67 |
68 | """ For debugging purposes, see https://github.com/realthunder/FreeCAD_assembly3/issues/236#issuecomment-651583969
69 | doc_name="driver_base"
70 | elem_label="wall_unfold_face"
71 | # ------------------------------------------------------------
72 | Gui.ActiveDocument=Gui.getDocument(doc_name)
73 | App.ActiveDocument=App.getDocument(doc_name)
74 | App.setActiveDocument(App.ActiveDocument.Name) # Interesting. Why do we need to assign this manually?
75 | doc=App.ActiveDocument
76 | element = doc.getObjectsByLabel(elem_label)[0].Proxy
77 | partGroup = element.getAssembly().getPartGroup()
78 | subname = element.getElementSubname(True)
79 | Gui.Selection.addSelection(partGroup, subname)
80 | """
81 |
82 | echo("\n\n\n")
83 | echo("--------------------- Updating All Unfold Operations ------------------------------\n")
84 |
85 |
86 | def get_related_views(doc, labels):
87 | related_views = {}
88 | for x in doc.Objects:
89 | if x.TypeId == 'TechDraw::DrawPage':
90 | for view in x.Views:
91 | try:
92 | _src = view.Source[0]
93 | except Exception as e:
94 | continue
95 | #print "source of ", view.Label, ' in ', x.Label, " is: ", _src.Label
96 | if _src.Label in labels:
97 | # This view uses our unfolded output, update this view at the end
98 | echo("* Found related TechDraw ProjGroup: %s\n" % view.Label)
99 | related_views[_src.Label] = view
100 | return related_views
101 |
102 |
103 | originals = [App.ActiveDocument, Gui.ActiveDocument]
104 | try:
105 | for doc_name, doc in App.listDocuments().items():
106 | unfold_objects = list(map(lambda x: x.Label, filter(
107 | lambda x: re.match('Unfold.*$', x.Label), doc.Objects)))
108 | if unfold_objects:
109 | error("Document '%s' contains objects with Unfold* prefix, please rename them: %s\n" %
110 | (doc_name, ', '.join(unfold_objects)))
111 | raise Exception("INFO: Stopping before a possible conflict.")
112 |
113 | for doc_name, doc in App.listDocuments().items():
114 | Gui.ActiveDocument = Gui.getDocument(doc_name)
115 | App.ActiveDocument = App.getDocument(doc_name)
116 | # Interesting. Why do we need to assign this manually?
117 | App.setActiveDocument(App.ActiveDocument.Name)
118 |
119 | for o in doc.Objects:
120 | try:
121 | _o = o.TypeId
122 | except:
123 | # probably we deleted this object before updating the existing unfold sketch.
124 | continue
125 |
126 | # find any Asm3 Element that matches with our magic postfix
127 | if o.TypeId == 'Part::FeaturePython':
128 | match = unfold_sketch_regex.match(o.Label)
129 | if match:
130 | output_name = o.Label[:-5] # remove the "_face" postfix
131 | output_name_solid = output_name + '_solid'
132 | echo("+++ In: %s, Unfold job: %s \n" %
133 | (doc_name, output_name))
134 |
135 | related_views = get_related_views(
136 | doc, [output_name, output_name_solid])
137 | related_view_count = len(related_views.keys())
138 | if related_view_count > 0:
139 | echo(
140 | "* Found %d TechDraw views containing that object.\n" % related_view_count)
141 | else:
142 | warn(
143 | "* No TechDraw views found related to %s, this isn't supposed to happen.\n" % (output_name))
144 |
145 | # backup current unfolded outputs
146 | tmp_postfix = "_TMP"
147 | old = None
148 | old_solid = None
149 | need_recomputation = False
150 | try:
151 | old = doc.getObjectsByLabel(output_name)[0]
152 | old.Label += tmp_postfix
153 | need_recomputation = True
154 | except:
155 | pass
156 | try:
157 | old_solid = doc.getObjectsByLabel(output_name_solid)[0]
158 | old_solid.Label += tmp_postfix
159 | need_recomputation = True
160 | except:
161 | pass
162 |
163 | if need_recomputation:
164 | doc.recompute()
165 |
166 | # Unfold the part
167 | Gui.Selection.clearSelection()
168 |
169 | # Get the unfold face id
170 | face_elem_label = output_name + '_face'
171 | face_elem = doc.getObjectsByLabel(face_elem_label)[0].Proxy
172 | partGroup = face_elem.getAssembly().getPartGroup()
173 | subname = face_elem.getElementSubname(True)
174 | Gui.Selection.addSelection(partGroup, subname)
175 |
176 | # Unfold
177 | Gui.runCommand('SMUnfoldUnattended')
178 |
179 | try:
180 | # Check if unfold operation is successful
181 | unfold_objects = list(map(lambda x: x.Label, filter(
182 | lambda x: re.match('Unfold.*$', x.Label), doc.Objects)))
183 | if not unfold_objects:
184 | raise Exception(
185 | "Can't unfold the sheetmetal object. Can you unfold manually?")
186 | else:
187 | Gui.Selection.clearSelection()
188 |
189 | # Get the newest object's names
190 | new_solid_name = list(map(lambda x: x.Label, filter(
191 | lambda x: re.match('Unfold[0-9]*$', x.Label), doc.Objects)))
192 | new_solid_name.sort(reverse=True)
193 | new_solid_name = new_solid_name[0]
194 |
195 | new_sketch_name = list(map(lambda x: x.Label, filter(
196 | lambda x: re.match('Unfold_Sketch[0-9]*$', x.Label), doc.Objects)))
197 | new_sketch_name.sort(reverse=True)
198 | new_sketch_name = new_sketch_name[0]
199 |
200 | # Solid object is useful for laser cut operations
201 | solid = doc.getObjectsByLabel(new_solid_name)[0]
202 | solid.Label = output_name_solid
203 |
204 | sketch = doc.getObjectsByLabel(new_sketch_name)[0]
205 | sketch.Label = output_name
206 |
207 | # Update the source of related views
208 | for name, view in related_views.items():
209 | if name == sketch.Label:
210 | obj = sketch
211 | elif name == solid.Label:
212 | obj = solid
213 | echo("* %s is used to update %s\n" %
214 | (obj.Label, view.Label))
215 | view.Source = [obj]
216 | # set visibility to false if a related view is found and updated.
217 | obj.Visibility = False
218 |
219 | # remove the temporary object
220 | needs_recomputation = False
221 | for o in [old, old_solid]:
222 | if o is not None:
223 | #echo("removing the old object: %s\n" % o.Name)
224 | doc.removeObject(o.Name)
225 | needs_recomputation = True
226 | if needs_recomputation:
227 | doc.recompute()
228 |
229 | except Exception as e:
230 | error("* %s (in %s): Something went wrong, restoring the previous sketches...\n" %
231 | (output_name, doc.Name))
232 | # For debugging purposes:
233 | error('Error on line %s: %s, %s.\n' %
234 | (sys.exc_info()[-1].tb_lineno, type(e).__name__, e))
235 |
236 | #raise Exception("Breaking the script execution before CLEANUP for debugging purposes\n")
237 |
238 | needs_recomputation = False
239 | for o in [old, old_solid]:
240 | if o is not None:
241 | orig_label = o.Label[:-(len(tmp_postfix))]
242 | warn(
243 | "!! %s: Restoring the previous object.\n" % orig_label)
244 | o.Label = orig_label
245 | o.Visibility = True
246 | needs_recomputation = True
247 | if needs_recomputation:
248 | doc.recompute()
249 |
250 | except Exception as e:
251 | warn("Script isn't terminated normally: %s\n" % e)
252 | finally:
253 | doc.recompute()
254 | App.ActiveDocument, Gui.ActiveDocument = originals
255 | App.setActiveDocument(App.ActiveDocument.Name)
256 |
--------------------------------------------------------------------------------
/SheetMetalJunction.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###################################################################################
3 | #
4 | # SheetMetalJunction.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 | from FreeCAD import Gui
27 | from PySide import QtCore, QtGui
28 |
29 | import FreeCAD, FreeCADGui, Part, os
30 | __dir__ = os.path.dirname(__file__)
31 | iconPath = os.path.join( __dir__, 'Resources', 'icons' )
32 | smEpsilon = 0.0000001
33 |
34 | # IMPORTANT: please remember to change the element map version in case of any
35 | # changes in modeling logic
36 | smElementMapVersion = 'sm1.'
37 |
38 | def smWarnDialog(msg):
39 | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error in macro MessageBox', msg)
40 | diag.setWindowModality(QtCore.Qt.ApplicationModal)
41 | diag.exec_()
42 |
43 | def smBelongToBody(item, body):
44 | if (body is None):
45 | return False
46 | for obj in body.Group:
47 | if obj.Name == item.Name:
48 | return True
49 | return False
50 |
51 | def smIsPartDesign(obj):
52 | return str(obj).find("
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 | from FreeCAD import Gui
27 | from PySide import QtCore, QtGui
28 |
29 | import FreeCAD, FreeCADGui, Part, os
30 | __dir__ = os.path.dirname(__file__)
31 | iconPath = os.path.join( __dir__, 'Resources', 'icons' )
32 | smEpsilon = 0.0000001
33 |
34 | # IMPORTANT: please remember to change the element map version in case of any
35 | # changes in modeling logic
36 | smElementMapVersion = 'sm1.'
37 |
38 | def smWarnDialog(msg):
39 | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error in macro MessageBox', msg)
40 | diag.setWindowModality(QtCore.Qt.ApplicationModal)
41 | diag.exec_()
42 |
43 | def smBelongToBody(item, body):
44 | if (body is None):
45 | return False
46 | for obj in body.Group:
47 | if obj.Name == item.Name:
48 | return True
49 | return False
50 |
51 | def smIsPartDesign(obj):
52 | return str(obj).find(" filletedface.Area :
80 | filletedface = joinface.makeFillet(radius, joinface.Edges)
81 | #Part.show(filletedface,'filletedface')
82 |
83 | cutface = filletedface.cut(facelist[0])
84 | cutface = cutface.cut(facelist[1])
85 | #Part.show(cutface1,'cutface1')
86 | if filletedoffsetface.Area > filletedface.Area :
87 | offsetsolid1 = cutface.makeOffsetShape(-thk, 0.0, fill = True)
88 | #Part.show(offsetsolid1,'offsetsolid1')
89 | else:
90 | offsetsolid1 = cutface.makeOffsetShape(-thk, 0.0, fill = True)
91 | #Part.show(offsetsolid1,'offsetsolid1')
92 | cutsolid = BOPTools.JoinAPI.cutout_legacy(resultSolid, offsetsolid1, 0.0)
93 | #Part.show(cutsolid,'cutsolid')
94 | offsetsolid1 = cutsolid.fuse(offsetsolid1)
95 | #Part.show(offsetsolid1,'offsetsolid1')
96 | resultSolid = BOPTools.JoinAPI.cutout_legacy(resultSolid, offsetsolid1, 0.0)
97 | #Part.show(resultsolid,'resultsolid')
98 | resultSolid = resultSolid.fuse(offsetsolid1)
99 | #Part.show(resultsolid,'resultsolid')
100 |
101 | return resultSolid
102 |
103 | class SMSolidBend:
104 | def __init__(self, obj):
105 | '''"Add Bend to Solid" '''
106 | selobj = Gui.Selection.getSelectionEx()[0]
107 |
108 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Bend Radius")
109 | obj.addProperty("App::PropertyLength","radius","Parameters", _tip_).radius = 1.0
110 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Thickness of sheetmetal")
111 | obj.addProperty("App::PropertyLength","thickness","Parameters", _tip_).thickness = 1.0
112 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Base object")
113 | obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters", _tip_).baseObject = (selobj.Object, selobj.SubElementNames)
114 | obj.Proxy = self
115 |
116 | def getElementMapVersion(self, _fp, ver, _prop, restored):
117 | if not restored:
118 | return smElementMapVersion + ver
119 |
120 | def execute(self, fp):
121 | '''"Print a short message when doing a recomputation, this method is mandatory" '''
122 | # pass selected object shape
123 | Main_Object = fp.baseObject[0].Shape.copy()
124 | s = smSolidBend(thk = fp.thickness.Value, radius = fp.radius.Value, selEdgeNames = fp.baseObject[1],
125 | MainObject = Main_Object)
126 | fp.Shape = s
127 | fp.baseObject[0].ViewObject.Visibility = False
128 |
129 |
130 | class SMBendViewProviderTree:
131 | "A View provider that nests children objects under the created one"
132 |
133 | def __init__(self, obj):
134 | obj.Proxy = self
135 | self.Object = obj.Object
136 |
137 | def attach(self, obj):
138 | self.Object = obj.Object
139 | return
140 |
141 | def setupContextMenu(self, viewObject, menu):
142 | action = menu.addAction(FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label))
143 | action.triggered.connect(lambda: self.startDefaultEditMode(viewObject))
144 | return False
145 |
146 | def startDefaultEditMode(self, viewObject):
147 | document = viewObject.Document.Document
148 | if not document.HasPendingTransaction:
149 | text = FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label)
150 | document.openTransaction(text)
151 | viewObject.Document.setEdit(viewObject.Object, 0)
152 |
153 | def updateData(self, fp, prop):
154 | return
155 |
156 | def getDisplayModes(self,obj):
157 | modes=[]
158 | return modes
159 |
160 | def setDisplayMode(self,mode):
161 | return mode
162 |
163 | def onChanged(self, vp, prop):
164 | return
165 |
166 | def __getstate__(self):
167 | # return {'ObjectName' : self.Object.Name}
168 | return None
169 |
170 | def __setstate__(self,state):
171 | if state is not None:
172 | import FreeCAD
173 | doc = FreeCAD.ActiveDocument #crap
174 | self.Object = doc.getObject(state['ObjectName'])
175 |
176 | def claimChildren(self):
177 | objs = []
178 | if hasattr(self.Object,"baseObject"):
179 | objs.append(self.Object.baseObject[0])
180 | return objs
181 |
182 | def getIcon(self):
183 | return os.path.join( iconPath , 'SheetMetal_AddBend.svg')
184 |
185 | def setEdit(self,vobj,mode):
186 | taskd = SMBendTaskPanel()
187 | taskd.obj = vobj.Object
188 | taskd.update()
189 | self.Object.ViewObject.Visibility=False
190 | self.Object.baseObject[0].ViewObject.Visibility=True
191 | FreeCADGui.Control.showDialog(taskd)
192 | return True
193 |
194 | def unsetEdit(self,vobj,mode):
195 | FreeCADGui.Control.closeDialog()
196 | self.Object.baseObject[0].ViewObject.Visibility=False
197 | self.Object.ViewObject.Visibility=True
198 | return False
199 |
200 | class SMBendViewProviderFlat:
201 | "A View provider that nests children objects under the created one"
202 |
203 | def __init__(self, obj):
204 | obj.Proxy = self
205 | self.Object = obj.Object
206 |
207 | def attach(self, obj):
208 | self.Object = obj.Object
209 | return
210 |
211 | def updateData(self, fp, prop):
212 | return
213 |
214 | def getDisplayModes(self,obj):
215 | modes=[]
216 | return modes
217 |
218 | def setDisplayMode(self,mode):
219 | return mode
220 |
221 | def onChanged(self, vp, prop):
222 | return
223 |
224 | def __getstate__(self):
225 | # return {'ObjectName' : self.Object.Name}
226 | return None
227 |
228 | def __setstate__(self,state):
229 | if state is not None:
230 | import FreeCAD
231 | doc = FreeCAD.ActiveDocument #crap
232 | self.Object = doc.getObject(state['ObjectName'])
233 |
234 | def claimChildren(self):
235 |
236 | return []
237 |
238 | def getIcon(self):
239 | return os.path.join( iconPath , 'SheetMetal_AddBend.svg')
240 |
241 | def setEdit(self,vobj,mode):
242 | taskd = SMBendTaskPanel()
243 | taskd.obj = vobj.Object
244 | taskd.update()
245 | self.Object.ViewObject.Visibility=False
246 | self.Object.baseObject[0].ViewObject.Visibility=True
247 | FreeCADGui.Control.showDialog(taskd)
248 | return True
249 |
250 | def unsetEdit(self,vobj,mode):
251 | FreeCADGui.Control.closeDialog()
252 | self.Object.baseObject[0].ViewObject.Visibility=False
253 | self.Object.ViewObject.Visibility=True
254 | return False
255 |
256 | class SMBendTaskPanel:
257 | '''A TaskPanel for the Sheetmetal'''
258 | def __init__(self):
259 |
260 | self.obj = None
261 | self.form = QtGui.QWidget()
262 | self.form.setObjectName("SMBendTaskPanel")
263 | self.form.setWindowTitle("Binded faces/edges list")
264 | self.grid = QtGui.QGridLayout(self.form)
265 | self.grid.setObjectName("grid")
266 | self.title = QtGui.QLabel(self.form)
267 | self.grid.addWidget(self.title, 0, 0, 1, 2)
268 | self.title.setText("Select new face(s)/Edge(s) and press Update")
269 |
270 | # tree
271 | self.tree = QtGui.QTreeWidget(self.form)
272 | self.grid.addWidget(self.tree, 1, 0, 1, 2)
273 | self.tree.setColumnCount(2)
274 | self.tree.setHeaderLabels(["Name","Subelement"])
275 |
276 | # buttons
277 | self.addButton = QtGui.QPushButton(self.form)
278 | self.addButton.setObjectName("addButton")
279 | self.addButton.setIcon(QtGui.QIcon(os.path.join( iconPath , 'SheetMetal_Update.svg')))
280 | self.grid.addWidget(self.addButton, 3, 0, 1, 2)
281 |
282 | QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.updateElement)
283 | self.update()
284 |
285 | def isAllowedAlterSelection(self):
286 | return True
287 |
288 | def isAllowedAlterView(self):
289 | return True
290 |
291 | def getStandardButtons(self):
292 | return int(QtGui.QDialogButtonBox.Ok)
293 |
294 | def update(self):
295 | 'fills the treewidget'
296 | self.tree.clear()
297 | if self.obj:
298 | f = self.obj.baseObject
299 | if isinstance(f[1],list):
300 | for subf in f[1]:
301 | #FreeCAD.Console.PrintLog("item: " + subf + "\n")
302 | item = QtGui.QTreeWidgetItem(self.tree)
303 | item.setText(0,f[0].Name)
304 | item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg"))
305 | item.setText(1,subf)
306 | else:
307 | item = QtGui.QTreeWidgetItem(self.tree)
308 | item.setText(0,f[0].Name)
309 | item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg"))
310 | item.setText(1,f[1][0])
311 | self.retranslateUi(self.form)
312 |
313 | def updateElement(self):
314 | if self.obj:
315 | sel = FreeCADGui.Selection.getSelectionEx()[0]
316 | if sel.HasSubObjects:
317 | obj = sel.Object
318 | for elt in sel.SubElementNames:
319 | if "Face" in elt or "Edge" in elt:
320 | face = self.obj.baseObject
321 | found = False
322 | if (face[0] == obj.Name):
323 | if isinstance(face[1],tuple):
324 | for subf in face[1]:
325 | if subf == elt:
326 | found = True
327 | else:
328 | if (face[1][0] == elt):
329 | found = True
330 | if not found:
331 | self.obj.baseObject = (sel.Object, sel.SubElementNames)
332 | self.update()
333 |
334 | def accept(self):
335 | FreeCAD.ActiveDocument.recompute()
336 | FreeCADGui.ActiveDocument.resetEdit()
337 | #self.obj.ViewObject.Visibility=True
338 | return True
339 |
340 | def retranslateUi(self, TaskPanel):
341 | #TaskPanel.setWindowTitle(QtGui.QApplication.translate("draft", "Faces", None))
342 | self.addButton.setText(QtGui.QApplication.translate("draft", "Update", None))
343 |
344 |
345 | class AddBendCommandClass():
346 | """Add Solid Bend command"""
347 |
348 | def GetResources(self):
349 | return {'Pixmap' : os.path.join( iconPath , 'SheetMetal_AddBend.svg'), # the name of a svg file available in the resources
350 | 'MenuText': QtCore.QT_TRANSLATE_NOOP('SheetMetal','Make Bend'),
351 | 'Accel': "S, B",
352 | 'ToolTip' : QtCore.QT_TRANSLATE_NOOP('SheetMetal','Create Bend where two walls come together on solids\n'
353 | '1. Select edge(s) to create bend on corner edge(s).\n'
354 | '2. Use Property editor to modify parameters')}
355 |
356 | def Activated(self):
357 | doc = FreeCAD.ActiveDocument
358 | view = Gui.ActiveDocument.ActiveView
359 | activeBody = None
360 | selobj = Gui.Selection.getSelectionEx()[0].Object
361 | if hasattr(view,'getActiveObject'):
362 | activeBody = view.getActiveObject('pdbody')
363 | if not smIsOperationLegal(activeBody, selobj):
364 | return
365 | doc.openTransaction("Add Bend")
366 | if activeBody is None or not smIsPartDesign(selobj):
367 | a = doc.addObject("Part::FeaturePython","SolidBend")
368 | SMSolidBend(a)
369 | SMBendViewProviderTree(a.ViewObject)
370 | else:
371 | #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name)
372 | a = doc.addObject("PartDesign::FeaturePython","SolidBend")
373 | SMSolidBend(a)
374 | SMBendViewProviderFlat(a.ViewObject)
375 | activeBody.addObject(a)
376 | FreeCADGui.Selection.clearSelection()
377 | doc.recompute()
378 | doc.commitTransaction()
379 | return
380 |
381 | def IsActive(self):
382 | if len(Gui.Selection.getSelection()) < 1 or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1:
383 | return False
384 | # selobj = Gui.Selection.getSelection()[0]
385 | for selFace in Gui.Selection.getSelectionEx()[0].SubObjects:
386 | if type(selFace) != Part.Edge :
387 | return False
388 | return True
389 |
390 | Gui.addCommand('SMMakeBend',AddBendCommandClass())
391 |
392 |
--------------------------------------------------------------------------------
/Resources/icons/SheetMetal_Forming.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/SheetMetalRelief.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###############################################################################
3 | #
4 | # SheetMetalRelief.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 | from FreeCAD import Gui
27 | from PySide import QtCore, QtGui
28 |
29 | import FreeCAD, FreeCADGui, Part, os
30 | __dir__ = os.path.dirname(__file__)
31 | iconPath = os.path.join( __dir__, 'Resources', 'icons' )
32 | smEpsilon = 0.0000001
33 |
34 | # IMPORTANT: please remember to change the element map version in case of any
35 | # changes in modeling logic
36 | smElementMapVersion = 'sm1.'
37 |
38 | def smWarnDialog(msg):
39 | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error in macro MessageBox', msg)
40 | diag.setWindowModality(QtCore.Qt.ApplicationModal)
41 | diag.exec_()
42 |
43 | def smBelongToBody(item, body):
44 | if (body is None):
45 | return False
46 | for obj in body.Group:
47 | if obj.Name == item.Name:
48 | return True
49 | return False
50 |
51 | def smIsPartDesign(obj):
52 | return str(obj).find("
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 | from FreeCAD import Gui
27 | from PySide import QtCore, QtGui
28 |
29 | import FreeCAD, Part, os, math
30 | __dir__ = os.path.dirname(__file__)
31 | iconPath = os.path.join( __dir__, 'Resources', 'icons' )
32 | smEpsilon = 0.0000001
33 |
34 | import SheetMetalBendSolid
35 |
36 | def smWarnDialog(msg):
37 | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error in macro MessageBox', msg)
38 | diag.setWindowModality(QtCore.Qt.ApplicationModal)
39 | diag.exec_()
40 |
41 | def smBelongToBody(item, body):
42 | if (body is None):
43 | return False
44 | for obj in body.Group:
45 | if obj.Name == item.Name:
46 | return True
47 | return False
48 |
49 | def smIsPartDesign(obj):
50 | return str(obj).find(" 0.0 :
96 | foldface = FoldShape.getElement(selFaceNames[0])
97 | tool = bendlinesketch.Shape.copy()
98 | normal = foldface.normalAt(0,0)
99 | thk = smthk(FoldShape, foldface)
100 | print(thk)
101 |
102 | # if not(flipped) :
103 | #offset = thk * kfactor
104 | #else :
105 | #offset = thk * (1 - kfactor )
106 |
107 | unfoldLength = ( bendR + kfactor * thk ) * bendA * math.pi / 180.0
108 | neutralRadius = ( bendR + kfactor * thk )
109 | #neutralLength = ( bendR + kfactor * thk ) * math.tan(math.radians(bendA / 2.0)) * 2.0
110 | #offsetdistance = neutralLength - unfoldLength
111 | #scalefactor = neutralLength / unfoldLength
112 | #print([neutralRadius, neutralLength, unfoldLength, offsetdistance, scalefactor])
113 |
114 | #To get facedir
115 | toolFaces = tool.extrude(normal * -thk)
116 | #Part.show(toolFaces, "toolFaces")
117 | cutSolid = BOPTools.SplitAPI.slice(FoldShape, toolFaces.Faces, "Standard", 0.0)
118 | #Part.show(cutSolid,"cutSolid_check")
119 |
120 | if not(invertbend) :
121 | solid0 = cutSolid.childShapes()[0]
122 | else :
123 | solid0 = cutSolid.childShapes()[1]
124 |
125 | cutFaceDir = smCutFace(toolFaces.Faces[0], solid0)
126 | #Part.show(cutFaceDir,"cutFaceDir")
127 | facenormal = cutFaceDir.Faces[0].normalAt(0,0)
128 | #print(facenormal)
129 |
130 | if position == "middle" :
131 | tool.translate(facenormal * -unfoldLength / 2.0 )
132 | toolFaces = tool.extrude(normal * -thk)
133 | elif position == "backward" :
134 | tool.translate(facenormal * -unfoldLength )
135 | toolFaces = tool.extrude(normal * -thk)
136 |
137 | #To get split solid
138 | solidlist = []
139 | toolExtr = toolFaces.extrude(facenormal * unfoldLength)
140 | #Part.show(toolExtr,"toolExtr")
141 | CutSolids = FoldShape.cut(toolExtr)
142 | #Part.show(Solids,"Solids")
143 | solid2list, solid1list = [], []
144 | for solid in CutSolids.Solids :
145 | checksolid = toolFaces.common(solid)
146 | if checksolid.Faces :
147 | solid1list.append(solid)
148 | else :
149 | solid2list.append(solid)
150 |
151 | if len(solid1list) > 1 :
152 | solid0 = solid1list[0].multiFuse(solid1list[1:])
153 | else :
154 | solid0 = solid1list[0]
155 | #Part.show(solid0,"solid0")
156 |
157 | if len(solid2list) > 1 :
158 | solid1 = solid2list[0].multiFuse(solid2list[1:])
159 | else :
160 | solid1 = solid2list[0]
161 | #Part.show(solid0,"solid0")
162 | #Part.show(solid1,"solid1")
163 |
164 | bendEdges = FoldShape.common(tool)
165 | #Part.show(bendEdges,"bendEdges")
166 | bendEdge = bendEdges.Edges[0]
167 | if not(flipped) :
168 | revAxisP = bendEdge.valueAt(bendEdge.FirstParameter) + normal * bendR
169 | else :
170 | revAxisP = bendEdge.valueAt(bendEdge.FirstParameter) - normal * (thk + bendR)
171 | revAxisV = bendEdge.valueAt(bendEdge.LastParameter) - bendEdge.valueAt(bendEdge.FirstParameter)
172 | revAxisV.normalize()
173 |
174 | # To check sktech line direction
175 | if (normal.cross(revAxisV).normalize() - facenormal).Length > smEpsilon:
176 | revAxisV = revAxisV * -1
177 | #print(revAxisV)
178 |
179 | if flipped :
180 | revAxisV = revAxisV * -1
181 | #print(revAxisV)
182 |
183 | # To get bend surface
184 | # revLine = Part.LineSegment(tool.Vertexes[0].Point, tool.Vertexes[-1].Point ).toShape()
185 | # bendSurf = revLine.revolve(revAxisP, revAxisV, bendA)
186 | #Part.show(bendSurf,"bendSurf")
187 |
188 | # bendSurfTest = bendSurf.makeOffsetShape(bendR/2.0, 0.0, fill = False)
189 | # #Part.show(bendSurfTest,"bendSurfTest")
190 | # offset = 1
191 | # if bendSurfTest.Area < bendSurf.Area and not(flipped) :
192 | # offset = -1
193 | # elif bendSurfTest.Area > bendSurf.Area and flipped :
194 | # offset = -1
195 | # #print(offset)
196 |
197 | # To get bend solid
198 | flatsolid = FoldShape.cut(solid0)
199 | flatsolid = flatsolid.cut( solid1)
200 | #Part.show(flatsolid,"flatsolid")
201 | flatfaces = foldface.common(flatsolid)
202 | #Part.show(flatfaces,"flatface")
203 | solid1.translate(facenormal * (-unfoldLength))
204 | #Part.show(solid1,"solid1")
205 | solid1.rotate(revAxisP, revAxisV, bendA)
206 | #Part.show(solid1,"rotatedsolid1")
207 | # bendSolidlist =[]
208 | for flatface in flatfaces.Faces :
209 | bendsolid = SheetMetalBendSolid.BendSolid(flatface, bendEdge, bendR, thk, neutralRadius, revAxisV, flipped)
210 | #Part.show(bendsolid,"bendsolid")
211 | solidlist.append(bendsolid)
212 |
213 | solidlist.append(solid0)
214 | solidlist.append(solid1)
215 | #resultsolid = Part.makeCompound(solidlist)
216 | #resultsolid = BOPTools.JoinAPI.connect(solidlist)
217 | resultsolid = solidlist[0].multiFuse(solidlist[1:])
218 |
219 | else :
220 | if bendlinesketch and bendA > 0.0 :
221 | resultsolid = FoldShape
222 |
223 | Gui.ActiveDocument.getObject(MainObject.Name).Visibility = False
224 | Gui.ActiveDocument.getObject(bendlinesketch.Name).Visibility = False
225 | return resultsolid
226 |
227 | class SMFoldWall:
228 | def __init__(self, obj):
229 | '''"Fold / Bend a Sheetmetal with given Bend Radius" '''
230 | selobj = Gui.Selection.getSelectionEx()
231 |
232 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Bend Radius")
233 | obj.addProperty("App::PropertyLength","radius","Parameters",_tip_).radius = 1.0
234 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Bend Angle")
235 | obj.addProperty("App::PropertyAngle","angle","Parameters",_tip_).angle = 90.0
236 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Base Object")
237 | obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters",_tip_).baseObject = (selobj[0].Object, selobj[0].SubElementNames)
238 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Bend Reference Line List")
239 | obj.addProperty("App::PropertyLink","BendLine","Parameters",_tip_).BendLine = selobj[1].Object
240 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Invert Solid Bend Direction")
241 | obj.addProperty("App::PropertyBool","invertbend","Parameters",_tip_).invertbend = False
242 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Neutral Axis Position")
243 | obj.addProperty("App::PropertyFloatConstraint","kfactor","Parameters",_tip_).kfactor = (0.5,0.0,1.0,0.01)
244 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Invert Bend Direction")
245 | obj.addProperty("App::PropertyBool","invert","Parameters",_tip_).invert = False
246 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Unfold Bend")
247 | obj.addProperty("App::PropertyBool","unfold","Parameters",_tip_).unfold = False
248 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Bend Line Position")
249 | obj.addProperty("App::PropertyEnumeration", "Position", "Parameters",_tip_).Position = ["forward", "middle", "backward"]
250 | obj.Proxy = self
251 |
252 | def execute(self, fp):
253 | '''"Print a short message when doing a recomputation, this method is mandatory" '''
254 |
255 | if (not hasattr(fp,"Position")):
256 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Bend Line Position")
257 | fp.addProperty("App::PropertyEnumeration", "Position", "Parameters",_tip_).Position = ["forward", "middle", "backward"]
258 |
259 | s = smFold(bendR = fp.radius.Value, bendA = fp.angle.Value, flipped = fp.invert, unfold = fp.unfold, kfactor = fp.kfactor, bendlinesketch = fp.BendLine,
260 | position = fp.Position, invertbend = fp.invertbend, selFaceNames = fp.baseObject[1], MainObject = fp.baseObject[0])
261 | fp.Shape = s
262 |
263 | class SMFoldViewProvider:
264 | "A View provider that nests children objects under the created one"
265 |
266 | def __init__(self, obj):
267 | obj.Proxy = self
268 | self.Object = obj.Object
269 |
270 | def attach(self, obj):
271 | self.Object = obj.Object
272 | return
273 |
274 | def updateData(self, fp, prop):
275 | return
276 |
277 | def getDisplayModes(self,obj):
278 | modes=[]
279 | return modes
280 |
281 | def setDisplayMode(self,mode):
282 | return mode
283 |
284 | def onChanged(self, vp, prop):
285 | return
286 |
287 | def __getstate__(self):
288 | # return {'ObjectName' : self.Object.Name}
289 | return None
290 |
291 | def __setstate__(self,state):
292 | if state is not None:
293 | import FreeCAD
294 | doc = FreeCAD.ActiveDocument #crap
295 | self.Object = doc.getObject(state['ObjectName'])
296 |
297 | def claimChildren(self):
298 | objs = []
299 | if hasattr(self.Object,"baseObject"):
300 | objs.append(self.Object.baseObject[0])
301 | objs.append(self.Object.BendLine)
302 | return objs
303 |
304 | def getIcon(self):
305 | return os.path.join( iconPath , 'SheetMetal_AddFoldWall.svg')
306 |
307 | class SMFoldPDViewProvider:
308 | "A View provider that nests children objects under the created one"
309 |
310 | def __init__(self, obj):
311 | obj.Proxy = self
312 | self.Object = obj.Object
313 |
314 | def attach(self, obj):
315 | self.Object = obj.Object
316 | return
317 |
318 | def updateData(self, fp, prop):
319 | return
320 |
321 | def getDisplayModes(self,obj):
322 | modes=[]
323 | return modes
324 |
325 | def setDisplayMode(self,mode):
326 | return mode
327 |
328 | def onChanged(self, vp, prop):
329 | return
330 |
331 | def __getstate__(self):
332 | # return {'ObjectName' : self.Object.Name}
333 | return None
334 |
335 | def __setstate__(self,state):
336 | if state is not None:
337 | import FreeCAD
338 | doc = FreeCAD.ActiveDocument #crap
339 | self.Object = doc.getObject(state['ObjectName'])
340 |
341 | def claimChildren(self):
342 | objs = []
343 | if hasattr(self.Object,"BendLine"):
344 | objs.append(self.Object.BendLine)
345 | return objs
346 |
347 | def getIcon(self):
348 | return os.path.join( iconPath , 'SheetMetal_AddFoldWall.svg')
349 |
350 | class AddFoldWallCommandClass():
351 | """Add Fold Wall command"""
352 |
353 | def GetResources(self):
354 | return {'Pixmap' : os.path.join( iconPath , 'SheetMetal_AddFoldWall.svg'), # the name of a svg file available in the resources
355 | 'MenuText': QtCore.QT_TRANSLATE_NOOP('SheetMetal','Fold a Wall'),
356 | 'Accel': "C, F",
357 | 'ToolTip' : QtCore.QT_TRANSLATE_NOOP('SheetMetal','Fold a wall of metal sheet\n'
358 | '1. Select a flat face on sheet metal and\n'
359 | '2. Select a bend line(sketch) on same face(size more than face) to create sheetmetal fold.\n'
360 | '3. Use Property editor to modify other parameters')}
361 |
362 | def Activated(self):
363 | doc = FreeCAD.ActiveDocument
364 | view = Gui.ActiveDocument.ActiveView
365 | activeBody = None
366 | selobj = Gui.Selection.getSelectionEx()[0].Object
367 | if hasattr(view,'getActiveObject'):
368 | activeBody = view.getActiveObject('pdbody')
369 | if not smIsOperationLegal(activeBody, selobj):
370 | return
371 | doc.openTransaction("Bend")
372 | if activeBody is None or not smIsPartDesign(selobj):
373 | a = doc.addObject("Part::FeaturePython","Fold")
374 | SMFoldWall(a)
375 | SMFoldViewProvider(a.ViewObject)
376 | else:
377 | #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name)
378 | a = doc.addObject("PartDesign::FeaturePython","Fold")
379 | SMFoldWall(a)
380 | SMFoldPDViewProvider(a.ViewObject)
381 | activeBody.addObject(a)
382 | doc.recompute()
383 | doc.commitTransaction()
384 | return
385 |
386 | def IsActive(self):
387 | if len(Gui.Selection.getSelection()) < 2 :
388 | return False
389 | selFace = Gui.Selection.getSelectionEx()[0].SubObjects[0]
390 | if type(selFace) != Part.Face:
391 | return False
392 | selobj = Gui.Selection.getSelection()[1]
393 | if not(selobj.isDerivedFrom('Sketcher::SketchObject')) :
394 | return False
395 | return True
396 |
397 | Gui.addCommand('SMFoldWall',AddFoldWallCommandClass())
398 |
--------------------------------------------------------------------------------
/SketchOnSheetMetalCmd.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ##############################################################################
3 | #
4 | # SketchOnSheetMetalCmd.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 | from FreeCAD import Gui
27 | from PySide import QtCore, QtGui
28 |
29 | import FreeCAD, Part, os, math
30 | __dir__ = os.path.dirname(__file__)
31 | iconPath = os.path.join( __dir__, 'Resources', 'icons' )
32 | smEpsilon = 0.0000001
33 |
34 | import SheetMetalBendSolid
35 |
36 | def smWarnDialog(msg):
37 | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error in macro MessageBox', msg)
38 | diag.setWindowModality(QtCore.Qt.ApplicationModal)
39 | diag.exec_()
40 |
41 | def smBelongToBody(item, body):
42 | if (body is None):
43 | return False
44 | for obj in body.Group:
45 | if obj.Name == item.Name:
46 | return True
47 | return False
48 |
49 | def smIsPartDesign(obj):
50 | return str(obj).find(" Facelist[1].Area :
64 | selFace = Facelist[0]
65 | else :
66 | selFace = Facelist[1]
67 | elif type(sel_item) == Part.Face :
68 | selFace = sel_item
69 | return selFace
70 |
71 | def smthk(obj, foldface) :
72 | normal = foldface.normalAt(0,0)
73 | theVol = obj.Volume
74 | if theVol < 0.0001:
75 | SMError("Shape is not a real 3D-object or to small for a metal-sheet!")
76 | else:
77 | # Make a first estimate of the thickness
78 | estimated_thk = theVol/(obj.Area / 2.0)
79 | # p1 = foldface.CenterOfMass
80 | for v in foldface.Vertexes :
81 | p1 = v.Point
82 | p2 = p1 + estimated_thk * -1.5 * normal
83 | e1 = Part.makeLine(p1, p2)
84 | thkedge = obj.common(e1)
85 | thk = thkedge.Length
86 | if thk > smEpsilon :
87 | break
88 | return thk
89 |
90 | def smCutFace(Face, obj) :
91 | # find face Modified During loop
92 | for face in obj.Faces :
93 | face_common = face.common(Face)
94 | if face_common.Faces :
95 | break
96 | return face
97 |
98 | def smGetEdge(Face, obj) :
99 | # find face Modified During loop
100 | for edge in obj.Edges :
101 | face_common = edge.common(Face)
102 | if face_common.Edges :
103 | break
104 | return edge
105 |
106 | def equal_angle(ang1, ang2, p=5):
107 | # compares two angles
108 | result = False
109 | if round(ang1 - ang2, p)==0:
110 | result = True
111 | if round((ang1-2.0*math.pi) - ang2, p)==0:
112 | result = True
113 | if round(ang1 - (ang2-2.0*math.pi), p)==0:
114 | result = True
115 | return result
116 |
117 | def bendAngle(theFace, edge_vec) :
118 | #Start to investigate the angles at self.__Shape.Faces[face_idx].ParameterRange[0]
119 | #Part.show(theFace,"theFace")
120 | #valuelist = theFace.ParameterRange
121 | #print(valuelist)
122 | angle_0 = theFace.ParameterRange[0]
123 | angle_1 = theFace.ParameterRange[1]
124 |
125 | # idea: identify the angle at edge_vec = P_edge.Vertexes[0].copy().Point
126 | # This will be = angle_start
127 | # calculate the tan_vec from valueAt
128 | edgeAngle, edgePar = theFace.Surface.parameter(edge_vec)
129 | #print('the angles: ', angle_0, ' ', angle_1, ' ', edgeAngle, ' ', edgeAngle - 2*math.pi)
130 |
131 | if equal_angle(angle_0, edgeAngle):
132 | angle_start = angle_0
133 | angle_end = angle_1
134 | else:
135 | angle_start = angle_1
136 | angle_end = angle_0
137 |
138 | bend_angle = angle_end - angle_start
139 | # angle_tan = angle_start + bend_angle/6.0 # need to have the angle_tan before correcting the sign
140 |
141 | if bend_angle < 0.0:
142 | bend_angle = -bend_angle
143 |
144 | #print(math.degrees(bend_angle))
145 | return math.degrees(bend_angle)
146 |
147 | def smSketchOnSheetMetal(kfactor = 0.5, sketch = '', flipped = False, selFaceNames = '', MainObject = None):
148 | resultSolid = MainObject.Shape.copy()
149 | selElement = resultSolid.getElement(selFaceNames[0])
150 | LargeFace = smFace(selElement, resultSolid)
151 | sketch_face = Part.makeFace(sketch.Shape.Wires,"Part::FaceMakerBullseye")
152 |
153 | #To get thk of sheet, top face normal
154 | thk = smthk(resultSolid, LargeFace)
155 | #print(thk)
156 |
157 | #To get top face normal, flatsolid
158 | solidlist = []
159 | normal = LargeFace.normalAt(0,0)
160 | #To check face direction
161 | coeff = normal.dot(sketch_face.Faces[0].normalAt(0,0))
162 | if coeff < 0 :
163 | sketch_face.reverse()
164 | Flatface = sketch_face.common(LargeFace)
165 | BalanceFaces = sketch_face.cut(Flatface)
166 | #Part.show(BalanceFace,"BalanceFace")
167 | Flatsolid = Flatface.extrude(normal * -thk)
168 | #Part.show(Flatsolid,"Flatsolid")
169 | solidlist.append(Flatsolid)
170 |
171 | if BalanceFaces.Faces :
172 | for BalanceFace in BalanceFaces.Faces :
173 | #Part.show(BalanceFace,"BalanceFace")
174 | TopFace = LargeFace
175 | #Part.show(TopFace,"TopFace")
176 | #flipped = False
177 | while BalanceFace.Faces :
178 | BendEdge = smGetEdge(BalanceFace, TopFace)
179 | #Part.show(BendEdge,"BendEdge")
180 | facelist = resultSolid.ancestorsOfType(BendEdge, Part.Face)
181 |
182 | #To get bend radius, bend angle
183 | for cylface in facelist :
184 | if issubclass(type(cylface.Surface),Part.Cylinder) :
185 | break
186 | if not(issubclass(type(cylface.Surface),Part.Cylinder)) :
187 | break
188 | #Part.show(cylface,"cylface")
189 | for planeface in facelist :
190 | if issubclass(type(planeface.Surface),Part.Plane) :
191 | break
192 | #Part.show(planeface,"planeface")
193 | normal = planeface.normalAt(0,0)
194 | revAxisV = cylface.Surface.Axis
195 | revAxisP = cylface.Surface.Center
196 | bendA = bendAngle(cylface, revAxisP)
197 | #print([bendA, revAxisV, revAxisP, cylface.Orientation])
198 |
199 | #To check bend direction
200 | offsetface = cylface.makeOffsetShape(-thk, 0.0, fill = False)
201 | #Part.show(offsetface,"offsetface")
202 | if offsetface.Area < cylface.Area :
203 | bendR = cylface.Surface.Radius - thk
204 | flipped = True
205 | else :
206 | bendR = cylface.Surface.Radius
207 | flipped = False
208 |
209 | #To arrive unfold Length, neutralRadius
210 | unfoldLength = ( bendR + kfactor * thk ) * abs(bendA) * math.pi / 180.0
211 | neutralRadius = ( bendR + kfactor * thk )
212 | #print([unfoldLength,neutralRadius])
213 |
214 | #To get faceNormal, bend face
215 | faceNormal = normal.cross(revAxisV).normalize()
216 | #print(faceNormal)
217 | if bendR < cylface.Surface.Radius :
218 | offsetSolid = cylface.makeOffsetShape(bendR/2.0, 0.0, fill = True)
219 | else:
220 | offsetSolid = cylface.makeOffsetShape(-bendR/2.0, 0.0, fill = True)
221 | #Part.show(offsetSolid,"offsetSolid")
222 | tool = BendEdge.copy()
223 | FaceArea = tool.extrude(faceNormal * -unfoldLength )
224 | #Part.show(FaceArea,"FaceArea")
225 | #Part.show(BalanceFace,"BalanceFace")
226 | SolidFace = offsetSolid.common(FaceArea)
227 | #Part.show(BendSolidFace,"BendSolidFace")
228 | if not(SolidFace.Faces):
229 | faceNormal = faceNormal * -1
230 | FaceArea = tool.extrude(faceNormal * -unfoldLength )
231 | BendSolidFace = BalanceFace.common(FaceArea)
232 | #Part.show(FaceArea,"FaceArea")
233 | #Part.show(BendSolidFace,"BendSolidFace")
234 | #print([bendR, bendA, revAxisV, revAxisP, normal, flipped, BendSolidFace.Faces[0].normalAt(0,0)])
235 |
236 | bendsolid = SheetMetalBendSolid.BendSolid(BendSolidFace.Faces[0], BendEdge, bendR, thk, neutralRadius, revAxisV, flipped)
237 | #Part.show(bendsolid,"bendsolid")
238 | solidlist.append(bendsolid)
239 |
240 | if flipped == True:
241 | bendA = -bendA
242 | if not(SolidFace.Faces):
243 | revAxisV = revAxisV * -1
244 | sketch_face = BalanceFace.cut(BendSolidFace)
245 | sketch_face.translate(faceNormal * unfoldLength)
246 | #Part.show(sketch_face,"sketch_face")
247 | sketch_face.rotate(revAxisP, -revAxisV, bendA)
248 | #Part.show(sketch_face,"Rsketch_face")
249 | TopFace = smCutFace(sketch_face, resultSolid)
250 | #Part.show(TopFace,"TopFace")
251 |
252 | #To get top face normal, flatsolid
253 | normal = TopFace.normalAt(0,0)
254 | Flatface = sketch_face.common(TopFace)
255 | BalanceFace = sketch_face.cut(Flatface)
256 | #Part.show(BalanceFace,"BalanceFace")
257 | Flatsolid = Flatface.extrude(normal * -thk)
258 | #Part.show(Flatsolid,"Flatsolid")
259 | solidlist.append(Flatsolid)
260 |
261 | #To get relief Solid fused
262 | if len(solidlist) > 1 :
263 | SMSolid = solidlist[0].multiFuse(solidlist[1:])
264 | #Part.show(SMSolid,"SMSolid")
265 | SMSolid = SMSolid.removeSplitter()
266 | else :
267 | SMSolid = solidlist[0]
268 | #Part.show(SMSolid,"SMSolid")
269 | resultSolid = resultSolid.cut(SMSolid)
270 |
271 | Gui.ActiveDocument.getObject(MainObject.Name).Visibility = False
272 | Gui.ActiveDocument.getObject(sketch.Name).Visibility = False
273 | return resultSolid
274 |
275 | class SMSketchOnSheet:
276 | def __init__(self, obj):
277 | '''"Add Sketch based cut On Sheet metal" '''
278 | selobj = Gui.Selection.getSelectionEx()
279 |
280 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Base Object")
281 | obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters",_tip_).baseObject = (selobj[0].Object, selobj[0].SubElementNames)
282 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Sketch on Sheetmetal")
283 | obj.addProperty("App::PropertyLink","Sketch","Parameters",_tip_).Sketch = selobj[1].Object
284 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Gap from Left Side")
285 | obj.addProperty("App::PropertyFloatConstraint","kfactor","Parameters",_tip_).kfactor = (0.5,0.0,1.0,0.01)
286 | obj.Proxy = self
287 |
288 | def execute(self, fp):
289 | '''"Print a short message when doing a recomputation, this method is mandatory" '''
290 |
291 | s = smSketchOnSheetMetal(kfactor = fp.kfactor, sketch = fp.Sketch, selFaceNames = fp.baseObject[1], MainObject = fp.baseObject[0])
292 | fp.Shape = s
293 |
294 | class SMSketchOnSheetVP:
295 | "A View provider that nests children objects under the created one"
296 |
297 | def __init__(self, obj):
298 | obj.Proxy = self
299 | self.Object = obj.Object
300 |
301 | def attach(self, obj):
302 | self.Object = obj.Object
303 | return
304 |
305 | def updateData(self, fp, prop):
306 | return
307 |
308 | def getDisplayModes(self,obj):
309 | modes=[]
310 | return modes
311 |
312 | def setDisplayMode(self,mode):
313 | return mode
314 |
315 | def onChanged(self, vp, prop):
316 | return
317 |
318 | def __getstate__(self):
319 | # return {'ObjectName' : self.Object.Name}
320 | return None
321 |
322 | def __setstate__(self,state):
323 | if state is not None:
324 | import FreeCAD
325 | doc = FreeCAD.ActiveDocument #crap
326 | self.Object = doc.getObject(state['ObjectName'])
327 |
328 | def claimChildren(self):
329 | objs = []
330 | if hasattr(self.Object,"baseObject"):
331 | objs.append(self.Object.baseObject[0])
332 | objs.append(self.Object.Sketch)
333 | return objs
334 |
335 | def getIcon(self):
336 | return os.path.join( iconPath , 'SheetMetal_SketchOnSheet.svg')
337 |
338 | class SMSketchOnSheetPDVP:
339 | "A View provider that nests children objects under the created one"
340 |
341 | def __init__(self, obj):
342 | obj.Proxy = self
343 | self.Object = obj.Object
344 |
345 | def attach(self, obj):
346 | self.Object = obj.Object
347 | return
348 |
349 | def updateData(self, fp, prop):
350 | return
351 |
352 | def getDisplayModes(self,obj):
353 | modes=[]
354 | return modes
355 |
356 | def setDisplayMode(self,mode):
357 | return mode
358 |
359 | def onChanged(self, vp, prop):
360 | return
361 |
362 | def __getstate__(self):
363 | # return {'ObjectName' : self.Object.Name}
364 | return None
365 |
366 | def __setstate__(self,state):
367 | if state is not None:
368 | import FreeCAD
369 | doc = FreeCAD.ActiveDocument #crap
370 | self.Object = doc.getObject(state['ObjectName'])
371 |
372 | def claimChildren(self):
373 | objs = []
374 | if hasattr(self.Object,"Sketch"):
375 | objs.append(self.Object.Sketch)
376 | return objs
377 |
378 | def getIcon(self):
379 | return os.path.join( iconPath , 'SheetMetal_SketchOnSheet.svg')
380 |
381 | class AddSketchOnSheetCommandClass():
382 | """Add Sketch On Sheet metal command"""
383 |
384 | def GetResources(self):
385 | return {'Pixmap' : os.path.join( iconPath , 'SheetMetal_SketchOnSheet.svg'), # the name of a svg file available in the resources
386 | 'MenuText': QtCore.QT_TRANSLATE_NOOP('SheetMetal','Sketch On Sheet metal'),
387 | 'Accel': "M, S",
388 | 'ToolTip' : QtCore.QT_TRANSLATE_NOOP('SheetMetal',' Extruded cut from Sketch On Sheet metal faces\n'
389 | '1. Select a flat face on sheet metal and\n'
390 | '2. Select a sketch on same face to create sheetmetal extruded cut.\n'
391 | '3. Use Property editor to modify other parameters')}
392 |
393 | def Activated(self):
394 | doc = FreeCAD.ActiveDocument
395 | view = Gui.ActiveDocument.ActiveView
396 | activeBody = None
397 | selobj = Gui.Selection.getSelectionEx()[0].Object
398 | if hasattr(view,'getActiveObject'):
399 | activeBody = view.getActiveObject('pdbody')
400 | if not smIsOperationLegal(activeBody, selobj):
401 | return
402 | doc.openTransaction("SketchOnSheet")
403 | if activeBody is None or not smIsPartDesign(selobj):
404 | a = doc.addObject("Part::FeaturePython","SketchOnSheet")
405 | SMSketchOnSheet(a)
406 | SMSketchOnSheetVP(a.ViewObject)
407 | else:
408 | #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name)
409 | a = doc.addObject("PartDesign::FeaturePython","SketchOnSheet")
410 | SMSketchOnSheet(a)
411 | SMSketchOnSheetPDVP(a.ViewObject)
412 | activeBody.addObject(a)
413 | doc.recompute()
414 | doc.commitTransaction()
415 | return
416 |
417 | def IsActive(self):
418 | if len(Gui.Selection.getSelection()) < 2 :
419 | return False
420 | # selobj = Gui.Selection.getSelection()[1]
421 | # if str(type(selobj)) != "" :
422 | # return False
423 | return True
424 |
425 | Gui.addCommand('SMSketchOnSheet',AddSketchOnSheetCommandClass())
426 |
--------------------------------------------------------------------------------
/SheetMetalFormingCmd.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | ###################################################################################
3 | #
4 | # SheetMetalCmd.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 | from FreeCAD import Gui
27 | from PySide import QtCore, QtGui
28 |
29 | import FreeCAD, FreeCADGui, Part, os, math
30 | __dir__ = os.path.dirname(__file__)
31 | iconPath = os.path.join( __dir__, 'Resources', 'icons' )
32 | smEpsilon = 0.0000001
33 |
34 | def smWarnDialog(msg):
35 | diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error in macro MessageBox', msg)
36 | diag.setWindowModality(QtCore.Qt.ApplicationModal)
37 | diag.exec_()
38 |
39 | def smBelongToBody(item, body):
40 | if (body is None):
41 | return False
42 | for obj in body.Group:
43 | if obj.Name == item.Name:
44 | return True
45 | return False
46 |
47 | def smIsPartDesign(obj):
48 | return str(obj).find(" smEpsilon :
73 | break
74 | return thk
75 |
76 | def angleBetween(ve1, ve2):
77 | # Find angle between two vectors in degrees
78 | return math.degrees(ve1.getAngle(ve2))
79 |
80 | def face_direction(face):
81 | yL = face.CenterOfMass
82 | uv = face.Surface.parameter(yL)
83 | nv = face.normalAt(uv[0], uv[1])
84 | direction = yL.sub(nv + yL)
85 | #print([direction, yL])
86 | return direction, yL
87 |
88 | def transform_tool(tool, base_face, tool_face, point = FreeCAD.Vector(0, 0, 0), angle = 0.0):
89 | # Find normal of faces & center to align faces
90 | direction1,yL1 = face_direction(base_face)
91 | direction2,yL2 = face_direction(tool_face)
92 |
93 | # Find angle between faces, axis of rotation & center of axis
94 | rot_angle = angleBetween(direction1, direction2)
95 | rot_axis = direction1.cross(direction2)
96 | if rot_axis == FreeCAD.Vector (0.0, 0.0, 0.0):
97 | rot_axis = FreeCAD.Vector(0, 1, 0).cross(direction2)
98 | rot_center = yL2
99 | #print([rot_center, rot_axis, rot_angle])
100 | tool.rotate(rot_center, rot_axis, -rot_angle)
101 | tool.translate(-yL2 + yL1)
102 | #Part.show(tool, "tool")
103 |
104 | tool.rotate(yL1, direction1, angle)
105 | tool.translate(point)
106 | #Part.show(tool,"tool")
107 | return tool
108 |
109 | def makeforming(tool, base, base_face, thk, tool_faces = None, point = FreeCAD.Vector(0, 0, 0), angle = 0.0) :
110 | ## faces = [ face for face in tool.Shape.Faces for tool_face in tool_faces if not(face.isSame(tool_face)) ]
111 | # faces = [ face for face in tool.Shape.Faces if not face in tool_faces ]
112 | # tool_shell = Part.makeShell(faces)
113 | # offsetshell = tool_shell.makeOffsetShape(thk, 0.0, inter = False, self_inter = False, offsetMode = 0, join = 2, fill = True)
114 | offsetshell = tool.makeThickness(tool_faces, thk, 0.0001, False, False, 0, 0)
115 | cutSolid = tool.fuse(offsetshell)
116 | offsetshell_tran = transform_tool(offsetshell, base_face, tool_faces[0], point, angle)
117 | #Part.show(offsetshell1, "offsetshell1")
118 | cutSolid_trans = transform_tool(cutSolid, base_face, tool_faces[0], point, angle)
119 | base = base.cut(cutSolid_trans)
120 | base = base.fuse(offsetshell_tran)
121 | #base.removeSplitter()
122 | #Part.show(base, "base")
123 | return base
124 |
125 | class SMBendWall:
126 | def __init__(self, obj):
127 | '''"Add Forming Wall" '''
128 | selobj = Gui.Selection.getSelectionEx()
129 |
130 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Offset from Center of Face")
131 | obj.addProperty("App::PropertyVectorDistance","offset","Parameters",_tip_)
132 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Suppress Forming Feature")
133 | obj.addProperty("App::PropertyBool","SuppressFeature","Parameters",_tip_).SuppressFeature = False
134 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Tool Position Angle")
135 | obj.addProperty("App::PropertyAngle","angle","Parameters",_tip_).angle = 0.0
136 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Thickness of Sheetmetal")
137 | obj.addProperty("App::PropertyDistance","thickness","Parameters",_tip_)
138 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Base Object")
139 | obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters",_tip_).baseObject = (selobj[0].Object, selobj[0].SubElementNames)
140 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Forming Tool Object")
141 | obj.addProperty("App::PropertyLinkSub", "toolObject", "Parameters",_tip_).toolObject = (selobj[1].Object, selobj[1].SubElementNames)
142 | _tip_ = QtCore.QT_TRANSLATE_NOOP("App::Property","Point Sketch on Sheetmetal")
143 | obj.addProperty("App::PropertyLink","Sketch","Parameters1",_tip_)
144 | obj.Proxy = self
145 |
146 | def execute(self, fp):
147 | '''"Print a short message when doing a recomputation, this method is mandatory" '''
148 |
149 | base = fp.baseObject[0].Shape
150 | base_face = base.getElement(fp.baseObject[1][0])
151 | thk = smthk(base, base_face)
152 | fp.thickness = thk
153 | tool = fp.toolObject[0].Shape
154 | tool_faces = [tool.getElement(fp.toolObject[1][i]) for i in range(len(fp.toolObject[1]))]
155 |
156 | offsetlist = []
157 | if fp.Sketch:
158 | sketch = fp.Sketch.Shape
159 | for e in sketch.Edges:
160 | #print(type(e.Curve))
161 | if isinstance(e.Curve, (Part.Circle, Part.ArcOfCircle)):
162 | pt1 = base_face.CenterOfMass
163 | pt2 = e.Curve.Center
164 | offsetPoint = pt2 - pt1
165 | #print(offsetPoint)
166 | offsetlist.append(offsetPoint)
167 | else:
168 | offsetlist.append(fp.offset)
169 |
170 | if not(fp.SuppressFeature) :
171 | for i in range(len(offsetlist)):
172 | a = makeforming(tool, base, base_face, thk, tool_faces, offsetlist[i], fp.angle.Value)
173 | base = a
174 | else :
175 | a = base
176 | fp.Shape = a
177 | Gui.ActiveDocument.getObject(fp.baseObject[0].Name).Visibility = False
178 | Gui.ActiveDocument.getObject(fp.toolObject[0].Name).Visibility = False
179 | if fp.Sketch:
180 | Gui.ActiveDocument.getObject(fp.Sketch.Name).Visibility = False
181 |
182 | class SMFormingVP:
183 | "A View provider that nests children objects under the created one"
184 |
185 | def __init__(self, obj):
186 | obj.Proxy = self
187 | self.Object = obj.Object
188 |
189 | def attach(self, obj):
190 | self.Object = obj.Object
191 | return
192 |
193 | def setupContextMenu(self, viewObject, menu):
194 | action = menu.addAction(FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label))
195 | action.triggered.connect(lambda: self.startDefaultEditMode(viewObject))
196 | return False
197 |
198 | def startDefaultEditMode(self, viewObject):
199 | document = viewObject.Document.Document
200 | if not document.HasPendingTransaction:
201 | text = FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label)
202 | document.openTransaction(text)
203 | viewObject.Document.setEdit(viewObject.Object, 0)
204 |
205 | def updateData(self, fp, prop):
206 | return
207 |
208 | def getDisplayModes(self,obj):
209 | modes=[]
210 | return modes
211 |
212 | def setDisplayMode(self,mode):
213 | return mode
214 |
215 | def onChanged(self, vp, prop):
216 | return
217 |
218 | def __getstate__(self):
219 | # return {'ObjectName' : self.Object.Name}
220 | return None
221 |
222 | def __setstate__(self,state):
223 | if state is not None:
224 | import FreeCAD
225 | doc = FreeCAD.ActiveDocument #crap
226 | self.Object = doc.getObject(state['ObjectName'])
227 |
228 | def claimChildren(self):
229 | objs = []
230 | if hasattr(self.Object,"baseObject"):
231 | objs.append(self.Object.baseObject[0])
232 | if hasattr(self.Object,"toolObject"):
233 | objs.append(self.Object.toolObject[0])
234 | if hasattr(self.Object,"Sketch"):
235 | objs.append(self.Object.Sketch)
236 | return objs
237 |
238 | def getIcon(self):
239 | return os.path.join( iconPath , 'SheetMetal_Forming.svg')
240 |
241 | def setEdit(self,vobj,mode):
242 | taskd = SMFormingWallTaskPanel()
243 | taskd.obj = vobj.Object
244 | taskd.update()
245 | self.Object.ViewObject.Visibility=False
246 | self.Object.baseObject[0].ViewObject.Visibility=True
247 | self.Object.toolObject[0].ViewObject.Visibility=True
248 | FreeCADGui.Control.showDialog(taskd)
249 | return True
250 |
251 | def unsetEdit(self,vobj,mode):
252 | FreeCADGui.Control.closeDialog()
253 | self.Object.baseObject[0].ViewObject.Visibility=False
254 | self.Object.toolObject[0].ViewObject.Visibility=False
255 | self.Object.ViewObject.Visibility=True
256 | return False
257 |
258 | class SMFormingPDVP:
259 | "A View provider that nests children objects under the created one"
260 |
261 | def __init__(self, obj):
262 | obj.Proxy = self
263 | self.Object = obj.Object
264 |
265 | def attach(self, obj):
266 | self.Object = obj.Object
267 | return
268 |
269 | def updateData(self, fp, prop):
270 | return
271 |
272 | def getDisplayModes(self,obj):
273 | modes=[]
274 | return modes
275 |
276 | def setDisplayMode(self,mode):
277 | return mode
278 |
279 | def onChanged(self, vp, prop):
280 | return
281 |
282 | def __getstate__(self):
283 | # return {'ObjectName' : self.Object.Name}
284 | return None
285 |
286 | def __setstate__(self,state):
287 | if state is not None:
288 | import FreeCAD
289 | doc = FreeCAD.ActiveDocument #crap
290 | self.Object = doc.getObject(state['ObjectName'])
291 |
292 | def claimChildren(self):
293 | objs = []
294 | if hasattr(self.Object,"toolObject"):
295 | objs.append(self.Object.toolObject[0])
296 | if hasattr(self.Object,"Sketch"):
297 | objs.append(self.Object.Sketch)
298 | return objs
299 |
300 | def getIcon(self):
301 | return os.path.join( iconPath , 'SheetMetal_Forming.svg')
302 |
303 | def setEdit(self,vobj,mode):
304 | taskd = SMFormingWallTaskPanel()
305 | taskd.obj = vobj.Object
306 | taskd.update()
307 | self.Object.ViewObject.Visibility=False
308 | self.Object.baseObject[0].ViewObject.Visibility=True
309 | self.Object.toolObject[0].ViewObject.Visibility=False
310 | FreeCADGui.Control.showDialog(taskd)
311 | return True
312 |
313 | def unsetEdit(self,vobj,mode):
314 | FreeCADGui.Control.closeDialog()
315 | self.Object.baseObject[0].ViewObject.Visibility=False
316 | self.Object.toolObject[0].ViewObject.Visibility=False
317 | self.Object.ViewObject.Visibility=True
318 | return False
319 |
320 | class SMFormingWallTaskPanel:
321 | '''A TaskPanel for the Sheetmetal'''
322 | def __init__(self):
323 |
324 | self.obj = None
325 | self.form = QtGui.QWidget()
326 | self.form.setObjectName("SMBendWallTaskPanel")
327 | self.form.setWindowTitle("Binded faces/edges list")
328 | self.grid = QtGui.QGridLayout(self.form)
329 | self.grid.setObjectName("grid")
330 | self.title = QtGui.QLabel(self.form)
331 | self.grid.addWidget(self.title, 0, 0, 1, 2)
332 | self.title.setText("Select new face(s)/Edge(s) and press Update")
333 |
334 | # tree
335 | self.tree = QtGui.QTreeWidget(self.form)
336 | self.grid.addWidget(self.tree, 1, 0, 1, 2)
337 | self.tree.setColumnCount(2)
338 | self.tree.setHeaderLabels(["Name","Subelement"])
339 |
340 | # buttons
341 | self.addButton = QtGui.QPushButton(self.form)
342 | self.addButton.setObjectName("addButton")
343 | self.addButton.setIcon(QtGui.QIcon(os.path.join( iconPath , 'SheetMetal_Update.svg')))
344 | self.grid.addWidget(self.addButton, 3, 0, 1, 2)
345 |
346 | QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.updateElement)
347 | self.update()
348 |
349 | def isAllowedAlterSelection(self):
350 | return True
351 |
352 | def isAllowedAlterView(self):
353 | return True
354 |
355 | def getStandardButtons(self):
356 | return int(QtGui.QDialogButtonBox.Ok)
357 |
358 | def update(self):
359 | 'fills the treewidget'
360 | self.tree.clear()
361 | if self.obj:
362 | f = self.obj.baseObject
363 | if isinstance(f[1],list):
364 | for subf in f[1]:
365 | #FreeCAD.Console.PrintLog("item: " + subf + "\n")
366 | item = QtGui.QTreeWidgetItem(self.tree)
367 | item.setText(0,f[0].Name)
368 | item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg"))
369 | item.setText(1,subf)
370 | else:
371 | item = QtGui.QTreeWidgetItem(self.tree)
372 | item.setText(0,f[0].Name)
373 | item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg"))
374 | item.setText(1,f[1][0])
375 |
376 | f = self.obj.toolObject
377 | if isinstance(f[1],list):
378 | for subf in f[1]:
379 | #FreeCAD.Console.PrintLog("item: " + subf + "\n")
380 | item = QtGui.QTreeWidgetItem(self.tree)
381 | item.setText(0,f[0].Name)
382 | item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg"))
383 | item.setText(1,subf)
384 | else:
385 | item = QtGui.QTreeWidgetItem(self.tree)
386 | item.setText(0,f[0].Name)
387 | item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg"))
388 | item.setText(1,f[1][0])
389 | self.retranslateUi(self.form)
390 |
391 | def updateElement(self):
392 | if self.obj:
393 | sel = FreeCADGui.Selection.getSelectionEx()[0]
394 | if sel.HasSubObjects:
395 | obj = sel.Object
396 | for elt in sel.SubElementNames:
397 | if "Face" in elt:
398 | face = self.obj.baseObject
399 | found = False
400 | if (face[0] == obj.Name):
401 | if isinstance(face[1],tuple):
402 | for subf in face[1]:
403 | if subf == elt:
404 | found = True
405 | else:
406 | if (face[1][0] == elt):
407 | found = True
408 | if not found:
409 | self.obj.baseObject = (sel.Object, sel.SubElementNames)
410 |
411 | sel = FreeCADGui.Selection.getSelectionEx()[1]
412 | if sel.HasSubObjects:
413 | obj = sel.Object
414 | for elt in sel.SubElementNames:
415 | if "Face" in elt:
416 | face = self.obj.toolObject
417 | found = False
418 | if (face[0] == obj.Name):
419 | if isinstance(face[1],tuple):
420 | for subf in face[1]:
421 | if subf == elt:
422 | found = True
423 | else:
424 | if (face[1][0] == elt):
425 | found = True
426 | if not found:
427 | self.obj.toolObject = (sel.Object, sel.SubElementNames)
428 | self.update()
429 |
430 | def accept(self):
431 | FreeCAD.ActiveDocument.recompute()
432 | FreeCADGui.ActiveDocument.resetEdit()
433 | #self.obj.ViewObject.Visibility=True
434 | return True
435 |
436 | def retranslateUi(self, TaskPanel):
437 | #TaskPanel.setWindowTitle(QtGui.QApplication.translate("draft", "Faces", None))
438 | self.addButton.setText(QtGui.QApplication.translate("draft", "Update", None))
439 |
440 |
441 | class AddFormingWallCommand():
442 | """Add Forming Wall command"""
443 |
444 | def GetResources(self):
445 | return {'Pixmap' : os.path.join( iconPath , 'SheetMetal_Forming.svg') , # the name of a svg file available in the resources
446 | 'MenuText': QtCore.QT_TRANSLATE_NOOP('SheetMetal','Make Forming in Wall') ,
447 | 'Accel': "M, F",
448 | 'ToolTip' : QtCore.QT_TRANSLATE_NOOP('SheetMetal','Make a forming using tool in metal sheet\n'
449 | '1. Select a flat face on sheet metal and\n'
450 | '2. Select face(s) on forming tool Shape to create Formed sheetmetal.\n'
451 | '3. Use Suppress in Property editor to disable during unfolding\n'
452 | '4. Use Property editor to modify other parameters')}
453 |
454 | def Activated(self):
455 | doc = FreeCAD.ActiveDocument
456 | view = Gui.ActiveDocument.ActiveView
457 | activeBody = None
458 | selobj = Gui.Selection.getSelectionEx()[0].Object
459 | if hasattr(view,'getActiveObject'):
460 | activeBody = view.getActiveObject('pdbody')
461 | if not smIsOperationLegal(activeBody, selobj):
462 | return
463 | doc.openTransaction("WallForming")
464 | if activeBody is None or not smIsPartDesign(selobj):
465 | a = doc.addObject("Part::FeaturePython","WallForming")
466 | SMBendWall(a)
467 | SMFormingVP(a.ViewObject)
468 | else:
469 | #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name)
470 | a = doc.addObject("PartDesign::FeaturePython","WallForming")
471 | SMBendWall(a)
472 | SMFormingPDVP(a.ViewObject)
473 | activeBody.addObject(a)
474 | doc.recompute()
475 | doc.commitTransaction()
476 | return
477 |
478 | def IsActive(self):
479 | if len(Gui.Selection.getSelection()) < 2 or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1:
480 | return False
481 | selobj = Gui.Selection.getSelection()[0]
482 | if str(type(selobj)) == "":
483 | return False
484 | for selFace in Gui.Selection.getSelectionEx()[0].SubObjects:
485 | if type(selFace) != Part.Face :
486 | return False
487 | return True
488 |
489 | Gui.addCommand('SMFormingWall', AddFormingWallCommand())
490 |
491 |
--------------------------------------------------------------------------------