File size: 12,109 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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# SPDX-License-Identifier: LGPL-2.1-or-later

# ***************************************************************************
# *   (c) 2009, 2010                                                        *
# *   Yorik van Havre <yorik@uncreated.net>, Ken Cline <cline@frii.com>     *
# *   (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de>           *
# *                                                                         *
# *   This file is part of the FreeCAD CAx development system.              *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENCE text file.                                 *
# *                                                                         *
# *   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 Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with FreeCAD; if not, write to the Free Software        *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************
"""Provides utility functions to do operations with groups.

The functions here are also used in the Arch Workbench as some of
the objects created with this workbench work like groups.
"""
## @package groups
# \ingroup draftutils
# \brief Provides utility functions to do operations with groups.

## \addtogroup draftutils
# @{
import FreeCAD as App
import draftutils.utils as utils

from draftutils.translate import translate
from draftutils.messages import _err


def is_group(obj):
    """Return True if the given object is considered a group.

    Parameters
    ----------
    obj : App::DocumentObject
        The object to check.

    Returns
    -------
    bool
        Returns `True` if `obj` is considered a group:

        The object is derived from `App::DocumentObjectGroup` but not
        a `'LayerContainer'`.

        Or the object is of the type `'Project'`, `'Site'`, `'Building'`,
        `'Floor'`, `'BuildingPart'` or `'Space'` from the Arch Workbench.
        Note that `'Floor'` and `'Building'` are obsolete types.

        Otherwise returns `False`.
    """
    typ = utils.get_type(obj)
    return (obj.isDerivedFrom("App::DocumentObjectGroup") and typ != "LayerContainer") or typ in (
        "Project",
        "Site",
        "Building",
        "Floor",
        "BuildingPart",
        "Space",
    )


def get_group_names(doc=None):
    """Return a list of names of existing groups in the document.

    Parameters
    ----------
    doc: App::Document, optional
        It defaults to `None`.
        A document on which to search group names.
        It if is `None` it will search the current document.

    Returns
    -------
    list of str
        A list of names of objects that are considered groups.
        See the is_group function.

        Otherwise returns an empty list.
    """
    if not doc:
        found, doc = utils.find_doc(App.activeDocument())

    if not found:
        _err(translate("draft", "No active document. Aborting."))
        return []

    glist = []

    for obj in doc.Objects:
        if is_group(obj):
            glist.append(obj.Name)

    return glist


def getGroupNames():
    """Return a list of group names. DEPRECATED."""
    utils.use_instead("get_group_names")
    return get_group_names()


def ungroup(obj):
    """Remove the object from any group to which it belongs.

    A "group" is any object returned by `get_group_names`.

    Parameters
    ----------
    obj: App::DocumentObject or str
        Any type of object.
        If it is a string, it must be the `Label` of that object.
        Since a label is not guaranteed to be unique in a document,
        it will use the first object found with this label.
    """
    if isinstance(obj, str):
        obj_str = obj

    found, obj = utils.find_object(obj, doc=App.activeDocument())
    if not found:
        _err(translate("draft", "Wrong input: object {} not in document.").format(obj_str))
        return None

    doc = obj.Document

    for name in get_group_names():
        group = doc.getObject(name)
        if obj in group.Group:
            # The list of objects cannot be modified directly,
            # so a new list is created, this new list is modified,
            # and then it is assigned over the older list.
            objects = group.Group
            objects.remove(obj)
            group.Group = objects


def get_windows(obj):
    """Return the windows and rebars inside a host.

    Parameters
    ----------
    obj: App::DocumentObject
        A scripted object of type `'Wall'` or `'Structure'`
        (Arch Workbench).
        This will be searched for objects of type `'Window'` and `'Rebar'`,
        and clones of them, and the found elements will be added
        to the output list.

        The function will search recursively all elements under `obj.OutList`,
        in case the windows and rebars are nested under other walls
        and structures.

    Returns
    -------
    list
        A list of all found windows and rebars in `obj`.
        If `obj` is itself a `'Window'` or a `'Rebar'`, or a clone of them,
        it will return the same `obj` element.
    """
    out = []
    if utils.get_type(obj) in ("Wall", "Structure"):
        for o in obj.OutList:
            out.extend(get_windows(o))
        for i in obj.InList:
            if utils.get_type(i.getLinkedObject()) == "Window" or utils.is_clone(obj, "Window"):
                if hasattr(i, "Hosts"):
                    if obj in i.Hosts:
                        out.append(i)
            elif utils.get_type(i) == "Rebar" or utils.is_clone(obj, "Rebar"):
                if hasattr(i, "Host"):
                    if obj == i.Host:
                        out.append(i)
    elif utils.get_type(obj.getLinkedObject()) in ("Window", "Rebar") or utils.is_clone(
        obj, ["Window", "Rebar"]
    ):
        out.append(obj)

    return out


