diff --git a/Init.py b/Init.py deleted file mode 100644 index 04b92cb..0000000 --- a/Init.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Init.py -# -# Copyright 2015 Shai Seger -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# -############################################################################## - -print("Sheet Metal workbench loaded") diff --git a/InitGui.py b/InitGui.py index 2aff492..09aec7d 100644 --- a/InitGui.py +++ b/InitGui.py @@ -99,8 +99,8 @@ def Initialize(self): FreeCAD.Qt.translate("SheetMetal", "&Sheet Metal"), self.list ) # creates a new menu # self.appendMenu(["An existing Menu","My submenu"],self.list) # appends a submenu to an existing menu - FreeCADGui.addPreferencePage(os.path.join(smWBpath, "SMprefs.ui"), "SheetMetal") - FreeCADGui.addIconPath(smWB_icons_path) + Gui.addPreferencePage(os.path.join(smWBpath, "Resources/panels/SMprefs.ui"), "SheetMetal") + Gui.addIconPath(smWB_icons_path) def Activated(self): "This function is executed when the workbench is activated" diff --git a/BaseShapeOptions.ui b/Resources/panels/BaseShapeOptions.ui similarity index 94% rename from BaseShapeOptions.ui rename to Resources/panels/BaseShapeOptions.ui index a41e6f6..4ec081c 100644 --- a/BaseShapeOptions.ui +++ b/Resources/panels/BaseShapeOptions.ui @@ -623,19 +623,6 @@ - - - - Create the base shape as a new body - - - Embed in new Body - - - true - - - diff --git a/Resources/panels/BendOnLinePanel.ui b/Resources/panels/BendOnLinePanel.ui new file mode 100644 index 0000000..a69c714 --- /dev/null +++ b/Resources/panels/BendOnLinePanel.ui @@ -0,0 +1,132 @@ + + + SMCreateBaseShapeTaskPanel + + + + 0 + 0 + 677 + 565 + + + + Generate Sheet Metal base shape + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + Bend Line + + + + + + + Base Object + + + + + + + Feature Name + + + + + + + + + + + + + + + + + + Flip Direction + + + + + + + Unbend + + + + + + + + + + + + Bend Radius + + + + + + + + + + + + + Bend Angle + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+ + Gui::PrefUnitSpinBox + Gui::QuantitySpinBox +
Gui/PrefWidgets.h
+
+
+ + +
diff --git a/Resources/panels/CornerReliefPanel.ui b/Resources/panels/CornerReliefPanel.ui new file mode 100644 index 0000000..184011b --- /dev/null +++ b/Resources/panels/CornerReliefPanel.ui @@ -0,0 +1,151 @@ + + + SMCreateBaseShapeTaskPanel + + + + 0 + 0 + 677 + 565 + + + + Generate Sheet Metal base shape + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Relief Size + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Relief Type + + + + + + + + + + Circular + + + true + + + + + + + Square + + + + + + + Scaling + + + + + + + Scale Factor + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+ + Gui::PrefUnitSpinBox + Gui::QuantitySpinBox +
Gui/PrefWidgets.h
+
+
+ + +
diff --git a/Resources/panels/CreateBaseShape.ui b/Resources/panels/CreateBaseShape.ui new file mode 100644 index 0000000..5fcf20d --- /dev/null +++ b/Resources/panels/CreateBaseShape.ui @@ -0,0 +1,84 @@ + + + SMCreateBaseShapeTaskPanel + + + + 0 + 0 + 677 + 565 + + + + Generate Sheet Metal base shape + + + + + + + + Sketch: + + + + + + + Select + + + + + + + + + + + + Extend sides and flange to close all gaps + + + Qt::RightToLeft + + + Reverse Direction + + + + + + + Create the base shape as a new body + + + Qt::RightToLeft + + + Center on Plane + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/Resources/panels/ExtendTaskPanel.ui b/Resources/panels/ExtendTaskPanel.ui new file mode 100644 index 0000000..87e72b3 --- /dev/null +++ b/Resources/panels/ExtendTaskPanel.ui @@ -0,0 +1,123 @@ + + + SMCreateBaseShapeTaskPanel + + + + 0 + 0 + 677 + 565 + + + + Generate Sheet Metal base shape + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + Feature Name + + + + + + + Base Element + + + + + + + Length + + + + + + + + + + + + + + + + Flange Side Offset + + + false + + + false + + + + + + SideA + + + + + + + SideB + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+ + Gui::PrefUnitSpinBox + Gui::QuantitySpinBox +
Gui/PrefWidgets.h
+
+
+ + +
diff --git a/Resources/panels/FlangeAdvancedParameters.ui b/Resources/panels/FlangeAdvancedParameters.ui new file mode 100644 index 0000000..6f15091 --- /dev/null +++ b/Resources/panels/FlangeAdvancedParameters.ui @@ -0,0 +1,233 @@ + + + SMBendWallTaskPanel + + + + 0 + 0 + 260 + 437 + + + + Advanced Parameters + + + + + + Relief Cuts + + + + + + Width + + + + + + + + 0 + 0 + + + + Depth + + + + + + + mm + + + 0.000000000000000 + + + + + + + mm + + + 0.000000000000000 + + + + + + + 1 + + + + + Rectangle + + + true + + + reliefTypeButtonGroup + + + + + + + Round + + + reliefTypeButtonGroup + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Auto Miter + + + + + + + + + + + + + mm + + + 0.000000000000000 + + + + + + + Maximum Extend Distance + + + + + + + mm + + + 0.000000000000000 + + + + + + + Minimum Gap + + + + + + + + + + Manual Miter + + + + + + deg + + + -360.000000000000000 + + + 360.000000000000000 + + + + + + + deg + + + -360.000000000000000 + + + 360.000000000000000 + + + + + + + Angle 2 + + + + + + + Anlge 1 + + + + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+
+ + reliefRectangle + reliefRound + reliefWidth + reliefDepth + autoMiterCheckbox + minGap + maxExDist + miterAngle1 + miterAngle2 + + + + + + +
diff --git a/Resources/panels/FlangeParameters.ui b/Resources/panels/FlangeParameters.ui new file mode 100644 index 0000000..1f6170e --- /dev/null +++ b/Resources/panels/FlangeParameters.ui @@ -0,0 +1,311 @@ + + + SMBendWallTaskPanel + + + + 0 + 0 + 259 + 506 + + + + Flange Parameters + + + + + + 0 + + + + + Select + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + true + + + + Name + + + + + SubElement + + + + + + + + Bend + + + + + + Type + + + + + + + + Material Outside + + + + + Material Inside + + + + + Thickness Outside + + + + + Offset + + + + + + + + Offset + + + + + + + false + + + mm + + + + + + + Radius + + + + + + + mm + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Angle + + + + + + + deg + + + -360.000000000000000 + + + 360.000000000000000 + + + 90.000000000000000 + + + + + + + Length + + + + + + + mm + + + 0.000000000000000 + + + + + + + 0 + + + + + Length mode + + + + + + + + Leg + + + + + Outer Sharp + + + + + Inner Sharp + + + + + Tangential + + + + + + + + + + + + Unfold + + + + + + + Reversed + + + + + + + + + + + + Side Offsets + + + + + + Side A + + + + + + + mm + + + + + + + Side B + + + + + + + true + + + mm + + + + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+
+ + tree + BendType + Offset + Radius + Angle + Length + UnfoldCheckbox + ReversedCheckbox + extend1 + + + +
diff --git a/SMprefs.ui b/Resources/panels/SMprefs.ui similarity index 100% rename from SMprefs.ui rename to Resources/panels/SMprefs.ui diff --git a/Resources/panels/StampPanel.ui b/Resources/panels/StampPanel.ui new file mode 100644 index 0000000..51c0230 --- /dev/null +++ b/Resources/panels/StampPanel.ui @@ -0,0 +1,165 @@ + + + SMCreateBaseShapeTaskPanel + + + + 0 + 0 + 677 + 565 + + + + Generate Sheet Metal base shape + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + Select Face + + + + + + + Select Tool + + + + + + + + + + Selected Feature + + + + + + + + + + + + + + + + Position Offset + + + + + + Offset X + + + + + + + + + + Offset Y + + + + + + + + + + Offset Z + + + + + + + + + + Angle + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Thickness + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+ + Gui::PrefUnitSpinBox + Gui::QuantitySpinBox +
Gui/PrefWidgets.h
+
+
+ + +
diff --git a/Resources/panels/UnfoldOptions.ui b/Resources/panels/UnfoldOptions.ui new file mode 100644 index 0000000..9b32b00 --- /dev/null +++ b/Resources/panels/UnfoldOptions.ui @@ -0,0 +1,213 @@ + + + SMUnfoldTaskPanel + + + + 0 + 0 + 277 + 231 + + + + Unfold sheet metal object + + + + + + Select Face + + + true + + + + + + + Material Settings + + + + + + + + Material Definition Sheet + + + + + + + + 0 + 0 + + + + + + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + Manual K-Factor + + + + + + + true + + + + 0 + 0 + + + + + 100 + 16777215 + + + + 3 + + + 2.000000000000000 + + + 0.100000000000000 + + + 0.500000000000000 + + + + + + + + + 0 + + + + + Standard + + + + + + + ANSI + + + true + + + kFactorStandardGroup + + + + + + + DIN + + + kFactorStandardGroup + + + + + + + + + + + + QLayout::SetMinimumSize + + + 0 + + + + + + 0 + 0 + + + + Unfold Transparency + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + % + + + 100 + + + 70 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + diff --git a/Resources/panels/UnfoldSketchOptions.ui b/Resources/panels/UnfoldSketchOptions.ui new file mode 100644 index 0000000..ffe9d11 --- /dev/null +++ b/Resources/panels/UnfoldSketchOptions.ui @@ -0,0 +1,170 @@ + + + SMUnfoldTaskPanel + + + + 0 + 0 + 278 + 180 + + + + Projection Sketch + + + + + + Generate projection sketch + + + + + + + QLayout::SetMinimumSize + + + 0 + + + + + Color + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 66 + 203 + 105 + + + + + + + + + + false + + + Separate projection layers + + + + + + + + + Bend lines color + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + + + + + + QLayout::SetDefaultConstraint + + + + + Internal lines color + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::ColorButton + QPushButton +
Gui/Widgets.h
+
+
+ + + + + +
diff --git a/lookup.ui b/Resources/panels/lookup.ui similarity index 100% rename from lookup.ui rename to Resources/panels/lookup.ui diff --git a/Tests/__init__.py b/SMTests/__init__.py similarity index 100% rename from Tests/__init__.py rename to SMTests/__init__.py diff --git a/Tests/testFolder.py b/SMTests/testFolder.py similarity index 100% rename from Tests/testFolder.py rename to SMTests/testFolder.py diff --git a/Tests/testKfactor.py b/SMTests/testKfactor.py similarity index 100% rename from Tests/testKfactor.py rename to SMTests/testKfactor.py diff --git a/SheetMetalBaseCmd.py b/SheetMetalBaseCmd.py index e88fb46..681d96b 100644 --- a/SheetMetalBaseCmd.py +++ b/SheetMetalBaseCmd.py @@ -1,371 +1,290 @@ -# -*- coding: utf-8 -*- -################################################################################### -# -# SheetMetalBaseCmd.py -# -# Copyright 2015 Shai Seger -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# -################################################################################### - -from FreeCAD import Gui -from PySide import QtCore, QtGui - -import FreeCAD, Part, os - -__dir__ = os.path.dirname(__file__) -iconPath = os.path.join(__dir__, "Resources", "icons") - -# add translations path -LanguagePath = os.path.join(__dir__, "translations") -Gui.addLanguagePath(LanguagePath) -Gui.updateLocale() - - -def smWarnDialog(msg): - diag = QtGui.QMessageBox( - QtGui.QMessageBox.Warning, - FreeCAD.Qt.translate("QMessageBox", "Error in macro MessageBox"), - msg, - ) - diag.setWindowModality(QtCore.Qt.ApplicationModal) - diag.exec_() - - -def smBelongToBody(item, body): - if body is None: - return False - for obj in body.Group: - if obj.Name == item.Name: - return True - return False - - -def smIsSketchObject(obj): - return str(obj).find(" 1: - wallSolid = solidlist[0].multiFuse(solidlist[1:]) - else: - wallSolid = solidlist[0] - # Part.show(wallSolid,"wallSolid") - - # Part.show(wallSolid,"wallSolid") - return wallSolid - - -class SMBaseBend: - def __init__(self, obj): - '''"Add wall or Wall with radius bend"''' - selobj = Gui.Selection.getSelectionEx()[0] - - _tip_ = FreeCAD.Qt.translate("App::Property", "Bend Radius") - obj.addProperty( - "App::PropertyLength", "radius", "Parameters", _tip_ - ).radius = 1.0 - _tip_ = FreeCAD.Qt.translate("App::Property", "Thickness of sheetmetal") - obj.addProperty( - "App::PropertyLength", "thickness", "Parameters", _tip_ - ).thickness = 1.0 - _tip_ = FreeCAD.Qt.translate("App::Property", "Relief Type") - obj.addProperty( - "App::PropertyEnumeration", "BendSide", "Parameters", _tip_ - ).BendSide = ["Outside", "Inside", "Middle"] - _tip_ = FreeCAD.Qt.translate("App::Property", "Length of wall") - obj.addProperty( - "App::PropertyLength", "length", "Parameters", _tip_ - ).length = 100.0 - _tip_ = FreeCAD.Qt.translate("App::Property", "Wall Sketch object") - obj.addProperty( - "App::PropertyLink", "BendSketch", "Parameters", _tip_ - ).BendSketch = selobj.Object - _tip_ = FreeCAD.Qt.translate("App::Property", "Extrude Symmetric to Plane") - obj.addProperty( - "App::PropertyBool", "MidPlane", "Parameters", _tip_ - ).MidPlane = False - _tip_ = FreeCAD.Qt.translate("App::Property", "Reverse Extrusion Direction") - obj.addProperty( - "App::PropertyBool", "Reverse", "Parameters", _tip_ - ).Reverse = False - obj.Proxy = self - - def execute(self, fp): - '''"Print a short message when doing a recomputation, this method is mandatory"''' - if not hasattr(fp, "MidPlane"): - _tip_ = FreeCAD.Qt.translate("App::Property", "Extrude Symmetric to Plane") - fp.addProperty( - "App::PropertyBool", "MidPlane", "Parameters", _tip_ - ).MidPlane = False - _tip_ = FreeCAD.Qt.translate("App::Property", "Reverse Extrusion Direction") - fp.addProperty( - "App::PropertyBool", "Reverse", "Parameters", _tip_ - ).Reverse = False - - s = smBase( - thk=fp.thickness.Value, - length=fp.length.Value, - radius=fp.radius.Value, - Side=fp.BendSide, - midplane=fp.MidPlane, - reverse=fp.Reverse, - MainObject=fp.BendSketch, - ) - - fp.Shape = s - obj = Gui.ActiveDocument.getObject(fp.BendSketch.Name) - if obj: - obj.Visibility = False - - -class SMBaseViewProvider: - "A View provider that nests children objects under the created one" - - def __init__(self, obj): - obj.Proxy = self - self.Object = obj.Object - - def attach(self, obj): - self.Object = obj.Object - return - - def updateData(self, fp, prop): - return - - def getDisplayModes(self, obj): - modes = [] - return modes - - def setDisplayMode(self, mode): - return mode - - def onChanged(self, vp, prop): - return - - def __getstate__(self): - # return {'ObjectName' : self.Object.Name} - return None - - def __setstate__(self, state): - self.loads(state) - - # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 - def dumps(self): - return None - - def loads(self, state): - if state is not None: - import FreeCAD - - doc = FreeCAD.ActiveDocument # crap - self.Object = doc.getObject(state["ObjectName"]) - - def claimChildren(self): - objs = [] - if hasattr(self, "Object") and hasattr(self.Object, "BendSketch"): - objs.append(self.Object.BendSketch) - return objs - - def getIcon(self): - return os.path.join(iconPath, "SheetMetal_AddBase.svg") - - -class AddBaseCommandClass: - """Add Base Wall command""" - - def GetResources(self): - return { - "Pixmap": os.path.join( - iconPath, "SheetMetal_AddBase.svg" - ), # the name of a svg file available in the resources - "MenuText": FreeCAD.Qt.translate("SheetMetal", "Make Base Wall"), - "Accel": "C, B", - "ToolTip": FreeCAD.Qt.translate( - "SheetMetal", - "Create a sheetmetal wall from a sketch\n" - "1. Select a Skech 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 - # selobj = Gui.Selection.getSelectionEx()[0].Object - if hasattr(view, "getActiveObject"): - activeBody = view.getActiveObject("pdbody") - # if not smIsOperationLegal(activeBody, selobj): - # return - doc.openTransaction("BaseBend") - if activeBody is None: - a = doc.addObject("Part::FeaturePython", "BaseBend") - SMBaseBend(a) - SMBaseViewProvider(a.ViewObject) - else: - # FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) - a = doc.addObject("PartDesign::FeaturePython", "BaseBend") - SMBaseBend(a) - SMBaseViewProvider(a.ViewObject) - activeBody.addObject(a) - doc.recompute() - doc.commitTransaction() - return - - def IsActive(self): - if len(Gui.Selection.getSelection()) != 1: - return False - selobj = Gui.Selection.getSelection()[0] - if not ( - selobj.isDerivedFrom("Sketcher::SketchObject") - or selobj.isDerivedFrom("PartDesign::ShapeBinder") - or selobj.isDerivedFrom("PartDesign::SubShapeBinder") - ): - return False - return True - - -Gui.addCommand("SheetMetal_AddBase", AddBaseCommandClass()) +# -*- coding: utf-8 -*- +################################################################################### +# +# SheetMetalBaseCmd.py +# +# Copyright 2015 Shai Seger +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +################################################################################### + +import FreeCAD, Part, os +import SheetMetalTools + +translate = FreeCAD.Qt.translate +icons_path = SheetMetalTools.icons_path +panels_path = SheetMetalTools.panels_path + + +def modifiedWire(WireList, radius, thk, length, normal, Side, sign): + # If sketch is one type, make a face by extruding & offset it to correct position + wire_extr = WireList.extrude(normal * sign * length) + # Part.show(wire_extr,"wire_extr") + + if Side == "Inside": + wire_extr = wire_extr.makeOffsetShape(thk / 2.0 * sign, 0.0, fill=False, join=2) + elif Side == "Outside": + wire_extr = wire_extr.makeOffsetShape( + -thk / 2.0 * sign, 0.0, fill=False, join=2 + ) + # Part.show(wire_extr,"wire_extr") + try: + filleted_extr = wire_extr.makeFillet((radius + thk / 2.0), wire_extr.Edges) + except: + filleted_extr = wire_extr + # Part.show(filleted_extr,"filleted_extr") + filleted_extr = filleted_extr.makeOffsetShape( + thk / 2.0 * sign, 0.0, fill=False, join=2 + ) + # Part.show(filleted_extr,"filleted_extr") + return filleted_extr + +def smBase( + thk=2.0, + length=10.0, + radius=1.0, + Side="Inside", + midplane=False, + reverse=False, + MainObject=None, +): + # To Get sketch normal + WireList = MainObject.Shape.Wires[0] + mat = MainObject.getGlobalPlacement().Rotation + normal = (mat.multVec(FreeCAD.Vector(0, 0, 1))).normalize() + # print([mat, normal]) + if WireList.isClosed(): + # If Closed sketch is there, make a face & extrude it + sketch_face = Part.makeFace(MainObject.Shape.Wires, "Part::FaceMakerBullseye") + thk = -1.0 * thk if reverse else thk + wallSolid = sketch_face.extrude(sketch_face.normalAt(0, 0) * thk) + if midplane: + wallSolid = Part.Solid( + wallSolid.translated(sketch_face.normalAt(0, 0) * thk * -0.5) + ) + else: + filleted_extr = modifiedWire(WireList, radius, thk, length, normal, Side, 1.0) + # Part.show(filleted_extr,"filleted_extr") + dist = WireList.Vertexes[0].Point.distanceToPlane( + FreeCAD.Vector(0, 0, 0), normal + ) + # print(dist) + slice_wire = filleted_extr.slice(normal, dist) + # print(slice_wire) + # Part.show(slice_wire[0],"slice_wire") + traj = slice_wire[0] + # Part.show(traj,"traj") + if midplane: + traj.translate(normal * -length / 2.0) + elif reverse: + traj.translate(normal * -length) + traj_extr = traj.extrude(normal * length) + # Part.show(traj_extr,"traj_extr") + solidlist = [] + for face in traj_extr.Faces: + solid = face.makeOffsetShape(thk, 0.0, fill=True) + solidlist.append(solid) + if len(solidlist) > 1: + wallSolid = solidlist[0].multiFuse(solidlist[1:]) + else: + wallSolid = solidlist[0] + # Part.show(wallSolid,"wallSolid") + # Part.show(wallSolid,"wallSolid") + return wallSolid + +class SMBaseBend: + def __init__(self, obj): + '''"Add wall or Wall with radius bend"''' + _tip_ = translate("App::Property", "Bend Radius") + obj.addProperty( + "App::PropertyLength", "radius", "Parameters", _tip_ + ).radius = 1.0 + _tip_ = translate("App::Property", "Thickness of sheetmetal") + obj.addProperty( + "App::PropertyLength", "thickness", "Parameters", _tip_ + ).thickness = 1.0 + _tip_ = translate("App::Property", "Relief Type") + obj.addProperty( + "App::PropertyEnumeration", "BendSide", "Parameters", _tip_ + ).BendSide = ["Outside", "Inside", "Middle"] + _tip_ = translate("App::Property", "Length of wall") + obj.addProperty( + "App::PropertyLength", "length", "Parameters", _tip_ + ).length = 100.0 + _tip_ = translate("App::Property", "Wall Sketch object") + obj.addProperty( + "App::PropertyLink", "BendSketch", "Parameters", _tip_ + ).BendSketch = None + _tip_ = translate("App::Property", "Extrude Symmetric to Plane") + obj.addProperty( + "App::PropertyBool", "MidPlane", "Parameters", _tip_ + ).MidPlane = False + _tip_ = translate("App::Property", "Reverse Extrusion Direction") + obj.addProperty( + "App::PropertyBool", "Reverse", "Parameters", _tip_ + ).Reverse = False + obj.Proxy = self + + def execute(self, fp): + '''"Print a short message when doing a recomputation, this method is mandatory"''' + if not hasattr(fp, "MidPlane"): + _tip_ = translate("App::Property", "Extrude Symmetric to Plane") + fp.addProperty( + "App::PropertyBool", "MidPlane", "Parameters", _tip_ + ).MidPlane = False + _tip_ = translate("App::Property", "Reverse Extrusion Direction") + fp.addProperty( + "App::PropertyBool", "Reverse", "Parameters", _tip_ + ).Reverse = False + s = smBase( + thk=fp.thickness.Value, + length=fp.length.Value, + radius=fp.radius.Value, + Side=fp.BendSide, + midplane=fp.MidPlane, + reverse=fp.Reverse, + MainObject=fp.BendSketch, + ) + + fp.Shape = s + + +########################################################################################################## +# Gui code +########################################################################################################## + +if SheetMetalTools.isGuiLoaded(): + from FreeCAD import Gui + + ########################################################################################################## + # View Provider + ########################################################################################################## + + class SMBaseViewProvider: + "A View provider that nests children objects under the created one" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def updateData(self, fp, prop): + return + + def getDisplayModes(self, obj): + modes = [] + return modes + + def setDisplayMode(self, mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + + doc = FreeCAD.ActiveDocument # crap + self.Object = doc.getObject(state["ObjectName"]) + + def claimChildren(self): + objs = [] + if hasattr(self, "Object") and hasattr(self.Object, "BendSketch"): + objs.append(self.Object.BendSketch) + return objs + + def getIcon(self): + return os.path.join(icons_path, "SheetMetal_AddBase.svg") + + ########################################################################################################## + # Command + ########################################################################################################## + + class AddBaseCommandClass: + """Add Base Wall command""" + + def GetResources(self): + return { + "Pixmap": os.path.join( + icons_path, "SheetMetal_AddBase.svg" + ), # the name of a svg file available in the resources + "MenuText": translate("SheetMetal", "Make Base Wall"), + "Accel": "C, B", + "ToolTip": translate( + "SheetMetal", + "Create a sheetmetal wall from a sketch\n" + "1. Select a Skech 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 + selobj = Gui.Selection.getSelectionEx()[0].Object + if hasattr(view, "getActiveObject"): + activeBody = view.getActiveObject("pdbody") + # if not SheetMetalTools.smIsOperationLegal(activeBody, selobj): + # return + doc.openTransaction("BaseBend") + if activeBody is None: + a = doc.addObject("Part::FeaturePython", "BaseBend") + SMBaseBend(a) + SMBaseViewProvider(a.ViewObject) + a.BendSketch = selobj + else: + # FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) + a = doc.addObject("PartDesign::FeaturePython", "BaseBend") + SMBaseBend(a) + SMBaseViewProvider(a.ViewObject) + a.BendSketch = selobj + activeBody.addObject(a) + doc.recompute() + doc.commitTransaction() + return + + def IsActive(self): + if len(Gui.Selection.getSelection()) != 1: + return False + selobj = Gui.Selection.getSelection()[0] + if not ( + selobj.isDerivedFrom("Sketcher::SketchObject") + or selobj.isDerivedFrom("PartDesign::ShapeBinder") + or selobj.isDerivedFrom("PartDesign::SubShapeBinder") + ): + return False + return True + + + Gui.addCommand("SheetMetal_AddBase", AddBaseCommandClass()) diff --git a/SheetMetalBaseShapeCmd.py b/SheetMetalBaseShapeCmd.py index 27c22d9..9526a37 100644 --- a/SheetMetalBaseShapeCmd.py +++ b/SheetMetalBaseShapeCmd.py @@ -23,141 +23,20 @@ # ################################################################################### -import Part, FreeCAD, FreeCADGui, os -from PySide import QtGui, QtCore -from FreeCAD import Gui -from SheetMetalCmd import smBend, smAddLengthProperty, smAddBoolProperty, smAddEnumProperty -from SheetMetalLogger import SMLogger +import FreeCAD, Part, os, SheetMetalTools +from SheetMetalCmd import smBend -modPath = os.path.dirname(__file__) -iconPath = os.path.join(modPath, "Resources", "icons") +icons_path = SheetMetalTools.icons_path +panels_path = SheetMetalTools.panels_path +language_path = SheetMetalTools.language_path -mw = FreeCADGui.getMainWindow() +base_shape_types = ["Flat", "L-Shape", "U-Shape", "Tub", "Hat", "Box"] +origin_location_types = ["-X,-Y", "-X,0", "-X,+Y", "0,-Y", "0,0", "0,+Y", "+X,-Y", "+X,0", "+X,+Y"] # IMPORTANT: please remember to change the element map version in case of any # changes in modeling logic smElementMapVersion = 'sm1.' -base_shape_types = ["Flat", "L-Shape", "U-Shape", "Tub", "Hat", "Box"] -origin_location_types = ["-X,-Y", "-X,0", "-X,+Y", "0,-Y", "0,0", "0,+Y", "+X,-Y", "+X,0", "+X,+Y"] - -########################################################################################################## -# Task -########################################################################################################## - -class BaseShapeTaskPanel: - def __init__(self): - QtCore.QDir.addSearchPath('Icons', iconPath) - path = f"{modPath}/BaseShapeOptions.ui" - self.form = FreeCADGui.PySideUic.loadUi(path) - self.formReady = False - self.firstTime = True - self.ShowAxisCross() - self.setupUi() - - - def _boolToState(self, bool): - return QtCore.Qt.Checked if bool else QtCore.Qt.Unchecked - - def _stateToBool(self, state): - return True if state == QtCore.Qt.Checked else False - - def setupUi(self): - #box = FreeCAD.ActiveDocument.addObject("Part::Box", "Box") - #bind = Gui.ExpressionBinding(self.form.bHeightSpin).bind(box,"Length") - #FreeCAD.ActiveDocument.openTransaction("BaseShape") - self.form.bRadiusSpin.valueChanged.connect(self.spinValChanged) - self.form.bThicknessSpin.valueChanged.connect(self.spinValChanged) - self.form.bWidthSpin.valueChanged.connect(self.spinValChanged) - self.form.bHeightSpin.valueChanged.connect(self.spinValChanged) - self.form.bFlangeWidthSpin.valueChanged.connect(self.spinValChanged) - self.form.bLengthSpin.valueChanged.connect(self.spinValChanged) - self.form.shapeType.currentIndexChanged.connect(self.typeChanged) - self.form.originLoc.currentIndexChanged.connect(self.spinValChanged) - self.form.chkFillGaps.stateChanged.connect(self.checkChanged) - self.form.update() - - #SMLogger.log(str(self.formReady) + " <2 \n") - def updateEnableState(self): - type = base_shape_types[self.form.shapeType.currentIndex()] - self.form.bFlangeWidthSpin.setEnabled(type in ["Hat", "Box"]) - self.form.bRadiusSpin.setEnabled(not type == "Flat") - self.form.bHeightSpin.setEnabled(not type == "Flat") - - def spinValChanged(self): - if not self.formReady: - return - self.updateObj() - self.obj.recompute() - - def typeChanged(self): - self.updateEnableState() - self.spinValChanged() - - def checkChanged(self): - self.spinValChanged() - - def ShowAxisCross(self): - self.hasAxisCross = FreeCADGui.ActiveDocument.ActiveView.hasAxisCross() - FreeCADGui.ActiveDocument.ActiveView.setAxisCross(True) - - def RevertAxisCross(self): - FreeCADGui.ActiveDocument.ActiveView.setAxisCross(self.hasAxisCross) - - def updateObj(self): - #spin = Gui.UiLoader().createWidget("Gui::QuantitySpinBox") - #SMLogger.log(str(self.form.bRadiusSpin.property('rawValue'))) - self.obj.radius = self.form.bRadiusSpin.property('value') - self.obj.thickness = self.form.bThicknessSpin.property('value') - self.obj.width = self.form.bWidthSpin.property('value') - self.obj.height = self.form.bHeightSpin.property('value') - self.obj.flangeWidth = self.form.bFlangeWidthSpin.property('value') - self.obj.length = self.form.bLengthSpin.property('value') - selected_type = self.form.shapeType.currentText() - if selected_type not in base_shape_types: - selected_type = base_shape_types[self.form.shapeType.currentIndex()] - self.obj.shapeType = selected_type - self.obj.originLoc = origin_location_types[self.form.originLoc.currentIndex()] - self.obj.fillGaps = self._stateToBool(self.form.chkFillGaps.checkState()) - - def accept(self): - doc = FreeCAD.ActiveDocument - self.updateObj() - if self.firstTime and self._stateToBool(self.form.chkNewBody.checkState()): - body = FreeCAD.activeDocument().addObject('PartDesign::Body','Body') - body.Label = 'Body' - body.addObject(self.obj) - FreeCADGui.ActiveDocument.ActiveView.setActiveObject('pdbody', body) - doc.commitTransaction() - FreeCADGui.Control.closeDialog() - doc.recompute() - self.RevertAxisCross() - - - def reject(self): - FreeCAD.ActiveDocument.abortTransaction() - FreeCADGui.Control.closeDialog() - FreeCAD.ActiveDocument.recompute() - self.RevertAxisCross() - - def updateSpin(self, spin, property): - Gui.ExpressionBinding(spin).bind(self.obj, property) - spin.setProperty('value', getattr(self.obj, property)) - pass - - def update(self): - self.updateSpin(self.form.bRadiusSpin, 'radius') - self.updateSpin(self.form.bThicknessSpin, 'thickness') - self.updateSpin(self.form.bWidthSpin, 'width') - self.updateSpin(self.form.bHeightSpin, 'height') - self.updateSpin(self.form.bFlangeWidthSpin, 'flangeWidth') - self.updateSpin(self.form.bLengthSpin, 'length') - self.form.shapeType.setCurrentText(self.obj.shapeType) - self.form.originLoc.setCurrentIndex(origin_location_types.index(self.obj.originLoc)) - self.form.chkFillGaps.setCheckState(self._boolToState(self.obj.fillGaps)) - self.form.chkNewBody.setVisible(self.firstTime) - self.formReady = True - ########################################################################################################## # Object class and creation function @@ -232,74 +111,6 @@ def smCreateBaseShape(type, thickness, radius, width, length, height, flangeWidt #SMLogger.message(str(faces)) return shape -class SMBaseShapeViewProviderFlat: - "A View provider that nests children objects under the created one" - - def __init__(self, obj): - obj.Proxy = self - self.Object = obj.Object - - def attach(self, obj): - self.Object = obj.Object - return - - def updateData(self, fp, prop): - return - - def getDisplayModes(self,obj): - modes=[] - return modes - - def setDisplayMode(self,mode): - return mode - - def onChanged(self, vp, prop): - return - - def __getstate__(self): - # return {'ObjectName' : self.Object.Name} - return None - - def __setstate__(self, state): - self.loads(state) - - # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 - def dumps(self): - return None - - def loads(self, state): - if state is not None: - self.Object = FreeCAD.ActiveDocument.getObject(state['ObjectName']) - - def claimChildren(self): - return [] - - def getIcon(self): - return os.path.join( iconPath , 'SheetMetal_AddBaseShape.svg') - - def setEdit(self, vobj, mode): - SMLogger.log( - FreeCAD.Qt.translate("Logger", "Base shape edit mode: ") + str(mode) - ) - if mode != 0: - return None - return super.setEdit(vobj, mode) - taskd = BaseShapeTaskPanel() - taskd.obj = vobj.Object - taskd.firstTime = False - taskd.update() - #self.Object.ViewObject.Visibility=False - Gui.Selection.clearSelection() - FreeCAD.ActiveDocument.openTransaction("BaseShape") - FreeCADGui.Control.showDialog(taskd) - #Gui.ActiveDocument.resetEdit() - return False - - def unsetEdit(self,vobj,mode): - FreeCADGui.Control.closeDialog() - self.Object.ViewObject.Visibility=True - return False - class SMBaseShape: def __init__(self, obj): @@ -308,57 +119,57 @@ def __init__(self, obj): obj.Proxy = self def _addVerifyProperties(self, obj): - smAddLengthProperty( + SheetMetalTools.smAddLengthProperty( obj, "thickness", FreeCAD.Qt.translate("SMBaseShape", "Thickness of sheetmetal", "Property"), 1.0, ) - smAddLengthProperty( + SheetMetalTools.smAddLengthProperty( obj, "radius", FreeCAD.Qt.translate("SMBaseShape", "Bend Radius", "Property"), 1.0, ) - smAddLengthProperty( + SheetMetalTools.smAddLengthProperty( obj, "width", FreeCAD.Qt.translate("SMBaseShape", "Shape width", "Property"), 20.0, ) - smAddLengthProperty( + SheetMetalTools.smAddLengthProperty( obj, "length", FreeCAD.Qt.translate("SMBaseShape", "Shape length", "Property"), 30.0, ) - smAddLengthProperty( + SheetMetalTools.smAddLengthProperty( obj, "height", FreeCAD.Qt.translate("SMBaseShape", "Shape height", "Property"), 10.0, ) - smAddLengthProperty( + SheetMetalTools.smAddLengthProperty( obj, "flangeWidth", FreeCAD.Qt.translate("SMBaseShape", "Width of top flange", "Property"), 5.0, ) - smAddEnumProperty( + SheetMetalTools.smAddEnumProperty( obj, "shapeType", FreeCAD.Qt.translate("SMBaseShape", "Base shape type", "Property"), base_shape_types, defval = "L-Shape" ) - smAddEnumProperty( + SheetMetalTools.smAddEnumProperty( obj, "originLoc", FreeCAD.Qt.translate("SMBaseShape", "Location of part origin", "Property"), origin_location_types, defval = "0,0" ) - smAddBoolProperty( + SheetMetalTools.smAddBoolProperty( obj, "fillGaps", FreeCAD.Qt.translate( @@ -389,45 +200,252 @@ def execute(self, fp): fp.Shape = s + def getBaseShapeTypes(): + return base_shape_types + + def getOriginLocationTypes(): + return origin_location_types + ########################################################################################################## -# Command +# Gui code ########################################################################################################## -class SMBaseshapeCommandClass: - """Open Base shape task""" - - def GetResources(self): - # add translations path - LanguagePath = os.path.join(modPath, "translations") - Gui.addLanguagePath(LanguagePath) - Gui.updateLocale() - return { - "Pixmap": os.path.join( - iconPath, "SheetMetal_AddBaseShape.svg" - ), # the name of a svg file available in the resources - "MenuText": FreeCAD.Qt.translate("SheetMetal", "Add base shape"), - "Accel": "H", - "ToolTip": FreeCAD.Qt.translate( - "SheetMetal", - "Add basic sheet metal object." - ), - } - - def Activated(self): - doc = FreeCAD.ActiveDocument - doc.openTransaction("BaseShape") - a = doc.addObject("PartDesign::FeaturePython","BaseShape") - SMBaseShape(a) - SMBaseShapeViewProviderFlat(a.ViewObject) - doc.recompute() - - dialog = BaseShapeTaskPanel() - dialog.obj = a - dialog.firstTime = True - dialog.update() - FreeCADGui.Control.showDialog(dialog) - - def IsActive(self): - return FreeCAD.ActiveDocument is not None - -Gui.addCommand("SheetMetal_BaseShape", SMBaseshapeCommandClass()) +if SheetMetalTools.isGuiLoaded(): + from PySide import QtCore + from FreeCAD import Gui + from SheetMetalLogger import SMLogger + + mw = Gui.getMainWindow() + + ########################################################################################################## + # Task + ########################################################################################################## + + class BaseShapeTaskPanel: + def __init__(self): + QtCore.QDir.addSearchPath('Icons', icons_path) + path = os.path.join(panels_path, 'BaseShapeOptions.ui') + self.form = Gui.PySideUic.loadUi(path) + self.formReady = False + self.ShowAxisCross() + self.setupUi() + + + def _boolToState(self, bool): + return QtCore.Qt.Checked if bool else QtCore.Qt.Unchecked + + def _stateToBool(self, state): + return True if state == QtCore.Qt.Checked else False + + def setupUi(self): + #box = FreeCAD.ActiveDocument.addObject("Part::Box", "Box") + #bind = Gui.ExpressionBinding(self.form.bHeightSpin).bind(box,"Length") + #FreeCAD.ActiveDocument.openTransaction("BaseShape") + self.form.bRadiusSpin.valueChanged.connect(self.spinValChanged) + self.form.bThicknessSpin.valueChanged.connect(self.spinValChanged) + self.form.bWidthSpin.valueChanged.connect(self.spinValChanged) + self.form.bHeightSpin.valueChanged.connect(self.spinValChanged) + self.form.bFlangeWidthSpin.valueChanged.connect(self.spinValChanged) + self.form.bLengthSpin.valueChanged.connect(self.spinValChanged) + self.form.shapeType.currentIndexChanged.connect(self.typeChanged) + self.form.originLoc.currentIndexChanged.connect(self.spinValChanged) + self.form.chkFillGaps.stateChanged.connect(self.checkChanged) + self.form.update() + + #SMLogger.log(str(self.formReady) + " <2 \n") + def updateEnableState(self): + type = base_shape_types[self.form.shapeType.currentIndex()] + self.form.bFlangeWidthSpin.setEnabled(type in ["Hat", "Box"]) + self.form.bRadiusSpin.setEnabled(not type == "Flat") + self.form.bHeightSpin.setEnabled(not type == "Flat") + + def spinValChanged(self): + if not self.formReady: + return + self.updateObj() + self.obj.recompute() + + def typeChanged(self): + self.updateEnableState() + self.spinValChanged() + + def checkChanged(self): + self.spinValChanged() + + def ShowAxisCross(self): + self.hasAxisCross = Gui.ActiveDocument.ActiveView.hasAxisCross() + Gui.ActiveDocument.ActiveView.setAxisCross(True) + + def RevertAxisCross(self): + Gui.ActiveDocument.ActiveView.setAxisCross(self.hasAxisCross) + + def updateObj(self): + #spin = Gui.UiLoader().createWidget("Gui::QuantitySpinBox") + #SMLogger.log(str(self.form.bRadiusSpin.property('rawValue'))) + self.obj.radius = self.form.bRadiusSpin.property('value') + self.obj.thickness = self.form.bThicknessSpin.property('value') + self.obj.width = self.form.bWidthSpin.property('value') + self.obj.height = self.form.bHeightSpin.property('value') + self.obj.flangeWidth = self.form.bFlangeWidthSpin.property('value') + self.obj.length = self.form.bLengthSpin.property('value') + selected_type = self.form.shapeType.currentText() + if selected_type not in base_shape_types: + selected_type = base_shape_types[self.form.shapeType.currentIndex()] + self.obj.shapeType = selected_type + self.obj.originLoc = origin_location_types[self.form.originLoc.currentIndex()] + self.obj.fillGaps = self._stateToBool(self.form.chkFillGaps.checkState()) + + def accept(self): + doc = FreeCAD.ActiveDocument + self.updateObj() + doc.commitTransaction() + Gui.Control.closeDialog() + doc.recompute() + Gui.ActiveDocument.resetEdit() + self.RevertAxisCross() + + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + Gui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + self.RevertAxisCross() + + def updateSpin(self, spin, property): + Gui.ExpressionBinding(spin).bind(self.obj, property) + spin.setProperty('value', getattr(self.obj, property)) + pass + + def update(self): + self.updateSpin(self.form.bRadiusSpin, 'radius') + self.updateSpin(self.form.bThicknessSpin, 'thickness') + self.updateSpin(self.form.bWidthSpin, 'width') + self.updateSpin(self.form.bHeightSpin, 'height') + self.updateSpin(self.form.bFlangeWidthSpin, 'flangeWidth') + self.updateSpin(self.form.bLengthSpin, 'length') + self.form.shapeType.setCurrentText(self.obj.shapeType) + self.form.originLoc.setCurrentIndex(origin_location_types.index(self.obj.originLoc)) + self.form.chkFillGaps.setCheckState(self._boolToState(self.obj.fillGaps)) + self.formReady = True + + + ########################################################################################################## + # View Provider + ########################################################################################################## + + class SMBaseShapeViewProviderFlat: + "A View provider that nests children objects under the created one" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + modes=[] + return modes + + def setDisplayMode(self,mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + self.Object = FreeCAD.ActiveDocument.getObject(state['ObjectName']) + + def claimChildren(self): + return [] + + def getIcon(self): + return os.path.join( icons_path , 'SheetMetal_AddBaseShape.svg') + + def setEdit(self, vobj, mode): + SMLogger.log( + FreeCAD.Qt.translate("Logger", "Base shape edit mode: ") + str(mode) + ) + if mode != 0: + return None + return super.setEdit(vobj, mode) + taskd = BaseShapeTaskPanel() + taskd.obj = vobj.Object + taskd.update() + #self.Object.ViewObject.Visibility=False + Gui.Selection.clearSelection() + FreeCAD.ActiveDocument.openTransaction("BaseShape") + Gui.Control.showDialog(taskd) + #Gui.ActiveDocument.resetEdit() + return False + + def unsetEdit(self,vobj,mode): + Gui.Control.closeDialog() + self.Object.ViewObject.Visibility=True + return False + + + ########################################################################################################## + # Command + ########################################################################################################## + + class SMBaseshapeCommandClass: + """Open Base shape task""" + + def GetResources(self): + # add translations path + Gui.addLanguagePath(language_path) + Gui.updateLocale() + return { + "Pixmap": os.path.join( + icons_path, "SheetMetal_AddBaseShape.svg" + ), # the name of a svg file available in the resources + "MenuText": FreeCAD.Qt.translate("SheetMetal", "Add base shape"), + "Accel": "H", + "ToolTip": FreeCAD.Qt.translate( + "SheetMetal", + "Add basic sheet metal object." + ), + } + + def Activated(self): + doc = FreeCAD.ActiveDocument + activeBody = None + view = Gui.ActiveDocument.ActiveView + if hasattr(view, "getActiveObject"): + activeBody = view.getActiveObject("pdbody") + doc.openTransaction("BaseShape") + a = doc.addObject("PartDesign::FeaturePython","BaseShape") + SMBaseShape(a) + SMBaseShapeViewProviderFlat(a.ViewObject) + if not activeBody: + activeBody = FreeCAD.activeDocument().addObject('PartDesign::Body','Body') + Gui.ActiveDocument.ActiveView.setActiveObject('pdbody', activeBody) + activeBody.addObject(a) + doc.recompute() + + dialog = BaseShapeTaskPanel() + dialog.obj = a + dialog.update() + Gui.Control.showDialog(dialog) + + def IsActive(self): + return FreeCAD.ActiveDocument is not None + + Gui.addCommand("SheetMetal_BaseShape", SMBaseshapeCommandClass()) diff --git a/SheetMetalBend.py b/SheetMetalBend.py index 6b31292..1ff2bce 100644 --- a/SheetMetalBend.py +++ b/SheetMetalBend.py @@ -1,455 +1,423 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# SheetMetalBend.py -# -# Copyright 2015 Shai Seger -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# -############################################################################## - -from FreeCAD import Gui -from PySide import QtCore, QtGui - -import FreeCAD, FreeCADGui, Part, os -import SheetMetalBaseCmd -__dir__ = os.path.dirname(__file__) -iconPath = os.path.join( __dir__, 'Resources', 'icons' ) -smEpsilon = 0.0000001 - -# add translations path -LanguagePath = os.path.join( __dir__, 'translations') -Gui.addLanguagePath(LanguagePath) -Gui.updateLocale() - -# IMPORTANT: please remember to change the element map version in case of any -# changes in modeling logic -smElementMapVersion = 'sm1.' - -def smWarnDialog(msg): - diag = QtGui.QMessageBox( - QtGui.QMessageBox.Warning, - FreeCAD.Qt.translate("QMessageBox", "Error in macro MessageBox"), - msg, - ) - diag.setWindowModality(QtCore.Qt.ApplicationModal) - diag.exec_() - -def smBelongToBody(item, body): - if (body is None): - return False - for obj in body.Group: - if obj.Name == item.Name: - return True - return False - -def smIsPartDesign(obj): - return str(obj).find(" 0: - # find thickness of sheet by distance from v1 to one of the edges comming out of edge[0] - # we assume all corners have same thickness - for dedge in MainObject.ancestorsOfType(edge.Vertexes[0], Part.Edge): - if not dedge.isSame(edge): - break - - thickness = v1.distToShape(dedge)[0] - - resultSolid = MainObject.makeFillet(radius, InnerEdgesToBend) - resultSolid = resultSolid.makeFillet(radius + thickness, OuterEdgesToBend) - - return resultSolid - -class SMSolidBend: - def __init__(self, obj): - '''"Add Bend to Solid" ''' - selobj = Gui.Selection.getSelectionEx()[0] - - _tip_ = FreeCAD.Qt.translate("App::Property","Bend Radius") - obj.addProperty("App::PropertyLength","radius","Parameters", _tip_).radius = 1.0 - - _tip_ = FreeCAD.Qt.translate("App::Property","Base object") - obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters", _tip_).baseObject = (selobj.Object, selobj.SubElementNames) - obj.Proxy = self - - def getElementMapVersion(self, _fp, ver, _prop, restored): - if not restored: - return smElementMapVersion + ver - - def execute(self, fp): - '''"Print a short message when doing a recomputation, this method is mandatory" ''' - # pass selected object shape - - Main_Object = fp.baseObject[0].Shape.copy() - s = smSolidBend(radius = fp.radius.Value, selEdgeNames = fp.baseObject[1], - MainObject = Main_Object) - fp.Shape = s - fp.baseObject[0].ViewObject.Visibility = False - - -class SMBendViewProviderTree: - "A View provider that nests children objects under the created one (Part WB style)" - - def __init__(self, obj): - obj.Proxy = self - self.Object = obj.Object - - def attach(self, obj): - self.Object = obj.Object - return - - def setupContextMenu(self, viewObject, menu): - action = menu.addAction(FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label)) - action.triggered.connect(lambda: self.startDefaultEditMode(viewObject)) - return False - - def startDefaultEditMode(self, viewObject): - document = viewObject.Document.Document - if not document.HasPendingTransaction: - text = FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label) - document.openTransaction(text) - viewObject.Document.setEdit(viewObject.Object, 0) - - def updateData(self, fp, prop): - return - - def getDisplayModes(self,obj): - modes=[] - return modes - - def setDisplayMode(self,mode): - return mode - - def onChanged(self, vp, prop): - return - - def __getstate__(self): - # return {'ObjectName' : self.Object.Name} - return None - - def __setstate__(self, state): - self.loads(state) - - # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 - def dumps(self): - return None - - def loads(self, state): - if state is not None: - import FreeCAD - doc = FreeCAD.ActiveDocument #crap - self.Object = doc.getObject(state['ObjectName']) - - def claimChildren(self): - objs = [] - if hasattr(self.Object,"baseObject"): - objs.append(self.Object.baseObject[0]) - return objs - - def getIcon(self): - return os.path.join( iconPath , 'SheetMetal_AddBend.svg') - - def setEdit(self,vobj,mode): - taskd = SMBendTaskPanel() - taskd.obj = vobj.Object - taskd.update() - self.Object.ViewObject.Visibility=False - self.Object.baseObject[0].ViewObject.Visibility=True - FreeCADGui.Control.showDialog(taskd) - return True - - def unsetEdit(self,vobj,mode): - FreeCADGui.Control.closeDialog() - self.Object.baseObject[0].ViewObject.Visibility=False - self.Object.ViewObject.Visibility=True - return False - -class SMBendViewProviderFlat: - "A View provider that place new objects under the base object (Part design WB style)" - - def __init__(self, obj): - obj.Proxy = self - self.Object = obj.Object - - def attach(self, obj): - self.Object = obj.Object - return - - def updateData(self, fp, prop): - return - - def getDisplayModes(self,obj): - modes=[] - return modes - - def setDisplayMode(self,mode): - return mode - - def onChanged(self, vp, prop): - return - - def __getstate__(self): - # return {'ObjectName' : self.Object.Name} - return None - - def __setstate__(self, state): - self.loads(state) - - # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 - def dumps(self): - return None - - def loads(self, state): - if state is not None: - import FreeCAD - doc = FreeCAD.ActiveDocument #crap - self.Object = doc.getObject(state['ObjectName']) - - def claimChildren(self): - - return [] - - def getIcon(self): - return os.path.join( iconPath , 'SheetMetal_AddBend.svg') - - def setEdit(self,vobj,mode): - taskd = SMBendTaskPanel() - taskd.obj = vobj.Object - taskd.update() - self.Object.ViewObject.Visibility=False - self.Object.baseObject[0].ViewObject.Visibility=True - FreeCADGui.Control.showDialog(taskd) - return True - - def unsetEdit(self,vobj,mode): - FreeCADGui.Control.closeDialog() - self.Object.baseObject[0].ViewObject.Visibility=False - self.Object.ViewObject.Visibility=True - return False - -class SMBendTaskPanel: - '''A TaskPanel for the Sheetmetal''' - def __init__(self): - - self.obj = None - self.form = QtGui.QWidget() - self.form.setObjectName("SMBendTaskPanel") - self.form.setWindowTitle("Binded faces/edges list") - self.grid = QtGui.QGridLayout(self.form) - self.grid.setObjectName("grid") - self.title = QtGui.QLabel(self.form) - self.grid.addWidget(self.title, 0, 0, 1, 2) - self.title.setText("Select new face(s)/Edge(s) and press Update") - - # tree - self.tree = QtGui.QTreeWidget(self.form) - self.grid.addWidget(self.tree, 1, 0, 1, 2) - self.tree.setColumnCount(2) - self.tree.setHeaderLabels(["Name","Subelement"]) - - # buttons - self.addButton = QtGui.QPushButton(self.form) - self.addButton.setObjectName("addButton") - self.addButton.setIcon(QtGui.QIcon(os.path.join( iconPath , 'SheetMetal_Update.svg'))) - self.grid.addWidget(self.addButton, 3, 0, 1, 2) - - QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.updateElement) - self.update() - - def isAllowedAlterSelection(self): - return True - - def isAllowedAlterView(self): - return True - - def getStandardButtons(self): - return QtGui.QDialogButtonBox.Ok - - def update(self): - 'fills the treewidget' - self.tree.clear() - if self.obj: - f = self.obj.baseObject - if isinstance(f[1],list): - for subf in f[1]: - #FreeCAD.Console.PrintLog("item: " + subf + "\n") - item = QtGui.QTreeWidgetItem(self.tree) - item.setText(0,f[0].Name) - item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg")) - item.setText(1,subf) - else: - item = QtGui.QTreeWidgetItem(self.tree) - item.setText(0,f[0].Name) - item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg")) - item.setText(1,f[1][0]) - self.retranslateUi(self.form) - - def updateElement(self): - if self.obj: - sel = FreeCADGui.Selection.getSelectionEx()[0] - if sel.HasSubObjects: - obj = sel.Object - for elt in sel.SubElementNames: - if "Face" in elt or "Edge" in elt: - face = self.obj.baseObject - found = False - if (face[0] == obj.Name): - if isinstance(face[1],tuple): - for subf in face[1]: - if subf == elt: - found = True - else: - if (face[1][0] == elt): - found = True - if not found: - self.obj.baseObject = (sel.Object, sel.SubElementNames) - self.update() - - def accept(self): - FreeCAD.ActiveDocument.recompute() - FreeCADGui.ActiveDocument.resetEdit() - #self.obj.ViewObject.Visibility=True - return True - - def retranslateUi(self, SMUnfoldTaskPanel): - #SMUnfoldTaskPanel.setWindowTitle(QtGui.QApplication.translate("draft", "Faces", None)) - self.addButton.setText(QtGui.QApplication.translate("draft", "Update", None)) - - -class AddBendCommandClass(): - """Add Solid Bend command""" - - def GetResources(self): - return {'Pixmap' : os.path.join( iconPath , 'SheetMetal_AddBend.svg'), # the name of a svg file available in the resources - 'MenuText': FreeCAD.Qt.translate('SheetMetal','Make Bend'), - 'Accel': "S, B", - 'ToolTip' : FreeCAD.Qt.translate('SheetMetal','Create Bend where two walls come together on solids\n' - '1. Select edge(s) to create bend on corner edge(s).\n' - '2. Use Property editor to modify parameters')} - - def Activated(self): - doc = FreeCAD.ActiveDocument - view = Gui.ActiveDocument.ActiveView - activeBody = None - selobj = Gui.Selection.getSelectionEx()[0].Object - viewConf = SheetMetalBaseCmd.GetViewConfig(selobj) - if hasattr(view,'getActiveObject'): - activeBody = view.getActiveObject('pdbody') - if not smIsOperationLegal(activeBody, selobj): - return - doc.openTransaction("Add Bend") - if activeBody is None or not smIsPartDesign(selobj): - a = doc.addObject("Part::FeaturePython","SolidBend") - SMSolidBend(a) - SMBendViewProviderTree(a.ViewObject) - else: - #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) - a = doc.addObject("PartDesign::FeaturePython","SolidBend") - SMSolidBend(a) - SMBendViewProviderFlat(a.ViewObject) - activeBody.addObject(a) - SheetMetalBaseCmd.SetViewConfig(a, viewConf) - if SheetMetalBaseCmd.autolink_enabled(): - root = SheetMetalBaseCmd.getOriginalBendObject(a) - if root: - a.setExpression("radius", root.Label + ".radius") - FreeCADGui.Selection.clearSelection() - doc.recompute() - doc.commitTransaction() - return - - def IsActive(self): - if len(Gui.Selection.getSelection()) < 1 or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1: - return False -# selobj = Gui.Selection.getSelection()[0] - for selFace in Gui.Selection.getSelectionEx()[0].SubObjects: - if type(selFace) != Part.Edge : - return False - return True - -Gui.addCommand("SheetMetal_AddBend", AddBendCommandClass()) - +# -*- coding: utf-8 -*- +############################################################################## +# +# SheetMetalBend.py +# +# Copyright 2015 Shai Seger +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +############################################################################## + +import Part, SheetMetalTools, os + +smEpsilon = SheetMetalTools.smEpsilon + +# IMPORTANT: please remember to change the element map version in case of any +# changes in modeling logic +smElementMapVersion = 'sm1.' + +def smGetClosestVert(vert, face): + closestVert = None + closestDist = 99999999 + for v in face.Vertexes: + if vert.isSame(v): + continue + d = vert.distToShape(v)[0] + if (d < closestDist): + closestDist = d + closestVert = v + return closestVert + + +# we look for a matching inner edge to the selected outer one +# this function finds a single vertex of that edge +def smFindMatchingVert(shape, edge, vertid): + facelist = shape.ancestorsOfType(edge, Part.Face) + edgeVerts = edge.Vertexes + v = edgeVerts[vertid] + vfacelist = shape.ancestorsOfType(v, Part.Face) + + # find the face that is not in facelist + for vface in vfacelist: + if not vface.isSame(facelist[0]) and not vface.isSame(facelist[1]): + break + + return smGetClosestVert(v, vface) + +def smFindEdgeByVerts(shape, vert1, vert2): + for edge in shape.Edges: + if vert1.isSame(edge.Vertexes[0]) and vert2.isSame(edge.Vertexes[1]): + break + if vert1.isSame(edge.Vertexes[1]) and vert2.isSame(edge.Vertexes[0]): + break + else: + edge = None + return edge + +def smSolidBend(radius = 1.0, selEdgeNames = '', MainObject = None): + InnerEdgesToBend = [] + OuterEdgesToBend = [] + for selEdgeName in selEdgeNames: + edge = MainObject.getElement(selEdgeName) + + + facelist = MainObject.ancestorsOfType(edge, Part.Face) + + # find matching inner edge to selected outer one + v1 = smFindMatchingVert(MainObject, edge, 0) + v2 = smFindMatchingVert(MainObject, edge, 1) + matchingEdge = smFindEdgeByVerts(MainObject, v1, v2) + if matchingEdge is not None: + InnerEdgesToBend.append(matchingEdge) + OuterEdgesToBend.append(edge) + + if len(InnerEdgesToBend) > 0: + # find thickness of sheet by distance from v1 to one of the edges comming out of edge[0] + # we assume all corners have same thickness + for dedge in MainObject.ancestorsOfType(edge.Vertexes[0], Part.Edge): + if not dedge.isSame(edge): + break + + thickness = v1.distToShape(dedge)[0] + + resultSolid = MainObject.makeFillet(radius, InnerEdgesToBend) + resultSolid = resultSolid.makeFillet(radius + thickness, OuterEdgesToBend) + + return resultSolid + + +class SMSolidBend: + def __init__(self, obj): + '''"Add Bend to Solid" ''' + + _tip_ = FreeCAD.Qt.translate("App::Property","Bend Radius") + obj.addProperty("App::PropertyLength","radius","Parameters", _tip_).radius = 1.0 + + _tip_ = FreeCAD.Qt.translate("App::Property","Base object") + obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters", _tip_) + + def getElementMapVersion(self, _fp, ver, _prop, restored): + if not restored: + return smElementMapVersion + ver + + def execute(self, fp): + '''"Print a short message when doing a recomputation, this method is mandatory" ''' + # pass selected object shape + + Main_Object = fp.baseObject[0].Shape.copy() + s = smSolidBend(radius = fp.radius.Value, selEdgeNames = fp.baseObject[1], + MainObject = Main_Object) + fp.Shape = s + +########################################################################################################## +# Gui code +########################################################################################################## + +if SheetMetalTools.isGuiLoaded(): + import FreeCAD + from FreeCAD import Gui + from PySide import QtCore, QtGui + + icons_path = SheetMetalTools.icons_path + + + class SMBendViewProviderTree: + "A View provider that nests children objects under the created one (Part WB style)" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def setupContextMenu(self, viewObject, menu): + action = menu.addAction(FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label)) + action.triggered.connect(lambda: self.startDefaultEditMode(viewObject)) + return False + + def startDefaultEditMode(self, viewObject): + document = viewObject.Document.Document + if not document.HasPendingTransaction: + text = FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label) + document.openTransaction(text) + viewObject.Document.setEdit(viewObject.Object, 0) + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + modes=[] + return modes + + def setDisplayMode(self,mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + doc = FreeCAD.ActiveDocument #crap + self.Object = doc.getObject(state['ObjectName']) + + def claimChildren(self): + objs = [] + if hasattr(self.Object,"baseObject"): + objs.append(self.Object.baseObject[0]) + return objs + + def getIcon(self): + return os.path.join( icons_path , 'SheetMetal_AddBend.svg') + + def setEdit(self,vobj,mode): + taskd = SMBendTaskPanel() + taskd.obj = vobj.Object + taskd.update() + self.Object.ViewObject.Visibility=False + self.Object.baseObject[0].ViewObject.Visibility=True + Gui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + Gui.Control.closeDialog() + self.Object.baseObject[0].ViewObject.Visibility=False + self.Object.ViewObject.Visibility=True + return False + + class SMBendViewProviderFlat: + "A View provider that place new objects under the base object (Part design WB style)" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + modes=[] + return modes + + def setDisplayMode(self,mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + doc = FreeCAD.ActiveDocument #crap + self.Object = doc.getObject(state['ObjectName']) + + def claimChildren(self): + + return [] + + def getIcon(self): + return os.path.join( icons_path , 'SheetMetal_AddBend.svg') + + def setEdit(self,vobj,mode): + taskd = SMBendTaskPanel() + taskd.obj = vobj.Object + taskd.update() + self.Object.ViewObject.Visibility=False + self.Object.baseObject[0].ViewObject.Visibility=True + Gui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + Gui.Control.closeDialog() + self.Object.baseObject[0].ViewObject.Visibility=False + self.Object.ViewObject.Visibility=True + return False + + class SMBendTaskPanel: + '''A TaskPanel for the Sheetmetal''' + def __init__(self): + + self.obj = None + self.form = QtGui.QWidget() + self.form.setObjectName("SMBendTaskPanel") + self.form.setWindowTitle("Binded faces/edges list") + self.grid = QtGui.QGridLayout(self.form) + self.grid.setObjectName("grid") + self.title = QtGui.QLabel(self.form) + self.grid.addWidget(self.title, 0, 0, 1, 2) + self.title.setText("Select new face(s)/Edge(s) and press Update") + + # tree + self.tree = QtGui.QTreeWidget(self.form) + self.grid.addWidget(self.tree, 1, 0, 1, 2) + self.tree.setColumnCount(2) + self.tree.setHeaderLabels(["Name","Subelement"]) + + # buttons + self.addButton = QtGui.QPushButton(self.form) + self.addButton.setObjectName("addButton") + self.addButton.setIcon(QtGui.QIcon(os.path.join( icons_path , 'SheetMetal_Update.svg'))) + self.grid.addWidget(self.addButton, 3, 0, 1, 2) + + QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.updateElement) + self.update() + + def isAllowedAlterSelection(self): + return True + + def isAllowedAlterView(self): + return True + + def getStandardButtons(self): + return QtGui.QDialogButtonBox.Ok + + def update(self): + 'fills the treewidget' + self.tree.clear() + if self.obj: + f = self.obj.baseObject + if isinstance(f[1],list): + for subf in f[1]: + #FreeCAD.Console.PrintLog("item: " + subf + "\n") + item = QtGui.QTreeWidgetItem(self.tree) + item.setText(0,f[0].Name) + item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg")) + item.setText(1,subf) + else: + item = QtGui.QTreeWidgetItem(self.tree) + item.setText(0,f[0].Name) + item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg")) + item.setText(1,f[1][0]) + self.retranslateUi(self.form) + + def updateElement(self): + if self.obj: + sel = Gui.Selection.getSelectionEx()[0] + if sel.HasSubObjects: + obj = sel.Object + for elt in sel.SubElementNames: + if "Face" in elt or "Edge" in elt: + face = self.obj.baseObject + found = False + if (face[0] == obj.Name): + if isinstance(face[1],tuple): + for subf in face[1]: + if subf == elt: + found = True + else: + if (face[1][0] == elt): + found = True + if not found: + self.obj.baseObject = (sel.Object, sel.SubElementNames) + self.update() + + def accept(self): + FreeCAD.ActiveDocument.recompute() + Gui.ActiveDocument.resetEdit() + #self.obj.ViewObject.Visibility=True + return True + + def retranslateUi(self, SMUnfoldTaskPanel): + #SMUnfoldTaskPanel.setWindowTitle(QtGui.QApplication.translate("draft", "Faces", None)) + self.addButton.setText(QtGui.QApplication.translate("draft", "Update", None)) + + + class AddBendCommandClass(): + """Add Solid Bend command""" + + def GetResources(self): + return {'Pixmap' : os.path.join( icons_path , 'SheetMetal_AddBend.svg'), # the name of a svg file available in the resources + 'MenuText': FreeCAD.Qt.translate('SheetMetal','Make Bend'), + 'Accel': "S, B", + 'ToolTip' : FreeCAD.Qt.translate('SheetMetal','Create Bend where two walls come together on solids\n' + '1. Select edge(s) to create bend on corner edge(s).\n' + '2. Use Property editor to modify parameters')} + + def Activated(self): + doc = FreeCAD.ActiveDocument + view = Gui.ActiveDocument.ActiveView + activeBody = None + sel = Gui.Selection.getSelectionEx()[0] + selobj = sel.Object + viewConf = SheetMetalTools.GetViewConfig(selobj) + if hasattr(view,'getActiveObject'): + activeBody = view.getActiveObject('pdbody') + if not SheetMetalTools.smIsOperationLegal(activeBody, selobj): + return + doc.openTransaction("Add Bend") + if activeBody is None or not SheetMetalTools.smIsPartDesign(selobj): + a = doc.addObject("Part::FeaturePython","SolidBend") + SMSolidBend(a) + a.baseObject = (selobj, sel.SubElementNames) + SMBendViewProviderTree(a.ViewObject) + else: + #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) + a = doc.addObject("PartDesign::FeaturePython","SolidBend") + SMSolidBend(a) + SMBendViewProviderFlat(a.ViewObject) + activeBody.addObject(a) + SheetMetalTools.SetViewConfig(a, viewConf) + if SheetMetalTools.is_autolink_enabled(): + root = SheetMetalTools.getOriginalBendObject(a) + if root: + a.setExpression("radius", root.Label + ".radius") + Gui.Selection.clearSelection() + doc.recompute() + doc.commitTransaction() + return + + def IsActive(self): + if len(Gui.Selection.getSelection()) < 1 or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1: + return False + # selobj = Gui.Selection.getSelection()[0] + for selFace in Gui.Selection.getSelectionEx()[0].SubObjects: + if type(selFace) != Part.Edge : + return False + return True + + Gui.addCommand("SheetMetal_AddBend", AddBendCommandClass()) + + \ No newline at end of file diff --git a/SheetMetalCmd.py b/SheetMetalCmd.py index 1c3534e..074a9d3 100644 --- a/SheetMetalCmd.py +++ b/SheetMetalCmd.py @@ -1,1937 +1,1927 @@ -# -*- coding: utf-8 -*- -################################################################################### -# -# SheetMetalCmd.py -# -# Copyright 2015 Shai Seger -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# -################################################################################### - -from FreeCAD import Gui -from PySide import QtCore, QtGui - -import FreeCAD, FreeCADGui, Part, os, math -import SheetMetalBaseCmd - -__dir__ = os.path.dirname(__file__) -iconPath = os.path.join(__dir__, "Resources", "icons") -smEpsilon = 0.0000001 - -# add translations path -LanguagePath = os.path.join(__dir__, "translations") -Gui.addLanguagePath(LanguagePath) -Gui.updateLocale() - -# IMPORTANT: please remember to change the element map version in case of any -# changes in modeling logic -smElementMapVersion = "sm1." - - -def smAddProperty(obj, proptype, name, proptip, defval=None, paramgroup="Parameters"): - """ - Add a property to a given object. - - Args: - - obj: The object to which the property should be added. - - proptype: The type of the property (e.g., "App::PropertyLength", "App::PropertyBool"). - - name: The name of the property. Non-translatable. - - proptip: The tooltip for the property. Need to be translated from outside. - - defval: The default value for the property (optional). - - paramgroup: The parameter group to which the property should belong (default is "Parameters"). - """ - if not hasattr(obj, name): - obj.addProperty(proptype, name, paramgroup, proptip) - if defval is not None: - setattr(obj, name, defval) - - -def smAddLengthProperty(obj, name, proptip, defval, paramgroup="Parameters"): - smAddProperty(obj, "App::PropertyLength", name, proptip, defval, paramgroup) - - -def smAddBoolProperty(obj, name, proptip, defval, paramgroup="Parameters"): - smAddProperty(obj, "App::PropertyBool", name, proptip, defval, paramgroup) - - -def smAddDistanceProperty(obj, name, proptip, defval, paramgroup="Parameters"): - smAddProperty(obj, "App::PropertyDistance", name, proptip, defval, paramgroup) - - -def smAddAngleProperty(obj, name, proptip, defval, paramgroup="Parameters"): - smAddProperty(obj, "App::PropertyAngle", name, proptip, defval, paramgroup) - - -def smAddFloatProperty(obj, name, proptip, defval, paramgroup="Parameters"): - smAddProperty(obj, "App::PropertyFloat", name, proptip, defval, paramgroup) - - -def smAddEnumProperty( - obj, name, proptip, enumlist, defval=None, paramgroup="Parameters" -): - if not hasattr(obj, name): - _tip_ = FreeCAD.Qt.translate("App::Property", proptip) - obj.addProperty("App::PropertyEnumeration", name, paramgroup, _tip_) - setattr(obj, name, enumlist) - if defval is not None: - setattr(obj, name, defval) - - -def smWarnDialog(msg): - diag = QtGui.QMessageBox( - QtGui.QMessageBox.Warning, - FreeCAD.Qt.translate("QMessageBox", "Error in macro MessageBox"), - msg - ) - diag.setWindowModality(QtCore.Qt.ApplicationModal) - diag.exec_() - - -def smBelongToBody(item, body): - if body is None: - return False - for obj in body.Group: - if obj.Name == item.Name: - return True - return False - - -def smIsPartDesign(obj): - return str(obj).find(" reliefW: - p3 = edge.valueAt(edge.FirstParameter + gap + reliefW) + dir.normalize() * ( - reliefD - reliefW / 2 - ) - p34 = ( - edge.valueAt(edge.FirstParameter + gap + reliefW / 2) - + dir.normalize() * reliefD - ) - p4 = edge.valueAt(edge.FirstParameter + gap) + dir.normalize() * ( - reliefD - reliefW / 2 - ) - e1 = Part.makeLine(p1, p2) - e2 = Part.makeLine(p2, p3) - e3 = Part.Arc(p3, p34, p4).toShape() - e4 = Part.makeLine(p4, p1) - else: - p3 = ( - edge.valueAt(edge.FirstParameter + gap + reliefW) - + dir.normalize() * reliefD - ) - p4 = edge.valueAt(edge.FirstParameter + gap) + dir.normalize() * reliefD - e1 = Part.makeLine(p1, p2) - e2 = Part.makeLine(p2, p3) - e3 = Part.makeLine(p3, p4) - e4 = Part.makeLine(p4, p1) - - w = Part.Wire([e1, e2, e3, e4]) - face = Part.Face(w) - if hasattr(face, "mapShapes"): - face.mapShapes([(edge, face)], [], op) - return face - - -def smMakeFace(edge, dir, extLen, gap1=0.0, gap2=0.0, angle1=0.0, angle2=0.0, op=""): - len1 = extLen * math.tan(math.radians(angle1)) - len2 = extLen * math.tan(math.radians(angle2)) - - p1 = edge.valueAt(edge.LastParameter - gap2) - p2 = edge.valueAt(edge.FirstParameter + gap1) - p3 = edge.valueAt(edge.FirstParameter + gap1 + len1) + dir.normalize() * extLen - p4 = edge.valueAt(edge.LastParameter - gap2 - len2) + dir.normalize() * extLen - - e2 = Part.makeLine(p2, p3) - e4 = Part.makeLine(p4, p1) - section = e4.section(e2) - - if section.Vertexes: - p5 = section.Vertexes[0].Point - w = Part.makePolygon([p1, p2, p5, p1]) - else: - w = Part.makePolygon([p1, p2, p3, p4, p1]) - face = Part.Face(w) - if hasattr(face, "mapShapes"): - face.mapShapes([(edge, face)], None, op) - return face - - -def smRestrict(var, fromVal, toVal): - if var < fromVal: - return fromVal - if var > toVal: - return toVal - return var - - -def smFace(selItem, obj): - # find face, if Edge Selected - if type(selItem) == Part.Edge: - Facelist = obj.ancestorsOfType(selItem, Part.Face) - if Facelist[0].Area < Facelist[1].Area: - selFace = Facelist[0] - else: - selFace = Facelist[1] - elif type(selItem) == Part.Face: - selFace = selItem - return selFace - - -def smModifiedFace(Face, obj): - # find face Modified During loop - for face in obj.Faces: - face_common = face.common(Face) - if face_common.Faces: - if face.Area == face_common.Faces[0].Area: - break - return face - - -def smGetEdge(Face, obj): - # find Edges that overlap - for edge in obj.Edges: - face_common = edge.common(Face) - if face_common.Edges: - break - return edge - - -def LineAngle(edge1, edge2): - # find angle between two lines - v1a = edge1.Vertexes[0].Point - v1b = edge1.Vertexes[1].Point - v2a = edge2.Vertexes[0].Point - v2b = edge2.Vertexes[1].Point - - # Find the right order of the wire verts to calculate the angle - # the order of the verts v1a v1b v2a v2b should be such that v1b is the closest vert to v2a - minlen = (v1a - v2a).Length - order = [v1b, v1a, v2a, v2b] - len = (v1a - v2b).Length - if (len < minlen): - minlen = len - order = [v1b, v1a, v2b, v2a] - len = (v1b - v2a).Length - if (len < minlen): - minlen = len - order = [v1a, v1b, v2a, v2b] - len = (v1b - v2b).Length - if (len < minlen): - order = [v1a, v1b, v2b, v2a] - - lineDir = order[1] - order[0] - edgeDir = order[3] - order[2] - - angleRad = edgeDir.getAngle(lineDir) - angle = math.degrees(angleRad) - #if (angle > 90): - # angle = 180.0 - angle - return angle - - -def smGetFace(Faces, obj): - # find face Name Modified obj - faceList = [] - for Face in Faces: - for i, face in enumerate(obj.Faces): - face_common = face.common(Face) - if face_common.Faces: - faceList.append("Face" + str(i + 1)) - # print(faceList) - return faceList - - -def LineExtend(edge, distance1, distance2): - # Extend a ine by given distances - return edge.Curve.toShape( - edge.FirstParameter - distance1, edge.LastParameter + distance2 - ) - - -def getParallel(edge1, edge2): - # Get intersection between two lines - e1 = edge1.Curve.toShape() - # Part.show(e1,'e1') - e2 = edge2.Curve.toShape() - # Part.show(e2,'e2') - section = e1.section(e2) - if section.Vertexes: - # Part.show(section,'section') - return False - else: - return True - - -def getCornerPoint(edge1, edge2): - # Get intersection between two lines - # Part.show(edge1,'edge1') - # Part.show(edge2,'edge21') - e1 = edge1.Curve.toShape() - # Part.show(e1,'e1') - e2 = edge2.Curve.toShape() - # Part.show(e2,'e2') - section = e1.section(e2) - if section.Vertexes: - # Part.show(section,'section') - cornerPoint = section.Vertexes[0].Point - return cornerPoint - - -def getGap(line1, line2, maxExtendGap, mingap): - # To find gap between two edges - gaps = 0.0 - extgap = 0.0 - section = line1.section(line2) - if section.Vertexes: - cornerPoint = section.Vertexes[0].Point - size1 = abs((cornerPoint - line2.Vertexes[0].Point).Length) - size2 = abs((cornerPoint - line2.Vertexes[1].Point).Length) - if size1 < size2: - gaps = size1 - else: - gaps = size2 - gaps = gaps + mingap - # print(gaps) - else: - cornerPoint = getCornerPoint(line1, line2) - line3 = LineExtend(line1, maxExtendGap, maxExtendGap) - # Part.show(line1,'line1') - line4 = LineExtend(line2, maxExtendGap, maxExtendGap) - # Part.show(line2,'line2') - section = line3.section(line4) - if section.Vertexes: - # cornerPoint = section.Vertexes[0].Point - # p1 = Part.Vertex(cornerPoint) - section1 = line1.section(line4) - size1 = abs((cornerPoint - line2.Vertexes[0].Point).Length) - size2 = abs((cornerPoint - line2.Vertexes[1].Point).Length) - # dist = cornerPoint.distanceToLine(line2.Curve.Location, line2.Curve.Direction) - # print(["gap",size1, size2, dist]) - # if section1.Vertexes : - # extgap = 0.0 - if size1 < size2: - extgap = size1 - else: - extgap = size2 - # if dist < smEpsilon : - # gaps = extgap - # extgap = 0.0 - if extgap > mingap: - extgap = extgap - mingap - # print(extgap) - return gaps, extgap, cornerPoint - - -def getSketchDetails(Sketch, sketchflip, sketchinvert, radius, thk): - # Convert Sketch lines to length. Angles between line - LengthList, bendAList = ([], []) - sketch_normal = Sketch.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, 1)) - e0 = Sketch.Placement.Rotation.multVec(FreeCAD.Vector(1, 0, 0)) - WireList = Sketch.Shape.Wires[0] - - # Create filleted wire at centre of thickness - wire_extr = WireList.extrude(sketch_normal * -50) - # Part.show(wire_extr,"wire_extr") - wire_extr_mir = WireList.extrude(sketch_normal * 50) - # Part.show(wire_extr_mir,"wire_extr_mir") - wire_extr = wire_extr.makeOffsetShape(thk / 2.0, 0.0, fill=False, join=2) - # Part.show(wire_extr,"wire_extr") - wire_extr_mir = wire_extr_mir.makeOffsetShape(-thk / 2.0, 0.0, fill=False, join=2) - # Part.show(wire_extr_mir,"wire_extr_mir") - if len(WireList.Edges) > 1: - filleted_extr = wire_extr.makeFillet((radius + thk / 2.0), wire_extr.Edges) - # Part.show(filleted_extr,"filleted_extr") - filleted_extr_mir = wire_extr_mir.makeFillet( - (radius + thk / 2.0), wire_extr_mir.Edges - ) - # Part.show(filleted_extr_mir,"filleted_extr_mir") - else: - filleted_extr = wire_extr - filleted_extr_mir = wire_extr_mir - # Part.show(filleted_extr,"filleted_extr") - sec_wirelist = filleted_extr_mir.section(filleted_extr) - # Part.show(sec_wirelist,"sec_wirelist") - - for edge in sec_wirelist.Edges: - if isinstance(edge.Curve, Part.Line): - LengthList.append(edge.Length) - - for i in range(len(WireList.Vertexes) - 1): - p1 = WireList.Vertexes[i].Point - p2 = WireList.Vertexes[i + 1].Point - e1 = p2 - p1 - # LengthList.append(e1.Length) - normal = e0.cross(e1) - coeff = sketch_normal.dot(normal) - if coeff >= 0: - sign = 1 - else: - sign = -1 - angle_rad = e0.getAngle(e1) - if sketchflip: - angle = sign * math.degrees(angle_rad) * -1 - else: - angle = sign * math.degrees(angle_rad) - bendAList.append(angle) - e0 = e1 - if sketchinvert: - LengthList.reverse() - bendAList.reverse() - # print(LengthList, bendAList) - return LengthList, bendAList - -def check_parallel(edge1, edge2): - v1 = edge1.Vertexes[0].Point - edge1.Vertexes[1].Point - v2 = edge2.Vertexes[0].Point - edge2.Vertexes[1].Point - if v1.isEqual(v2,0.00001): - return True, edge2.Vertexes[0].Point - edge1.Vertexes[0].Point - if v1.isEqual(v2,0.00001) or v1.isEqual(-v2,0.00001): - return True, edge2.Vertexes[0].Point - edge1.Vertexes[1].Point - return False, None - -def sheet_thk(MainObject, selFaceName): - selItem = MainObject.getElement(selFaceName) - selFace = smFace(selItem, MainObject) - # find the narrow edge - thk = 999999.0 - thkDir = None - if type(selItem) == Part.Face: - for edge in selFace.Edges: - if abs(edge.Length) < thk: - thk = abs(edge.Length) - else: - # if selected item is edge, try to find closest parallel edge - works better - # when object is refined and faces are not rectangle - for edge in selFace.Edges: - if edge.isSame(selItem): - continue - isParallel, distVect = check_parallel(selItem, edge) - if isParallel: - dist = distVect.Length - if dist < thk: - thk = dist - thkDir = distVect - thkDir.normalize() - return thk, thkDir - - -def smEdge(selFaceName, MainObject): - # find Edge, if Face Selected - selItem = MainObject.getElement(selFaceName) - thkDir = None - if type(selItem) == Part.Face: - # find the narrow edge - thk = 999999.0 - for edge in selItem.Edges: - if abs(edge.Length) < thk: - thk = abs(edge.Length) - thkEdge = edge - - # find a length edge = revolve axis direction - p0 = thkEdge.valueAt(thkEdge.FirstParameter) - for lenEdge in selItem.Edges: - p1 = lenEdge.valueAt(lenEdge.FirstParameter) - p2 = lenEdge.valueAt(lenEdge.LastParameter) - if lenEdge.isSame(thkEdge): - continue - if (p1 - p0).Length < smEpsilon: - revAxisV = p2 - p1 - break - if (p2 - p0).Length < smEpsilon: - revAxisV = p1 - p2 - break - seledge = lenEdge - selFace = selItem - elif type(selItem) == Part.Edge: - thk, thkDir = sheet_thk(MainObject, selFaceName) - seledge = selItem - selFace = smFace(selItem, MainObject) - p1 = seledge.valueAt(seledge.FirstParameter) - p2 = seledge.valueAt(seledge.LastParameter) - revAxisV = p2 - p1 - #print(str(revAxisV)) - return seledge, selFace, thk, revAxisV, thkDir - - -def getBendetail(selItemNames, MainObject, bendR, bendA, isflipped, offset, gap1, gap2): - mainlist = [] - edgelist = [] - nogap_edgelist = [] - for selItemName in selItemNames: - lenEdge, selFace, thk, revAxisV, thkDir = smEdge(selItemName, MainObject) - - # find the large face connected with selected face - list2 = MainObject.ancestorsOfType(lenEdge, Part.Face) - for Cface in list2: - if not (Cface.isSame(selFace)): - break - - # main Length Edge - revAxisV.normalize() - if thkDir is None: - pThkDir1 = selFace.CenterOfMass - pThkDir2 = lenEdge.Curve.value(lenEdge.Curve.parameter(pThkDir1)) - thkDir = pThkDir1.sub(pThkDir2).normalize() - #print(str(thkDir)) - FaceDir = selFace.normalAt(0, 0) - - # make sure the direction vector is correct in respect to the normal - if (thkDir.cross(revAxisV).normalize() - FaceDir).Length < smEpsilon: - revAxisV = revAxisV * -1 - - flipped = isflipped - # restrict angle - if bendA < 0: - bendA = -bendA - flipped = not flipped - - if type(MainObject.getElement(selItemName)) == Part.Edge: - flipped = not flipped - - if not (flipped): - revAxisP = lenEdge.valueAt(lenEdge.FirstParameter) + thkDir * (bendR + thk) - revAxisV = revAxisV * -1 - else: - revAxisP = lenEdge.valueAt(lenEdge.FirstParameter) + thkDir * -bendR - # Part.show(lenEdge,'lenEdge') - mainlist.append( - [ - Cface, - selFace, - thk, - lenEdge, - revAxisP, - revAxisV, - thkDir, - FaceDir, - bendA, - flipped, - ] - ) - if offset < 0.0: - dist = lenEdge.valueAt(lenEdge.FirstParameter).distanceToPlane( - FreeCAD.Vector(0, 0, 0), FaceDir - ) - # print(dist) - slice_wire = Cface.slice(FaceDir, dist + offset) - # print(slice_wire) - trimLenEdge = slice_wire[0].Edges[0] - else: - # Produce Offset Edge - trimLenEdge = lenEdge.copy() - trimLenEdge.translate(selFace.normalAt(0, 0) * offset) - # Part.show(trimLenEdge,'trimLenEdge1') - nogap_edgelist.append(trimLenEdge) - trimLenEdge = LineExtend(trimLenEdge, -gap1, -gap2) - # Part.show(trimLenEdge,'trimLenEdge2') - edgelist.append(trimLenEdge) - # print(mainlist) - trimedgelist = InsideEdge(edgelist) - nogaptrimedgelist = InsideEdge(nogap_edgelist) - return mainlist, trimedgelist, nogaptrimedgelist - - -def InsideEdge(edgelist): - import BOPTools.JoinFeatures - - newedgelist = [] - for i, e in enumerate(edgelist): - for j, ed in enumerate(edgelist): - if i != j: - section = e.section(ed) - if section.Vertexes: - edgeShape = BOPTools.JoinAPI.cutout_legacy(e, ed, tolerance=0.0) - e = edgeShape - # Part.show(e,"newedge") - newedgelist.append(e) - return newedgelist - - -def smMiter( - mainlist, - trimedgelist, - bendR=1.0, - miterA1=0.0, - miterA2=0.0, - extLen=10.0, - gap1=0.0, - gap2=0.0, - offset=0.0, - reliefD=1.0, - automiter=True, - extend1=0.0, - extend2=0.0, - mingap=0.1, - maxExtendGap=5.0, -): - if not (automiter): - miterA1List = [miterA1 for n in mainlist] - miterA2List = [miterA2 for n in mainlist] - gap1List = [gap1 for n in mainlist] - gap2List = [gap2 for n in mainlist] - extgap1List = [extend1 for n in mainlist] - extgap2List = [extend2 for n in mainlist] - else: - miterA1List = [0.0 for n in mainlist] - miterA2List = [0.0 for n in mainlist] - gap1List = [gap1 for n in mainlist] - gap2List = [gap2 for n in mainlist] - extgap1List = [extend1 for n in mainlist] - extgap2List = [extend2 for n in mainlist] - - facelist, tranfacelist = ([], []) - extfacelist, exttranfacelist = ([], []) - lenedgelist, tranedgelist = ([], []) - for i, sublist in enumerate(mainlist): - # find the narrow edge - ( - Cface, - selFace, - thk, - MlenEdge, - revAxisP, - revAxisV, - thkDir, - FaceDir, - bendA, - flipped, - ) = sublist - - # Produce Offset Edge - lenEdge = trimedgelist[i].copy() - #Part.show(lenEdge) - revAxisP = revAxisP + FaceDir * offset - - # narrow the wall, if we have gaps - BendFace = smMakeFace( - lenEdge, FaceDir, extLen, gap1 - extend1, gap2 - extend2, op="SMB" - ) - if BendFace.normalAt(0, 0) != thkDir: - BendFace.reverse() - #Part.show(BendFace) - transBendFace = BendFace.copy() - BendFace.rotate(revAxisP, revAxisV, bendA) - - #Part.show(BendFace,'BendFace') - facelist.append(BendFace) - transBendFace.translate(thkDir * thk) - transBendFace.rotate(revAxisP, revAxisV, bendA) - tranfacelist.append(transBendFace) - #Part.show(transBendFace,'transBendFace') - - # narrow the wall, if we have gaps - BendFace = smMakeFace( - lenEdge, - FaceDir, - extLen, - gap1 - extend1 - maxExtendGap, - gap2 - extend2 - maxExtendGap, - op="SMB", - ) - if BendFace.normalAt(0, 0) != thkDir: - BendFace.reverse() - - transBendFace = BendFace.copy() - BendFace.rotate(revAxisP, revAxisV, bendA) - #Part.show(BendFace,'BendFaceB') - extfacelist.append(BendFace) - transBendFace.translate(thkDir * thk) - transBendFace.rotate(revAxisP, revAxisV, bendA) - exttranfacelist.append(transBendFace) - #Part.show(transBendFace,'transBendFaceB') - - # edge_len = lenEdge.copy() - edge_len = LineExtend(lenEdge, (-gap1 + extend1), (-gap2 + extend2)) - edge_len.rotate(revAxisP, revAxisV, bendA) - lenedgelist.append(edge_len) - # Part.show(edge_len,'edge_len') - - # edge_len = lenEdge.copy() - edge_len = LineExtend(lenEdge, (-gap1 + extend1), (-gap2 + extend2)) - edge_len.translate(thkDir * thk) - edge_len.rotate(revAxisP, revAxisV, bendA) - tranedgelist.append(edge_len) - # Part.show(edge_len,'edge_len') - - # check faces intersect each other - for i in range(len(facelist)): - for j in range(len(lenedgelist)): - if ( - i != j - and facelist[i].isCoplanar(facelist[j]) - and not (getParallel(lenedgelist[i], lenedgelist[j])) - ): - # Part.show(lenedgelist[i],'edge_len1') - # Part.show(lenedgelist[j],'edge_len2') - gaps1, extgap1, cornerPoint1 = getGap( - lenedgelist[i], lenedgelist[j], maxExtendGap, mingap - ) - gaps2, extgap2, cornerPoint2 = getGap( - tranedgelist[i], tranedgelist[j], maxExtendGap, mingap - ) - # print([gaps1,gaps2, extgap1, extgap2]) - gaps = max(gaps1, gaps2) - extgap = min(extgap1, extgap2) - p1 = lenedgelist[j].valueAt(lenedgelist[j].FirstParameter) - p2 = lenedgelist[j].valueAt(lenedgelist[j].LastParameter) - Angle = LineAngle(lenedgelist[i], lenedgelist[j]) - # print(Angle) - if gaps > 0.0: - # walledge_common = lenedgelist[j].section(lenedgelist[i]) - # vp1 = walledge_common.Vertexes[0].Point - dist1 = (p1 - cornerPoint1).Length - dist2 = (p2 - cornerPoint1).Length - if abs(dist1) < abs(dist2): - miterA1List[j] = Angle / 2.0 - if gaps > 0.0: - gap1List[j] = gaps - else: - gap1List[j] = 0.0 - elif abs(dist2) < abs(dist1): - miterA2List[j] = Angle / 2.0 - if gaps > 0.0: - gap2List[j] = gaps - else: - gap2List[j] = 0.0 - elif extgap != 0.0 and (extgap + mingap) < maxExtendGap: - wallface_common = facelist[j].common(facelist[i]) - dist1 = (p1 - cornerPoint1).Length - dist2 = (p2 - cornerPoint1).Length - if abs(dist1) < abs(dist2): - if wallface_common.Faces: - miterA1List[j] = Angle / 2.0 - else: - miterA1List[j] = -Angle / 2.0 - if extgap > 0.0: - extgap1List[j] = extgap - else: - extgap1List[j] = 0.0 - elif abs(dist2) < abs(dist1): - if wallface_common.Faces: - miterA2List[j] = Angle / 2.0 - else: - miterA2List[j] = -Angle / 2.0 - if extgap > 0.0: - extgap2List[j] = extgap - else: - extgap2List[j] = 0.0 - elif i != j and not (getParallel(lenedgelist[i], lenedgelist[j])): - # Part.show(lenedgelist[i],'edge_len1') - # Part.show(lenedgelist[j],'edge_len2') - # Part.show(tranedgelist[i],'edge_len1') - # Part.show(tranedgelist[j],'edge_len2') - gaps1, extgap1, cornerPoint1 = getGap( - lenedgelist[i], lenedgelist[j], maxExtendGap, mingap - ) - gaps2, extgap2, cornerPoint2 = getGap( - tranedgelist[i], tranedgelist[j], maxExtendGap, mingap - ) - # print([gaps1, gaps2, extgap1, extgap2]) - gaps = max(gaps1, gaps2) - extgap = min(extgap1, extgap2) - p1 = lenedgelist[j].valueAt(lenedgelist[j].FirstParameter) - p2 = lenedgelist[j].valueAt(lenedgelist[j].LastParameter) - if gaps > 0.0: - wallface_common = facelist[j].section(facelist[i]) - # Part.show(facelist[j],'facelist') - # Part.show(facelist[i],'facelist') - wallface_common1 = tranfacelist[j].section(tranfacelist[i]) - # Part.show(tranfacelist[j],'tranfacelist') - # Part.show(tranfacelist[i],'tranfacelist') - # Part.show(wallface_common,'wallface_common') - if wallface_common.Edges: - vp1 = wallface_common.Vertexes[0].Point - vp2 = wallface_common.Vertexes[1].Point - elif wallface_common1.Edges: - vp1 = wallface_common1.Vertexes[0].Point - vp2 = wallface_common1.Vertexes[1].Point - dist1 = (p1 - vp1).Length - dist2 = (p2 - vp1).Length - if abs(dist1) < abs(dist2): - edgedir = (p1 - p2).normalize() - dist3 = (cornerPoint1 - vp1).Length - dist4 = (cornerPoint1 - vp2).Length - if dist4 < dist3: - lineDir = (vp2 - vp1).normalize() - else: - lineDir = (vp1 - vp2).normalize() - angle1 = edgedir.getAngle(lineDir) - Angle2 = math.degrees(angle1) - Angle = 90 - Angle2 - # print([Angle, Angle2, 'ext']) - miterA1List[j] = Angle - if gaps > 0.0: - gap1List[j] = gaps - else: - gap1List[j] = 0.0 - elif abs(dist2) < abs(dist1): - edgedir = (p2 - p1).normalize() - dist3 = (cornerPoint1 - vp1).Length - dist4 = (cornerPoint1 - vp2).Length - if dist4 < dist3: - lineDir = (vp2 - vp1).normalize() - else: - lineDir = (vp1 - vp2).normalize() - angle1 = edgedir.getAngle(lineDir) - Angle2 = math.degrees(angle1) - Angle = 90 - Angle2 - # print([Angle, Angle2, 'ext']) - miterA2List[j] = Angle - if gaps > 0.0: - gap2List[j] = gaps - else: - gap2List[j] = 0.0 - elif extgap != 0.0 and (extgap + mingap) < maxExtendGap: - wallface_common = extfacelist[j].section(extfacelist[i]) - # Part.show(extfacelist[j],'extfacelist') - # Part.show(extfacelist[i],'extfacelist') - wallface_common1 = exttranfacelist[j].section( - exttranfacelist[i] - ) - # Part.show(exttranfacelist[j],'exttranfacelist') - # Part.show(exttranfacelist[i],'exttranfacelist') - # Part.show(wallface_common,'wallface_common') - if wallface_common.Edges: - vp1 = wallface_common.Vertexes[0].Point - vp2 = wallface_common.Vertexes[1].Point - elif wallface_common1.Edges: - vp1 = wallface_common1.Vertexes[0].Point - vp2 = wallface_common1.Vertexes[1].Point - dist1 = (p1 - vp1).Length - dist2 = (p2 - vp1).Length - if abs(dist1) < abs(dist2): - edgedir = (p1 - p2).normalize() - dist3 = (cornerPoint1 - vp1).Length - dist4 = (cornerPoint1 - vp2).Length - if dist4 < dist3: - lineDir = (vp2 - vp1).normalize() - else: - lineDir = (vp1 - vp2).normalize() - angle1 = edgedir.getAngle(lineDir) - Angle2 = math.degrees(angle1) - Angle = 90 - Angle2 - # print([Angle, Angle2, 'ext']) - miterA1List[j] = Angle - if extgap > 0.0: - extgap1List[j] = extgap - else: - extgap1List[j] = 0.0 - elif abs(dist2) < abs(dist1): - edgedir = (p2 - p1).normalize() - dist3 = (cornerPoint1 - vp1).Length - dist4 = (cornerPoint1 - vp2).Length - if dist4 < dist3: - lineDir = (vp2 - vp1).normalize() - else: - lineDir = (vp1 - vp2).normalize() - angle1 = edgedir.getAngle(lineDir) - Angle2 = math.degrees(angle1) - Angle = 90 - Angle2 - # print([Angle, Angle2, 'ext']) - miterA2List[j] = Angle - if extgap > 0.0: - extgap2List[j] = extgap - else: - extgap2List[j] = 0.0 - - # print(miterA1List, miterA2List, gap1List, gap2List, extgap1List, extgap2List) - return miterA1List, miterA2List, gap1List, gap2List, extgap1List, extgap2List - - -def smBend( - thk, - bendR=1.0, - bendA=90.0, - miterA1=0.0, - miterA2=0.0, - BendType="Material Outside", - flipped=False, - unfold=False, - offset=0.0, - extLen=10.0, - gap1=0.0, - gap2=0.0, - reliefType="Rectangle", - reliefW=0.8, - reliefD=1.0, - minReliefgap=1.0, - extend1=0.0, - extend2=0.0, - kfactor=0.45, - ReliefFactor=0.7, - UseReliefFactor=False, - selFaceNames="", - MainObject=None, - maxExtendGap=5.0, - mingap=0.1, - automiter=True, - sketch=None, - extendType="Simple", - LengthSpec="Leg", -): - # if sketch is as wall - sketches = False - if sketch: - if sketch.Shape.Wires[0].isClosed(): - sketches = True - else: - pass - - # Add Bend Type details - if BendType == "Material Outside": - offset = 0.0 - inside = False - elif BendType == "Material Inside": - offset = -(thk + bendR) - inside = True - elif BendType == "Thickness Outside": - offset = -bendR - inside = True - elif BendType == "Offset": - if offset < 0.0: - inside = True - else: - inside = False - - if LengthSpec == "Leg": - pass - elif LengthSpec == "Tangential": - if bendA >= 90.0: - extLen -= thk + bendR - else: - extLen -= (bendR + thk) / math.tan(math.radians(90.0 - bendA / 2)) - elif LengthSpec == "Inner Sharp": - extLen -= (bendR) / math.tan(math.radians(90.0 - bendA / 2)) - elif LengthSpec == "Outer Sharp": - extLen -= (bendR + thk) / math.tan(math.radians(90.0 - bendA / 2)) - - if not (sketches): - mainlist, trimedgelist, nogaptrimedgelist = getBendetail( - selFaceNames, MainObject, bendR, bendA, flipped, offset, gap1, gap2 - ) - ( - miterA1List, - miterA2List, - gap1List, - gap2List, - extend1List, - extend2List, - ) = smMiter( - mainlist, - trimedgelist, - bendR=bendR, - miterA1=miterA1, - miterA2=miterA2, - extLen=extLen, # gap1 = gap1, gap2 = gap2, - offset=offset, - automiter=automiter, - extend1=extend1, - extend2=extend2, - mingap=mingap, - maxExtendGap=maxExtendGap, - ) - - # print(miterA1List, miterA2List, gap1List, gap2List, extend1List, extend2List) - else: - ( - miterA1List, - miterA2List, - gap1List, - gap2List, - extend1List, - extend2List, - reliefDList, - ) = ([0.0], [0.0], [gap1], [gap2], [extend1], [extend2], [reliefD]) - agap1, agap2 = gap1, gap2 - # print([agap1,agap1]) - - # mainlist = getBendetail(selFaceNames, MainObject, bendR, bendA, flipped) - thk_faceList = [] - resultSolid = MainObject - for i, sublist in enumerate(mainlist): - # find the narrow edge - ( - Cface, - selFace, - thk, - AlenEdge, - revAxisP, - revAxisV, - thkDir, - FaceDir, - bendA, - flipped, - ) = sublist - gap1, gap2 = (gap1List[i], gap2List[i]) - # print([gap1,gap2]) - extend1, extend2 = (extend1List[i], extend2List[i]) - # Part.show(lenEdge,'lenEdge1') - selFace = smModifiedFace(selFace, resultSolid) - # Part.show(selFace,'selFace') - Cface = smModifiedFace(Cface, resultSolid) - # Part.show(Cface,'Cface') - # main Length Edge - MlenEdge = smGetEdge(AlenEdge, resultSolid) - # Part.show(MlenEdge,'MlenEdge') - lenEdge = trimedgelist[i] - noGap_lenEdge = nogaptrimedgelist[i] - leng = lenEdge.Length - # Part.show(lenEdge,'lenEdge') - - # Add as offset to set any distance - if UseReliefFactor: - reliefW = thk * ReliefFactor - reliefD = thk * ReliefFactor - - # if sketch is as wall - sketches = False - if sketch: - if sketch.Shape.Wires[0].isClosed(): - sketches = True - else: - pass - - if sketches: - sketch_face = Part.makeFace(sketch.Shape.Wires, "Part::FaceMakerBullseye") - sketch_face.translate(thkDir * -thk) - if inside: - sketch_face.translate(FaceDir * offset) - sketch_Shape = lenEdge.common(sketch_face) - sketch_Edge = sketch_Shape.Edges[0] - gap1 = ( - lenEdge.valueAt(lenEdge.FirstParameter) - - sketch_Edge.valueAt(sketch_Edge.FirstParameter) - ).Length - gap2 = ( - lenEdge.valueAt(lenEdge.LastParameter) - - sketch_Edge.valueAt(sketch_Edge.LastParameter) - ).Length - - # CutSolids list for collecting Solids - CutSolids = [] - # remove relief if needed - if reliefD > 0.0 and reliefW > 0.0: - if agap1 > minReliefgap: - reliefFace1 = smMakeReliefFace( - lenEdge, - FaceDir * -1, - gap1 - reliefW, - reliefW, - reliefD, - reliefType, - op="SMF", - ) - reliefSolid1 = reliefFace1.extrude(thkDir * thk) - # Part.show(reliefSolid1, "reliefSolid1") - CutSolids.append(reliefSolid1) - if inside: - reliefFace1 = smMakeReliefFace( - lenEdge, - FaceDir * -1, - gap1 - reliefW, - reliefW, - offset, - reliefType, - op="SMF", - ) - reliefSolid1 = reliefFace1.extrude(thkDir * thk) - # Part.show(reliefSolid1, "reliefSolid1") - CutSolids.append(reliefSolid1) - if agap2 > minReliefgap: - reliefFace2 = smMakeReliefFace( - lenEdge, - FaceDir * -1, - lenEdge.Length - gap2, - reliefW, - reliefD, - reliefType, - op="SMFF", - ) - reliefSolid2 = reliefFace2.extrude(thkDir * thk) - # Part.show(reliefSolid2, "reliefSolid2") - CutSolids.append(reliefSolid2) - if inside: - reliefFace2 = smMakeReliefFace( - lenEdge, - FaceDir * -1, - lenEdge.Length - gap2, - reliefW, - offset, - reliefType, - op="SMFF", - ) - reliefSolid2 = reliefFace2.extrude(thkDir * thk) - # Part.show(reliefSolid2,"reliefSolid2") - CutSolids.append(reliefSolid2) - - # remove bend face if present - if inside: - if ( - MlenEdge.Vertexes[0].Point - MlenEdge.valueAt(MlenEdge.FirstParameter) - ).Length < smEpsilon: - vertex0 = MlenEdge.Vertexes[0] - vertex1 = MlenEdge.Vertexes[1] - else: - vertex1 = MlenEdge.Vertexes[0] - vertex0 = MlenEdge.Vertexes[1] - Noffset_1 = abs( - ( - MlenEdge.valueAt(MlenEdge.FirstParameter) - - noGap_lenEdge.valueAt(noGap_lenEdge.FirstParameter) - ).Length - ) - Noffset_2 = abs( - ( - MlenEdge.valueAt(MlenEdge.FirstParameter) - - noGap_lenEdge.valueAt(noGap_lenEdge.LastParameter) - ).Length - ) - Noffset1 = min(Noffset_1, Noffset_2) - Noffset_1 = abs( - ( - MlenEdge.valueAt(MlenEdge.LastParameter) - - noGap_lenEdge.valueAt(noGap_lenEdge.FirstParameter) - ).Length - ) - Noffset_2 = abs( - ( - MlenEdge.valueAt(MlenEdge.LastParameter) - - noGap_lenEdge.valueAt(noGap_lenEdge.LastParameter) - ).Length - ) - Noffset2 = min(Noffset_1, Noffset_2) - # print([Noffset1, Noffset1]) - if agap1 <= minReliefgap: - Edgelist = selFace.ancestorsOfType(vertex0, Part.Edge) - for ed in Edgelist: - if not (MlenEdge.isSame(ed)): - list1 = resultSolid.ancestorsOfType(ed, Part.Face) - for Rface in list1: - # print(type(Rface.Surface)) - if not (selFace.isSame(Rface)): - for edge in Rface.Edges: - # print(type(edge.Curve)) - if issubclass( - type(edge.Curve), - (Part.Circle or Part.BSplineSurface), - ): - RfaceE = Rface.makeOffsetShape( - -Noffset1, 0.0, fill=True - ) - # Part.show(RfaceE,"RfaceSolid1") - CutSolids.append(RfaceE) - break - if agap2 <= minReliefgap: - Edgelist = selFace.ancestorsOfType(vertex1, Part.Edge) - for ed in Edgelist: - if not (MlenEdge.isSame(ed)): - list1 = resultSolid.ancestorsOfType(ed, Part.Face) - for Rface in list1: - # print(type(Rface.Surface)) - if not (selFace.isSame(Rface)): - for edge in Rface.Edges: - # print(type(edge.Curve)) - if issubclass( - type(edge.Curve), - (Part.Circle or Part.BSplineSurface), - ): - RfaceE = Rface.makeOffsetShape( - -Noffset2, 0.0, fill=True - ) - # Part.show(RfaceE,"RfaceSolid2") - CutSolids.append(RfaceE) - break - - # remove offset solid from sheetmetal, if inside offset - Ref_lenEdge = lenEdge.copy().translate(FaceDir * -offset) - cutgap_1 = ( - AlenEdge.valueAt(AlenEdge.FirstParameter) - - Ref_lenEdge.valueAt(Ref_lenEdge.FirstParameter) - ).Length - cutgap_2 = ( - AlenEdge.valueAt(AlenEdge.FirstParameter) - - Ref_lenEdge.valueAt(Ref_lenEdge.LastParameter) - ).Length - cutgap1 = min(cutgap_1, cutgap_2) - dist = AlenEdge.valueAt(AlenEdge.FirstParameter).distanceToLine( - Ref_lenEdge.Curve.Location, Ref_lenEdge.Curve.Direction - ) - # print(dist) - if dist < smEpsilon: - cutgap1 = cutgap1 * -1.0 - cutgap_1 = ( - AlenEdge.valueAt(AlenEdge.LastParameter) - - Ref_lenEdge.valueAt(Ref_lenEdge.FirstParameter) - ).Length - cutgap_2 = ( - AlenEdge.valueAt(AlenEdge.LastParameter) - - Ref_lenEdge.valueAt(Ref_lenEdge.LastParameter) - ).Length - cutgap2 = min(cutgap_1, cutgap_2) - dist = AlenEdge.valueAt(AlenEdge.LastParameter).distanceToLine( - Ref_lenEdge.Curve.Location, Ref_lenEdge.Curve.Direction - ) - # print(dist) - if dist < smEpsilon: - cutgap2 = cutgap2 * -1.0 - # print([cutgap1, cutgap2]) - CutFace = smMakeFace(AlenEdge, thkDir, thk, cutgap1, cutgap2, op="SMC") - # Part.show(CutFace2,"CutFace2") - CutSolid = CutFace.extrude(FaceDir * offset) - # Part.show(CutSolid,"CutSolid") - CfaceSolid = Cface.extrude(thkDir * thk) - CutSolid = CutSolid.common(CfaceSolid) - CutSolids.append(CutSolid) - - # Produce Main Solid for Inside Bends - if CutSolids: - if len(CutSolids) == 1: - resultSolid = resultSolid.cut(CutSolids[0]) - else: - Solid = CutSolids[0].multiFuse(CutSolids[1:]) - Solid.removeSplitter() - # Part.show(Solid) - resultSolid = resultSolid.cut(Solid) - - # Produce Offset Solid - if offset > 0.0: - # create wall - offset_face = smMakeFace(lenEdge, FaceDir, -offset, op="SMO") - OffsetSolid = offset_face.extrude(thkDir * thk) - resultSolid = resultSolid.fuse(OffsetSolid) - - # Adjust revolving center to new point - if not (flipped): - revAxisP = lenEdge.valueAt(lenEdge.FirstParameter) + thkDir * (bendR + thk) - else: - revAxisP = lenEdge.valueAt(lenEdge.FirstParameter) + thkDir * -bendR - - # wallSolid = None - if sketches: - Wall_face = Part.makeFace(sketch.Shape.Wires, "Part::FaceMakerBullseye") - if inside: - Wall_face.translate(FaceDir * offset) - FaceAxisP = sketch_Edge.valueAt(sketch_Edge.FirstParameter) + thkDir * thk - FaceAxisV = sketch_Edge.valueAt( - sketch_Edge.FirstParameter - ) - sketch_Edge.valueAt(sketch_Edge.LastParameter) - Wall_face.rotate(FaceAxisP, FaceAxisV, -90.0) - wallSolid = Wall_face.extrude(thkDir * -thk) - # Part.show(wallSolid) - wallSolid.rotate(revAxisP, revAxisV, bendA) - - elif extLen > 0.0: - # create wall - Wall_face = smMakeFace( - lenEdge, - FaceDir, - extLen, - gap1 - extend1, - gap2 - extend2, - miterA1List[i], - miterA2List[i], - op="SMW", - ) - wallSolid = Wall_face.extrude(thkDir * thk) - # Part.show(wallSolid,"wallSolid") - wallSolid.rotate(revAxisP, revAxisV, bendA) - # Part.show(wallSolid.Faces[2]) - thk_faceList.append(wallSolid.Faces[2]) - - # Produce bend Solid - if not (unfold): - if bendA > 0.0: - # create bend - # narrow the wall if we have gaps - revFace = smMakeFace(lenEdge, thkDir, thk, gap1, gap2, op="SMR") - if revFace.normalAt(0, 0) != FaceDir: - revFace.reverse() - bendSolid = revFace.revolve(revAxisP, revAxisV, bendA) - # Part.show(bendSolid) - resultSolid = resultSolid.fuse(bendSolid) - if wallSolid: - resultSolid = resultSolid.fuse(wallSolid) - # Part.show(resultSolid,"resultSolid") - - # Produce unfold Solid - else: - if bendA > 0.0: - # create bend - unfoldLength = (bendR + kfactor * thk) * bendA * math.pi / 180.0 - # narrow the wall if we have gaps - unfoldFace = smMakeFace(lenEdge, thkDir, thk, gap1, gap2, op="SMR") - if unfoldFace.normalAt(0, 0) != FaceDir: - unfoldFace.reverse() - unfoldSolid = unfoldFace.extrude(FaceDir * unfoldLength) - # Part.show(unfoldSolid) - resultSolid = resultSolid.fuse(unfoldSolid) - - if extLen > 0.0: - wallSolid.rotate(revAxisP, revAxisV, -bendA) - # Part.show(wallSolid, "wallSolid") - wallSolid.translate(FaceDir * unfoldLength) - resultSolid = resultSolid.fuse(wallSolid) - # Part.show(resultSolid, "resultSolid") - return resultSolid, thk_faceList - - -class SMBendWall: - def __init__(self, obj): - '''"Add Wall with radius bend"''' - selobj = Gui.Selection.getSelectionEx()[0] - self._addProperties(obj) - - _tip_ = FreeCAD.Qt.translate("App::Property", "Base Object") - obj.addProperty( - "App::PropertyLinkSub", "baseObject", "Parameters", _tip_ - ).baseObject = (selobj.Object, selobj.SubElementNames) - obj.Proxy = self - - def _addProperties(self, obj): - smAddLengthProperty( - obj, "radius", FreeCAD.Qt.translate("App::Property", "Bend Radius"), 1.0 - ) - smAddLengthProperty( - obj, "length", FreeCAD.Qt.translate("App::Property", "Length of Wall"), 10.0 - ) - smAddDistanceProperty( - obj, - "gap1", - FreeCAD.Qt.translate("App::Property", "Gap from Left Side"), - 0.0, - ) - smAddDistanceProperty( - obj, - "gap2", - FreeCAD.Qt.translate("App::Property", "Gap from Right Side"), - 0.0, - ) - smAddBoolProperty( - obj, - "invert", - FreeCAD.Qt.translate("App::Property", "Invert Bend Direction"), - False, - ) - smAddAngleProperty( - obj, "angle", FreeCAD.Qt.translate("App::Property", "Bend Angle"), 90.0 - ) - smAddDistanceProperty( - obj, - "extend1", - FreeCAD.Qt.translate("App::Property", "Extend from Left Side"), - 0.0, - ) - smAddDistanceProperty( - obj, - "extend2", - FreeCAD.Qt.translate("App::Property", "Extend from Right Side"), - 0.0, - ) - smAddEnumProperty( - obj, - "BendType", - FreeCAD.Qt.translate("App::Property", "Bend Type"), - ["Material Outside", "Material Inside", "Thickness Outside", "Offset"], - ) - smAddEnumProperty( - obj, - "LengthSpec", - FreeCAD.Qt.translate("App::Property", "Type of Length Specification"), - ["Leg", "Outer Sharp", "Inner Sharp", "Tangential"], - ) - smAddLengthProperty( - obj, - "reliefw", - FreeCAD.Qt.translate("App::Property", "Relief Width"), - 0.8, - "ParametersRelief", - ) - smAddLengthProperty( - obj, - "reliefd", - FreeCAD.Qt.translate("App::Property", "Relief Depth"), - 1.0, - "ParametersRelief", - ) - smAddBoolProperty( - obj, - "UseReliefFactor", - FreeCAD.Qt.translate("App::Property", "Use Relief Factor"), - False, - "ParametersRelief", - ) - smAddEnumProperty( - obj, - "reliefType", - FreeCAD.Qt.translate("App::Property", "Relief Type"), - ["Rectangle", "Round"], - None, - "ParametersRelief", - ) - smAddFloatProperty( - obj, - "ReliefFactor", - FreeCAD.Qt.translate("App::Property", "Relief Factor"), - 0.7, - "ParametersRelief", - ) - smAddAngleProperty( - obj, - "miterangle1", - FreeCAD.Qt.translate("App::Property", "Bend Miter Angle from Left Side"), - 0.0, - "ParametersMiterangle", - ) - smAddAngleProperty( - obj, - "miterangle2", - FreeCAD.Qt.translate("App::Property", "Bend Miter Angle from Right Side"), - 0.0, - "ParametersMiterangle", - ) - smAddLengthProperty( - obj, - "minGap", - FreeCAD.Qt.translate("App::Property", "Auto Miter Minimum Gap"), - 0.2, - "ParametersEx", - ) - smAddLengthProperty( - obj, - "maxExtendDist", - FreeCAD.Qt.translate("App::Property", "Auto Miter maximum Extend Distance"), - 5.0, - "ParametersEx", - ) - smAddLengthProperty( - obj, - "minReliefGap", - FreeCAD.Qt.translate("App::Property", "Minimum Gap to Relief Cut"), - 1.0, - "ParametersEx", - ) - smAddDistanceProperty( - obj, - "offset", - FreeCAD.Qt.translate("App::Property", "Offset Bend"), - 0.0, - "ParametersEx", - ) - smAddBoolProperty( - obj, - "AutoMiter", - FreeCAD.Qt.translate("App::Property", "Enable Auto Miter"), - True, - "ParametersEx", - ) - smAddBoolProperty( - obj, - "unfold", - FreeCAD.Qt.translate("App::Property", "Shows Unfold View of Current Bend"), - False, - "ParametersEx", - ) - smAddProperty( - obj, - "App::PropertyFloatConstraint", - "kfactor", - FreeCAD.Qt.translate( - "App::Property", - "Location of Neutral Line. Caution: Using ANSI standards, not DIN.", - ), - (0.5, 0.0, 1.0, 0.01), - "ParametersEx", - ) - smAddBoolProperty( - obj, - "sketchflip", - FreeCAD.Qt.translate("App::Property", "Flip Sketch Direction"), - False, - "ParametersEx2", - ) - smAddBoolProperty( - obj, - "sketchinvert", - FreeCAD.Qt.translate("App::Property", "Invert Sketch Start"), - False, - "ParametersEx2", - ) - smAddProperty( - obj, - "App::PropertyLink", - "Sketch", - FreeCAD.Qt.translate("App::Property", "Sketch Object"), - None, - "ParametersEx2", - ) - smAddProperty( - obj, - "App::PropertyFloatList", - "LengthList", - FreeCAD.Qt.translate("App::Property", "Length of Wall List"), - None, - "ParametersEx3", - ) - smAddProperty( - obj, - "App::PropertyFloatList", - "bendAList", - FreeCAD.Qt.translate("App::Property", "Bend Angle List"), - None, - "ParametersEx3", - ) - - def getElementMapVersion(self, _fp, ver, _prop, restored): - if not restored: - return smElementMapVersion + ver - - def execute(self, fp): - '''"Print a short message when doing a recomputation, this method is mandatory"''' - - self._addProperties(fp) - - # 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) - - # get LengthList, bendAList - bendAList = [fp.angle.Value] - LengthList = [fp.length.Value] - # print face - - # pass selected object shape - Main_Object = fp.baseObject[0].Shape.copy() - face = fp.baseObject[1] - thk, thkDir = sheet_thk(Main_Object, face[0]) - - if fp.Sketch: - WireList = fp.Sketch.Shape.Wires[0] - if not (WireList.isClosed()): - LengthList, bendAList = getSketchDetails( - fp.Sketch, fp.sketchflip, fp.sketchinvert, fp.radius.Value, thk - ) - else: - if fp.Sketch.Support: - fp.baseObject = (fp.Sketch.Support[0][0], fp.Sketch.Support[0][1]) - LengthList = [10.0] - fp.LengthList = LengthList - fp.bendAList = bendAList - # print(LengthList, bendAList) - - # extend value needed for first bend set only - extend1_list = [0.0 for n in LengthList] - extend2_list = [0.0 for n in LengthList] - 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 LengthList] - gap2_list = [0.0 for n in LengthList] - gap1_list[0] = fp.gap1.Value - gap2_list[0] = fp.gap2.Value - # print(gap1_list, gap2_list) - - for i in range(len(LengthList)): - s, f = smBend( - thk, - bendR=fp.radius.Value, - bendA=bendAList[i], - miterA1=fp.miterangle1.Value, - miterA2=fp.miterangle2.Value, - BendType=fp.BendType, - flipped=fp.invert, - unfold=fp.unfold, - extLen=LengthList[i], - 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=fp.offset.Value, - ReliefFactor=fp.ReliefFactor, - UseReliefFactor=fp.UseReliefFactor, - automiter=fp.AutoMiter, - selFaceNames=face, - MainObject=Main_Object, - sketch=fp.Sketch, - mingap=fp.minGap.Value, - maxExtendGap=fp.maxExtendDist.Value, - LengthSpec=fp.LengthSpec, - ) - faces = smGetFace(f, s) - face = faces - Main_Object = s - - fp.Shape = s - fp.baseObject[0].ViewObject.Visibility = False - if fp.Sketch: - fp.Sketch.ViewObject.Visibility = False - - -class SMViewProviderTree: - "A View provider that nests children objects under the created one" - - def __init__(self, obj): - obj.Proxy = self - self.Object = obj.Object - - def attach(self, obj): - self.Object = obj.Object - return - - def updateData(self, fp, prop): - return - - def getDisplayModes(self, obj): - modes = [] - return modes - - def setDisplayMode(self, mode): - return mode - - def onChanged(self, vp, prop): - return - - def __getstate__(self): - # return {'ObjectName' : self.Object.Name} - return None - - def __setstate__(self, state): - self.loads(state) - - # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 - def dumps(self): - return None - - def loads(self, state): - if state is not None: - import FreeCAD - - doc = FreeCAD.ActiveDocument # crap - self.Object = doc.getObject(state["ObjectName"]) - - def claimChildren(self): - objs = [] - if hasattr(self.Object, "baseObject"): - objs.append(self.Object.baseObject[0]) - if hasattr(self.Object, "Sketch"): - objs.append(self.Object.Sketch) - return objs - - def getIcon(self): - return os.path.join(iconPath, "SheetMetal_AddWall.svg") - - def setEdit(self, vobj, mode): - taskd = SMBendWallTaskPanel() - taskd.obj = vobj.Object - taskd.update() - self.Object.ViewObject.Visibility = False - self.Object.baseObject[0].ViewObject.Visibility = True - FreeCADGui.Control.showDialog(taskd) - return True - - def unsetEdit(self, vobj, mode): - FreeCADGui.Control.closeDialog() - self.Object.baseObject[0].ViewObject.Visibility = False - self.Object.ViewObject.Visibility = True - return False - - -class SMViewProviderFlat: - "A View provider that places objects flat under base object" - - def __init__(self, obj): - obj.Proxy = self - self.Object = obj.Object - - def attach(self, obj): - self.Object = obj.Object - return - - def setupContextMenu(self, viewObject, menu): - action = menu.addAction( - FreeCAD.Qt.translate("QObject", "Edit %1").replace( - "%1", viewObject.Object.Label - ) - ) - action.triggered.connect(lambda: self.startDefaultEditMode(viewObject)) - return False - - def startDefaultEditMode(self, viewObject): - document = viewObject.Document.Document - if not document.HasPendingTransaction: - text = FreeCAD.Qt.translate("QObject", "Edit %1").replace( - "%1", viewObject.Object.Label - ) - document.openTransaction(text) - viewObject.Document.setEdit(viewObject.Object, 0) - - def updateData(self, fp, prop): - return - - def getDisplayModes(self, obj): - modes = [] - return modes - - def setDisplayMode(self, mode): - return mode - - def onChanged(self, vp, prop): - return - - def __getstate__(self): - # return {'ObjectName' : self.Object.Name} - return None - - def __setstate__(self, state): - self.loads(state) - - # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 - def dumps(self): - return None - - def loads(self, state): - if state is not None: - import FreeCAD - - doc = FreeCAD.ActiveDocument # crap - self.Object = doc.getObject(state["ObjectName"]) - - def claimChildren(self): - objs = [] - if hasattr(self.Object, "Sketch"): - objs.append(self.Object.Sketch) - return objs - - def getIcon(self): - return os.path.join(iconPath, "SheetMetal_AddWall.svg") - - def setEdit(self, vobj, mode): - taskd = SMBendWallTaskPanel() - taskd.obj = vobj.Object - taskd.update() - self.Object.ViewObject.Visibility = False - self.Object.baseObject[0].ViewObject.Visibility = True - FreeCADGui.Control.showDialog(taskd) - return True - - def unsetEdit(self, vobj, mode): - FreeCADGui.Control.closeDialog() - self.Object.baseObject[0].ViewObject.Visibility = False - self.Object.ViewObject.Visibility = True - return False - - -class SMBendWallTaskPanel: - """A TaskPanel for the Sheetmetal""" - - def __init__(self): - self.obj = None - self.form = QtGui.QWidget() - self.form.setObjectName("SMBendWallTaskPanel") - self.form.setWindowTitle("Binded faces/edges list") - self.grid = QtGui.QGridLayout(self.form) - self.grid.setObjectName("grid") - self.title = QtGui.QLabel(self.form) - self.grid.addWidget(self.title, 0, 0, 1, 2) - self.title.setText("Select new face(s)/Edge(s) and press Update") - - # tree - self.tree = QtGui.QTreeWidget(self.form) - self.grid.addWidget(self.tree, 1, 0, 1, 2) - self.tree.setColumnCount(2) - self.tree.setHeaderLabels(["Name", "Subelement"]) - - # buttons - self.addButton = QtGui.QPushButton(self.form) - self.addButton.setObjectName("addButton") - self.addButton.setIcon( - QtGui.QIcon(os.path.join(iconPath, "SheetMetal_Update.svg")) - ) - self.grid.addWidget(self.addButton, 3, 0, 1, 2) - - QtCore.QObject.connect( - self.addButton, QtCore.SIGNAL("clicked()"), self.updateElement - ) - self.update() - - def isAllowedAlterSelection(self): - return True - - def isAllowedAlterView(self): - return True - - def getStandardButtons(self): - return QtGui.QDialogButtonBox.Ok - - def update(self): - "fills the treewidget" - self.tree.clear() - if self.obj: - f = self.obj.baseObject - if isinstance(f[1], list): - for subf in f[1]: - # FreeCAD.Console.PrintLog("item: " + subf + "\n") - item = QtGui.QTreeWidgetItem(self.tree) - item.setText(0, f[0].Name) - item.setIcon(0, QtGui.QIcon(":/icons/Tree_Part.svg")) - item.setText(1, subf) - else: - item = QtGui.QTreeWidgetItem(self.tree) - item.setText(0, f[0].Name) - item.setIcon(0, QtGui.QIcon(":/icons/Tree_Part.svg")) - item.setText(1, f[1][0]) - self.retranslateUi(self.form) - - def updateElement(self): - if not self.obj: - return - - sel = FreeCADGui.Selection.getSelectionEx()[0] - if not sel.HasSubObjects: - self.update() - return - - obj = sel.Object - for elt in sel.SubElementNames: - if "Face" in elt or "Edge" in elt: - face = self.obj.baseObject - found = False - if face[0] == obj.Name: - if isinstance(face[1], tuple): - for subf in face[1]: - if subf == elt: - found = True - else: - if face[1][0] == elt: - found = True - if not found: - self.obj.baseObject = (sel.Object, sel.SubElementNames) - self.update() - - def accept(self): - FreeCAD.ActiveDocument.recompute() - FreeCADGui.ActiveDocument.resetEdit() - # self.obj.ViewObject.Visibility=True - return True - - def retranslateUi(self, TaskPanel): - # TaskPanel.setWindowTitle(QtGui.QApplication.translate("draft", "Faces", None)) - self.addButton.setText(QtGui.QApplication.translate("draft", "Update", None)) - - -class AddWallCommandClass: - """Add Wall command""" - - def GetResources(self): - return { - "Pixmap": os.path.join( - iconPath, "SheetMetal_AddWall.svg" - ), # the name of a svg file available in the resources - "MenuText": FreeCAD.Qt.translate("SheetMetal", "Make Wall"), - "Accel": "W", - "ToolTip": FreeCAD.Qt.translate( - "SheetMetal", - "Extends one or more face, connected by a bend on existing sheet metal.\n" - "1. Select edges or thickness side faces 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 - selobj = Gui.Selection.getSelectionEx()[0].Object - viewConf = SheetMetalBaseCmd.GetViewConfig(selobj) - if hasattr(view, "getActiveObject"): - activeBody = view.getActiveObject("pdbody") - if not smIsOperationLegal(activeBody, selobj): - return - doc.openTransaction("Bend") - if activeBody is None or not smIsPartDesign(selobj): - a = doc.addObject("Part::FeaturePython", "Bend") - SMBendWall(a) - SMViewProviderTree(a.ViewObject) - else: - # FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) - a = doc.addObject("PartDesign::FeaturePython", "Bend") - SMBendWall(a) - SMViewProviderFlat(a.ViewObject) - activeBody.addObject(a) - SheetMetalBaseCmd.SetViewConfig(a, viewConf) - FreeCADGui.Selection.clearSelection() - if SheetMetalBaseCmd.autolink_enabled(): - root = SheetMetalBaseCmd.getOriginalBendObject(a) - if root: - a.setExpression("radius", root.Label + ".radius") - doc.recompute() - doc.commitTransaction() - return - - def IsActive(self): - if ( - len(Gui.Selection.getSelection()) < 1 - or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1 - ): - return False - selobj = Gui.Selection.getSelection()[0] - for selobj in Gui.Selection.getSelection(): - if selobj.isDerivedFrom("Sketcher::SketchObject"): - return False - for selFace in Gui.Selection.getSelectionEx()[0].SubObjects: - if type(selFace) == Part.Vertex: - return False - return True - - -Gui.addCommand("SheetMetal_AddWall", AddWallCommandClass()) +# -*- coding: utf-8 -*- +################################################################################### +# +# SheetMetalCmd.py +# +# Copyright 2015 Shai Seger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +################################################################################### + +import FreeCAD, Part, math +import SheetMetalTools + +# IMPORTANT: please remember to change the element map version in case of any +# changes in modeling logic +smElementMapVersion = "sm1." + +smEpsilon = SheetMetalTools.smEpsilon + +translate = FreeCAD.Qt.translate + +def smStrEdge(e): + return ( + "[" + + str(e.valueAt(e.FirstParameter)) + + " , " + + str(e.valueAt(e.LastParameter)) + + "]" + ) + + +def smMakeReliefFace(edge, dir, gap, reliefW, reliefD, reliefType, op=""): + p1 = edge.valueAt(edge.FirstParameter + gap) + p2 = edge.valueAt(edge.FirstParameter + gap + reliefW) + if reliefType == "Round" and reliefD > reliefW: + p3 = edge.valueAt(edge.FirstParameter + gap + reliefW) + dir.normalize() * ( + reliefD - reliefW / 2 + ) + p34 = ( + edge.valueAt(edge.FirstParameter + gap + reliefW / 2) + + dir.normalize() * reliefD + ) + p4 = edge.valueAt(edge.FirstParameter + gap) + dir.normalize() * ( + reliefD - reliefW / 2 + ) + e1 = Part.makeLine(p1, p2) + e2 = Part.makeLine(p2, p3) + e3 = Part.Arc(p3, p34, p4).toShape() + e4 = Part.makeLine(p4, p1) + else: + p3 = ( + edge.valueAt(edge.FirstParameter + gap + reliefW) + + dir.normalize() * reliefD + ) + p4 = edge.valueAt(edge.FirstParameter + gap) + dir.normalize() * reliefD + e1 = Part.makeLine(p1, p2) + e2 = Part.makeLine(p2, p3) + e3 = Part.makeLine(p3, p4) + e4 = Part.makeLine(p4, p1) + + w = Part.Wire([e1, e2, e3, e4]) + face = Part.Face(w) + if hasattr(face, "mapShapes"): + face.mapShapes([(edge, face)], [], op) + return face + + +def smMakeFace(edge, dir, extLen, gap1=0.0, + gap2=0.0, angle1=0.0, angle2=0.0, op=""): + len1 = extLen * math.tan(math.radians(angle1)) + len2 = extLen * math.tan(math.radians(angle2)) + + p1 = edge.valueAt(edge.LastParameter - gap2) + p2 = edge.valueAt(edge.FirstParameter + gap1) + p3 = edge.valueAt(edge.FirstParameter + gap1 + len1) + dir.normalize() * extLen + p4 = edge.valueAt(edge.LastParameter - gap2 - len2) + dir.normalize() * extLen + + e2 = Part.makeLine(p2, p3) + e4 = Part.makeLine(p4, p1) + section = e4.section(e2) + + if section.Vertexes: + p5 = section.Vertexes[0].Point + w = Part.makePolygon([p1, p2, p5, p1]) + else: + w = Part.makePolygon([p1, p2, p3, p4, p1]) + face = Part.Face(w) + if hasattr(face, "mapShapes"): + face.mapShapes([(edge, face)], None, op) + return face + + +def smRestrict(var, fromVal, toVal): + if var < fromVal: + return fromVal + if var > toVal: + return toVal + return var + + +def smFace(selItem, obj): + # find face, if Edge Selected + if type(selItem) == Part.Edge: + Facelist = obj.ancestorsOfType(selItem, Part.Face) + if Facelist[0].Area < Facelist[1].Area: + selFace = Facelist[0] + else: + selFace = Facelist[1] + elif type(selItem) == Part.Face: + selFace = selItem + return selFace + + +def smModifiedFace(Face, obj): + # find face Modified During loop + for face in obj.Faces: + face_common = face.common(Face) + if face_common.Faces: + if face.Area == face_common.Faces[0].Area: + break + return face + + +def smGetEdge(Face, obj): + # find Edges that overlap + for edge in obj.Edges: + face_common = edge.common(Face) + if face_common.Edges: + break + return edge + + +def LineAngle(edge1, edge2): + # find angle between two lines + v1a = edge1.Vertexes[0].Point + v1b = edge1.Vertexes[1].Point + v2a = edge2.Vertexes[0].Point + v2b = edge2.Vertexes[1].Point + + # Find the right order of the wire verts to calculate the angle + # the order of the verts v1a v1b v2a v2b should be such that v1b is the closest vert to v2a + minlen = (v1a - v2a).Length + order = [v1b, v1a, v2a, v2b] + len = (v1a - v2b).Length + if (len < minlen): + minlen = len + order = [v1b, v1a, v2b, v2a] + len = (v1b - v2a).Length + if (len < minlen): + minlen = len + order = [v1a, v1b, v2a, v2b] + len = (v1b - v2b).Length + if (len < minlen): + order = [v1a, v1b, v2b, v2a] + + lineDir = order[1] - order[0] + edgeDir = order[3] - order[2] + + angleRad = edgeDir.getAngle(lineDir) + angle = math.degrees(angleRad) + #if (angle > 90): + # angle = 180.0 - angle + return angle + + +def smGetFace(Faces, obj): + # find face Name Modified obj + faceList = [] + for Face in Faces: + for i, face in enumerate(obj.Faces): + face_common = face.common(Face) + if face_common.Faces: + faceList.append("Face" + str(i + 1)) + # print(faceList) + return faceList + + +def LineExtend(edge, distance1, distance2): + # Extend a ine by given distances + return edge.Curve.toShape( + edge.FirstParameter - distance1, edge.LastParameter + distance2 + ) + + +def getParallel(edge1, edge2): + # Get intersection between two lines + e1 = edge1.Curve.toShape() + # Part.show(e1,'e1') + e2 = edge2.Curve.toShape() + # Part.show(e2,'e2') + section = e1.section(e2) + if section.Vertexes: + # Part.show(section,'section') + return False + else: + return True + + +def getCornerPoint(edge1, edge2): + # Get intersection between two lines + # Part.show(edge1,'edge1') + # Part.show(edge2,'edge21') + e1 = edge1.Curve.toShape() + # Part.show(e1,'e1') + e2 = edge2.Curve.toShape() + # Part.show(e2,'e2') + section = e1.section(e2) + if section.Vertexes: + # Part.show(section,'section') + cornerPoint = section.Vertexes[0].Point + return cornerPoint + + +def getGap(line1, line2, maxExtendGap, mingap): + # To find gap between two edges + gaps = 0.0 + extgap = 0.0 + section = line1.section(line2) + if section.Vertexes: + cornerPoint = section.Vertexes[0].Point + size1 = abs((cornerPoint - line2.Vertexes[0].Point).Length) + size2 = abs((cornerPoint - line2.Vertexes[1].Point).Length) + if size1 < size2: + gaps = size1 + else: + gaps = size2 + gaps = gaps + mingap + # print(gaps) + else: + cornerPoint = getCornerPoint(line1, line2) + line3 = LineExtend(line1, maxExtendGap, maxExtendGap) + # Part.show(line1,'line1') + line4 = LineExtend(line2, maxExtendGap, maxExtendGap) + # Part.show(line2,'line2') + section = line3.section(line4) + if section.Vertexes: + # cornerPoint = section.Vertexes[0].Point + # p1 = Part.Vertex(cornerPoint) + section1 = line1.section(line4) + size1 = abs((cornerPoint - line2.Vertexes[0].Point).Length) + size2 = abs((cornerPoint - line2.Vertexes[1].Point).Length) + # dist = cornerPoint.distanceToLine(line2.Curve.Location, line2.Curve.Direction) + # print(["gap",size1, size2, dist]) + # if section1.Vertexes : + # extgap = 0.0 + if size1 < size2: + extgap = size1 + else: + extgap = size2 + # if dist < smEpsilon : + # gaps = extgap + # extgap = 0.0 + if extgap > mingap: + extgap = extgap - mingap + # print(extgap) + return gaps, extgap, cornerPoint + + +def getSketchDetails(Sketch, sketchflip, sketchinvert, radius, thk): + # Convert Sketch lines to length. Angles between line + LengthList, bendAList = ([], []) + sketch_normal = Sketch.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, 1)) + e0 = Sketch.Placement.Rotation.multVec(FreeCAD.Vector(1, 0, 0)) + WireList = Sketch.Shape.Wires[0] + + # Create filleted wire at centre of thickness + wire_extr = WireList.extrude(sketch_normal * -50) + # Part.show(wire_extr,"wire_extr") + wire_extr_mir = WireList.extrude(sketch_normal * 50) + # Part.show(wire_extr_mir,"wire_extr_mir") + wire_extr = wire_extr.makeOffsetShape(thk / 2.0, 0.0, fill=False, join=2) + # Part.show(wire_extr,"wire_extr") + wire_extr_mir = wire_extr_mir.makeOffsetShape(-thk / 2.0, 0.0, fill=False, join=2) + # Part.show(wire_extr_mir,"wire_extr_mir") + if len(WireList.Edges) > 1: + filleted_extr = wire_extr.makeFillet((radius + thk / 2.0), wire_extr.Edges) + # Part.show(filleted_extr,"filleted_extr") + filleted_extr_mir = wire_extr_mir.makeFillet( + (radius + thk / 2.0), wire_extr_mir.Edges + ) + # Part.show(filleted_extr_mir,"filleted_extr_mir") + else: + filleted_extr = wire_extr + filleted_extr_mir = wire_extr_mir + # Part.show(filleted_extr,"filleted_extr") + sec_wirelist = filleted_extr_mir.section(filleted_extr) + # Part.show(sec_wirelist,"sec_wirelist") + + for edge in sec_wirelist.Edges: + if isinstance(edge.Curve, Part.Line): + LengthList.append(edge.Length) + + for i in range(len(WireList.Vertexes) - 1): + p1 = WireList.Vertexes[i].Point + p2 = WireList.Vertexes[i + 1].Point + e1 = p2 - p1 + # LengthList.append(e1.Length) + normal = e0.cross(e1) + coeff = sketch_normal.dot(normal) + if coeff >= 0: + sign = 1 + else: + sign = -1 + angle_rad = e0.getAngle(e1) + if sketchflip: + angle = sign * math.degrees(angle_rad) * -1 + else: + angle = sign * math.degrees(angle_rad) + bendAList.append(angle) + e0 = e1 + if sketchinvert: + LengthList.reverse() + bendAList.reverse() + # print(LengthList, bendAList) + return LengthList, bendAList + +def check_parallel(edge1, edge2): + v1 = edge1.Vertexes[0].Point - edge1.Vertexes[1].Point + v2 = edge2.Vertexes[0].Point - edge2.Vertexes[1].Point + if v1.isEqual(v2,0.00001): + return True, edge2.Vertexes[0].Point - edge1.Vertexes[0].Point + if v1.isEqual(v2,0.00001) or v1.isEqual(-v2,0.00001): + return True, edge2.Vertexes[0].Point - edge1.Vertexes[1].Point + return False, None + +def sheet_thk(MainObject, selFaceName): + selItem = MainObject.getElement(selFaceName) + selFace = smFace(selItem, MainObject) + # find the narrow edge + thk = 999999.0 + thkDir = None + if type(selItem) == Part.Face: + for edge in selFace.Edges: + if abs(edge.Length) < thk: + thk = abs(edge.Length) + else: + # if selected item is edge, try to find closest parallel edge - works better + # when object is refined and faces are not rectangle + for edge in selFace.Edges: + if edge.isSame(selItem): + continue + isParallel, distVect = check_parallel(selItem, edge) + if isParallel: + dist = distVect.Length + if dist < thk: + thk = dist + thkDir = distVect + thkDir.normalize() + return thk, thkDir + + +def smEdge(selFaceName, MainObject): + # find Edge, if Face Selected + selItem = MainObject.getElement(selFaceName) + thkDir = None + if type(selItem) == Part.Face: + # find the narrow edge + thk = 999999.0 + for edge in selItem.Edges: + if abs(edge.Length) < thk: + thk = abs(edge.Length) + thkEdge = edge + + # find a length edge = revolve axis direction + p0 = thkEdge.valueAt(thkEdge.FirstParameter) + for lenEdge in selItem.Edges: + p1 = lenEdge.valueAt(lenEdge.FirstParameter) + p2 = lenEdge.valueAt(lenEdge.LastParameter) + if lenEdge.isSame(thkEdge): + continue + if (p1 - p0).Length < smEpsilon: + revAxisV = p2 - p1 + break + if (p2 - p0).Length < smEpsilon: + revAxisV = p1 - p2 + break + seledge = lenEdge + selFace = selItem + elif type(selItem) == Part.Edge: + thk, thkDir = sheet_thk(MainObject, selFaceName) + seledge = selItem + selFace = smFace(selItem, MainObject) + p1 = seledge.valueAt(seledge.FirstParameter) + p2 = seledge.valueAt(seledge.LastParameter) + revAxisV = p2 - p1 + #print(str(revAxisV)) + return seledge, selFace, thk, revAxisV, thkDir + + +def getBendetail(selItemNames, MainObject, bendR, bendA, isflipped, offset, gap1, gap2): + mainlist = [] + edgelist = [] + nogap_edgelist = [] + for selItemName in selItemNames: + lenEdge, selFace, thk, revAxisV, thkDir = smEdge(selItemName, MainObject) + + # find the large face connected with selected face + list2 = MainObject.ancestorsOfType(lenEdge, Part.Face) + for Cface in list2: + if not (Cface.isSame(selFace)): + break + + # main Length Edge + revAxisV.normalize() + if thkDir is None: + pThkDir1 = selFace.CenterOfMass + pThkDir2 = lenEdge.Curve.value(lenEdge.Curve.parameter(pThkDir1)) + thkDir = pThkDir1.sub(pThkDir2).normalize() + #print(str(thkDir)) + FaceDir = selFace.normalAt(0, 0) + + # make sure the direction vector is correct in respect to the normal + if (thkDir.cross(revAxisV).normalize() - FaceDir).Length < smEpsilon: + revAxisV = revAxisV * -1 + + flipped = isflipped + # restrict angle + if bendA < 0: + bendA = -bendA + flipped = not flipped + + if type(MainObject.getElement(selItemName)) == Part.Edge: + flipped = not flipped + + if not (flipped): + revAxisP = lenEdge.valueAt(lenEdge.FirstParameter) + thkDir * (bendR + thk) + revAxisV = revAxisV * -1 + else: + revAxisP = lenEdge.valueAt(lenEdge.FirstParameter) + thkDir * -bendR + # Part.show(lenEdge,'lenEdge') + mainlist.append( + [ + Cface, + selFace, + thk, + lenEdge, + revAxisP, + revAxisV, + thkDir, + FaceDir, + bendA, + flipped, + ] + ) + if offset < 0.0: + dist = lenEdge.valueAt(lenEdge.FirstParameter).distanceToPlane( + FreeCAD.Vector(0, 0, 0), FaceDir + ) + # print(dist) + slice_wire = Cface.slice(FaceDir, dist + offset) + # print(slice_wire) + trimLenEdge = slice_wire[0].Edges[0] + else: + # Produce Offset Edge + trimLenEdge = lenEdge.copy() + trimLenEdge.translate(selFace.normalAt(0, 0) * offset) + # Part.show(trimLenEdge,'trimLenEdge1') + nogap_edgelist.append(trimLenEdge) + trimLenEdge = LineExtend(trimLenEdge, -gap1, -gap2) + # Part.show(trimLenEdge,'trimLenEdge2') + edgelist.append(trimLenEdge) + # print(mainlist) + trimedgelist = InsideEdge(edgelist) + nogaptrimedgelist = InsideEdge(nogap_edgelist) + return mainlist, trimedgelist, nogaptrimedgelist + + +def InsideEdge(edgelist): + import BOPTools.JoinFeatures + + newedgelist = [] + for i, e in enumerate(edgelist): + for j, ed in enumerate(edgelist): + if i != j: + section = e.section(ed) + if section.Vertexes: + edgeShape = BOPTools.JoinAPI.cutout_legacy(e, ed, tolerance=0.0) + e = edgeShape + # Part.show(e,"newedge") + newedgelist.append(e) + return newedgelist + + +def smMiter( + mainlist, + trimedgelist, + bendR=1.0, + miterA1=0.0, + miterA2=0.0, + extLen=10.0, + gap1=0.0, + gap2=0.0, + offset=0.0, + reliefD=1.0, + automiter=True, + extend1=0.0, + extend2=0.0, + mingap=0.1, + maxExtendGap=5.0, +): + if not (automiter): + miterA1List = [miterA1 for n in mainlist] + miterA2List = [miterA2 for n in mainlist] + gap1List = [gap1 for n in mainlist] + gap2List = [gap2 for n in mainlist] + extgap1List = [extend1 for n in mainlist] + extgap2List = [extend2 for n in mainlist] + else: + miterA1List = [0.0 for n in mainlist] + miterA2List = [0.0 for n in mainlist] + gap1List = [gap1 for n in mainlist] + gap2List = [gap2 for n in mainlist] + extgap1List = [extend1 for n in mainlist] + extgap2List = [extend2 for n in mainlist] + + facelist, tranfacelist = ([], []) + extfacelist, exttranfacelist = ([], []) + lenedgelist, tranedgelist = ([], []) + for i, sublist in enumerate(mainlist): + # find the narrow edge + ( + Cface, + selFace, + thk, + MlenEdge, + revAxisP, + revAxisV, + thkDir, + FaceDir, + bendA, + flipped, + ) = sublist + + # Produce Offset Edge + lenEdge = trimedgelist[i].copy() + #Part.show(lenEdge) + revAxisP = revAxisP + FaceDir * offset + + # narrow the wall, if we have gaps + BendFace = smMakeFace( + lenEdge, FaceDir, extLen, gap1 - extend1, gap2 - extend2, op="SMB" + ) + if BendFace.normalAt(0, 0) != thkDir: + BendFace.reverse() + #Part.show(BendFace) + transBendFace = BendFace.copy() + BendFace.rotate(revAxisP, revAxisV, bendA) + + #Part.show(BendFace,'BendFace') + facelist.append(BendFace) + transBendFace.translate(thkDir * thk) + transBendFace.rotate(revAxisP, revAxisV, bendA) + tranfacelist.append(transBendFace) + #Part.show(transBendFace,'transBendFace') + + # narrow the wall, if we have gaps + BendFace = smMakeFace( + lenEdge, + FaceDir, + extLen, + gap1 - extend1 - maxExtendGap, + gap2 - extend2 - maxExtendGap, + op="SMB", + ) + if BendFace.normalAt(0, 0) != thkDir: + BendFace.reverse() + + transBendFace = BendFace.copy() + BendFace.rotate(revAxisP, revAxisV, bendA) + #Part.show(BendFace,'BendFaceB') + extfacelist.append(BendFace) + transBendFace.translate(thkDir * thk) + transBendFace.rotate(revAxisP, revAxisV, bendA) + exttranfacelist.append(transBendFace) + #Part.show(transBendFace,'transBendFaceB') + + # edge_len = lenEdge.copy() + edge_len = LineExtend(lenEdge, (-gap1 + extend1), (-gap2 + extend2)) + edge_len.rotate(revAxisP, revAxisV, bendA) + lenedgelist.append(edge_len) + # Part.show(edge_len,'edge_len') + + # edge_len = lenEdge.copy() + edge_len = LineExtend(lenEdge, (-gap1 + extend1), (-gap2 + extend2)) + edge_len.translate(thkDir * thk) + edge_len.rotate(revAxisP, revAxisV, bendA) + tranedgelist.append(edge_len) + # Part.show(edge_len,'edge_len') + + # check faces intersect each other + for i in range(len(facelist)): + for j in range(len(lenedgelist)): + if ( + i != j + and facelist[i].isCoplanar(facelist[j]) + and not (getParallel(lenedgelist[i], lenedgelist[j])) + ): + # Part.show(lenedgelist[i],'edge_len1') + # Part.show(lenedgelist[j],'edge_len2') + gaps1, extgap1, cornerPoint1 = getGap( + lenedgelist[i], lenedgelist[j], maxExtendGap, mingap + ) + gaps2, extgap2, cornerPoint2 = getGap( + tranedgelist[i], tranedgelist[j], maxExtendGap, mingap + ) + # print([gaps1,gaps2, extgap1, extgap2]) + gaps = max(gaps1, gaps2) + extgap = min(extgap1, extgap2) + p1 = lenedgelist[j].valueAt(lenedgelist[j].FirstParameter) + p2 = lenedgelist[j].valueAt(lenedgelist[j].LastParameter) + Angle = LineAngle(lenedgelist[i], lenedgelist[j]) + # print(Angle) + if gaps > 0.0: + # walledge_common = lenedgelist[j].section(lenedgelist[i]) + # vp1 = walledge_common.Vertexes[0].Point + dist1 = (p1 - cornerPoint1).Length + dist2 = (p2 - cornerPoint1).Length + if abs(dist1) < abs(dist2): + miterA1List[j] = Angle / 2.0 + if gaps > 0.0: + gap1List[j] = gaps + else: + gap1List[j] = 0.0 + elif abs(dist2) < abs(dist1): + miterA2List[j] = Angle / 2.0 + if gaps > 0.0: + gap2List[j] = gaps + else: + gap2List[j] = 0.0 + elif extgap != 0.0 and (extgap + mingap) < maxExtendGap: + wallface_common = facelist[j].common(facelist[i]) + dist1 = (p1 - cornerPoint1).Length + dist2 = (p2 - cornerPoint1).Length + if abs(dist1) < abs(dist2): + if wallface_common.Faces: + miterA1List[j] = Angle / 2.0 + else: + miterA1List[j] = -Angle / 2.0 + if extgap > 0.0: + extgap1List[j] = extgap + else: + extgap1List[j] = 0.0 + elif abs(dist2) < abs(dist1): + if wallface_common.Faces: + miterA2List[j] = Angle / 2.0 + else: + miterA2List[j] = -Angle / 2.0 + if extgap > 0.0: + extgap2List[j] = extgap + else: + extgap2List[j] = 0.0 + elif i != j and not (getParallel(lenedgelist[i], lenedgelist[j])): + # Part.show(lenedgelist[i],'edge_len1') + # Part.show(lenedgelist[j],'edge_len2') + # Part.show(tranedgelist[i],'edge_len1') + # Part.show(tranedgelist[j],'edge_len2') + gaps1, extgap1, cornerPoint1 = getGap( + lenedgelist[i], lenedgelist[j], maxExtendGap, mingap + ) + gaps2, extgap2, cornerPoint2 = getGap( + tranedgelist[i], tranedgelist[j], maxExtendGap, mingap + ) + # print([gaps1, gaps2, extgap1, extgap2]) + gaps = max(gaps1, gaps2) + extgap = min(extgap1, extgap2) + p1 = lenedgelist[j].valueAt(lenedgelist[j].FirstParameter) + p2 = lenedgelist[j].valueAt(lenedgelist[j].LastParameter) + if gaps > 0.0: + wallface_common = facelist[j].section(facelist[i]) + # Part.show(facelist[j],'facelist') + # Part.show(facelist[i],'facelist') + wallface_common1 = tranfacelist[j].section(tranfacelist[i]) + # Part.show(tranfacelist[j],'tranfacelist') + # Part.show(tranfacelist[i],'tranfacelist') + # Part.show(wallface_common,'wallface_common') + if wallface_common.Edges: + vp1 = wallface_common.Vertexes[0].Point + vp2 = wallface_common.Vertexes[1].Point + elif wallface_common1.Edges: + vp1 = wallface_common1.Vertexes[0].Point + vp2 = wallface_common1.Vertexes[1].Point + dist1 = (p1 - vp1).Length + dist2 = (p2 - vp1).Length + if abs(dist1) < abs(dist2): + edgedir = (p1 - p2).normalize() + dist3 = (cornerPoint1 - vp1).Length + dist4 = (cornerPoint1 - vp2).Length + if dist4 < dist3: + lineDir = (vp2 - vp1).normalize() + else: + lineDir = (vp1 - vp2).normalize() + angle1 = edgedir.getAngle(lineDir) + Angle2 = math.degrees(angle1) + Angle = 90 - Angle2 + # print([Angle, Angle2, 'ext']) + miterA1List[j] = Angle + if gaps > 0.0: + gap1List[j] = gaps + else: + gap1List[j] = 0.0 + elif abs(dist2) < abs(dist1): + edgedir = (p2 - p1).normalize() + dist3 = (cornerPoint1 - vp1).Length + dist4 = (cornerPoint1 - vp2).Length + if dist4 < dist3: + lineDir = (vp2 - vp1).normalize() + else: + lineDir = (vp1 - vp2).normalize() + angle1 = edgedir.getAngle(lineDir) + Angle2 = math.degrees(angle1) + Angle = 90 - Angle2 + # print([Angle, Angle2, 'ext']) + miterA2List[j] = Angle + if gaps > 0.0: + gap2List[j] = gaps + else: + gap2List[j] = 0.0 + elif extgap != 0.0 and (extgap + mingap) < maxExtendGap: + wallface_common = extfacelist[j].section(extfacelist[i]) + # Part.show(extfacelist[j],'extfacelist') + # Part.show(extfacelist[i],'extfacelist') + wallface_common1 = exttranfacelist[j].section( + exttranfacelist[i] + ) + # Part.show(exttranfacelist[j],'exttranfacelist') + # Part.show(exttranfacelist[i],'exttranfacelist') + # Part.show(wallface_common,'wallface_common') + if wallface_common.Edges: + vp1 = wallface_common.Vertexes[0].Point + vp2 = wallface_common.Vertexes[1].Point + elif wallface_common1.Edges: + vp1 = wallface_common1.Vertexes[0].Point + vp2 = wallface_common1.Vertexes[1].Point + dist1 = (p1 - vp1).Length + dist2 = (p2 - vp1).Length + if abs(dist1) < abs(dist2): + edgedir = (p1 - p2).normalize() + dist3 = (cornerPoint1 - vp1).Length + dist4 = (cornerPoint1 - vp2).Length + if dist4 < dist3: + lineDir = (vp2 - vp1).normalize() + else: + lineDir = (vp1 - vp2).normalize() + angle1 = edgedir.getAngle(lineDir) + Angle2 = math.degrees(angle1) + Angle = 90 - Angle2 + # print([Angle, Angle2, 'ext']) + miterA1List[j] = Angle + if extgap > 0.0: + extgap1List[j] = extgap + else: + extgap1List[j] = 0.0 + elif abs(dist2) < abs(dist1): + edgedir = (p2 - p1).normalize() + dist3 = (cornerPoint1 - vp1).Length + dist4 = (cornerPoint1 - vp2).Length + if dist4 < dist3: + lineDir = (vp2 - vp1).normalize() + else: + lineDir = (vp1 - vp2).normalize() + angle1 = edgedir.getAngle(lineDir) + Angle2 = math.degrees(angle1) + Angle = 90 - Angle2 + # print([Angle, Angle2, 'ext']) + miterA2List[j] = Angle + if extgap > 0.0: + extgap2List[j] = extgap + else: + extgap2List[j] = 0.0 + + # print(miterA1List, miterA2List, gap1List, gap2List, extgap1List, extgap2List) + return miterA1List, miterA2List, gap1List, gap2List, extgap1List, extgap2List + + +def smBend( + thk, + bendR=1.0, + bendA=90.0, + miterA1=0.0, + miterA2=0.0, + BendType="Material Outside", + flipped=False, + unfold=False, + offset=0.0, + extLen=10.0, + gap1=0.0, + gap2=0.0, + reliefType="Rectangle", + reliefW=0.8, + reliefD=1.0, + minReliefgap=1.0, + extend1=0.0, + extend2=0.0, + kfactor=0.45, + ReliefFactor=0.7, + UseReliefFactor=False, + selFaceNames="", + MainObject=None, + maxExtendGap=5.0, + mingap=0.1, + automiter=True, + sketch=None, + extendType="Simple", + LengthSpec="Leg", +): + # if sketch is as wall + sketches = False + if sketch: + if sketch.Shape.Wires[0].isClosed(): + sketches = True + else: + pass + + # Add Bend Type details + if BendType == "Material Outside": + offset = 0.0 + inside = False + elif BendType == "Material Inside": + offset = -(thk + bendR) + inside = True + elif BendType == "Thickness Outside": + offset = -bendR + inside = True + elif BendType == "Offset": + if offset < 0.0: + inside = True + else: + inside = False + + if LengthSpec == "Leg": + pass + elif LengthSpec == "Tangential": + if bendA >= 90.0: + extLen -= thk + bendR + else: + extLen -= (bendR + thk) / math.tan(math.radians(90.0 - bendA / 2)) + elif LengthSpec == "Inner Sharp": + extLen -= (bendR) / math.tan(math.radians(90.0 - bendA / 2)) + elif LengthSpec == "Outer Sharp": + extLen -= (bendR + thk) / math.tan(math.radians(90.0 - bendA / 2)) + + if not (sketches): + mainlist, trimedgelist, nogaptrimedgelist = getBendetail( + selFaceNames, MainObject, bendR, bendA, flipped, offset, gap1, gap2 + ) + ( + miterA1List, + miterA2List, + gap1List, + gap2List, + extend1List, + extend2List, + ) = smMiter( + mainlist, + trimedgelist, + bendR=bendR, + miterA1=miterA1, + miterA2=miterA2, + extLen=extLen, # gap1 = gap1, gap2 = gap2, + offset=offset, + automiter=automiter, + extend1=extend1, + extend2=extend2, + mingap=mingap, + maxExtendGap=maxExtendGap, + ) + + # print(miterA1List, miterA2List, gap1List, gap2List, extend1List, extend2List) + else: + ( + miterA1List, + miterA2List, + gap1List, + gap2List, + extend1List, + extend2List, + reliefDList, + ) = ([0.0], [0.0], [gap1], [gap2], [extend1], [extend2], [reliefD]) + agap1, agap2 = gap1, gap2 + # print([agap1,agap1]) + + # mainlist = getBendetail(selFaceNames, MainObject, bendR, bendA, flipped) + thk_faceList = [] + resultSolid = MainObject + for i, sublist in enumerate(mainlist): + # find the narrow edge + ( + Cface, + selFace, + thk, + AlenEdge, + revAxisP, + revAxisV, + thkDir, + FaceDir, + bendA, + flipped, + ) = sublist + gap1, gap2 = (gap1List[i], gap2List[i]) + # print([gap1,gap2]) + extend1, extend2 = (extend1List[i], extend2List[i]) + # Part.show(lenEdge,'lenEdge1') + selFace = smModifiedFace(selFace, resultSolid) + # Part.show(selFace,'selFace') + Cface = smModifiedFace(Cface, resultSolid) + # Part.show(Cface,'Cface') + # main Length Edge + MlenEdge = smGetEdge(AlenEdge, resultSolid) + # Part.show(MlenEdge,'MlenEdge') + lenEdge = trimedgelist[i] + noGap_lenEdge = nogaptrimedgelist[i] + leng = lenEdge.Length + # Part.show(lenEdge,'lenEdge') + + # Add as offset to set any distance + if UseReliefFactor: + reliefW = thk * ReliefFactor + reliefD = thk * ReliefFactor + + # if sketch is as wall + sketches = False + if sketch: + if sketch.Shape.Wires[0].isClosed(): + sketches = True + else: + pass + + if sketches: + sketch_face = Part.makeFace(sketch.Shape.Wires, "Part::FaceMakerBullseye") + sketch_face.translate(thkDir * -thk) + if inside: + sketch_face.translate(FaceDir * offset) + sketch_Shape = lenEdge.common(sketch_face) + sketch_Edge = sketch_Shape.Edges[0] + gap1 = ( + lenEdge.valueAt(lenEdge.FirstParameter) + - sketch_Edge.valueAt(sketch_Edge.FirstParameter) + ).Length + gap2 = ( + lenEdge.valueAt(lenEdge.LastParameter) + - sketch_Edge.valueAt(sketch_Edge.LastParameter) + ).Length + + # CutSolids list for collecting Solids + CutSolids = [] + # remove relief if needed + if reliefD > 0.0 and reliefW > 0.0: + if agap1 > minReliefgap: + reliefFace1 = smMakeReliefFace( + lenEdge, + FaceDir * -1, + gap1 - reliefW, + reliefW, + reliefD, + reliefType, + op="SMF", + ) + reliefSolid1 = reliefFace1.extrude(thkDir * thk) + # Part.show(reliefSolid1, "reliefSolid1") + CutSolids.append(reliefSolid1) + if inside: + reliefFace1 = smMakeReliefFace( + lenEdge, + FaceDir * -1, + gap1 - reliefW, + reliefW, + offset, + reliefType, + op="SMF", + ) + reliefSolid1 = reliefFace1.extrude(thkDir * thk) + # Part.show(reliefSolid1, "reliefSolid1") + CutSolids.append(reliefSolid1) + if agap2 > minReliefgap: + reliefFace2 = smMakeReliefFace( + lenEdge, + FaceDir * -1, + lenEdge.Length - gap2, + reliefW, + reliefD, + reliefType, + op="SMFF", + ) + reliefSolid2 = reliefFace2.extrude(thkDir * thk) + # Part.show(reliefSolid2, "reliefSolid2") + CutSolids.append(reliefSolid2) + if inside: + reliefFace2 = smMakeReliefFace( + lenEdge, + FaceDir * -1, + lenEdge.Length - gap2, + reliefW, + offset, + reliefType, + op="SMFF", + ) + reliefSolid2 = reliefFace2.extrude(thkDir * thk) + # Part.show(reliefSolid2,"reliefSolid2") + CutSolids.append(reliefSolid2) + + # remove bend face if present + if inside: + if ( + MlenEdge.Vertexes[0].Point - MlenEdge.valueAt(MlenEdge.FirstParameter) + ).Length < smEpsilon: + vertex0 = MlenEdge.Vertexes[0] + vertex1 = MlenEdge.Vertexes[1] + else: + vertex1 = MlenEdge.Vertexes[0] + vertex0 = MlenEdge.Vertexes[1] + Noffset_1 = abs( + ( + MlenEdge.valueAt(MlenEdge.FirstParameter) + - noGap_lenEdge.valueAt(noGap_lenEdge.FirstParameter) + ).Length + ) + Noffset_2 = abs( + ( + MlenEdge.valueAt(MlenEdge.FirstParameter) + - noGap_lenEdge.valueAt(noGap_lenEdge.LastParameter) + ).Length + ) + Noffset1 = min(Noffset_1, Noffset_2) + Noffset_1 = abs( + ( + MlenEdge.valueAt(MlenEdge.LastParameter) + - noGap_lenEdge.valueAt(noGap_lenEdge.FirstParameter) + ).Length + ) + Noffset_2 = abs( + ( + MlenEdge.valueAt(MlenEdge.LastParameter) + - noGap_lenEdge.valueAt(noGap_lenEdge.LastParameter) + ).Length + ) + Noffset2 = min(Noffset_1, Noffset_2) + # print([Noffset1, Noffset1]) + if agap1 <= minReliefgap: + Edgelist = selFace.ancestorsOfType(vertex0, Part.Edge) + for ed in Edgelist: + if not (MlenEdge.isSame(ed)): + list1 = resultSolid.ancestorsOfType(ed, Part.Face) + for Rface in list1: + # print(type(Rface.Surface)) + if not (selFace.isSame(Rface)): + for edge in Rface.Edges: + # print(type(edge.Curve)) + if issubclass( + type(edge.Curve), + (Part.Circle or Part.BSplineSurface), + ): + RfaceE = Rface.makeOffsetShape( + -Noffset1, 0.0, fill=True + ) + # Part.show(RfaceE,"RfaceSolid1") + CutSolids.append(RfaceE) + break + if agap2 <= minReliefgap: + Edgelist = selFace.ancestorsOfType(vertex1, Part.Edge) + for ed in Edgelist: + if not (MlenEdge.isSame(ed)): + list1 = resultSolid.ancestorsOfType(ed, Part.Face) + for Rface in list1: + # print(type(Rface.Surface)) + if not (selFace.isSame(Rface)): + for edge in Rface.Edges: + # print(type(edge.Curve)) + if issubclass( + type(edge.Curve), + (Part.Circle or Part.BSplineSurface), + ): + RfaceE = Rface.makeOffsetShape( + -Noffset2, 0.0, fill=True + ) + # Part.show(RfaceE,"RfaceSolid2") + CutSolids.append(RfaceE) + break + + # remove offset solid from sheetmetal, if inside offset + Ref_lenEdge = lenEdge.copy().translate(FaceDir * -offset) + cutgap_1 = ( + AlenEdge.valueAt(AlenEdge.FirstParameter) + - Ref_lenEdge.valueAt(Ref_lenEdge.FirstParameter) + ).Length + cutgap_2 = ( + AlenEdge.valueAt(AlenEdge.FirstParameter) + - Ref_lenEdge.valueAt(Ref_lenEdge.LastParameter) + ).Length + cutgap1 = min(cutgap_1, cutgap_2) + dist = AlenEdge.valueAt(AlenEdge.FirstParameter).distanceToLine( + Ref_lenEdge.Curve.Location, Ref_lenEdge.Curve.Direction + ) + # print(dist) + if dist < smEpsilon: + cutgap1 = cutgap1 * -1.0 + cutgap_1 = ( + AlenEdge.valueAt(AlenEdge.LastParameter) + - Ref_lenEdge.valueAt(Ref_lenEdge.FirstParameter) + ).Length + cutgap_2 = ( + AlenEdge.valueAt(AlenEdge.LastParameter) + - Ref_lenEdge.valueAt(Ref_lenEdge.LastParameter) + ).Length + cutgap2 = min(cutgap_1, cutgap_2) + dist = AlenEdge.valueAt(AlenEdge.LastParameter).distanceToLine( + Ref_lenEdge.Curve.Location, Ref_lenEdge.Curve.Direction + ) + # print(dist) + if dist < smEpsilon: + cutgap2 = cutgap2 * -1.0 + # print([cutgap1, cutgap2]) + CutFace = smMakeFace(AlenEdge, thkDir, thk, cutgap1, cutgap2, op="SMC") + # Part.show(CutFace2,"CutFace2") + CutSolid = CutFace.extrude(FaceDir * offset) + # Part.show(CutSolid,"CutSolid") + CfaceSolid = Cface.extrude(thkDir * thk) + CutSolid = CutSolid.common(CfaceSolid) + CutSolids.append(CutSolid) + + # Produce Main Solid for Inside Bends + if CutSolids: + if len(CutSolids) == 1: + resultSolid = resultSolid.cut(CutSolids[0]) + else: + Solid = CutSolids[0].multiFuse(CutSolids[1:]) + Solid.removeSplitter() + # Part.show(Solid) + resultSolid = resultSolid.cut(Solid) + + # Produce Offset Solid + if offset > 0.0: + # create wall + offset_face = smMakeFace(lenEdge, FaceDir, -offset, op="SMO") + OffsetSolid = offset_face.extrude(thkDir * thk) + resultSolid = resultSolid.fuse(OffsetSolid) + + # Adjust revolving center to new point + if not (flipped): + revAxisP = lenEdge.valueAt(lenEdge.FirstParameter) + thkDir * (bendR + thk) + else: + revAxisP = lenEdge.valueAt(lenEdge.FirstParameter) + thkDir * -bendR + + # wallSolid = None + if sketches: + Wall_face = Part.makeFace(sketch.Shape.Wires, "Part::FaceMakerBullseye") + if inside: + Wall_face.translate(FaceDir * offset) + FaceAxisP = sketch_Edge.valueAt(sketch_Edge.FirstParameter) + thkDir * thk + FaceAxisV = sketch_Edge.valueAt( + sketch_Edge.FirstParameter + ) - sketch_Edge.valueAt(sketch_Edge.LastParameter) + Wall_face.rotate(FaceAxisP, FaceAxisV, -90.0) + wallSolid = Wall_face.extrude(thkDir * -thk) + # Part.show(wallSolid) + wallSolid.rotate(revAxisP, revAxisV, bendA) + + elif extLen > 0.0: + # create wall + Wall_face = smMakeFace( + lenEdge, + FaceDir, + extLen, + gap1 - extend1, + gap2 - extend2, + miterA1List[i], + miterA2List[i], + op="SMW", + ) + wallSolid = Wall_face.extrude(thkDir * thk) + # Part.show(wallSolid,"wallSolid") + wallSolid.rotate(revAxisP, revAxisV, bendA) + # Part.show(wallSolid.Faces[2]) + thk_faceList.append(wallSolid.Faces[2]) + + # Produce bend Solid + if not (unfold): + if bendA > 0.0: + # create bend + # narrow the wall if we have gaps + revFace = smMakeFace(lenEdge, thkDir, thk, gap1, gap2, op="SMR") + if revFace.normalAt(0, 0) != FaceDir: + revFace.reverse() + bendSolid = revFace.revolve(revAxisP, revAxisV, bendA) + # Part.show(bendSolid) + resultSolid = resultSolid.fuse(bendSolid) + if wallSolid: + resultSolid = resultSolid.fuse(wallSolid) + # Part.show(resultSolid,"resultSolid") + + # Produce unfold Solid + else: + if bendA > 0.0: + # create bend + unfoldLength = (bendR + kfactor * thk) * bendA * math.pi / 180.0 + # narrow the wall if we have gaps + unfoldFace = smMakeFace(lenEdge, thkDir, thk, gap1, gap2, op="SMR") + if unfoldFace.normalAt(0, 0) != FaceDir: + unfoldFace.reverse() + unfoldSolid = unfoldFace.extrude(FaceDir * unfoldLength) + # Part.show(unfoldSolid) + resultSolid = resultSolid.fuse(unfoldSolid) + + if extLen > 0.0: + wallSolid.rotate(revAxisP, revAxisV, -bendA) + # Part.show(wallSolid, "wallSolid") + wallSolid.translate(FaceDir * unfoldLength) + resultSolid = resultSolid.fuse(wallSolid) + # Part.show(resultSolid, "resultSolid") + return resultSolid, thk_faceList + + +class SMBendWall: + def __init__(self, obj): + '''"Add Wall with radius bend"''' + self._addProperties(obj) + + _tip_ = translate("App::Property", "Base Object") + obj.addProperty( + "App::PropertyLinkSub", "baseObject", "Parameters", _tip_ + ) + obj.Proxy = self + + def _addProperties(self, obj): + SheetMetalTools.smAddLengthProperty( + obj, "radius", translate("App::Property", "Bend Radius"), 1.0 + ) + SheetMetalTools.smAddLengthProperty( + obj, "length", translate("App::Property", "Length of Wall"), 10.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.smAddAngleProperty( + obj, "angle", translate("App::Property", "Bend Angle"), 90.0 + ) + 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.smAddEnumProperty( + obj, + "LengthSpec", + translate("App::Property", "Type of Length Specification"), + ["Leg", "Outer Sharp", "Inner Sharp", "Tangential"], + ) + 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.smAddDistanceProperty( + obj, + "offset", + translate("App::Property", "Offset Bend"), + 0.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::PropertyLink", + "Sketch", + translate("App::Property", "Sketch Object"), + None, + "ParametersEx2", + ) + SheetMetalTools.smAddProperty( + obj, + "App::PropertyFloatList", + "LengthList", + translate("App::Property", "Length of Wall List"), + None, + "ParametersEx3", + ) + SheetMetalTools.smAddProperty( + obj, + "App::PropertyFloatList", + "bendAList", + translate("App::Property", "Bend Angle List"), + None, + "ParametersEx3", + ) + + def getElementMapVersion(self, _fp, ver, _prop, restored): + if not restored: + return smElementMapVersion + ver + + def execute(self, fp): + '''"Print a short message when doing a recomputation, this method is mandatory"''' + + self._addProperties(fp) + + # 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) + + # get LengthList, bendAList + bendAList = [fp.angle.Value] + LengthList = [fp.length.Value] + # print face + + # pass selected object shape + Main_Object = fp.baseObject[0].Shape.copy() + face = fp.baseObject[1] + thk, thkDir = sheet_thk(Main_Object, face[0]) + + if fp.Sketch: + WireList = fp.Sketch.Shape.Wires[0] + if not (WireList.isClosed()): + LengthList, bendAList = getSketchDetails( + fp.Sketch, fp.sketchflip, fp.sketchinvert, fp.radius.Value, thk + ) + else: + if fp.Sketch.Support: + fp.baseObject = (fp.Sketch.Support[0][0], fp.Sketch.Support[0][1]) + LengthList = [10.0] + fp.LengthList = LengthList + fp.bendAList = bendAList + # print(LengthList, bendAList) + + # extend value needed for first bend set only + extend1_list = [0.0 for n in LengthList] + extend2_list = [0.0 for n in LengthList] + 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 LengthList] + gap2_list = [0.0 for n in LengthList] + gap1_list[0] = fp.gap1.Value + gap2_list[0] = fp.gap2.Value + # print(gap1_list, gap2_list) + + for i in range(len(LengthList)): + s, f = smBend( + thk, + bendR=fp.radius.Value, + bendA=bendAList[i], + miterA1=fp.miterangle1.Value, + miterA2=fp.miterangle2.Value, + BendType=fp.BendType, + flipped=fp.invert, + unfold=fp.unfold, + extLen=LengthList[i], + 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=fp.offset.Value, + ReliefFactor=fp.ReliefFactor, + UseReliefFactor=fp.UseReliefFactor, + automiter=fp.AutoMiter, + selFaceNames=face, + MainObject=Main_Object, + sketch=fp.Sketch, + mingap=fp.minGap.Value, + maxExtendGap=fp.maxExtendDist.Value, + LengthSpec=fp.LengthSpec, + ) + faces = smGetFace(f, s) + face = faces + Main_Object = s + + fp.Shape = s + + +########################################################################################################## +# Gui code +########################################################################################################## + +if SheetMetalTools.isGuiLoaded(): + import os + from FreeCAD import Gui + from PySide import QtGui + + icons_path = SheetMetalTools.icons_path + panels_path = SheetMetalTools.panels_path + smEpsilon = SheetMetalTools.smEpsilon + + class SMViewProviderTree: + "A View provider that nests children objects under the created one" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def updateData(self, fp, prop): + return + + def getDisplayModes(self, obj): + modes = [] + return modes + + def setDisplayMode(self, mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + + doc = FreeCAD.ActiveDocument # crap + self.Object = doc.getObject(state["ObjectName"]) + + def claimChildren(self): + objs = [] + if hasattr(self.Object, "baseObject"): + objs.append(self.Object.baseObject[0]) + if hasattr(self.Object, "Sketch"): + objs.append(self.Object.Sketch) + return objs + + def getIcon(self): + return os.path.join(icons_path, "SheetMetal_AddWall.svg") + + def setEdit(self, vobj, mode): + taskd = SMBendWallTaskPanel(vobj.Object) + Gui.Control.showDialog(taskd) + return True + + def unsetEdit(self, vobj, mode): + Gui.Control.closeDialog() + Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.NormalSelection) + self.Object.baseObject[0].ViewObject.Visibility = False + self.Object.ViewObject.Visibility = True + return False + + + class SMViewProviderFlat: + "A View provider that places objects flat under base object" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def setupContextMenu(self, viewObject, menu): + action = menu.addAction( + FreeCAD.Qt.translate("QObject", "Edit %1").replace( + "%1", viewObject.Object.Label + ) + ) + action.triggered.connect(lambda: self.startDefaultEditMode(viewObject)) + return False + + def startDefaultEditMode(self, viewObject): + document = viewObject.Document.Document + if not document.HasPendingTransaction: + text = FreeCAD.Qt.translate("QObject", "Edit %1").replace( + "%1", viewObject.Object.Label + ) + document.openTransaction(text) + viewObject.Document.setEdit(viewObject.Object, 0) + + def updateData(self, fp, prop): + return + + def getDisplayModes(self, obj): + modes = [] + return modes + + def setDisplayMode(self, mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + + doc = FreeCAD.ActiveDocument # crap + self.Object = doc.getObject(state["ObjectName"]) + + def claimChildren(self): + objs = [] + if hasattr(self.Object, "Sketch"): + objs.append(self.Object.Sketch) + return objs + + def getIcon(self): + return os.path.join(icons_path, "SheetMetal_AddWall.svg") + + def setEdit(self, vobj, mode): + taskd = SMBendWallTaskPanel(vobj.Object) + Gui.Control.showDialog(taskd) + return True + + def unsetEdit(self, vobj, mode): + Gui.Control.closeDialog() + Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.NormalSelection) + self.Object.baseObject[0].ViewObject.Visibility = False + self.Object.ViewObject.Visibility = True + return False + + + class SMBendWallTaskPanel: + """A TaskPanel for the Sheetmetal""" + + def __init__(self, obj): + self.obj = obj + path = os.path.join(panels_path, "FlangeParameters.ui") + path2 = os.path.join(panels_path, "FlangeAdvancedParameters.ui") + self.SelModeActive = False + self.form = [] + self.form.append(Gui.PySideUic.loadUi(path)) + self.form.append(Gui.PySideUic.loadUi(path2)) + self.update() + # flange parameters connects + self.form[0].AddRemove.toggled.connect(self.toggleSelectionMode) + self.form[0].BendType.currentIndexChanged.connect(self.updateProperties) + self.form[0].Offset.valueChanged.connect(self.updateProperties) + self.form[0].Radius.valueChanged.connect(self.updateProperties) + self.form[0].Angle.valueChanged.connect(self.updateProperties) + self.form[0].Length.valueChanged.connect(self.updateProperties) + self.form[0].LengthSpec.currentIndexChanged.connect(self.updateProperties) + self.form[0].UnfoldCheckbox.toggled.connect(self.updateProperties) + self.form[0].ReversedCheckbox.toggled.connect(self.updateProperties) + self.form[0].extend1.valueChanged.connect(self.updateProperties) + self.form[0].extend2.valueChanged.connect(self.updateProperties) + # advanced flange parameters connects + self.form[1].reliefTypeButtonGroup.buttonToggled.connect(self.updateProperties) + self.form[1].reliefWidth.valueChanged.connect(self.updateProperties) + self.form[1].reliefDepth.valueChanged.connect(self.updateProperties) + self.form[1].autoMiterCheckbox.toggled.connect(self.updateProperties) + self.form[1].minGap.valueChanged.connect(self.updateProperties) + self.form[1].maxExDist.valueChanged.connect(self.updateProperties) + self.form[1].miterAngle1.valueChanged.connect(self.updateProperties) + self.form[1].miterAngle2.valueChanged.connect(self.updateProperties) + + def isAllowedAlterSelection(self): + return True + + def isAllowedAlterView(self): + return True + + def getStandardButtons(self): + return QtGui.QDialogButtonBox.Ok + + def updateProperties(self): + self.obj.BendType = self.form[0].BendType.currentIndex() + if self.obj.BendType == "Offset": + self.form[0].Offset.setEnabled(True) + else: + self.form[0].Offset.setEnabled(False) + self.obj.offset = self.form[0].Offset.property("value") + self.obj.radius = self.form[0].Radius.property("value") + self.obj.angle = self.form[0].Angle.property("value") + self.obj.length = self.form[0].Length.property("value") + self.obj.LengthSpec = self.form[0].LengthSpec.currentIndex() + self.obj.unfold = self.form[0].UnfoldCheckbox.isChecked() + self.obj.invert = self.form[0].ReversedCheckbox.isChecked() + self.obj.extend1 = self.form[0].extend1.property("value") + self.obj.extend2 = self.form[0].extend2.property("value") + self.obj.reliefType = ( + "Rectangle" if self.form[1].reliefRectangle.isChecked() else "Round" + ) + self.obj.reliefw = self.form[1].reliefWidth.property("value") + self.obj.reliefd = self.form[1].reliefDepth.property("value") + self.obj.AutoMiter = self.form[1].autoMiterCheckbox.isChecked() + self.obj.minGap = self.form[1].minGap.property("value") + self.obj.maxExtendDist = self.form[1].maxExDist.property("value") + self.obj.miterangle1 = self.form[1].miterAngle1.property("value") + self.obj.miterangle2 = self.form[1].miterAngle2.property("value") + self.obj.Document.recompute() + + def update(self): + # load property values + typeList = ["Material Outside","Material Inside","Thickness Outside","Offset"] + lSpecList = ["Leg","Outer Sharp","Inner Sharp","Tangential"] + self.form[0].BendType.setProperty("currentIndex", typeList.index(self.obj.BendType)) + if self.obj.BendType == "Offset": + self.form[0].Offset.setEnabled(True) + else: + self.form[0].Offset.setEnabled(False) + self.form[0].Offset.setProperty("value", self.obj.offset) + self.form[0].Radius.setProperty("value", self.obj.radius) + self.form[0].Angle.setProperty("value", self.obj.angle) + self.form[0].Length.setProperty("value", self.obj.length) + self.form[0].LengthSpec.setProperty("currentIndex", lSpecList.index(self.obj.LengthSpec)) + self.form[0].UnfoldCheckbox.setChecked(self.obj.unfold) + self.form[0].ReversedCheckbox.setChecked(self.obj.invert) + self.form[0].extend1.setProperty("value", self.obj.extend1) + self.form[0].extend2.setProperty("value", self.obj.extend2) + # fill the treewidget + self.form[0].tree.clear() + f = self.obj.baseObject + if isinstance(f[1], list): + for subf in f[1]: + # FreeCAD.Console.PrintLog("item: " + subf + "\n") + item = QtGui.QTreeWidgetItem(self.form[0].tree) + item.setText(0, f[0].Name) + item.setIcon(0, QtGui.QIcon(":/icons/Tree_Part.svg")) + item.setText(1, subf) + else: + item = QtGui.QTreeWidgetItem(self.form[0].tree) + item.setText(0, f[0].Name) + item.setIcon(0, QtGui.QIcon(":/icons/Tree_Part.svg")) + item.setText(1, f[1][0]) + # Advanced parameters update + if self.obj.reliefType == "Rectangle": + self.form[1].reliefRectangle.setChecked(True) + else: + self.form[1].reliefRound.setChecked(True) + self.form[1].reliefDepth.setProperty("value", self.obj.reliefd) + self.form[1].reliefWidth.setProperty("value", self.obj.reliefw) + self.form[1].autoMiterCheckbox.setChecked(self.obj.AutoMiter) + self.form[1].minGap.setProperty("value", self.obj.minGap) + self.form[1].maxExDist.setProperty("value", self.obj.maxExtendDist) + self.form[1].miterAngle1.setProperty("value", self.obj.miterangle1) + self.form[1].miterAngle2.setProperty("value", self.obj.miterangle2) + + def toggleSelectionMode(self): + if not self.SelModeActive: + self.obj.Visibility=False + self.obj.baseObject[0].Visibility=True + Gui.Selection.clearSelection() + Gui.Selection.addSelection(self.obj.baseObject[0],self.obj.baseObject[1]) + Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.GreedySelection) + self.SelModeActive=True + self.form[0].AddRemove.setText('Preview') + else: + self.updateElement() + Gui.Selection.clearSelection() + Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.NormalSelection) + self.obj.Document.recompute() + self.obj.baseObject[0].Visibility=False + self.obj.Visibility=True + self.SelModeActive=False + self.form[0].AddRemove.setText('Select') + + def updateElement(self): + if not self.obj: + return + + sel = Gui.Selection.getSelectionEx()[0] + if not sel.HasSubObjects: + self.update() + return + + obj = sel.Object + for elt in sel.SubElementNames: + if "Face" in elt or "Edge" in elt: + face = self.obj.baseObject + found = False + if face[0] == obj.Name: + if isinstance(face[1], tuple): + for subf in face[1]: + if subf == elt: + found = True + else: + if face[1][0] == elt: + found = True + if not found: + self.obj.baseObject = (sel.Object, sel.SubElementNames) + self.update() + + def accept(self): + FreeCAD.ActiveDocument.recompute() + Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.NormalSelection) + self.obj.Document.commitTransaction() + Gui.Control.closeDialog() + Gui.ActiveDocument.resetEdit() + # self.obj.ViewObject.Visibility=True + return True + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + Gui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + class AddWallCommandClass: + """Add Wall command""" + + def GetResources(self): + return { + "Pixmap": os.path.join( + icons_path, "SheetMetal_AddWall.svg" + ), # the name of a svg file available in the resources + "MenuText": FreeCAD.Qt.translate("SheetMetal", "Make Wall"), + "Accel": "W", + "ToolTip": FreeCAD.Qt.translate( + "SheetMetal", + "Extends one or more face, connected by a bend on existing sheet metal.\n" + "1. Select edges or thickness side faces 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 + sel = Gui.Selection.getSelectionEx()[0] + selobj = sel.Object + viewConf = SheetMetalTools.GetViewConfig(selobj) + if hasattr(view, "getActiveObject"): + activeBody = view.getActiveObject("pdbody") + if not SheetMetalTools.smIsOperationLegal(activeBody, selobj): + return + doc.openTransaction("Bend") + if activeBody is None or not SheetMetalTools.smIsPartDesign(selobj): + a = doc.addObject("Part::FeaturePython", "Bend") + SMBendWall(a) + a.baseObject = (selobj, sel.SubElementNames) + SMViewProviderTree(a.ViewObject) + else: + # FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) + a = doc.addObject("PartDesign::FeaturePython", "Bend") + SMBendWall(a) + a.baseObject = (selobj, sel.SubElementNames) + SMViewProviderFlat(a.ViewObject) + activeBody.addObject(a) + SheetMetalTools.SetViewConfig(a, viewConf) + Gui.Selection.clearSelection() + if SheetMetalTools.is_autolink_enabled(): + root = SheetMetalTools.getOriginalBendObject(a) + if root: + a.setExpression("radius", root.Label + ".radius") + dialog = SMBendWallTaskPanel(a) + doc.recompute() + Gui.Control.showDialog(dialog) + return + + def IsActive(self): + if ( + len(Gui.Selection.getSelection()) < 1 + or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1 + ): + return False + selobj = Gui.Selection.getSelection()[0] + for selobj in Gui.Selection.getSelection(): + if selobj.isDerivedFrom("Sketcher::SketchObject"): + return False + for selFace in Gui.Selection.getSelectionEx()[0].SubObjects: + if type(selFace) == Part.Vertex: + return False + return True + + + Gui.addCommand("SheetMetal_AddWall", AddWallCommandClass()) + diff --git a/SheetMetalCornerReliefCmd.py b/SheetMetalCornerReliefCmd.py index 4548a52..8465717 100644 --- a/SheetMetalCornerReliefCmd.py +++ b/SheetMetalCornerReliefCmd.py @@ -23,64 +23,10 @@ # ################################################################################### -from FreeCAD import Gui -from PySide import QtCore, QtGui - -import FreeCAD, Part, os, math - -__dir__ = os.path.dirname(__file__) -iconPath = os.path.join(__dir__, "Resources", "icons") -smEpsilon = 0.0000001 - -# add translations path -LanguagePath = os.path.join(__dir__, "translations") -Gui.addLanguagePath(LanguagePath) -Gui.updateLocale() - -import BOPTools.SplitFeatures, BOPTools.JoinFeatures -import SheetMetalBendSolid -import SheetMetalBaseCmd - -from SheetMetalLogger import SMLogger, UnfoldException, BendException, TreeException - - -def smWarnDialog(msg): - diag = QtGui.QMessageBox( - QtGui.QMessageBox.Warning, - FreeCAD.Qt.translate("QMessageBox", "Error in macro MessageBox"), - msg, - ) - diag.setWindowModality(QtCore.Qt.ApplicationModal) - diag.exec_() - - -def smBelongToBody(item, body): - if body is None: - return False - for obj in body.Group: - if obj.Name == item.Name: - return True - return False - - -def smIsPartDesign(obj): - return str(obj).find(" 0.0: resultsolid = FoldShape - Gui.ActiveDocument.getObject(MainObject.Name).Visibility = False - Gui.ActiveDocument.getObject(bendlinesketch.Name).Visibility = False return resultsolid class SMFoldWall: def __init__(self, obj): '''"Fold / Bend a Sheetmetal with given Bend Radius"''' - selobj = Gui.Selection.getSelectionEx() _tip_ = FreeCAD.Qt.translate("App::Property", "Bend Radius") obj.addProperty( @@ -358,11 +259,11 @@ def __init__(self, obj): _tip_ = FreeCAD.Qt.translate("App::Property", "Base Object") obj.addProperty( "App::PropertyLinkSub", "baseObject", "Parameters", _tip_ - ).baseObject = (selobj[0].Object, selobj[0].SubElementNames) + ) _tip_ = FreeCAD.Qt.translate("App::Property", "Bend Reference Line List") obj.addProperty( "App::PropertyLink", "BendLine", "Parameters", _tip_ - ).BendLine = selobj[1].Object + ) _tip_ = FreeCAD.Qt.translate("App::Property", "Invert Solid Bend Direction") obj.addProperty( "App::PropertyBool", "invertbend", "Parameters", _tip_ @@ -408,171 +309,186 @@ def execute(self, fp): fp.Shape = s -class SMFoldViewProvider: - "A View provider that nests children objects under the created one" +########################################################################################################## +# Gui code +########################################################################################################## + +if SheetMetalTools.isGuiLoaded(): + from FreeCAD import Gui + + icons_path = SheetMetalTools.icons_path - def __init__(self, obj): - obj.Proxy = self - self.Object = obj.Object - def attach(self, obj): - self.Object = obj.Object - return + class SMFoldViewProvider: + "A View provider that nests children objects under the created one" - def updateData(self, fp, prop): - return + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object - def getDisplayModes(self, obj): - modes = [] - return modes + def attach(self, obj): + self.Object = obj.Object + return + + def updateData(self, fp, prop): + return - def setDisplayMode(self, mode): - return mode + def getDisplayModes(self, obj): + modes = [] + return modes - def onChanged(self, vp, prop): - return + def setDisplayMode(self, mode): + return mode - def __getstate__(self): - # return {'ObjectName' : self.Object.Name} - return None + def onChanged(self, vp, prop): + return - def __setstate__(self, state): - self.loads(state) + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None - # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 - def dumps(self): - return None + def __setstate__(self, state): + self.loads(state) - def loads(self, state): - if state is not None: - import FreeCAD + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None - doc = FreeCAD.ActiveDocument # crap - self.Object = doc.getObject(state["ObjectName"]) + def loads(self, state): + if state is not None: + import FreeCAD - def claimChildren(self): - objs = [] - if hasattr(self.Object, "baseObject"): - objs.append(self.Object.baseObject[0]) - objs.append(self.Object.BendLine) - return objs + doc = FreeCAD.ActiveDocument # crap + self.Object = doc.getObject(state["ObjectName"]) - def getIcon(self): - return os.path.join(iconPath, "SheetMetal_AddFoldWall.svg") + def claimChildren(self): + objs = [] + if hasattr(self.Object, "baseObject"): + objs.append(self.Object.baseObject[0]) + objs.append(self.Object.BendLine) + return objs + def getIcon(self): + return os.path.join(icons_path, "SheetMetal_AddFoldWall.svg") -class SMFoldPDViewProvider: - "A View provider that nests children objects under the created one" - def __init__(self, obj): - obj.Proxy = self - self.Object = obj.Object - - def attach(self, obj): - self.Object = obj.Object - return - - def updateData(self, fp, prop): - return - - def getDisplayModes(self, obj): - modes = [] - return modes - - def setDisplayMode(self, mode): - return mode - - def onChanged(self, vp, prop): - return - - def __getstate__(self): - # return {'ObjectName' : self.Object.Name} - return None - - def __setstate__(self, state): - self.loads(state) - - # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 - def dumps(self): - return None - - def loads(self, state): - if state is not None: - import FreeCAD - - doc = FreeCAD.ActiveDocument # crap - self.Object = doc.getObject(state["ObjectName"]) - - def claimChildren(self): - objs = [] - if hasattr(self.Object, "BendLine"): - objs.append(self.Object.BendLine) - return objs - - def getIcon(self): - return os.path.join(iconPath, "SheetMetal_AddFoldWall.svg") - - -class AddFoldWallCommandClass: - """Add Fold Wall command""" - - def GetResources(self): - return { - "Pixmap": os.path.join( - iconPath, "SheetMetal_AddFoldWall.svg" - ), # the name of a svg file available in the resources - "MenuText": FreeCAD.Qt.translate("SheetMetal", "Fold a Wall"), - "Accel": "C, F", - "ToolTip": FreeCAD.Qt.translate( - "SheetMetal", - "Fold a wall of metal sheet\n" - "1. Select a flat face on sheet metal and\n" - "2. Select a bend line (sketch) on same face (ends of sketch bend lines must" - " extend beyond edges of face) to create sheetmetal fold.\n" - "3. Use Property editor to modify other parameters", - ), - } - - def Activated(self): - doc = FreeCAD.ActiveDocument - view = Gui.ActiveDocument.ActiveView - activeBody = None - selobj = Gui.Selection.getSelectionEx()[0].Object - viewConf = SheetMetalBaseCmd.GetViewConfig(selobj) - if hasattr(view, "getActiveObject"): - activeBody = view.getActiveObject("pdbody") - if not smIsOperationLegal(activeBody, selobj): + class SMFoldPDViewProvider: + "A View provider that nests children objects under the created one" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object return - doc.openTransaction("Bend") - if activeBody is None or not smIsPartDesign(selobj): - a = doc.addObject("Part::FeaturePython", "Fold") - SMFoldWall(a) - SMFoldViewProvider(a.ViewObject) - else: - # FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) - a = doc.addObject("PartDesign::FeaturePython", "Fold") - SMFoldWall(a) - SMFoldPDViewProvider(a.ViewObject) - activeBody.addObject(a) - SheetMetalBaseCmd.SetViewConfig(a, viewConf) - if SheetMetalBaseCmd.autolink_enabled(): - root = SheetMetalBaseCmd.getOriginalBendObject(a) - if root: - a.setExpression("radius", root.Label + ".radius") - doc.recompute() - doc.commitTransaction() - return - - def IsActive(self): - if len(Gui.Selection.getSelection()) < 2: - return False - selFace = Gui.Selection.getSelectionEx()[0].SubObjects[0] - if type(selFace) != Part.Face: - return False - selobj = Gui.Selection.getSelection()[1] - if not (selobj.isDerivedFrom("Sketcher::SketchObject")): - return False - return True - - -Gui.addCommand("SheetMetal_AddFoldWall", AddFoldWallCommandClass()) + + def updateData(self, fp, prop): + return + + def getDisplayModes(self, obj): + modes = [] + return modes + + def setDisplayMode(self, mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + + doc = FreeCAD.ActiveDocument # crap + self.Object = doc.getObject(state["ObjectName"]) + + def claimChildren(self): + objs = [] + if hasattr(self.Object, "BendLine"): + objs.append(self.Object.BendLine) + return objs + + def getIcon(self): + return os.path.join(icons_path, "SheetMetal_AddFoldWall.svg") + + + class AddFoldWallCommandClass: + """Add Fold Wall command""" + + def GetResources(self): + return { + "Pixmap": os.path.join( + icons_path, "SheetMetal_AddFoldWall.svg" + ), # the name of a svg file available in the resources + "MenuText": FreeCAD.Qt.translate("SheetMetal", "Fold a Wall"), + "Accel": "C, F", + "ToolTip": FreeCAD.Qt.translate( + "SheetMetal", + "Fold a wall of metal sheet\n" + "1. Select a flat face on sheet metal and\n" + "2. Select a bend line (sketch) on same face (ends of sketch bend lines must" + " extend beyond edges of face) to create sheetmetal fold.\n" + "3. Use Property editor to modify other parameters", + ), + } + + def Activated(self): + doc = FreeCAD.ActiveDocument + view = Gui.ActiveDocument.ActiveView + activeBody = None + sel = Gui.Selection.getSelectionEx() + selobj = Gui.Selection.getSelectionEx()[0].Object + viewConf = SheetMetalTools.GetViewConfig(selobj) + if hasattr(view, "getActiveObject"): + activeBody = view.getActiveObject("pdbody") + if not SheetMetalTools.smIsOperationLegal(activeBody, selobj): + return + doc.openTransaction("Bend") + if activeBody is None or not SheetMetalTools.smIsPartDesign(selobj): + a = doc.addObject("Part::FeaturePython", "Fold") + SMFoldWall(a) + a.baseObject = (selobj, sel[0].SubElementNames) + a.BendLine = sel[1].Object + SMFoldViewProvider(a.ViewObject) + else: + # FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) + a = doc.addObject("PartDesign::FeaturePython", "Fold") + SMFoldWall(a) + a.baseObject = (selobj, sel[0].SubElementNames) + a.BendLine = sel[1].Object + SMFoldPDViewProvider(a.ViewObject) + activeBody.addObject(a) + SheetMetalTools.SetViewConfig(a, viewConf) + if SheetMetalTools.is_autolink_enabled(): + root = SheetMetalTools.getOriginalBendObject(a) + if root: + a.setExpression("radius", root.Label + ".radius") + doc.recompute() + doc.commitTransaction() + return + + def IsActive(self): + if len(Gui.Selection.getSelection()) < 2: + return False + selFace = Gui.Selection.getSelectionEx()[0].SubObjects[0] + if type(selFace) != Part.Face: + return False + selobj = Gui.Selection.getSelection()[1] + if not (selobj.isDerivedFrom("Sketcher::SketchObject")): + return False + return True + + + Gui.addCommand("SheetMetal_AddFoldWall", AddFoldWallCommandClass()) diff --git a/SheetMetalFormingCmd.py b/SheetMetalFormingCmd.py index d00e822..85e6fad 100644 --- a/SheetMetalFormingCmd.py +++ b/SheetMetalFormingCmd.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ################################################################################### # -# SheetMetalCmd.py +# SheetMetalFormingCmd.py # # Copyright 2015 Shai Seger # @@ -23,57 +23,11 @@ # ################################################################################### -from FreeCAD import Gui -from PySide import QtCore, QtGui +import FreeCAD, Part, math, os, SheetMetalTools +from SheetMetalLogger import SMLogger -import FreeCAD, FreeCADGui, Part, os, math -import SheetMetalBaseCmd +smEpsilon = SheetMetalTools.smEpsilon -from SheetMetalLogger import SMLogger, UnfoldException, BendException, TreeException - -__dir__ = os.path.dirname(__file__) -iconPath = os.path.join( __dir__, 'Resources', 'icons' ) -smEpsilon = 0.0000001 - -# add translations path -LanguagePath = os.path.join( __dir__, 'translations') -Gui.addLanguagePath(LanguagePath) -Gui.updateLocale() - -def smWarnDialog(msg): - diag = QtGui.QMessageBox( - QtGui.QMessageBox.Warning, - FreeCAD.Qt.translate("QMessageBox", "Error in macro MessageBox"), - msg, - ) - diag.setWindowModality(QtCore.Qt.ApplicationModal) - diag.exec_() - -def smBelongToBody(item, body): - if (body is None): - return False - for obj in body.Group: - if obj.Name == item.Name: - return True - return False - -def smIsPartDesign(obj): - return str(obj).find("": - return False - for selFace in Gui.Selection.getSelectionEx()[0].SubObjects: - if type(selFace) != Part.Face : + if not found: + self.obj.toolObject = (sel.Object, sel.SubElementNames) + self.update() + + def accept(self): + FreeCAD.ActiveDocument.recompute() + Gui.ActiveDocument.resetEdit() + #self.obj.ViewObject.Visibility=True + return True + + def retranslateUi(self, TaskPanel): + #TaskPanel.setWindowTitle(QtGui.QApplication.translate("draft", "Faces", None)) + self.addButton.setText(QtGui.QApplication.translate("draft", "Update", None)) + + + class AddFormingWallCommand(): + """Add Forming Wall command""" + + def GetResources(self): + return {'Pixmap' : os.path.join( icons_path , 'SheetMetal_Forming.svg') , # the name of a svg file available in the resources + 'MenuText': FreeCAD.Qt.translate('SheetMetal','Make Forming in Wall') , + 'Accel': "M, F", + 'ToolTip' : FreeCAD.Qt.translate('SheetMetal','Make a forming using tool in metal sheet\n' + '1. Select a flat face on sheet metal and\n' + '2. Select face(s) on forming tool Shape to create Formed sheetmetal.\n' + '3. Use Suppress in Property editor to disable during unfolding\n' + '4. Use Property editor to modify other parameters')} + + def Activated(self): + doc = FreeCAD.ActiveDocument + view = Gui.ActiveDocument.ActiveView + activeBody = None + sel = Gui.Selection.getSelectionEx() + selobj = Gui.Selection.getSelectionEx()[0].Object + viewConf = SheetMetalTools.GetViewConfig(selobj) + if hasattr(view,'getActiveObject'): + activeBody = view.getActiveObject('pdbody') + if not SheetMetalTools.smIsOperationLegal(activeBody, selobj): + return + doc.openTransaction("WallForming") + if activeBody is None or not SheetMetalTools.smIsPartDesign(selobj): + a = doc.addObject("Part::FeaturePython","WallForming") + SMBendWall(a) + a.baseObject = (selobj, sel[0].SubElementNames) + a.toolObject = (sel[1].Object, sel[1].SubElementNames) + SMFormingVP(a.ViewObject) + else: + #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) + a = doc.addObject("PartDesign::FeaturePython","WallForming") + SMBendWall(a) + a.baseObject = (selobj, sel[0].SubElementNames) + a.toolObject = (sel[1].Object, sel[1].SubElementNames) + SMFormingPDVP(a.ViewObject) + activeBody.addObject(a) + SheetMetalTools.SetViewConfig(a, viewConf) + doc.recompute() + doc.commitTransaction() + return + + def IsActive(self): + if len(Gui.Selection.getSelection()) < 2 or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1: return False - return True + selobj = Gui.Selection.getSelection()[0] + if str(type(selobj)) == "": + return False + for selFace in Gui.Selection.getSelectionEx()[0].SubObjects: + if type(selFace) != Part.Face : + return False + return True -Gui.addCommand("SheetMetal_Forming", AddFormingWallCommand()) + Gui.addCommand("SheetMetal_Forming", AddFormingWallCommand()) diff --git a/SheetMetalJunction.py b/SheetMetalJunction.py index bbce494..ce0103d 100644 --- a/SheetMetalJunction.py +++ b/SheetMetalJunction.py @@ -1,410 +1,380 @@ -# -*- coding: utf-8 -*- -################################################################################### -# -# SheetMetalJunction.py -# -# Copyright 2015 Shai Seger -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# -################################################################################### - -from FreeCAD import Gui -from PySide import QtCore, QtGui - -import FreeCAD, FreeCADGui, Part, os -import SheetMetalBaseCmd -__dir__ = os.path.dirname(__file__) -iconPath = os.path.join( __dir__, 'Resources', 'icons' ) -smEpsilon = 0.0000001 - -# add translations path -LanguagePath = os.path.join( __dir__, 'translations') -Gui.addLanguagePath(LanguagePath) -Gui.updateLocale() - -# IMPORTANT: please remember to change the element map version in case of any -# changes in modeling logic -smElementMapVersion = 'sm1.' - -def smWarnDialog(msg): - diag = QtGui.QMessageBox( - QtGui.QMessageBox.Warning, - FreeCAD.Qt.translate("QMessageBox", "Error in macro MessageBox"), - msg, - ) - diag.setWindowModality(QtCore.Qt.ApplicationModal) - diag.exec_() - -def smBelongToBody(item, body): - if (body is None): - return False - for obj in body.Group: - if obj.Name == item.Name: - return True - return False - -def smIsPartDesign(obj): - return str(obj).find(" +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +################################################################################### + +import FreeCAD, Part, os, SheetMetalTools + +# IMPORTANT: please remember to change the element map version in case of any +# changes in modeling logic +smElementMapVersion = 'sm1.' + +def smJunction(gap = 2.0, selEdgeNames = '', MainObject = None): + import BOPTools.SplitFeatures, BOPTools.JoinFeatures + + resultSolid = MainObject + for selEdgeName in selEdgeNames: + edge = MainObject.getElement(selEdgeName) + + facelist = MainObject.ancestorsOfType(edge, Part.Face) + #for face in facelist : + # Part.show(face,'face') + + joinface = facelist[0].fuse(facelist[1]) + #Part.show(joinface,'joinface') + filletedface = joinface.makeFillet(gap, joinface.Edges) + #Part.show(filletedface,'filletedface') + + cutface1= facelist[0].cut(filletedface) + #Part.show(cutface1,'cutface1') + offsetsolid1 = cutface1.makeOffsetShape(-gap, 0.0, fill = True) + #Part.show(offsetsolid1,'offsetsolid1') + + cutface2 = facelist[1].cut(filletedface) + #Part.show(cutface2,'cutface2') + offsetsolid2 = cutface2.makeOffsetShape(-gap, 0.0, fill = True) + #Part.show(offsetsolid2,'offsetsolid2') + cutsolid = offsetsolid1.fuse(offsetsolid2) + #Part.show(cutsolid,'cutsolid') + resultSolid = resultSolid.cut(cutsolid) + #Part.show(resultsolid,'resultsolid') + + return resultSolid + +class SMJunction: + def __init__(self, obj): + '''"Add Gap to Solid" ''' + + _tip_ = FreeCAD.Qt.translate("App::Property","Junction Gap") + obj.addProperty("App::PropertyLength","gap","Parameters",_tip_).gap = 2.0 + _tip_ = FreeCAD.Qt.translate("App::Property","Base Object") + obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters",_tip_) + obj.Proxy = self + + def getElementMapVersion(self, _fp, ver, _prop, restored): + if not restored: + return smElementMapVersion + ver + + def execute(self, fp): + '''"Print a short message when doing a recomputation, this method is mandatory" ''' + # pass selected object shape + Main_Object = fp.baseObject[0].Shape.copy() + s = smJunction(gap = fp.gap.Value, selEdgeNames = fp.baseObject[1], MainObject = Main_Object) + fp.Shape = s + + +########################################################################################################## +# Gui code +########################################################################################################## + +if SheetMetalTools.isGuiLoaded(): + from FreeCAD import Gui + from PySide import QtCore, QtGui + + icons_path = SheetMetalTools.icons_path + + # add translations path + Gui.addLanguagePath(SheetMetalTools.language_path) + Gui.updateLocale() + + + class SMJViewProviderTree: + "A View provider that nests children objects under the created one" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def setupContextMenu(self, viewObject, menu): + action = menu.addAction(FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label)) + action.triggered.connect(lambda: self.startDefaultEditMode(viewObject)) + return False + + def startDefaultEditMode(self, viewObject): + document = viewObject.Document.Document + if not document.HasPendingTransaction: + text = FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label) + document.openTransaction(text) + viewObject.Document.setEdit(viewObject.Object, 0) + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + modes=[] + return modes + + def setDisplayMode(self,mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + doc = FreeCAD.ActiveDocument #crap + self.Object = doc.getObject(state['ObjectName']) + + def claimChildren(self): + objs = [] + if hasattr(self.Object,"baseObject"): + objs.append(self.Object.baseObject[0]) + return objs + + def getIcon(self): + return os.path.join( icons_path , 'SheetMetal_AddJunction.svg') + + def setEdit(self,vobj,mode): + taskd = SMJunctionTaskPanel() + taskd.obj = vobj.Object + taskd.update() + self.Object.ViewObject.Visibility=False + self.Object.baseObject[0].ViewObject.Visibility=True + Gui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + Gui.Control.closeDialog() + self.Object.baseObject[0].ViewObject.Visibility=False + self.Object.ViewObject.Visibility=True + return False + + class SMJViewProviderFlat: + "A View provider that nests children objects under the created one" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + modes=[] + return modes + + def setDisplayMode(self,mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + doc = FreeCAD.ActiveDocument #crap + self.Object = doc.getObject(state['ObjectName']) + + def claimChildren(self): + return [] + + def getIcon(self): + return os.path.join( icons_path , 'SheetMetal_AddJunction.svg') + + def setEdit(self,vobj,mode): + taskd = SMJunctionTaskPanel() + taskd.obj = vobj.Object + taskd.update() + self.Object.ViewObject.Visibility=False + self.Object.baseObject[0].ViewObject.Visibility=True + Gui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + Gui.Control.closeDialog() + self.Object.baseObject[0].ViewObject.Visibility=False + self.Object.ViewObject.Visibility=True + return False + + class SMJunctionTaskPanel: + '''A TaskPanel for the Sheetmetal''' + def __init__(self): + + self.obj = None + self.form = QtGui.QWidget() + self.form.setObjectName("SMJunctionTaskPanel") + self.form.setWindowTitle("Binded edges list") + self.grid = QtGui.QGridLayout(self.form) + self.grid.setObjectName("grid") + self.title = QtGui.QLabel(self.form) + self.grid.addWidget(self.title, 0, 0, 1, 2) + self.title.setText("Select new Edge(s) and press Update") + + # tree + self.tree = QtGui.QTreeWidget(self.form) + self.grid.addWidget(self.tree, 1, 0, 1, 2) + self.tree.setColumnCount(2) + self.tree.setHeaderLabels(["Name","Subelement"]) + + # buttons + self.addButton = QtGui.QPushButton(self.form) + self.addButton.setObjectName("addButton") + self.addButton.setIcon(QtGui.QIcon(os.path.join( icons_path , 'SheetMetal_Update.svg'))) + self.grid.addWidget(self.addButton, 3, 0, 1, 2) + + QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.updateElement) + self.update() + + def isAllowedAlterSelection(self): + return True + + def isAllowedAlterView(self): + return True + + def getStandardButtons(self): + return QtGui.QDialogButtonBox.Ok + + def update(self): + 'fills the treewidget' + self.tree.clear() + if self.obj: + f = self.obj.baseObject + if isinstance(f[1],list): + for subf in f[1]: + #FreeCAD.Console.PrintLog("item: " + subf + "\n") + item = QtGui.QTreeWidgetItem(self.tree) + item.setText(0,f[0].Name) + item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg")) + item.setText(1,subf) + else: + item = QtGui.QTreeWidgetItem(self.tree) + item.setText(0,f[0].Name) + item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg")) + item.setText(1,f[1][0]) + self.retranslateUi(self.form) + + def updateElement(self): + if self.obj: + sel = Gui.Selection.getSelectionEx()[0] + if sel.HasSubObjects: + obj = sel.Object + for elt in sel.SubElementNames: + if "Edge" in elt: + edge = self.obj.baseObject + found = False + if (edge[0] == obj.Name): + if isinstance(edge[1],tuple): + for subf in edge[1]: + if subf == elt: + found = True + else: + if (edge[1][0] == elt): + found = True + if not found: + self.obj.baseObject = (sel.Object, sel.SubElementNames) + self.update() + + def accept(self): + FreeCAD.ActiveDocument.recompute() + Gui.ActiveDocument.resetEdit() + #self.obj.ViewObject.Visibility=True + return True + + def retranslateUi(self, TaskPanel): + #TaskPanel.setWindowTitle(QtGui.QApplication.translate("draft", "edges", None)) + self.addButton.setText(QtGui.QApplication.translate("draft", "Update", None)) + + + class AddJunctionCommandClass(): + """Add Junction command""" + + def GetResources(self): + return {'Pixmap' : os.path.join( icons_path , 'SheetMetal_AddJunction.svg'), # the name of a svg file available in the resources + 'MenuText': FreeCAD.Qt.translate('SheetMetal','Make Junction'), + 'Accel': "S, J", + 'ToolTip' : FreeCAD.Qt.translate('SheetMetal','Create a rip where two walls come together on solids.\n' + '1. Select edge(s) to create rip on corner edge(s).\n' + '2. Use Property editor to modify parameters')} + + def Activated(self): + doc = FreeCAD.ActiveDocument + view = Gui.ActiveDocument.ActiveView + activeBody = None + sel = Gui.Selection.getSelectionEx()[0] + selobj = Gui.Selection.getSelectionEx()[0].Object + viewConf = SheetMetalTools.GetViewConfig(selobj) + if hasattr(view,'getActiveObject'): + activeBody = view.getActiveObject('pdbody') + if not SheetMetalTools.smIsOperationLegal(activeBody, selobj): + return + doc.openTransaction("Add Junction") + if activeBody is None or not SheetMetalTools.smIsPartDesign(selobj): + a = doc.addObject("Part::FeaturePython","Junction") + SMJunction(a) + a.baseObject = (selobj, sel.SubElementNames) + SMJViewProviderTree(a.ViewObject) + else: + #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) + a = doc.addObject("PartDesign::FeaturePython","Junction") + SMJunction(a) + a.baseObject = (selobj, sel.SubElementNames) + SMJViewProviderFlat(a.ViewObject) + activeBody.addObject(a) + SheetMetalTools.SetViewConfig(a, viewConf) + Gui.Selection.clearSelection() + doc.recompute() + doc.commitTransaction() + return + + def IsActive(self): + if len(Gui.Selection.getSelection()) < 1 or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1: + return False + # selobj = Gui.Selection.getSelection()[0] + for selEdge in Gui.Selection.getSelectionEx()[0].SubObjects: + if type(selEdge) != Part.Edge : + return False + return True + + Gui.addCommand("SheetMetal_AddJunction", AddJunctionCommandClass()) + \ No newline at end of file diff --git a/SheetMetalKfactor.py b/SheetMetalKfactor.py index fc41c9b..9ffb615 100644 --- a/SheetMetalKfactor.py +++ b/SheetMetalKfactor.py @@ -76,7 +76,7 @@ def getSpreadSheetNames(): availableMdsObjects = [] for candidate in candidateSpreadSheets: try: - KFactorLookupTable(candidate.Label) + KFactorLookupTable(candidate) availableMdsObjects.append(candidate) except ValueError as e: FreeCAD.Console.PrintWarning( @@ -90,16 +90,7 @@ def getSpreadSheetNames(): class KFactorLookupTable: cell_regex = re.compile("^([A-Z]+)([0-9]+)$") - def __init__(self, material_sheet): - lookup_sheet = FreeCAD.ActiveDocument.getObjectsByLabel(material_sheet) - if len(lookup_sheet) >= 1: - lookup_sheet = lookup_sheet[0] - else: - raise ValueError( - "No spreadsheet found containing material definition: %s" - % material_sheet - ) - + def __init__(self, lookup_sheet): key_cell = self.find_cell_by_label(lookup_sheet, "Radius / Thickness") value_cell, k_factor_standard = self.find_k_factor_cell(lookup_sheet) @@ -175,7 +166,7 @@ def build_k_factor_lookup( def get_k_factor_standard(self, sheet, options_cell, k_factor_standard): if options_cell is not None: - opt_col, opt_row = get_cell_tuple(options_cell) + opt_col, opt_row = self.get_cell_tuple(options_cell) i = 1 while True: opt_key_cell = "%s%i" % (opt_col, opt_row + i) diff --git a/SheetMetalLogger.py b/SheetMetalLogger.py index d0c4263..a779bba 100644 --- a/SheetMetalLogger.py +++ b/SheetMetalLogger.py @@ -25,7 +25,6 @@ ################################################################################### import FreeCAD -from PySide import QtCore, QtGui class SMLogger: @classmethod diff --git a/SheetMetalRelief.py b/SheetMetalRelief.py index 21558c3..cceb207 100644 --- a/SheetMetalRelief.py +++ b/SheetMetalRelief.py @@ -1,458 +1,428 @@ -# -*- coding: utf-8 -*- -############################################################################### -# -# SheetMetalRelief.py -# -# Copyright 2015 Shai Seger -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# -############################################################################### - -from FreeCAD import Gui -from PySide import QtCore, QtGui - -import FreeCAD, FreeCADGui, Part, os -import SheetMetalBaseCmd - -__dir__ = os.path.dirname(__file__) -iconPath = os.path.join( __dir__, 'Resources', 'icons' ) -smEpsilon = 0.0000001 - -# add translations path -LanguagePath = os.path.join( __dir__, 'translations') -Gui.addLanguagePath(LanguagePath) -Gui.updateLocale() - -# IMPORTANT: please remember to change the element map version in case of any -# changes in modeling logic -smElementMapVersion = 'sm1.' - -def smWarnDialog(msg): - diag = QtGui.QMessageBox( - QtGui.QMessageBox.Warning, - FreeCAD.Qt.translate("QMessageBox", "Error in macro MessageBox"), - msg, - ) - diag.setWindowModality(QtCore.Qt.ApplicationModal) - diag.exec_() - -def smBelongToBody(item, body): - if (body is None): - return False - for obj in body.Group: - if obj.Name == item.Name: - return True - return False - -def smIsPartDesign(obj): - return str(obj).find(" +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +############################################################################### + +import FreeCAD, Part, os, SheetMetalTools + +# IMPORTANT: please remember to change the element map version in case of any +# changes in modeling logic +smElementMapVersion = 'sm1.' +smEpsilon = SheetMetalTools.smEpsilon + +def smMakeFace(vertex, face, edges, relief): + + if edges[0].Vertexes[0].isSame(vertex) : + Edgedir1 = edges[0].Vertexes[1].Point - edges[0].Vertexes[0].Point + else : + Edgedir1 = edges[0].Vertexes[0].Point - edges[0].Vertexes[1].Point + Edgedir1.normalize() + + if edges[1].Vertexes[0].isSame(vertex) : + Edgedir2 = edges[1].Vertexes[1].Point - edges[1].Vertexes[0].Point + else : + Edgedir2 = edges[1].Vertexes[0].Point - edges[1].Vertexes[1].Point + Edgedir2.normalize() + normal = face.normalAt(0,0) + Edgedir3 = normal.cross(Edgedir1) + Edgedir4 = normal.cross(Edgedir2) + + p1 = vertex.Point + p2 = p1 + relief * Edgedir1 + p3 = p2 + relief * Edgedir3 + if not(face.isInside(p3,0.0,True)) : + p3 = p2 + relief * Edgedir3 * -1 + p6 = p1 + relief * Edgedir2 + p5 = p6 + relief * Edgedir4 + if not(face.isInside(p5, 0.0,True)) : + p5 = p6 + relief * Edgedir4 * -1 + #print([p1,p2,p3,p5,p6,p1]) + + e1 = Part.makeLine(p2, p3) + #Part.show(e1,'e1') + e2 = Part.makeLine(p5, p6) + #Part.show(e2,'e2') + section = e1.section(e2) + #Part.show(section1,'section1') + + if section.Vertexes : + wire = Part.makePolygon([p1,p2,p3,p6,p1]) + else : + p41 = p3 + relief * Edgedir1 * -1 + p42 = p5 + relief * Edgedir2 * -1 + e1 = Part.Line(p3, p41).toShape() + #Part.show(e1,'e1') + e2 = Part.Line(p42, p5).toShape() + #Part.show(e2,'e2') + section = e1.section(e2) + #Part.show(section1,'section1') + p4 = section.Vertexes[0].Point + wire = Part.makePolygon([p1,p2,p3,p4,p5,p6,p1]) + + extface = Part.Face(wire) + return extface + +def smRelief(relief = 2.0, selVertexNames = ' ', MainObject = None): + + resultSolid = MainObject + for selVertexName in selVertexNames: + vertex = MainObject.getElement(selVertexName) + facelist = MainObject.ancestorsOfType(vertex, Part.Face) + + extsolidlist = [] + for face in facelist : + #Part.show(face,'face') + edgelist =face.ancestorsOfType(vertex, Part.Edge) + #for edge in edgelist : + #Part.show(edge,'edge') + extface = smMakeFace(vertex, face, edgelist, relief) + #Part.show(extface,'extface') + extsolid = extface.extrude(relief * face.normalAt(0,0)*-1) + extsolidlist.append(extsolid) + + cutsolid = extsolidlist[0].multiFuse(extsolidlist[1:]) + #Part.show(cutsolid,'cutsolid') + cutsolid = cutsolid.removeSplitter() + resultSolid = resultSolid.cut(cutsolid) + #Part.show(resultsolid,'resultsolid') + + return resultSolid + + +class SMRelief: + def __init__(self, obj): + '''"Add Relief to Solid" ''' + _tip_ = FreeCAD.Qt.translate("App::Property","Relief Size") + obj.addProperty("App::PropertyLength","relief","Parameters",_tip_).relief = 2.0 + _tip_ = FreeCAD.Qt.translate("App::Property","Base Object") + obj.addProperty("App::PropertyLinkSub", "baseObject", "Parameters",_tip_) + obj.Proxy = self + + def getElementMapVersion(self, _fp, ver, _prop, restored): + if not restored: + return smElementMapVersion + ver + + def execute(self, fp): + '''"Print a short message when doing a recomputation, this method is mandatory" ''' + # pass selected object shape + Main_Object = fp.baseObject[0].Shape.copy() + s = smRelief(relief = fp.relief.Value, selVertexNames = fp.baseObject[1], MainObject = Main_Object) + fp.Shape = s + + +########################################################################################################## +# Gui code +########################################################################################################## + +if SheetMetalTools.isGuiLoaded(): + from PySide import QtCore, QtGui + from FreeCAD import Gui + + icons_path = SheetMetalTools.icons_path + + # add translations path + Gui.addLanguagePath(SheetMetalTools.language_path) + Gui.updateLocale() + + + class SMReliefViewProviderTree: + "A View provider that nests children objects under the created one" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def setupContextMenu(self, viewObject, menu): + action = menu.addAction(FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label)) + action.triggered.connect(lambda: self.startDefaultEditMode(viewObject)) + return False + + def startDefaultEditMode(self, viewObject): + document = viewObject.Document.Document + if not document.HasPendingTransaction: + text = FreeCAD.Qt.translate("QObject", "Edit %1").replace("%1", viewObject.Object.Label) + document.openTransaction(text) + viewObject.Document.setEdit(viewObject.Object, 0) + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + modes=[] + return modes + + def setDisplayMode(self,mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + doc = FreeCAD.ActiveDocument #crap + self.Object = doc.getObject(state['ObjectName']) + + def claimChildren(self): + objs = [] + if hasattr(self.Object,"baseObject"): + objs.append(self.Object.baseObject[0]) + return objs + + def getIcon(self): + return os.path.join( icons_path , 'SheetMetal_AddRelief.svg') + + def setEdit(self,vobj,mode): + taskd = SMReliefTaskPanel() + taskd.obj = vobj.Object + taskd.update() + self.Object.ViewObject.Visibility=False + self.Object.baseObject[0].ViewObject.Visibility=True + Gui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + Gui.Control.closeDialog() + self.Object.baseObject[0].ViewObject.Visibility=False + self.Object.ViewObject.Visibility=True + return False + + class SMReliefViewProviderFlat: + "A View provider that nests children objects under the created one" + + def __init__(self, obj): + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + self.Object = obj.Object + return + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + modes=[] + return modes + + def setDisplayMode(self,mode): + return mode + + def onChanged(self, vp, prop): + return + + def __getstate__(self): + # return {'ObjectName' : self.Object.Name} + return None + + def __setstate__(self, state): + self.loads(state) + + # dumps and loads replace __getstate__ and __setstate__ post v. 0.21.2 + def dumps(self): + return None + + def loads(self, state): + if state is not None: + import FreeCAD + doc = FreeCAD.ActiveDocument #crap + self.Object = doc.getObject(state['ObjectName']) + + def claimChildren(self): + + return [] + + def getIcon(self): + return os.path.join( icons_path , 'SheetMetal_AddRelief.svg') + + def setEdit(self,vobj,mode): + taskd = SMReliefTaskPanel() + taskd.obj = vobj.Object + taskd.update() + self.Object.ViewObject.Visibility=False + self.Object.baseObject[0].ViewObject.Visibility=True + Gui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + Gui.Control.closeDialog() + self.Object.baseObject[0].ViewObject.Visibility=False + self.Object.ViewObject.Visibility=True + return False + + class SMReliefTaskPanel: + '''A TaskPanel for the Sheetmetal''' + def __init__(self): + + self.obj = None + self.form = QtGui.QWidget() + self.form.setObjectName("SMReliefTaskPanel") + self.form.setWindowTitle("Binded vertexes list") + self.grid = QtGui.QGridLayout(self.form) + self.grid.setObjectName("grid") + self.title = QtGui.QLabel(self.form) + self.grid.addWidget(self.title, 0, 0, 1, 2) + self.title.setText("Select new vertex(es) and press Update") + + # tree + self.tree = QtGui.QTreeWidget(self.form) + self.grid.addWidget(self.tree, 1, 0, 1, 2) + self.tree.setColumnCount(2) + self.tree.setHeaderLabels(["Name","Subelement"]) + + # buttons + self.addButton = QtGui.QPushButton(self.form) + self.addButton.setObjectName("addButton") + self.addButton.setIcon(QtGui.QIcon(os.path.join( icons_path , 'SheetMetal_Update.svg'))) + self.grid.addWidget(self.addButton, 3, 0, 1, 2) + + QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.updateElement) + self.update() + + def isAllowedAlterSelection(self): + return True + + def isAllowedAlterView(self): + return True + + def getStandardButtons(self): + return QtGui.QDialogButtonBox.Ok + + def update(self): + 'fills the treewidget' + self.tree.clear() + if self.obj: + f = self.obj.baseObject + if isinstance(f[1],list): + for subf in f[1]: + #FreeCAD.Console.PrintLog("item: " + subf + "\n") + item = QtGui.QTreeWidgetItem(self.tree) + item.setText(0,f[0].Name) + item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg")) + item.setText(1,subf) + else: + item = QtGui.QTreeWidgetItem(self.tree) + item.setText(0,f[0].Name) + item.setIcon(0,QtGui.QIcon(":/icons/Tree_Part.svg")) + item.setText(1,f[1][0]) + self.retranslateUi(self.form) + + def updateElement(self): + if self.obj: + sel = Gui.Selection.getSelectionEx()[0] + if sel.HasSubObjects: + obj = sel.Object + for elt in sel.SubElementNames: + if "Vertex" in elt: + vertex = self.obj.baseObject + found = False + if (vertex[0] == obj.Name): + if isinstance(vertex[1],tuple): + for subf in vertex[1]: + if subf == elt: + found = True + else: + if (vertex[1][0] == elt): + found = True + if not found: + self.obj.baseObject = (sel.Object, sel.SubElementNames) + self.update() + + def accept(self): + FreeCAD.ActiveDocument.recompute() + Gui.ActiveDocument.resetEdit() + #self.obj.ViewObject.Visibility=True + return True + + def retranslateUi(self, TaskPanel): + #TaskPanel.setWindowTitle(QtGui.QApplication.translate("draft", "vertexs", None)) + self.addButton.setText(QtGui.QApplication.translate("draft", "Update", None)) + + + class AddReliefCommandClass(): + """Add Relief command""" + + def GetResources(self): + return {'Pixmap' : os.path.join( icons_path , 'SheetMetal_AddRelief.svg'), # the name of a svg file available in the resources + 'MenuText': FreeCAD.Qt.translate('SheetMetal','Make Relief'), + 'Accel': "S, R", + 'ToolTip' : FreeCAD.Qt.translate('SheetMetal','Modify an Individual solid corner to create Relief.\n' + '1. Select Vertex(es) to create Relief on Solid corner Vertex(es).\n' + '2. Use Property editor to modify default parameters')} + + def Activated(self): + doc = FreeCAD.ActiveDocument + view = Gui.ActiveDocument.ActiveView + activeBody = None + sel = Gui.Selection.getSelectionEx()[0] + selobj = Gui.Selection.getSelectionEx()[0].Object + viewConf = SheetMetalTools.GetViewConfig(selobj) + if hasattr(view,'getActiveObject'): + activeBody = view.getActiveObject('pdbody') + if not SheetMetalTools.smIsOperationLegal(activeBody, selobj): + return + doc.openTransaction("Add Relief") + if activeBody is None or not SheetMetalTools.smIsPartDesign(selobj): + a = doc.addObject("Part::FeaturePython","Relief") + SMRelief(a) + a.baseObject = (selobj, sel.SubElementNames) + SMReliefViewProviderTree(a.ViewObject) + else: + #FreeCAD.Console.PrintLog("found active body: " + activeBody.Name) + a = doc.addObject("PartDesign::FeaturePython","Relief") + SMRelief(a) + a.baseObject = (selobj, sel.SubElementNames) + SMReliefViewProviderFlat(a.ViewObject) + activeBody.addObject(a) + SheetMetalTools.SetViewConfig(a, viewConf) + Gui.Selection.clearSelection() + doc.recompute() + doc.commitTransaction() + return + + def IsActive(self): + if len(Gui.Selection.getSelection()) < 1 or len(Gui.Selection.getSelectionEx()[0].SubElementNames) < 1: + return False + # selobj = Gui.Selection.getSelection()[0] + for selVertex in Gui.Selection.getSelectionEx()[0].SubObjects: + if type(selVertex) != Part.Vertex : + return False + return True + + Gui.addCommand("SheetMetal_AddRelief", AddReliefCommandClass()) + diff --git a/SheetMetalTools.py b/SheetMetalTools.py new file mode 100644 index 0000000..333b655 --- /dev/null +++ b/SheetMetalTools.py @@ -0,0 +1,129 @@ +import FreeCAD, os +from SheetMetalLogger import SMLogger + +translate = FreeCAD.Qt.translate + +mod_path = os.path.dirname(__file__) +icons_path = os.path.join(mod_path, "Resources", "icons") +panels_path = os.path.join(mod_path, "Resources", "panels") +language_path = os.path.join(mod_path, "translations") +params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/SheetMetal") +smEpsilon = 0.0000001 + +def isGuiLoaded(): + try: + return FreeCAD.GuiUp + except: + return False + +def smBelongToBody(item, body): + if body is None: + return False + for obj in body.Group: + if obj.Name == item.Name: + return True + return False + +def smIsPartDesign(obj): + return str(obj).find("\n" + "
  • Either select 'Manual K-factor'
  • \n" + "
  • Or use a Material Definition Sheet
  • \n" + "", + ).format(mds_help_url) + QtGui.QMessageBox.warning( + None, FreeCAD.Qt.translate("QMessageBox", "Warning"), msg + ) + return None + else: + self.object.useManualKFactor = False + self.object.materialSheet = FreeCAD.ActiveDocument.getObjectsByLabel( + self.form[0].availableMds.currentText() + )[0] + self.object.ViewObject.Transparency = self.form[0].transSpin.value() + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + if self.form[1].chkSketch.isChecked() and self.object.foldComp: + FreeCAD.ActiveDocument.openTransaction("Unfold sketch projection") + shape = self.object.Shape + foldLines = self.object.foldComp.Edges + norm = self.object.baseObject[0].getSubObject(self.object.baseObject[1][0]).normalAt(0,0) + splitSketches = self.form[1].chkSeparate.isChecked() + genSketchColor = self.form[1].genColor.property("color").name() + bendSketchColor = self.form[1].bendColor.property("color").name() + intSketchColor = self.form[1].internalColor.property("color").name() + processUnfoldSketches(shape, foldLines, norm, splitSketches, genSketchColor, bendSketchColor, intSketchColor) + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + Gui.Control.closeDialog() + Gui.ActiveDocument.resetEdit() + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + Gui.Control.closeDialog() + Gui.ActiveDocument.resetEdit() + FreeCAD.ActiveDocument.recompute() + + def populateMdsList(self): + sheetnames = SheetMetalKfactor.getSpreadSheetNames() + self.form[0].availableMds.clear() + + self.form[0].availableMds.addItem("Please select") + for mds in sheetnames: + if mds.Label.startswith("material_"): + self.form[0].availableMds.addItem(mds.Label) + self.form[0].availableMds.addItem("Manual K-Factor") + + selMdsIndex = -1 + if self.object.materialSheet: + selMdsIndex = self._getMdsIndex(self.object.materialSheet.Label) + elif self.object.useManualKFactor: + selMdsIndex = self.form[0].availableMds.count()-1 + + if selMdsIndex >= 0: + self.form[0].availableMds.setCurrentIndex(selMdsIndex) + elif len(sheetnames) == 1: + self.form[0].availableMds.setCurrentIndex(1) + elif engineering_mode_enabled(): + self.form[0].availableMds.setCurrentIndex(0) + elif len(sheetnames) == 0: + self.form[0].availableMds.setCurrentIndex(1) + + def chkSketchChange(self): + self.form[1].genColor.setEnabled(self.form[1].chkSketch.isChecked()) + self.form[1].chkSeparate.setEnabled(self.form[1].chkSketch.isChecked()) + enabled = self.form[1].chkSketch.isChecked() and self.form[1].chkSeparate.isChecked() + self.form[1].bendColor.setEnabled(enabled) + self.form[1].internalColor.setEnabled(enabled) + + def availableMdsChange(self): + isManualK = self._isManualKSelected() + self.form[0].kfactorAnsi.setEnabled(isManualK) + self.form[0].kfactorDin.setEnabled(isManualK) + self.form[0].kFactSpin.setEnabled(isManualK) + self.object.useManualKFactor = isManualK + if not isManualK: + self.object.materialSheet = FreeCAD.ActiveDocument.getObjectsByLabel( + self.form[0].availableMds.currentText() + )[0] + self.object.recompute() + + def toggleSelectionMode(self): + if not self.SelModeActive: + self.object.Visibility=False + self.object.baseObject[0].Visibility=True + Gui.Selection.clearSelection() + Gui.Selection.addSelection(self.object.baseObject[0],self.object.baseObject[1]) + self.SelModeActive=True + self.form[0].selectFaceButton.setText('Preview') + else: + sel = Gui.Selection.getSelectionEx()[0] + self.object.baseObject = [ sel.Object, sel.SubElementNames[0] ] + Gui.Selection.clearSelection() + self.object.Document.recompute() + self.object.Visibility=True + self.SelModeActive=False + self.form[0].selectFaceButton.setText('Select Face') + +######## Commands ######## class SMUnfoldUnattendedCommandClass: """Unfold object""" def GetResources(self): - __dir__ = os.path.dirname(__file__) - iconPath = os.path.join(__dir__, "Resources", "icons") + icons_path = SheetMetalTools.icons_path return { "Pixmap": os.path.join( - iconPath, "SheetMetal_UnfoldUnattended.svg" + icons_path, "SheetMetal_UnfoldUnattended.svg" ), # the name of a svg file available in the resources "MenuText": FreeCAD.Qt.translate("SheetMetal", "Unattended Unfold"), "Accel": "U", @@ -60,26 +320,21 @@ def GetResources(self): def Activated(self): SMLogger.message(FreeCAD.Qt.translate("Logger", "Running unattended unfold...")) - try: - taskd = SMUnfoldTaskPanel() - except ValueError as e: - SMLogger.error(e.args[0]) - return - + doc = FreeCAD.ActiveDocument + sel = Gui.Selection.getSelectionEx()[0] + doc.openTransaction("Unattended Unfold") + obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Unfold") + SMUnfold(obj) + SMUnfoldVP(obj.ViewObject) + obj.baseObject = [ sel.Object, sel.SubElementNames[0] ] pg = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/SheetMetal") - if pg.GetBool("separateSketches"): - taskd.form.chkSeparate.setCheckState(QtCore.Qt.CheckState.Checked) - else: - taskd.form.chkSeparate.setCheckState(QtCore.Qt.CheckState.Unchecked) - if pg.GetBool("genSketch"): - taskd.form.chkSketch.setCheckState(QtCore.Qt.CheckState.Checked) - else: - taskd.form.chkSketch.setCheckState(QtCore.Qt.CheckState.Unchecked) - taskd.form.bendColor.setProperty("color", pg.GetString("bendColor")) - taskd.form.genColor.setProperty("color", pg.GetString("genColor")) - taskd.form.internalColor.setProperty("color", pg.GetString("intColor")) - # taskd.new_mds_name = taskd.material_sheet_name - taskd.accept() + obj.kFactorStandard = pg.GetString("kFactorStandard", "ansi") + obj.ViewObject.Transparency = pg.GetInt("genObjTransparency", 50) + obj.kfactor = pg.GetFloat("manualKFactor", KFACTOR) + Gui.Selection.clearSelection() + doc.recompute() + dialog = SMUnfoldTaskPanel(obj) + dialog.accept() return def IsActive(self): @@ -100,15 +355,13 @@ class SMUnfoldCommandClass: """Unfold object""" def GetResources(self): - __dir__ = os.path.dirname(__file__) - iconPath = os.path.join(__dir__, "Resources", "icons") + icons_path = SheetMetalTools.icons_path # add translations path - LanguagePath = os.path.join(__dir__, "translations") - Gui.addLanguagePath(LanguagePath) + Gui.addLanguagePath(SheetMetalTools.language_path) Gui.updateLocale() return { "Pixmap": os.path.join( - iconPath, "SheetMetal_Unfold.svg" + icons_path, "SheetMetal_Unfold.svg" ), # the name of a svg file available in the resources "MenuText": FreeCAD.Qt.translate("SheetMetal", "Unfold"), "Accel": "U", @@ -121,17 +374,21 @@ def GetResources(self): } def Activated(self): - dialog = SMUnfoldTaskPanel() - FreeCADGui.Control.showDialog(dialog) - - # try: - # taskd = SMUnfoldTaskPanel() - # except ValueError as e: - # SMErrorBox(e.args[0]) - # return - - # FreeCADGui.Control.showDialog(taskd) - # return + doc = FreeCAD.ActiveDocument + sel = Gui.Selection.getSelectionEx()[0] + doc.openTransaction("Unfold") + obj = doc.addObject("Part::FeaturePython", "Unfold") + SMUnfold(obj) + SMUnfoldVP(obj.ViewObject) + obj.baseObject = [ sel.Object, sel.SubElementNames[0] ] + pg = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/SheetMetal") + obj.kFactorStandard = pg.GetString("kFactorStandard", "ansi") + obj.ViewObject.Transparency = pg.GetInt("genObjTransparency", 50) + obj.kfactor = pg.GetFloat("manualKFactor", KFACTOR) + Gui.Selection.clearSelection() + doc.recompute() + dialog = SMUnfoldTaskPanel(obj) + Gui.Control.showDialog(dialog) def IsActive(self): if ( diff --git a/SheetMetalUnfolder.py b/SheetMetalUnfolder.py index 5bd37e4..f441f9a 100644 --- a/SheetMetalUnfolder.py +++ b/SheetMetalUnfolder.py @@ -103,20 +103,14 @@ def main(): """ -import Part, FreeCAD, FreeCADGui, os, sys +import FreeCAD, Part, sys, SheetMetalTools, SheetMetalKfactor from FreeCAD import Base -import DraftVecUtils, math, time -import Draft +import math, time # import traceback # traceback.print_exc() -try: - from TechDraw import projectEx -except ImportError: - from Drawing import projectEx - from lookup import get_val_from_range import tempfile @@ -125,8 +119,14 @@ def main(): from SheetMetalLogger import SMLogger, UnfoldException, BendException, TreeException +# IMPORTANT: please remember to change the element map version in case of any +# changes in modeling logic +smElementMapVersion = "sm1." + KFACTORSTANDARD = None +translate = FreeCAD.Qt.translate + # TODO: Error Codes # - Put error numbers into the text # - Put user help into more texts @@ -422,7 +422,7 @@ def k_Factor(self): @k_Factor.setter def k_Factor(self, val): SMLogger.error( - FreeCAD.Qt.translate( + translate( "Logger", "k_Factor is a readonly property! Won't set to:" ), val, @@ -465,11 +465,12 @@ def dump(self): print("index Unfold list:") print(self.index_unfold_list) - def __init__(self, TheShape, f_idx, k_factor_lookup): + def __init__(self, TheShape, f_idx, k_factor_lookup, obj): self.cFaceTol = 0.002 # tolerance to detect counter-face vertices # this high tolerance was needed for more real parts self.root = None # make_new_face_node adds the root node if parent_node == None self.__Shape = TheShape.copy() + self.obj = obj self.error_code = None self.failed_face_idx = None self.k_factor_lookup = k_factor_lookup @@ -1152,8 +1153,8 @@ def make_new_face_node(self, face_idx, P_node, P_edge, wires_e_lists): counter_found = False if counter_found: - if hasattr(FreeCADGui.Selection.getSelection()[0], "Refine"): - if FreeCADGui.Selection.getSelection()[0].Refine is True: + if hasattr(self.obj, "Refine"): + if self.obj.Refine is True: distance = self.__Shape.Faces[i].distToShape( self.__Shape.Faces[face_idx] )[0] @@ -1572,8 +1573,8 @@ def Bend_analysis(self, face_idx, parent_node=None, parent_edge=None): if not self.handle_hole( parent_node, face_idx, edge, child_face, child_index ): - if hasattr(FreeCADGui.Selection.getSelection()[0], "Refine"): - if FreeCADGui.Selection.getSelection()[0].Refine is True: + if hasattr(self.obj, "Refine"): + if self.obj.Refine is True: if not self.handle_chamfer( face_idx, edge, child_face, child_face_idx ): @@ -2495,7 +2496,7 @@ def unbendPoint(poi): # theFace = Part.makeFilledFace(wires) theFace = faces[0] SMLogger.error( - FreeCAD.Qt.translate("Logger", "at line {} got exception: ").format( + translate("Logger", "at line {} got exception: ").format( str(exc_tb.tb_lineno), ), str(e), @@ -2880,71 +2881,19 @@ def build_new_wire(self, wire, face_idx, wire_idx): return wire.copy(), False -# from Defeaturing WB: Export to Step -def sew_Shape(): +def sew_Shape(obj): """checking Shape""" - - doc = FreeCAD.ActiveDocument - docG = FreeCADGui.ActiveDocument - - sel = FreeCADGui.Selection.getSelection() - if len(sel) == 1: - o = sel[0] - if hasattr(o, "Shape"): - sh = o.Shape.copy() - sh.sewShape() - sl = Part.Solid(sh) - docG.getObject(o.Name).Visibility = False - Part.show(sl) - ao = FreeCAD.ActiveDocument.ActiveObject - ao.Label = "Solid" - docG.ActiveObject.ShapeColor = docG.getObject(o.Name).ShapeColor - docG.ActiveObject.LineColor = docG.getObject(o.Name).LineColor - docG.ActiveObject.PointColor = docG.getObject(o.Name).PointColor - docG.ActiveObject.DiffuseColor = docG.getObject(o.Name).DiffuseColor - docG.ActiveObject.Transparency = docG.getObject(o.Name).Transparency - else: - FreeCAD.Console.PrintError("select only one object") + if hasattr(obj, "Shape"): + sh = obj.Shape.copy() + sh.sewShape() + sl = Part.Solid(sh) + return sl -def makeSolidExpSTEP(): - doc = FreeCAD.ActiveDocument - docG = FreeCADGui.ActiveDocument - if doc is not None: - fname = doc.FileName - if len(fname) == 0: - fileNm = "untitled" - else: - fileNm = os.path.basename(fname) - fileNm = os.path.splitext(fileNm)[0] - tempdir = tempfile.gettempdir() # get the current temporary directory - # print(tempdir) - # fileNm = os.path.basename(fname) - # tempfilepath = os.path.join(tempdir,fname.rstrip(".fcstd").rstrip(".FCStd") + u'_cp.stp') - tempfilepath = os.path.join(tempdir, fileNm + "_cp.stp") - print(tempfilepath) - sel = FreeCADGui.Selection.getSelection() - if len(sel) == 1: - __objs__ = [] - __objs__.append(sel[0]) - import ImportGui - - stop - ImportGui.export(__objs__, tempfilepath) - del __objs__ - # docG.getObject(sel[0].Name).Visibility = False - ImportGui.insert(tempfilepath, doc.Name) - FreeCADGui.SendMsgToActiveView("ViewFit") - else: - FreeCAD.Console.PrintError("Select only one object") - else: - FreeCAD.Console.PrintError("Select only one object") - - -## - +def getUnfold(k_factor_lookup, solid, subelement, facename, kFactorStandard): + global KFACTORSTANDARD + KFACTORSTANDARD = kFactorStandard -def getUnfold(k_factor_lookup, solid, subelement, facename): resPart = None normalVect = None folds = None @@ -2960,7 +2909,7 @@ def getUnfold(k_factor_lookup, solid, subelement, facename): startzeit = time.process_time() TheTree = SheetTree( - solid.Shape, f_number, k_factor_lookup + solid.Shape, f_number, k_factor_lookup, solid ) # initializes the tree-structure if TheTree.error_code is None: TheTree.Bend_analysis( @@ -3038,11 +2987,18 @@ def getUnfold(k_factor_lookup, solid, subelement, facename): "Trying to repeat the unfold process again with the Sewed copied Shape\n" ) FreeCAD.ActiveDocument.openTransaction("sanitize") - sew_Shape() + sewedShape = sew_Shape(solid) + solid.Visibility = False + ob = Part.show(sewedShape,"Solid") + ob.Label = solid.Label + "_copy" + if SheetMetalTools.isGuiLoaded(): + ob.ViewObject.ShapeColor = solid.ViewObject.ShapeColor + ob.ViewObject.LineColor = solid.ViewObject.LineColor + ob.ViewObject.PointColor = solid.ViewObject.PointColor + ob.ViewObject.DiffuseColor = solid.ViewObject.DiffuseColor + ob.ViewObject.Transparency = solid.ViewObject.Transparency FreeCAD.ActiveDocument.commitTransaction() - ob = FreeCAD.ActiveDocument.ActiveObject ob_Name = ob.Name - ob.Label = solid.Label + "_copy" faceSel = facename err_code = TheTree.error_code else: @@ -3102,144 +3058,8 @@ def SMmakeSketchfromEdges(edges, name): return usk -def processUnfold( - k_factor_lookup, - object, - referenceFace, - faceName, - genSketch=True, - splitSketches=False, - sketchColor="#000080", - bendSketchColor="#c00000", - internalSketchColor="#ff5733", - transparency=0.7, - kFactorStandard="ansi", -): - global KFACTORSTANDARD - KFACTORSTANDARD = kFactorStandard - - unfoldShape = None - unfold_sketch = None - unfold_sketch_outline = None - unfold_sketch_bend = None - unfold_sketch_internal = None - - try: - shape, foldComp, norm, thename, err_cd, fSel, obN = getUnfold( - k_factor_lookup, object, referenceFace, faceName - ) - foldLines = foldComp.Edges - except Exception as e: - exc_type, exc_obj, exc_tb = sys.exc_info() - SMLogger.error( - FreeCAD.Qt.translate("Logger", "exception at line ") - + str(exc_tb.tb_lineno), - e.args, - ) - SMLogger.error(e.args) - raise UnfoldException() - - if shape is None: - raise UnfoldException() - - unfoldShape = FreeCAD.ActiveDocument.addObject("Part::Feature", "Unfold") - unfoldShape.Shape = shape - - if genSketch: - # locate the projection face - unfoldobj = shape - for face in shape.Faces: - fnorm = face.normalAt(0, 0) - isSameDir = abs(fnorm.dot(norm) - 1.0) < 0.00001 - if isSameDir: - unfoldobj = face - break - edges = [] - perimEdges = projectEx(unfoldobj, norm)[0] - edges.append(perimEdges) - if len(foldLines) > 0: - co = Part.makeCompound(foldLines) - foldEdges = projectEx(co, norm)[0] - - if not splitSketches: - edges.append(foldEdges) - unfold_sketch = generateSketch(edges, "Unfold_Sketch", sketchColor) - FreeCAD.ActiveDocument.recompute() - - if splitSketches: - tidy = False - try: - newface = Part.makeFace(unfold_sketch.Shape, "Part::FaceMakerBullseye") - - try: - owEdgs = newface.OuterWire.Edges - faceEdgs = newface.Edges - except: - exc_type, exc_obj, exc_tb = sys.exc_info() - SMLogger.error( - FreeCAD.Qt.translate( - "Logger", - "Exception at line {}" - ": Outline Sketch failed, re-trying after tidying up", - ).format(str(exc_tb.tb_lineno)) - ) - tidy = True - owEdgs = unfold_sketch.Shape.Edges - faceEdgs = unfold_sketch.Shape.Edges - FreeCAD.ActiveDocument.recompute() - - unfold_sketch_outline = generateSketch( - owEdgs, "Unfold_Sketch_Outline", sketchColor - ) - - if tidy: - SMLogger.error( - FreeCAD.Qt.translate( - "Logger", "tidying up Unfold_Sketch_Outline" - ) - ) - intEdgs = [] - idx = [] - for i, e in enumerate(faceEdgs): - for oe in owEdgs: - if oe.hashCode() == e.hashCode(): - idx.append(i) - for i, e in enumerate(faceEdgs): - if i not in idx: - intEdgs.append(e) - if len(intEdgs) > 0: - unfold_sketch_internal = generateSketch( - intEdgs, "Unfold_Sketch_Internal", internalSketchColor - ) - - except Exception as e: - print(e) - exc_type, exc_obj, exc_tb = sys.exc_info() - SMLogger.error( - FreeCAD.Qt.translate( - "Logger", - "Exception at line {}: Outline Sketch not created", - ).format(str(exc_tb.tb_lineno)) - ) - - if len(foldLines) > 0 and splitSketches: - unfold_sketch_bend = generateSketch( - foldEdges, "Unfold_Sketch_bends", bendSketchColor - ) - - if FreeCAD.GuiUp: - unfoldShape.ViewObject.Transparency = transparency - - return ( - unfoldShape, - unfold_sketch, - unfold_sketch_outline, - unfold_sketch_bend, - unfold_sketch_internal, - ) - - def generateSketch(edges, name, color): + import Draft p = Part.makeCompound(edges) try: sk = Draft.makeSketch( @@ -3253,9 +3073,163 @@ def generateSketch(edges, name, color): SMLogger.warning(FreeCAD.Qt.translate("Logger", "discretizing Sketch")) sk = SMmakeSketchfromEdges(p.Edges, name) - if FreeCAD.GuiUp: + if SheetMetalTools.isGuiLoaded(): rgb_color = tuple(float(int(color[i : i + 2], 16)) for i in (1, 3, 5)) sk.ViewObject.LineColor = rgb_color sk.ViewObject.PointColor = rgb_color return sk + +def processUnfoldSketches(shape, foldLines, norm, splitSketches, genSketchColor, bendSketchColor, intSketchColor): + try: + from TechDraw import projectEx + except ImportError: + from Drawing import projectEx + + # locate the projection face + unfoldobj = shape + for face in shape.Faces: + fnorm = face.normalAt(0, 0) + isSameDir = abs(fnorm.dot(norm) - 1.0) < 0.00001 + if isSameDir: + unfoldobj = face + break + edges = [] + perimEdges = projectEx(unfoldobj, norm)[0] + edges.append(perimEdges) + if len(foldLines) > 0: + co = Part.makeCompound(foldLines) + foldEdges = projectEx(co, norm)[0] + if not splitSketches: + edges.append(foldEdges) + unfold_sketch = generateSketch(edges, "Unfold_Sketch", genSketchColor) + FreeCAD.ActiveDocument.recompute() + if splitSketches: + tidy = False + try: + newface = Part.makeFace(unfold_sketch.Shape, "Part::FaceMakerBullseye") + try: + owEdgs = newface.OuterWire.Edges + faceEdgs = newface.Edges + except: + exc_type, exc_obj, exc_tb = sys.exc_info() + SMLogger.error( + FreeCAD.Qt.translate( + "Logger", + "Exception at line {}" + ": Outline Sketch failed, re-trying after tidying up", + ).format(str(exc_tb.tb_lineno)) + ) + tidy = True + owEdgs = unfold_sketch.Shape.Edges + faceEdgs = unfold_sketch.Shape.Edges + FreeCAD.ActiveDocument.recompute() + unfold_sketch_outline = generateSketch( + owEdgs, "Unfold_Sketch_Outline", genSketchColor + ) + if tidy: + SMLogger.error( + FreeCAD.Qt.translate( + "Logger", "tidying up Unfold_Sketch_Outline" + ) + ) + intEdgs = [] + idx = [] + for i, e in enumerate(faceEdgs): + for oe in owEdgs: + if oe.hashCode() == e.hashCode(): + idx.append(i) + for i, e in enumerate(faceEdgs): + if i not in idx: + intEdgs.append(e) + if len(intEdgs) > 0: + unfold_sketch_internal = generateSketch( + intEdgs, "Unfold_Sketch_Internal", intSketchColor + ) + except Exception as e: + print(e) + exc_type, exc_obj, exc_tb = sys.exc_info() + SMLogger.error( + FreeCAD.Qt.translate( + "Logger", + "Exception at line {}: Outline Sketch not created", + ).format(str(exc_tb.tb_lineno)) + ) + if len(foldLines) > 0 and splitSketches: + unfold_sketch_bend = generateSketch( + foldEdges, "Unfold_Sketch_bends", bendSketchColor + ) + +class SMUnfold: + def __init__(self, obj): + '''"Add wall or Wall with radius bend"''' + SheetMetalTools.smAddProperty( + obj, + "App::PropertyFloatConstraint", + "kfactor", + translate( "App::Property", "Manual K-Factor value" ), + (0.4, 0.0, 2.0, 0.01), + "Unfold" + ) + SheetMetalTools.smAddEnumProperty( + obj, + "kFactorStandard", + translate( "App::Property", "K-Factor standard" ), + ["ansi","din"], + None, + "Unfold" + ) + SheetMetalTools.smAddProperty( + obj, + "App::PropertyLinkSub", + "baseObject", + translate( "App::Property", "Base Object" ), + None, + "Unfold", + ) + SheetMetalTools.smAddProperty( + obj, + "App::PropertyLink", + "materialSheet", + translate( "App::Property", "Material definition sheet" ), + None, + "Unfold", + ) + SheetMetalTools.smAddBoolProperty( + obj, + "useManualKFactor", + translate("App::Property", + "Enable manually defining K-Factor value, otherwise the lookup table is used"), + False, + "Unfold", + ) + obj.addProperty( + "Part::PropertyPartShape", + "foldComp", + translate("App::Property", + "Fold lines compound"), + "Unfold", + read_only=True, + hidden=True + ) + obj.Proxy = self + + def getElementMapVersion(self, _fp, ver, _prop, restored): + if not restored: + return smElementMapVersion + ver + + def execute(self, fp): + '''"Print a short message when doing a recomputation, this method is mandatory"''' + kf_lookup = {1: fp.kfactor} + if fp.materialSheet and fp.useManualKFactor: + lookupTable = SheetMetalKfactor.KFactorLookupTable(fp.materialSheet) + kf_lookup = lookupTable.k_factor_lookup + shape, foldComp, norm, thename, err_cd, fSel, obN = getUnfold( + k_factor_lookup=kf_lookup, + solid=fp.baseObject[0], + subelement=fp.baseObject[0].getSubObject(fp.baseObject[1][0]), + facename=fp.baseObject[1][0], + kFactorStandard=fp.kFactorStandard) + fp.Shape = shape + fp.foldComp = foldComp + diff --git a/SketchOnSheetMetalCmd.py b/SketchOnSheetMetalCmd.py index 234e15e..2e1b722 100644 --- a/SketchOnSheetMetalCmd.py +++ b/SketchOnSheetMetalCmd.py @@ -23,62 +23,10 @@ # ############################################################################## -from FreeCAD import Gui -from PySide import QtCore, QtGui - -import FreeCAD, Part, os, math -import SheetMetalBaseCmd - -__dir__ = os.path.dirname(__file__) -iconPath = os.path.join(__dir__, "Resources", "icons") -smEpsilon = 0.0000001 - -# add translations path -LanguagePath = os.path.join(__dir__, "translations") -Gui.addLanguagePath(LanguagePath) -Gui.updateLocale() - -import SheetMetalBendSolid -from SheetMetalLogger import SMLogger, UnfoldException, BendException, TreeException - - -def smWarnDialog(msg): - diag = QtGui.QMessageBox( - QtGui.QMessageBox.Warning, - FreeCAD.Qt.translate("QMessageBox", "Error in macro MessageBox"), - msg, - ) - diag.setWindowModality(QtCore.Qt.ApplicationModal) - diag.exec_() - - -def smBelongToBody(item, body): - if body is None: - return False - for obj in body.Group: - if obj.Name == item.Name: - return True - return False - - -def smIsPartDesign(obj): - return str(obj).find(" -# Copyright 2023 Ondsel Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# -############################################################################## - -from PySide import QtCore, QtGui -from SheetMetalLogger import SMLogger, UnfoldException -from engineering_mode import engineering_mode_enabled -import FreeCAD -import FreeCADGui -import SheetMetalKfactor -import importDXF -import importSVG -import os -import SheetMetalUnfolder as smu - -modPath = os.path.dirname(__file__).replace("\\", "/") - -GENSKETCHCOLOR = "#000080" -BENDSKETCHCOLOR = "#c00000" -INTSKETCHCOLOR = "#ff5733" -KFACTOR = 0.40 - -last_selected_mds = "none" -mds_help_url = "https://github.com/shaise/FreeCAD_SheetMetal#material-definition-sheet" - -mw = FreeCADGui.getMainWindow() - - -class SMUnfoldTaskPanel: - def __init__(self): - path = f"{modPath}/UnfoldOptions.ui" - self.form = FreeCADGui.PySideUic.loadUi(path) - self.pg = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/SheetMetal") - - # Technical Debt. - # The command that gets us here is defined in SheetMetalUnfoldCmd.py. - # It limits the selection to planar faces. - # However, once the dialog is open, the user can change the selection - # and select any kind of geometry. This is wrong. - - # if it is desirable to allow user to change the selection with the - # dialog open, then a selectiongate should be written to limit - # what the user can select. - # If we want to prevent changing selection, then something else has to - # happen. - # For now, we are setting the reference plane when the user activates - # the command. Any change by the user is ignored. - - self.referenceFace = FreeCADGui.Selection.getSelectionEx()[0].SubObjects[0] - self.facename = FreeCADGui.Selection.getSelectionEx()[0].SubElementNames[0] - self.object = FreeCADGui.Selection.getSelectionEx()[0].Object - - # End Technical debt - - self.setupUi() - - def _boolToState(self, bool): - return QtCore.Qt.Checked if bool else QtCore.Qt.Unchecked - - def _getExportType(self, typeonly=False): - if not typeonly and not self.form.chkExport.isChecked(): - return None - if self.form.svgExport.isChecked(): - return "svg" - else: - return "dxf" - - def _isManualKSelected(self): - return self.form.availableMds.currentIndex() == ( - self.form.availableMds.count() - 1 - ) - - def _isNoMdsSelected(self): - return self.form.availableMds.currentIndex() == 0 - - def _updateSelectedMds(self): - global last_selected_mds - last_selected_mds = self.form.availableMds.currentText() - - def _getLastSelectedMdsIndex(self): - global last_selected_mds - for i in range(self.form.availableMds.count()): - if self.form.availableMds.itemText(i) == last_selected_mds: - return i - return -1 - - def _getData(self): - kFactorStandard = "din" if self.form.kfactorDin.isChecked() else "ansi" - - results = { - "exportType": self._getExportType(), - "genObjTransparency": self.form.transSpin.value(), - "genSketchColor": self.form.genColor.property("color").name(), - "bendSketchColor": self.form.bendColor.property("color").name(), - "intSketchColor": self.form.internalColor.property("color").name(), - "separateSketches": self.form.chkSeparate.isChecked(), - "genSketch": self.form.chkSketch.isChecked(), - "kFactorStandard": kFactorStandard, - } - - if self._isManualKSelected(): - results["lookupTable"] = {1: self.form.kFactSpin.value()} - elif self._isNoMdsSelected(): - msg = FreeCAD.Qt.translate( - "Logger", "Unfold operation needs to know K-factor value(s) to be used." - ) - SMLogger.warning(msg) - msg += FreeCAD.Qt.translate( - "QMessageBox", - "
      \n" - "
    1. Either select 'Manual K-factor'
    2. \n" - "
    3. Or use a Material Definition Sheet
    4. \n" - "
    ", - ).format(mds_help_url) - QtGui.QMessageBox.warning( - None, FreeCAD.Qt.translate("QMessageBox", "Warning"), msg - ) - return None - else: - lookupTable = SheetMetalKfactor.KFactorLookupTable( - self.form.availableMds.currentText() - ) - results["lookupTable"] = lookupTable.k_factor_lookup - - self.pg.SetString("kFactorStandard", str(results["kFactorStandard"])) - self.pg.SetFloat("manualKFactor", float(self.form.kFactSpin.value())) - self.pg.SetBool("genSketch", results["genSketch"]) - - self.pg.SetString("genColor", results["genSketchColor"]) - self.pg.SetString("bendColor", results["bendSketchColor"]) - self.pg.SetString("internalColor", results["intSketchColor"]) - self.pg.SetBool("separateSketches", results["separateSketches"]) - self.pg.SetBool("exportEn", self.form.chkExport.isChecked()) - self.pg.SetString("exportType", self._getExportType(True)) - - return results - - def setupUi(self): - kFactorStandard = self.pg.GetString("kFactorStandard", "ansi") - if kFactorStandard == "ansi": - self.form.kfactorAnsi.setChecked(True) - else: - self.form.kfactorDin.setChecked(True) - - self.form.chkSketch.stateChanged.connect(self.chkSketchChange) - self.form.chkSeparate.stateChanged.connect(self.chkSketchChange) - self.form.availableMds.currentIndexChanged.connect(self.availableMdsChacnge) - - self.form.chkSeparate.setCheckState( - self._boolToState(self.pg.GetBool("separateSketches")) - ) - self.form.chkSketch.setCheckState( - self._boolToState(self.pg.GetBool("genSketch")) - ) - - self.form.genColor.setProperty( - "color", self.pg.GetString("genColor", GENSKETCHCOLOR) - ) - self.form.bendColor.setProperty( - "color", self.pg.GetString("bendColor", BENDSKETCHCOLOR) - ) - self.form.internalColor.setProperty( - "color", self.pg.GetString("internalColor", INTSKETCHCOLOR) - ) - - self.form.transSpin.setValue(self.pg.GetInt("genObjTransparency", 50)) - self.form.kFactSpin.setValue(self.pg.GetFloat("manualKFactor", KFACTOR)) - - self.form.chkSeparate.setEnabled(self.pg.GetBool("separateSketches", False)) - - self.form.chkExport.setCheckState( - self._boolToState(self.pg.GetBool("exportEn", False)) - ) - if self.pg.GetString("exportType", "dxf") == "dxf": - self.form.dxfExport.setChecked(True) - else: - self.form.svgExport.setChecked(True) - - self.chkSketchChange() - self.populateMdsList() - self.availableMdsChacnge() - - self.form.update() - FreeCAD.ActiveDocument.openTransaction("Unfold") - - def accept(self): - self._updateSelectedMds() - params = self._getData() - if params is None: - return - - try: - result = smu.processUnfold( - params["lookupTable"], - self.object, - self.referenceFace, - self.facename, - genSketch=self.form.chkSketch.isChecked(), - splitSketches=self.form.chkSeparate.isChecked(), - sketchColor=params["genSketchColor"], - bendSketchColor=params["bendSketchColor"], - internalSketchColor=params["intSketchColor"], - transparency=params["genObjTransparency"], - kFactorStandard=params["kFactorStandard"], - ) - if result: - self.doExport(result[1]) - - FreeCAD.ActiveDocument.commitTransaction() - FreeCADGui.ActiveDocument.resetEdit() - FreeCADGui.Control.closeDialog() - FreeCAD.ActiveDocument.recompute() - else: - FreeCAD.ActiveDocument.abortTransaction() - FreeCADGui.Control.closeDialog() - FreeCAD.ActiveDocument.recompute() - - except UnfoldException: - msg = ( - FreeCAD.Qt.translate( - "QMessageBox", - "Unfold is failing.\n" - "Please try to select a different face to unfold your object\n\n" - "If the opposite face also fails then switch Refine to false on feature ", - ) - + FreeCADGui.Selection.getSelection()[0].Name - ) - QtGui.QMessageBox.question( - None, - FreeCAD.Qt.translate("QMessageBox", "Warning"), - msg, - QtGui.QMessageBox.Ok, - ) - - except Exception as e: - raise e - - def reject(self): - FreeCAD.ActiveDocument.abortTransaction() - FreeCADGui.Control.closeDialog() - FreeCAD.ActiveDocument.recompute() - - def doExport(self, obj): - # Not sure we should be doing export in this dialog but if we want to, - # it should be handled here and not in the unfold function. - - # This implementation of export is limited because it doesn't export - # split sketches. More reason to potentially remove it entirely and - # let the user use the standard export functions - - if obj is None: - return - - if self._getExportType() is None: - return - - __objs__ = [] - __objs__.append(obj) - filename = f"{FreeCAD.ActiveDocument.FileName[0:-6]}-{obj.Name}.{self._getExportType()}" - print("Exporting to " + filename) - - if self._getExportType() == "dxf": - importDXF.export(__objs__, filename) - else: - importSVG.export(__objs__, filename) - del __objs__ - - def populateMdsList(self): - sheetnames = SheetMetalKfactor.getSpreadSheetNames() - self.form.availableMds.clear() - - self.form.availableMds.addItem("Please select") - for mds in sheetnames: - if mds.Label.startswith("material_"): - self.form.availableMds.addItem(mds.Label) - self.form.availableMds.addItem("Manual K-Factor") - - selMdsIndex = self._getLastSelectedMdsIndex() - - if selMdsIndex >= 0: - self.form.availableMds.setCurrentIndex(selMdsIndex) - elif len(sheetnames) == 1: - self.form.availableMds.setCurrentIndex(1) - elif engineering_mode_enabled(): - self.form.availableMds.setCurrentIndex(0) - elif len(sheetnames) == 0: - self.form.availableMds.setCurrentIndex(1) - - def chkSketchChange(self): - self.form.chkSeparate.setEnabled(self.form.chkSketch.isChecked()) - if self.form.chkSketch.isChecked(): - self.form.dxfExport.show() - self.form.svgExport.show() - self.form.genColor.setEnabled(True) - else: - self.form.dxfExport.hide() - self.form.svgExport.hide() - self.form.genColor.setEnabled(False) - - enabled = self.form.chkSketch.isChecked() and self.form.chkSeparate.isChecked() - self.form.bendColor.setEnabled(enabled) - self.form.internalColor.setEnabled(enabled) - - def availableMdsChacnge(self): - isManualK = self._isManualKSelected() - self.form.kfactorAnsi.setEnabled(isManualK) - self.form.kfactorDin.setEnabled(isManualK) - self.form.kFactSpin.setEnabled(isManualK) diff --git a/UnfoldOptions.ui b/UnfoldOptions.ui deleted file mode 100644 index b8e361f..0000000 --- a/UnfoldOptions.ui +++ /dev/null @@ -1,331 +0,0 @@ - - - SMUnfoldTaskPanel - - - - 0 - 0 - 676 - 565 - - - - Unfold sheet metal object - - - - - - - - Generate projection sketch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 66 - 203 - 105 - - - - - - - - - - Separate projection layers - - - - - - - - - Bend lines color - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - Internal lines color - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - - - - - Export sketch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - DXF - - - true - - - exportGroup - - - - - - - SVG - - - exportGroup - - - - - - - - - - - Material Definition Sheet - - - - - - - - - - - - QLayout::SetDefaultConstraint - - - - - Manual K-Factor - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - - 0 - 0 - - - - - 100 - 16777215 - - - - 3 - - - 2.000000000000000 - - - 0.100000000000000 - - - 0.500000000000000 - - - - - - - ANSI - - - kFactorGroup - - - - - - - DIN - - - kFactorGroup - - - - - - - - - QLayout::SetMinimumSize - - - 0 - - - - - - 0 - 0 - - - - Unfold object transparency - - - - - - - - 0 - 0 - - - - - 100 - 16777215 - - - - % - - - 100 - - - 70 - - - - - - - - - Qt::Vertical - - - - 20 - 272 - - - - - - - - - Gui::ColorButton - QPushButton -
    Gui/Widgets.h
    -
    -
    - - - - - - -
    diff --git a/package.xml b/package.xml index b7be1a2..6226a2c 100644 --- a/package.xml +++ b/package.xml @@ -2,8 +2,8 @@ SheetMetal Workbench A simple sheet metal tools workbench for FreeCAD. - 0.4.15 - 2024-05-29 + 0.5.2 + 2024-05-31 Shai Seger LGPL-2.1-or-later https://github.com/shaise/FreeCAD_SheetMetal