File size: 7,160 Bytes
985c397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# SPDX-License-Identifier: LGPL-2.1-or-later

# *****************************************************************************
# *                                                                           *
# *   Copyright (c) 2014 Jonathan Wiedemann <wood.galaxy@gmail.com> (cutplan) *
# *   Copyright (c) 2019 Jerome Laverroux <jerome.laverroux@free.fr> (cutline)*
# *   Copyright (c) 2023 FreeCAD Project Association                          *
# *                                                                           *
# *   This file is part of FreeCAD.                                           *
# *                                                                           *
# *   FreeCAD 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.1 of the    *
# *   License, or (at your option) any later version.                         *
# *                                                                           *
# *   FreeCAD 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        *
# *   Lesser General Public License for more details.                         *
# *                                                                           *
# *   You should have received a copy of the GNU Lesser General Public        *
# *   License along with FreeCAD. If not, see                                 *
# *   <https://www.gnu.org/licenses/>.                                        *
# *                                                                           *
# *****************************************************************************

__title__ = "FreeCAD CutPlane"
__author__ = "Jonathan Wiedemann"
__url__ = "https://www.freecad.org"

## @package ArchCutPlane
#  \ingroup ARCH
#  \brief The Cut plane object and tools
#
#  This module handles the Cut Plane object

import FreeCAD
import ArchCommands
import Draft
import Part

if FreeCAD.GuiUp:
    from PySide import QtCore, QtGui
    import FreeCADGui
    from draftutils.translate import translate
else:
    # \cond
    def translate(ctxt, txt):
        return txt

    # \endcond


# _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

    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.

    Parameters
    ----------
    baseObj: Part::FeaturePython object or selection set (a list of Gui::SelectionObject objects)
        Object to be cut or a selection set: `FreeCADGui.Selection.getSelectionEx("", 0)`.
        If a selection set is provided it should contain baseObj and cutterShp, in that order.

    cutterShp: Part.Shape, optional
        Defaults to `None` in which case cutterShp should be in the baseObj selection set.
        Either a face or an edge. An edge is extruded along the Draft working plane normal.
        The shape should be in the global coordinate system.

    side: 0 or 1, optional
        Defaults to 0.
        Behind = 0, front = 1.
    """
    if (
        isinstance(baseObj, list)
        and len(baseObj) >= 1
        and baseObj[0].isDerivedFrom("Gui::SelectionObject")
    ):
        baseObj, baseShp, cutterShp = _getShapes(baseObj)
        baseParent = baseObj.getParentGeoFeatureGroup()
    else:
        baseShp = baseObj.Shape
        baseParent = baseObj.getParentGeoFeatureGroup()
        if baseParent is not None:
            baseShp = baseShp.transformGeometry(baseParent.getGlobalPlacement().toMatrix())

    if cutterShp.ShapeType != "Face":
        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")
        if baseParent is not None:
            cutVolume.Placement = baseParent.getGlobalPlacement().inverse()
        obj.Shape = Part.Compound([cutVolume])
        if baseParent is not None:
            baseParent.addObject(obj)
        if "Additions" in baseObj.PropertiesList:
            ArchCommands.removeComponents(obj, baseObj)  # Also changes the obj colors.
        else:
            Draft.format_object(obj, baseObj)
            cutObj = FreeCAD.ActiveDocument.addObject("Part::Cut", "CutPlane")
            if baseParent is not None:
                baseParent.addObject(cutObj)
            cutObj.Base = baseObj
            cutObj.Tool = obj