diff --git a/InitGui.py b/InitGui.py
index a208b70..c3961c4 100644
--- a/InitGui.py
+++ b/InitGui.py
@@ -78,6 +78,7 @@ def Initialize(self):
"SheetMetal_NewSketch",
"SheetMetal_AddBase",
"SheetMetal_AddWall",
+ "SheetMetal_AddHem",
"SheetMetal_Extrude",
"SheetMetal_ExtendBySketch",
"SheetMetal_AddFoldWall",
diff --git a/Resources/SheetMetal.qrc b/Resources/SheetMetal.qrc
index 1b20b46..e619021 100644
--- a/Resources/SheetMetal.qrc
+++ b/Resources/SheetMetal.qrc
@@ -7,6 +7,7 @@
icons/SheetMetal_AddJunction.svg
icons/SheetMetal_AddRelief.svg
icons/SheetMetal_AddWall.svg
+ icons/SheetMetal_AddHem.svg
icons/SheetMetal_Extrude.svg
icons/SheetMetal_Forming.svg
icons/SheetMetal_SketchOnSheet.svg
diff --git a/Resources/icons/SheetMetal_AddHem.svg b/Resources/icons/SheetMetal_AddHem.svg
new file mode 100644
index 0000000..16f71bb
--- /dev/null
+++ b/Resources/icons/SheetMetal_AddHem.svg
@@ -0,0 +1,131 @@
+
+
+
+
diff --git a/SheetMetalCmd.py b/SheetMetalCmd.py
index e9d05c5..d785c7b 100644
--- a/SheetMetalCmd.py
+++ b/SheetMetalCmd.py
@@ -40,6 +40,8 @@
# List of properties to be saved as defaults.
smAddWallDefaultVars = ["BendType", "LengthSpec", ("angle", "defaultAngle"),
("radius", "defaultRadius"), "AutoMiter", ("kfactor", "defaultKFactor")]
+smAddHemDefaultVars = ["HemType", ("width", "defaultWidth"),
+ ("radius", "defaultRadius"), "AutoMiter", ("kfactor", "defaultKFactor")]
translate = FreeCAD.Qt.translate
@@ -2055,6 +2057,299 @@ def execute(self, fp):
fp.Shape = s
+class SMHem:
+ def __init__(self, obj, selobj, sel_items, refAngOffset=None, checkRefFace=False):
+ """Add Hem on an edge."""
+ self.addVerifyProperties(obj, refAngOffset, checkRefFace)
+
+ _tip_ = translate("App::Property", "Base Object")
+ obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters", _tip_
+ ).baseObject = (selobj, sel_items)
+ obj.Proxy = self
+ SheetMetalTools.taskRestoreDefaults(obj, smAddHemDefaultVars)
+
+ def addVerifyProperties(self, obj, refAngOffset=None, checkRefFace=False):
+ SheetMetalTools.smAddEnumProperty(obj,
+ "HemType",
+ translate("App::Property", "Hem Type"),
+ ["Flat", "Teardrop", "Rolled"])
+ SheetMetalTools.smAddLengthProperty(obj,
+ "radius",
+ translate("App::Property", "Bend Radius"),
+ 1.0)
+ SheetMetalTools.smAddLengthProperty(obj,
+ "width",
+ translate("App::Property", "Width of Hem"),
+ 10.0)
+ SheetMetalTools.smAddLengthProperty(obj,
+ "clearance",
+ translate("App::Property", "Clearance of Hem"),
+ 0.0)
+ SheetMetalTools.smAddDistanceProperty(obj,
+ "gap1",
+ translate("App::Property", "Gap from Left Side"),
+ 0.0)
+ SheetMetalTools.smAddDistanceProperty(obj,
+ "gap2",
+ translate("App::Property", "Gap from Right Side"),
+ 0.0)
+ SheetMetalTools.smAddBoolProperty(obj,
+ "invert",
+ translate("App::Property", "Invert Bend Direction"),
+ False)
+ SheetMetalTools.smAddDistanceProperty(obj,
+ "extend1",
+ translate("App::Property", "Extend from Left Side"),
+ 0.0)
+ SheetMetalTools.smAddDistanceProperty(obj,
+ "extend2",
+ translate("App::Property", "Extend from Right Side"),
+ 0.0)
+ SheetMetalTools.smAddEnumProperty(obj,
+ "BendType",
+ translate("App::Property", "Bend Type"),
+ ["Material Outside", "Material Inside", "Thickness Outside", "Offset"])
+ SheetMetalTools.smAddLengthProperty(obj,
+ "reliefw",
+ translate("App::Property", "Relief Width"),
+ 0.8,
+ "ParametersRelief")
+ SheetMetalTools.smAddLengthProperty(obj,
+ "reliefd",
+ translate("App::Property", "Relief Depth"),
+ 1.0,
+ "ParametersRelief")
+ SheetMetalTools.smAddBoolProperty(obj,
+ "UseReliefFactor",
+ translate("App::Property", "Use Relief Factor"),
+ False,
+ "ParametersRelief")
+ SheetMetalTools.smAddEnumProperty(obj,
+ "reliefType",
+ translate("App::Property", "Relief Type"),
+ ["Rectangle", "Round"],
+ None,
+ "ParametersRelief")
+ SheetMetalTools.smAddFloatProperty(obj,
+ "ReliefFactor",
+ translate("App::Property", "Relief Factor"),
+ 0.7,
+ "ParametersRelief")
+ SheetMetalTools.smAddAngleProperty(obj,
+ "miterangle1",
+ translate("App::Property", "Bend Miter Angle from Left Side"),
+ 0.0,
+ "ParametersMiterangle")
+ SheetMetalTools.smAddAngleProperty(obj,
+ "miterangle2",
+ translate("App::Property", "Bend Miter Angle from Right Side"),
+ 0.0,
+ "ParametersMiterangle")
+ SheetMetalTools.smAddLengthProperty(obj,
+ "minGap",
+ translate("App::Property", "Auto Miter Minimum Gap"),
+ 0.2,
+ "ParametersEx")
+ SheetMetalTools.smAddLengthProperty(obj,
+ "maxExtendDist",
+ translate("App::Property", "Auto Miter maximum Extend Distance"),
+ 5.0,
+ "ParametersEx")
+ SheetMetalTools.smAddLengthProperty(obj,
+ "minReliefGap",
+ translate("App::Property", "Minimum Gap to Relief Cut"),
+ 1.0,
+ "ParametersEx")
+ SheetMetalTools.smAddBoolProperty(obj,
+ "AutoMiter",
+ translate("App::Property", "Enable Auto Miter"),
+ True,
+ "ParametersEx")
+ SheetMetalTools.smAddBoolProperty(obj,
+ "unfold",
+ translate("App::Property", "Shows Unfold View of Current Bend"),
+ False,
+ "ParametersEx")
+ SheetMetalTools.smAddProperty(obj,
+ "App::PropertyFloatConstraint",
+ "kfactor",
+ translate("App::Property",
+ "Location of Neutral Line. Caution: Using ANSI standards, not DIN."),
+ (0.5, 0.0, 1.0, 0.01),
+ "ParametersEx")
+ SheetMetalTools.smAddBoolProperty(obj,
+ "sketchflip",
+ translate("App::Property", "Flip Sketch Direction"),
+ False,
+ "ParametersEx2")
+ SheetMetalTools.smAddBoolProperty(obj,
+ "sketchinvert",
+ translate("App::Property", "Invert Sketch Start"),
+ False,
+ "ParametersEx2")
+ SheetMetalTools.smAddProperty(obj,
+ "App::PropertyFloatList",
+ "LegLengthList",
+ translate("App::Property", "Leg Lenghts List"),
+ None,
+ "ParametersEx3")
+ SheetMetalTools.smAddBoolProperty(obj,
+ "Perforate",
+ FreeCAD.Qt.translate("App::Property", "Enable Perforation"),
+ False,
+ "ParametersPerforation")
+ SheetMetalTools.smAddAngleProperty(obj,
+ "PerforationAngle",
+ FreeCAD.Qt.translate("App::Property", "Perforation Angle"),
+ 0.0,
+ "ParametersPerforation")
+ SheetMetalTools.smAddLengthProperty(obj,
+ "PerforationInitialLength",
+ FreeCAD.Qt.translate("App::Property", "Initial Perforation Length"),
+ 5.0,
+ "ParametersPerforation")
+ SheetMetalTools.smAddLengthProperty(obj,
+ "PerforationMaxLength",
+ FreeCAD.Qt.translate("App::Property", "Perforation Max Length"),
+ 5.0,
+ "ParametersPerforation")
+ SheetMetalTools.smAddLengthProperty(obj,
+ "NonperforationMaxLength",
+ FreeCAD.Qt.translate("App::Property", "Non-Perforation Max Length"),
+ 5.0,
+ "ParametersPerforation")
+ SheetMetalTools.smAddProperty(obj,
+ "App::PropertyLinkSub",
+ "OffsetFaceReference",
+ "Face reference for offset",
+ refAngOffset,
+ "ParametersEx")
+ SheetMetalTools.smAddEnumProperty(obj,
+ "OffsetType",
+ "Offset Type",
+ ["Material Outside", "Material Inside", "Thickness Outside", "Offset"],
+ "Material Inside",
+ "ParametersEx")
+ SheetMetalTools.smAddDistanceProperty(obj,
+ "OffsetTypeOffset",
+ "Works when offset face reference is on. It offsets by "
+ "a normal distance from the offsets reference face.",
+ 0.0,
+ "ParametersEx")
+ SheetMetalTools.smAddBoolProperty(obj,
+ "SupplAngleRef",
+ "Supplementary angle reference",
+ False,
+ "ParametersEx")
+
+ def getElementMapVersion(self, _fp, ver, _prop, restored):
+ if not restored:
+ return smElementMapVersion + ver
+ return None
+
+ def execute(self, fp):
+ """Print a short message when doing a recomputation.
+
+ Note:
+ This method is mandatory.
+
+ """
+ self.addVerifyProperties(fp)
+
+ bendAList = [fp.width.Value]
+ LegLengthList = [fp.width.Value]
+ bendR = fp.radius.Value
+ allowedAutoMiter = fp.AutoMiter
+
+ # Restrict some params.
+ fp.miterangle1.Value = smRestrict(fp.miterangle1.Value, -80.0, 80.0)
+ fp.miterangle2.Value = smRestrict(fp.miterangle2.Value, -80.0, 80.0)
+
+ # Pass selected object shape.
+ Main_Object = fp.baseObject[0].Shape.copy()
+ face = fp.baseObject[1]
+ thk, thkDir = sheet_thk(Main_Object, face[0])
+
+ fp.LegLengthList = LegLengthList
+ # print(LegLengthList)
+
+ # Extend value needed for first bend set only.
+ extend1_list = [0.0 for n in LegLengthList]
+ extend2_list = [0.0 for n in LegLengthList]
+ extend1_list[0] = fp.extend1.Value
+ extend2_list[0] = fp.extend2.Value
+ # print(extend1_list, extend2_list)
+
+ # Gap value needed for first bend set only.
+ gap1_list = [0.0 for n in LegLengthList]
+ gap2_list = [0.0 for n in LegLengthList]
+ gap1_list[0] = fp.gap1.Value
+ gap2_list[0] = fp.gap2.Value
+ # print(gap1_list, gap2_list)
+
+ # Compute geometrical parameters for selected hem type
+ for i, _ in enumerate(LegLengthList):
+ if fp.HemType == "Flat": # has a width and clearance, and bend radius (if clearance)
+ bendAList[i] = 180.0
+ bendR = fp.clearance.Value/2.0 # still 0 if no clearance
+ elif fp.HemType == "Teardrop": # has a width, clearance, and bend radius
+ if fp.clearance.Value == 0.0:
+ theta = math.atan(fp.radius.Value/fp.width.Value)
+ bendAList[i] = 180.0 + 2*math.degrees(theta)
+ LegLengthList[i] = fp.width.Value
+ else:
+ w = fp.width.Value
+ c = fp.clearance.Value
+ theta = math.atan((w-math.sqrt(w**2-2.0*bendR*c+c**2))/c)
+ bendAList[i] = 180.0 + math.degrees(2*theta)
+ LegLengthList[i] = c*(math.cos(2*theta)-1)/math.sin(2*theta)+w
+ elif fp.HemType == "Rolled": # has bend radius, no clearance, and no width
+ bendAList[i] = 270.0 + math.degrees(math.asin(bendR/(bendR+thk)))
+ LegLengthList[i] = 0.0
+ allowedAutoMiter = False
+
+ for i, LegLength in enumerate(LegLengthList):
+ s, f = smBend(
+ thk,
+ bendR=bendR,
+ bendA=bendAList[i],
+ miterA1=fp.miterangle1.Value,
+ miterA2=fp.miterangle2.Value,
+ BendType=fp.BendType,
+ flipped=fp.invert,
+ unfold=fp.unfold,
+ extLen=LegLength,
+ reliefType=fp.reliefType,
+ gap1=gap1_list[i],
+ gap2=gap2_list[i],
+ reliefW=fp.reliefw.Value,
+ reliefD=fp.reliefd.Value,
+ minReliefgap=fp.minReliefGap.Value,
+ extend1=extend1_list[i],
+ extend2=extend2_list[i],
+ kfactor=fp.kfactor,
+ #offset=offsetValue,
+ ReliefFactor=fp.ReliefFactor,
+ UseReliefFactor=fp.UseReliefFactor,
+ automiter=allowedAutoMiter,
+ selFaceNames=face,
+ MainObject=Main_Object,
+ #sketch=fp.Sketch,
+ mingap=fp.minGap.Value,
+ maxExtendGap=fp.maxExtendDist.Value,
+ #LengthSpec=fp.LengthSpec,
+ Perforate=fp.Perforate,
+ PerforationAngle=fp.PerforationAngle.Value,
+ PerforationInitialLength=fp.PerforationInitialLength.Value,
+ PerforationMaxLength=fp.PerforationMaxLength.Value,
+ NonperforationMaxLength=fp.NonperforationMaxLength.Value)
+ faces = smGetFace(f, s)
+ face = faces
+ Main_Object = s
+
+ fp.Shape = s
+
+
###################################################################################################
# Gui code
###################################################################################################
@@ -2365,6 +2660,137 @@ def IsActive(self):
if not False in geomTest and geomTest is not None:
return True
return None
+
+
+ class AddHemCommandClass:
+ """Add Hem command."""
+
+ def GetResources(self):
+ return {
+ # The name of a svg file available in the resources.
+ "Pixmap": os.path.join(icons_path, "SheetMetal_AddHem.svg"),
+ "MenuText": FreeCAD.Qt.translate("SheetMetal", "Make Hem"),
+ "Accel": "Z",
+ "ToolTip": FreeCAD.Qt.translate(
+ "SheetMetal",
+ "Creat hems on edges.\n"
+ "1. Select edges to create bends with walls.\n"
+ "2. Use Property editor to modify other parameters",
+ ),
+ }
+
+ def Activated(self):
+ doc = FreeCAD.ActiveDocument
+ view = Gui.ActiveDocument.ActiveView
+ activeBody = None
+
+ # Get the sheet metal object.
+ try:
+ for obj in Gui.Selection.getSelectionEx():
+ if not "Plane" in obj.Object.TypeId:
+ for subElem in obj.SubElementNames:
+ if type(obj.Object.Shape.getElement(subElem)) == Part.Edge:
+ sel = obj
+ break
+ selobj = sel.Object
+ except:
+ raise Exception("At least one edge must be selected to create a hem.")
+
+ selSubNames = list(sel.SubElementNames)
+ selSubObjs = sel.SubObjects
+
+ # Remove faces for wall creation reference because only
+ # edges should be used as reference to create hems.
+ for subObjName in selSubNames:
+ if type(selobj.Shape.getElement(subObjName)) == Part.Face:
+ selSubNames.remove(subObjName)
+ if len(selSubNames) < 1:
+ raise Exception("At least one edge must be selected to create a hem.")
+
+ # Get only one selected face to use for reference to angle
+ # and offset.
+ faceCount = 0
+ refAngOffset = None
+ checkRefFace = False
+ for obj in Gui.Selection.getSelectionEx():
+ for subObj in obj.SubObjects:
+ if type(subObj) == Part.Face and not "Plane" in obj.Object.TypeId:
+ faceCount += 1
+ if faceCount == 1:
+ for subObjName in obj.SubElementNames:
+ if obj.Object.Shape.getElement(subObjName).isEqual(subObj):
+ refAngOffset = [obj.Object, subObjName]
+ checkRefFace = True
+ else:
+ print("If more than one face is selected, "
+ "only the first is used for "
+ "reference to angle and offset.")
+ if "Plane" in obj.Object.TypeId and faceCount == 0:
+ if obj.Object.TypeId == "App::Plane":
+ refAngOffset = [obj.Object, ""]
+ else:
+ refAngOffset = [obj.Object, obj.SubElementNames[0]]
+ checkRefFace = True
+
+ viewConf = SheetMetalTools.GetViewConfig(selobj)
+ if hasattr(view, "getActiveObject"):
+ activeBody = view.getActiveObject("pdbody")
+ if not SheetMetalTools.smIsOperationLegal(activeBody, selobj):
+ return
+ doc.openTransaction("Hem")
+ if activeBody is None or not SheetMetalTools.smIsPartDesign(selobj):
+ newObj = doc.addObject("Part::FeaturePython", "Hem")
+ SMHem(newObj, selobj, selSubNames, refAngOffset, checkRefFace)
+ SMViewProviderTree(newObj.ViewObject)
+ else:
+ # FreeCAD.Console.PrintLog("found active body: " + activeBody.Name)
+ newObj = doc.addObject("PartDesign::FeaturePython", "Hem")
+ SMHem(newObj, selobj, selSubNames, refAngOffset, checkRefFace)
+ SMViewProviderFlat(newObj.ViewObject)
+ activeBody.addObject(newObj)
+ SheetMetalTools.SetViewConfig(newObj, viewConf)
+ Gui.Selection.clearSelection()
+ if SheetMetalTools.is_autolink_enabled():
+ root = SheetMetalTools.getOriginalBendObject(newObj)
+ if root:
+ if hasattr(root, "Radius"):
+ newObj.setExpression("radius", root.Label + ".Radius")
+ elif hasattr(root, "radius"):
+ newObj.setExpression("radius", root.Label + ".radius")
+ newObj.baseObject[0].ViewObject.Visibility = False
+ doc.recompute()
+ # 'checkRefFace' turn bendtype to 'Offset' when face
+ # reference is before the command.
+ #dialog = SMHemTaskPanel(newObj, checkRefFace)
+ #SheetMetalTools.updateTaskTitleIcon(dialog)
+ #Gui.Control.showDialog(dialog)
+ return
+
+ def IsActive(self):
+ selobj = Gui.Selection.getSelectionEx()
+ objSM = None
+ # In this iteration, we will find which selected object is
+ # the sheet metal part, 'cause the user can select a face
+ # for reference (this object will not be the sheet metal
+ # part).
+ for obj in selobj:
+ for subObj in obj.SubObjects:
+ if type(subObj) == Part.Edge:
+ objSM = obj
+
+ # Test if any selected subObject in the sheet metal
+ # isn't edge.
+ geomTest = []
+ for subObj in objSM.SubObjects:
+ if type(subObj) == Part.Edge:
+ geomTest.append(True)
+ else:
+ geomTest.append(False)
+
+ if not False in geomTest and geomTest is not None:
+ return True
+ return None
Gui.addCommand("SheetMetal_AddWall", AddWallCommandClass())
+ Gui.addCommand("SheetMetal_AddHem", AddHemCommandClass())