diff --git a/src/Mod/Arch/ArchCutPlane.py b/src/Mod/Arch/ArchCutPlane.py index fc4c16ba6a..fdf903ad19 100644 --- a/src/Mod/Arch/ArchCutPlane.py +++ b/src/Mod/Arch/ArchCutPlane.py @@ -31,7 +31,7 @@ if FreeCAD.GuiUp: from draftutils.translate import translate else: # \cond - def translate(ctxt,txt): + def translate(ctxt, txt): return txt # \endcond @@ -45,11 +45,67 @@ __url__ = "https://www.freecad.org" # # This module handles the Cut Plane object -def getPlanWithLine(line): - """Function to make a plane along Normal plan""" +# _getShapes(FreeCADGui.Selection.getSelectionEx("", 0)) +def _getShapes(sels): + """Check and process the user selection. + Returns a tuple: (baseObj, baseShp, cutterShp). + baseShp and cutterShp are in the global coordinate system, cutterShp is a planar face. + If the selection is not valid one or more items in the tuple will be `None`. + """ + if not sels: + return None, None, None + objs = [] + needSubEle = False + for sel in sels: + for sub in sel.SubElementNames if sel.SubElementNames else [""]: + objs.append(Part.getShape(sel.Object, sub, needSubElement=needSubEle, retType=1)) + needSubEle = True + if len(objs) != 2: + return None, None, None + baseShp, _, baseObj = objs[0] + cutterShp, _, _ = objs[1] + if baseShp.isNull(): + return baseObj, None, None + if cutterShp.isNull(): + return baseObj, baseShp, None + if cutterShp.ShapeType == "Edge": + if isinstance(cutterShp.Curve, Part.Line): + cutterShp = _extrudeEdge(cutterShp) + else: + try: + cutterShp = Part.Face(Part.Wire(cutterShp)) + except Part.OCCError: + pass + elif cutterShp.ShapeType == "Wire": + if len(cutterShp.Edges) == 1 and isinstance(cutterShp.Edges[0].Curve, Part.Line): + cutterShp = _extrudeEdge(cutterShp.Edges[0]) + else: + try: + cutterShp = Part.Face(cutterShp) + except Part.OCCError: + pass + if not cutterShp.Faces and cutterShp.Vertexes: + plane = cutterShp.findPlane() + if plane is not None: + # Directly creating a face from the plane results in an almost + # endless face that ArchCommands.getCutVolume() cannot handle. + # We therefore create a small triangular face. + pt_main = cutterShp.Vertexes[0].Point + mtx = plane.Rotation.toMatrix() + pt_u = mtx.col(0) + pt_main + pt_v = mtx.col(1) + pt_main + cutterShp = Part.Face(Part.makePolygon([pt_main, pt_u, pt_v, pt_main])) + # _extrudeEdge can create a face with a zero area (if the edge is parallel to the WP normal): + if not cutterShp.Faces \ + or cutterShp.Faces[0].Area < 1e-6 \ + or cutterShp.findPlane() is None: + return baseObj, baseShp, None + return baseObj, baseShp, cutterShp.Faces[0] + +def _extrudeEdge(edge): + """Exrude an edge along the WP normal""" import WorkingPlane - w = WorkingPlane.get_working_plane().axis - return line.extrude(w) + return edge.extrude(WorkingPlane.get_working_plane().axis) def cutComponentwithPlane(baseObj, cutterShp=None, side=0): """cut an object with a plane defined by a face. @@ -72,14 +128,7 @@ def cutComponentwithPlane(baseObj, cutterShp=None, side=0): if isinstance(baseObj, list) \ and len(baseObj) >= 1 \ and baseObj[0].isDerivedFrom("Gui::SelectionObject"): - objs = [] - needSubEle = False - for sel in baseObj: - for sub in sel.SubElementNames if sel.SubElementNames else [""]: - objs.append(Part.getShape(sel.Object, sub, needSubElement=needSubEle, retType=1)) - needSubEle = True - baseShp, _, baseObj = objs[0] - cutterShp, _, _ = objs[1] + baseObj, baseShp, cutterShp = _getShapes(baseObj) baseParent = baseObj.getParentGeoFeatureGroup() else: baseShp = baseObj.Shape @@ -88,12 +137,12 @@ def cutComponentwithPlane(baseObj, cutterShp=None, side=0): baseShp = baseShp.transformGeometry(baseParent.getGlobalPlacement().toMatrix()) if cutterShp.ShapeType != "Face": - cutterShp = getPlanWithLine(cutterShp) + cutterShp = _extrudeEdge(cutterShp) cutVolume = ArchCommands.getCutVolume(cutterShp, baseShp) cutVolume = cutVolume[2] if side == 0 else cutVolume[1] if cutVolume: - obj = FreeCAD.ActiveDocument.addObject("Part::Feature","CutVolume") + obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "CutVolume") if baseParent is not None: cutVolume.Placement = baseParent.getGlobalPlacement().inverse() obj.Shape = Part.Compound([cutVolume]) @@ -103,67 +152,41 @@ def cutComponentwithPlane(baseObj, cutterShp=None, side=0): ArchCommands.removeComponents(obj, baseObj) # Also changes the obj colors. else: Draft.format_object(obj, baseObj) - cutObj = FreeCAD.ActiveDocument.addObject("Part::Cut","CutPlane") + cutObj = FreeCAD.ActiveDocument.addObject("Part::Cut", "CutPlane") if baseParent is not None: baseParent.addObject(cutObj) cutObj.Base = baseObj cutObj.Tool = obj -class _CommandCutLine: - "the Arch CutPlane command definition" - def GetResources(self): - return {"Pixmap": "Arch_CutLine", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Arch_CutLine", "Cut with line"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP("Arch_CutLine", "Cut an object with a line")} - - def IsActive(self): - return len(FreeCADGui.Selection.getSelection()) > 1 - - def Activated(self): - sels = FreeCADGui.Selection.getSelectionEx() - if len(sels) != 2: - FreeCAD.Console.PrintError("You must select exactly two objects, the shape to be cut and a line\n") - return - if not sels[1].SubObjects: - FreeCAD.Console.PrintError("You must select a line from the second object (cut line), not the whole object\n") - return - panel=_CutPlaneTaskPanel(linecut=True) - FreeCADGui.Control.showDialog(panel) - class _CommandCutPlane: "the Arch CutPlane command definition" def GetResources(self): return {"Pixmap": "Arch_CutPlane", - "MenuText": QtCore.QT_TRANSLATE_NOOP("Arch_CutPlane","Cut with plane"), - "ToolTip": QtCore.QT_TRANSLATE_NOOP("Arch_CutPlane","Cut an object with a plane")} + "MenuText": QtCore.QT_TRANSLATE_NOOP("Arch_CutPlane", "Cut with plane"), + "ToolTip": QtCore.QT_TRANSLATE_NOOP("Arch_CutPlane", "Cut an object with a plane")} def IsActive(self): return len(FreeCADGui.Selection.getSelection()) > 1 def Activated(self): - sels = FreeCADGui.Selection.getSelectionEx() - if len(sels) != 2: - FreeCAD.Console.PrintError("You must select exactly two objects, the shape to be cut and the cut plane\n") + baseObj, baseShp, cutterShp = _getShapes(FreeCADGui.Selection.getSelectionEx("", 0)) + if baseObj is None: + FreeCAD.Console.PrintError( + translate("Arch", "Select two objects, an object to be cut and an object defining a cutting plane, in that order\n") + ) return - if not sels[1].SubObjects: - FreeCAD.Console.PrintError("You must select a face from the second object (cut plane), not the whole object\n") + if baseShp is None: + FreeCAD.Console.PrintError(translate("Arch", "The first object does not have a shape\n")) + return + if cutterShp is None: + FreeCAD.Console.PrintError(translate("Arch", "The second object does not define a plane\n")) return panel=_CutPlaneTaskPanel() FreeCADGui.Control.showDialog(panel) class _CutPlaneTaskPanel: - def __init__(self,linecut=False): - sels = FreeCADGui.Selection.getSelectionEx("", 0) - shapes = [] - needSubEle = False - for sel in sels: - for sub in sel.SubElementNames if sel.SubElementNames else [""]: - shapes.append(Part.getShape(sel.Object, sub, needSubElement=needSubEle, retType=0)) - needSubEle = True - self.base = shapes[0] - self.plan = shapes[1] - if linecut: - self.plan = getPlanWithLine(self.plan) + def __init__(self): + _, self.base, self.cutter = _getShapes(FreeCADGui.Selection.getSelectionEx("", 0)) self.previewObj = FreeCAD.ActiveDocument.addObject("Part::Feature", "PreviewCutVolume") self.previewObj.ViewObject.ShapeColor = (1.00, 0.00, 0.00) @@ -180,7 +203,7 @@ class _CutPlaneTaskPanel: self.combobox = QtGui.QComboBox() self.combobox.setCurrentIndex(0) self.grid.addWidget(self.combobox, 2, 1) - QtCore.QObject.connect(self.combobox,QtCore.SIGNAL("currentIndexChanged(int)"),self.previewCutVolume) + QtCore.QObject.connect(self.combobox,QtCore.SIGNAL("currentIndexChanged(int)"), self.previewCutVolume) self.retranslateUi(self.form) self.previewCutVolume(self.combobox.currentIndex()) @@ -190,16 +213,12 @@ class _CutPlaneTaskPanel: def accept(self): FreeCAD.ActiveDocument.removeObject(self.previewObj.Name) side = self.combobox.currentIndex() - sels = FreeCADGui.Selection.getSelectionEx() - if len(sels) > 1 and sels[1].SubObjects: - FreeCAD.ActiveDocument.openTransaction(translate("Arch","Cutting")) - FreeCADGui.addModule("Arch") - FreeCADGui.doCommand("sels = FreeCADGui.Selection.getSelectionEx('', 0)") - FreeCADGui.doCommand("Arch.cutComponentwithPlane(sels, side=" + str(side) + ")") - FreeCAD.ActiveDocument.commitTransaction() - FreeCAD.ActiveDocument.recompute() - return True - FreeCAD.Console.PrintError("Wrong selection\n") + FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Cutting")) + FreeCADGui.addModule("Arch") + FreeCADGui.doCommand("sels = FreeCADGui.Selection.getSelectionEx('', 0)") + FreeCADGui.doCommand("Arch.cutComponentwithPlane(sels, side=" + str(side) + ")") + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() return True def reject(self): @@ -211,7 +230,7 @@ class _CutPlaneTaskPanel: return int(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel) def previewCutVolume(self, i): - cutVolume = ArchCommands.getCutVolume(self.plan,self.base) + cutVolume = ArchCommands.getCutVolume(self.cutter, self.base) if i == 1: cutVolume = cutVolume[1] else: @@ -220,12 +239,11 @@ class _CutPlaneTaskPanel: self.previewObj.Shape = cutVolume def retranslateUi(self, TaskPanel): - TaskPanel.setWindowTitle(QtGui.QApplication.translate("Arch", "Cut Plane", None)) - self.title.setText(QtGui.QApplication.translate("Arch", "Cut Plane options", None)) - self.infoText.setText(QtGui.QApplication.translate("Arch", "Which side to cut", None)) - self.combobox.addItems([QtGui.QApplication.translate("Arch", "Behind", None), - QtGui.QApplication.translate("Arch", "Front", None)]) + TaskPanel.setWindowTitle(translate("Arch", "Cut Plane")) + self.title.setText(translate("Arch", "Cut Plane options")) + self.infoText.setText(translate("Arch", "Which side to cut")) + self.combobox.addItems([translate("Arch", "Behind"), translate("Arch", "Front")]) if FreeCAD.GuiUp: - FreeCADGui.addCommand('Arch_CutPlane',_CommandCutPlane()) - FreeCADGui.addCommand('Arch_CutLine', _CommandCutLine()) + FreeCADGui.addCommand("Arch_CutPlane", _CommandCutPlane()) + diff --git a/src/Mod/Arch/InitGui.py b/src/Mod/Arch/InitGui.py index 464bf2bb8e..7e036fd1cb 100644 --- a/src/Mod/Arch/InitGui.py +++ b/src/Mod/Arch/InitGui.py @@ -111,7 +111,6 @@ class ArchWorkbench(FreeCADGui.Workbench): list(pipe_group.GetCommands(pipe_group))), ("Arch_PipeTools", ), "Arch_CutPlane", - "Arch_CutLine", "Arch_Add", "Arch_Remove", "Arch_Survey"] diff --git a/src/Mod/Arch/Resources/Arch.qrc b/src/Mod/Arch/Resources/Arch.qrc index 551e61015a..a4124cc8a7 100644 --- a/src/Mod/Arch/Resources/Arch.qrc +++ b/src/Mod/Arch/Resources/Arch.qrc @@ -20,7 +20,6 @@ icons/Arch_Component_Tree.svg icons/Arch_CurtainWall.svg icons/Arch_CurtainWall_Tree.svg - icons/Arch_CutLine.svg icons/Arch_CutPlane.svg icons/Arch_Equipment.svg icons/Arch_Equipment_Clone.svg diff --git a/src/Mod/Arch/Resources/icons/Arch_CutLine.svg b/src/Mod/Arch/Resources/icons/Arch_CutLine.svg deleted file mode 100644 index 8a6c8afffa..0000000000 --- a/src/Mod/Arch/Resources/icons/Arch_CutLine.svg +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - [wood galaxy] - - - Arch_CutPlane - 2014-11-11 - https://www.freecad.org/wiki/index.php?title=Artwork - - - FreeCAD - - - FreeCAD/src/Mod/Arch/Resources/icons/Arch_CutPlane.svg - - - FreeCAD LGPL2+ - - - - - [agryson] Alexander Gryson - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -