File size: 6,178 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
# SPDX-License-Identifier: LGPL-2.1-or-later

# /***************************************************************************
# *   Copyright (c) 2016 Victor Titov (DeepSOIC) <vv.titov@gmail.com>       *
# *                                                                         *
# *   This file is part of the FreeCAD CAx development system.              *
# *                                                                         *
# *   This library is free software; you can redistribute it and/or         *
# *   modify it under the terms of the GNU Library General Public           *
# *   License as published by the Free Software Foundation; either          *
# *   version 2 of the License, or (at your option) any later version.      *
# *                                                                         *
# *   This library  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 Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with this library; see the file COPYING.LIB. If not,    *
# *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
# *   Suite 330, Boston, MA  02111-1307, USA                                *
# *                                                                         *
# ***************************************************************************/

__title__ = "BOPTools.Utils module"
__author__ = "DeepSOIC"
__url__ = "https://www.freecad.org"
__doc__ = "Utility code, used by various modules of BOPTools."


class HashableShape(object):
    "Decorator for Part.Shape, that can be used as key in dicts. Based on isSame method."

    def __init__(self, shape):
        self.Shape = shape
        self.hash = shape.hashCode()

    def __eq__(self, other):
        return self.Shape.isSame(other.Shape)

    def __hash__(self):
        return self.hash


class HashableShape_Deep(object):
    """Similar to HashableShape, except that the things the shape is composed of are compared.

    Example:
        >>> wire2 = Part.Wire(wire1.childShapes())
        >>> wire2.isSame(wire1)
        False # <--- the wire2 is a new wire, although made of edges of wire1
        >>> HashableShape_Deep(wire2) == HashableShape_Deep(wire1)
        True # <--- made of same set of elements
    """

    def __init__(self, shape):
        self.Shape = shape
        self.hash = 0
        for el in shape.childShapes():
            self.hash = self.hash ^ el.hashCode()

    def __eq__(self, other):
        # avoiding extensive comparison for now. Just doing a few extra tests should reduce the already-low chances of false-positives
        if self.hash == other.hash:
            if len(self.Shape.childShapes()) == len(other.Shape.childShapes()):
                if self.Shape.ShapeType == other.Shape.ShapeType:
                    return True
        return False

    def __hash__(self):
        return self.hash


def compoundLeaves(shape_or_compound):
    """compoundLeaves(shape_or_compound): extracts all non-compound shapes from a nested compound.
    Note: shape_or_compound may be a non-compound; then, it is the only thing in the
    returned list."""

    if shape_or_compound.ShapeType == "Compound":
        leaves = []
        for child in shape_or_compound.childShapes():
            leaves.extend(compoundLeaves(child))
        return leaves
    else:
        return [shape_or_compound]


def upgradeToAggregateIfNeeded(list_of_shapes, types=None):
    """upgradeToAggregateIfNeeded(list_of_shapes, types = None): upgrades non-aggregate type
    shapes to aggregate-type shapes if the list has a mix of aggregate and non-aggregate
    type shapes. Returns the new list. Recursively traverses into compounds.

    aggregate shape types are Wire, Shell, CompSolid
    non-aggregate shape types are Vertex, Edge, Face, Solid
    Compounds are something special: they are recursively traversed to upgrade the
    contained shapes.

    Examples:
    list_of_shapes contains only faces -> nothing happens
    list_of_shapes contains faces and shells -> faces are converted to shells

    'types' argument is needed for recursive traversal. Do not supply."""

    import Part

    if types is None:
        types = set()
    for shape in list_of_shapes:
        types.add(shape.ShapeType)
        subshapes = compoundLeaves(shape)
        for subshape in subshapes:
            types.add(subshape.ShapeType)
    if "Wire" in types:
        list_of_shapes = [
            (Part.Wire([shape]) if shape.ShapeType == "Edge" else shape) for shape in list_of_shapes
        ]
    if "Shell" in types:
        list_of_shapes = [
            (Part.makeShell([shape]) if shape.ShapeType == "Face" else shape)
            for shape in list_of_shapes
        ]
    if "CompSolid" in types:
        list_of_shapes = [
            (Part.CompSolid([shape]) if shape.ShapeType == "Solid" else shape)
            for shape in list_of_shapes
        ]
    if "Compound" in types:
        list_of_shapes = [
            (
                Part.makeCompound(upgradeToAggregateIfNeeded(shape.childShapes(), types))
                if shape.ShapeType == "Compound"
                else shape
            )
            for shape in list_of_shapes
        ]
    return list_of_shapes


# adapted from http://stackoverflow.com/a/3603824/6285007
class FrozenClass(object):
    """FrozenClass: prevents adding new attributes to class outside of __init__"""

    __isfrozen = False

    def __setattr__(self, key, value):
        if self.__isfrozen and not hasattr(self, key):
            raise TypeError(
                "{cls} has no attribute {attr}".format(cls=self.__class__.__name__, attr=key)
            )
        object.__setattr__(self, key, value)

    def _freeze(self):
        self.__isfrozen = True

    def _unfreeze(self):
        self.__isfrozen = False