# -*- coding: utf-8 -*- # *************************************************************************** # * (c) 2009, 2010 * # * Yorik van Havre , Ken Cline * # * (c) 2020 Eliud Cabrera Castillo * # * * # * 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) ## @}