def get_group_contents(objectslist, walls=False, addgroups=False, spaces=False, noarchchild=False):
    """Return a list of objects from expanding the input groups.

    The function accepts any type of object, although it is most useful
    with "groups", as it is meant to unpack the objects inside these groups.

    Parameters
    ----------
    objectslist: list
        If any object in the list is considered a group, see the `is_group`
        function, its contents (`obj.Group`) are extracted and added to the
        output list.

        Single items that aren't groups are added to the output list
        as is.

    walls: bool, optional
        It defaults to `False`.
        If it is `True`, Wall and Structure objects (Arch Workbench)
        are treated as groups; they are scanned for Window, Door,
        and Rebar objects, and these are added to the output list.

    addgroups: bool, optional
        It defaults to `False`.
        If it is `True`, the group itself is kept as part of the output list.

    spaces: bool, optional
        It defaults to `False`.
        If it is `True`, Arch Spaces are added to the output list even
        when addgroups is False (their contents are always added).

    noarchchild: bool, optional
        It defaults to `False`.
        If it is `True`, the objects inside Building and BuildingParts
        (Arch Workbench) aren't added to the output list.

    Returns
    -------
    list
        The list of objects from each group present in `objectslist`,
        plus any other individual object given in `objectslist`.
    """
    newlist = []
    if not isinstance(objectslist, list):
        objectslist = [objectslist]

    for obj in objectslist:
        if obj:
            if is_group(obj):
                if addgroups or (spaces and utils.get_type(obj) == "Space"):
                    newlist.append(obj)
                if not (noarchchild and utils.get_type(obj) in ("Building", "BuildingPart")):
                    newlist.extend(
                        get_group_contents(obj.Group, walls, addgroups, spaces, noarchchild)
                    )
            else:
                # print("adding ", obj.Name)
                newlist.append(obj)
                if walls:
                    newlist.extend(get_windows(obj))

    # Clean possible duplicates
    cleanlist = []
    for obj in newlist:
        if obj not in cleanlist:
            cleanlist.append(obj)

    return cleanlist


def getGroupContents(objectslist, walls=False, addgroups=False, spaces=False, noarchchild=False):
    """Return a list of objects from groups. DEPRECATED."""
    utils.use_instead("get_group_contents")

    return get_group_contents(objectslist, walls, addgroups, spaces, noarchchild)


def get_movable_children(objectslist, recursive=True, _donelist=[]):
    """Return a list of objects with child objects that move with a host.

    Builds a list of objects with all child objects (`obj.OutList`)
    that have their `MoveWithHost` attribute set to `True`.
    This function is mostly useful for Arch Workbench objects.

    Parameters
    ----------
    objectslist: list of App::DocumentObject
        A single scripted object or list of objects.

    recursive: bool, optional
        It defaults to `True`, in which case the function
        is called recursively to also extract the children of children
        objects.
        Otherwise, only direct children of the input objects
        are added to the output list.

    _donelist: list
        List of object names. Used internally to prevent an endless loop.

    Returns
    -------
    list
        List of children objects that have their `MoveWithHost` attribute
        set to `True`.
    """
    added = []
    if not isinstance(objectslist, list):
        objectslist = [objectslist]

    for obj in objectslist:
        if obj.Name in _donelist:
            continue

        _donelist.append(obj.Name)

        # Skips some objects that should never move their children
        if utils.get_type(obj) not in (
            "App::Part",
            "PartDesign::Body",
            "Clone",
            "SectionPlane",
            "Facebinder",
            "BuildingPart",
            "App::Link",
        ):
            children = obj.OutList
            if (
                hasattr(obj, "Proxy")
                and obj.Proxy
                and hasattr(obj.Proxy, "getSiblings")
                and utils.get_type(obj) != "Window"
            ):
                # children.extend(obj.Proxy.getSiblings(obj))
                pass

            for child in children:
                if hasattr(child, "MoveWithHost") and child.MoveWithHost:
                    if hasattr(obj, "CloneOf") and obj.CloneOf:
                        if obj.CloneOf.Name != child.Name:
                            added.append(child)
                    else:
                        added.append(child)

            if recursive:
                added.extend(get_movable_children(children, recursive, _donelist))

    return added


def getMovableChildren(objectslist, recursive=True):
    """Return a list of objects with child objects. DEPRECATED."""
    utils.use_instead("get_movable_children")
    return get_movable_children(objectslist, recursive)


## @}