Download src/Mod/Draft/draftfunctions/downgrade.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 16.8 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftfunctions/downgrade.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/draftfunctions/downgrade.py
-
curl -L -o downgrade.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftfunctions/downgrade.py
16.8 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> * | |
| # * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> * | |
| # * Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> * | |
| # * Copyright (c) 2025 The FreeCAD Project Association * | |
| # * * | |
| # * 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. * | |
| # * * | |
| # * This program 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 program; if not, write to the Free Software * | |
| # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * | |
| # * USA * | |
| # * * | |
| # *************************************************************************** | |
| """Provides functions to downgrade objects by different methods. | |
| See also the `upgrade` function. | |
| """ | |
| ## @package downgrade | |
| # \ingroup draftfunctions | |
| # \brief Provides functions to downgrade objects by different methods. | |
| ## \addtogroup draftfunctions | |
| # @{ | |
| import FreeCAD as App | |
| from draftmake import make_copy | |
| from draftutils import utils | |
| from draftutils import params | |
| from draftutils import gui_utils | |
| from draftutils.groups import is_group | |
| from draftutils.messages import _msg | |
| from draftutils.translate import translate | |
| def downgrade(objects, delete=False, force=None): | |
| """Downgrade the given objects. | |
| This is a counterpart to `upgrade`. | |
| Parameters | |
| ---------- | |
| objects: Part::Feature or list | |
| A single object to downgrade or a list | |
| containing various such objects. | |
| delete: bool, optional | |
| It defaults to `False`. | |
| If it is `True`, the old objects are deleted, and only the resulting | |
| object is kept. | |
| force: str, optional | |
| It defaults to `None`. | |
| Its value can be used to force a certain method of downgrading. | |
| It can be any of: `'explode'`, `'shapify'`, `'subtr'`, `'splitFaces'`, | |
| `'cut2'`, `'getWire'`, `'splitWires'`, or `'splitCompounds'`. | |
| Returns | |
| ------- | |
| tuple | |
| A tuple containing two lists, a list of new objects | |
| and a list of objects to be deleted. | |
| See Also | |
| -------- | |
| upgrade | |
| """ | |
| # definitions of actions to perform | |
| def explode(obj): | |
| """Explode a Draft block or array.""" | |
| obj_pl = obj.Placement | |
| # block: | |
| if getattr(obj, "Components", []): | |
| delete_list.append(obj) | |
| for comp in obj.Components: | |
| # Objects in Components are in the same parent group as the block. | |
| comp.Placement = obj_pl.multiply(comp.Placement) | |
| comp.Visibility = True | |
| return True | |
| # array: | |
| if getattr(obj, "Base", None) is None: | |
| return False | |
| if not hasattr(obj, "PlacementList"): | |
| return False | |
| # Array must be added to delete_list before base. See can_be_deleted. | |
| delete_list.append(obj) | |
| base = obj.Base | |
| if not base.Visibility: | |
| # Delete base if it is not visible. The can_be_deleted | |
| # function will check if it is not used elsewhere. | |
| delete_list.append(base) | |
| new_list = [] | |
| if getattr(obj, "ExpandArray", False): | |
| delete_list.extend(obj.ElementList) | |
| for lnk in obj.ElementList: | |
| newobj = make_copy.make_copy(base) | |
| newobj.Placement = obj_pl.multiply(lnk.Placement) | |
| newobj.Visibility = True | |
| new_list.append(newobj) | |
| else: | |
| for arr_pl in obj.PlacementList: | |
| newobj = make_copy.make_copy(base) | |
| newobj.Placement = obj_pl.multiply(arr_pl) | |
| newobj.Visibility = True | |
| new_list.append(newobj) | |
| add_to_parent(obj, new_list) | |
| add_list.extend(new_list) | |
| return True | |
| def cut2(objects): | |
| """Cut 2nd object from 1st.""" | |
| newobj = doc.addObject("Part::Cut", "Cut") | |
| newobj.Base = objects[0] | |
| newobj.Tool = objects[1] | |
| format(objects[0], [newobj]) | |
| add_to_parent(objects[0], [newobj]) | |
| add_list.append(newobj) | |
| return True | |
| def splitCompounds(objects): | |
| """Split solids contained in compound objects into new objects.""" | |
| result = False | |
| for obj in objects: | |
| if not obj.Shape.Solids: | |
| continue | |
| new_list = [] | |
| for solid in obj.Shape.Solids: | |
| newobj = doc.addObject("Part::Feature", "Solid") | |
| newobj.Shape = solid | |
| new_list.append(newobj) | |
| format(obj, new_list) | |
| add_to_parent(obj, new_list) | |
| add_list.extend(new_list) | |
| delete_list.append(obj) | |
| result = True | |
| return result | |
| def splitFaces(objects): | |
| """Split faces contained in objects into new objects.""" | |
| result = False | |
| preserveFaceColor = params.get_param("preserveFaceColor") | |
| preserveFaceNames = params.get_param("preserveFaceNames") | |
| for obj in objects: | |
| if not obj.Shape.Faces: | |
| continue | |
| new_list = [] | |
| if App.GuiUp and preserveFaceColor and obj.ViewObject: | |
| colors = obj.ViewObject.DiffuseColor | |
| else: | |
| colors = None | |
| label = getattr(obj, "Label", "") | |
| for ind, face in enumerate(obj.Shape.Faces): | |
| newobj = doc.addObject("Part::Feature", "Face") | |
| newobj.Shape = face | |
| format(obj, [newobj]) | |
| if App.GuiUp and preserveFaceColor and colors: | |
| # At this point, some single-color objects might have just | |
| # a single value in colors for all faces, so we handle that: | |
| if ind < len(colors): | |
| color = colors[ind] | |
| else: | |
| color = colors[0] | |
| newobj.ViewObject.ShapeColor = color | |
| if preserveFaceNames: | |
| newobj.Label = "{} {}".format(label, newobj.Label) | |
| new_list.append(newobj) | |
| add_to_parent(obj, new_list) | |
| add_list.extend(new_list) | |
| delete_list.append(obj) | |
| result = True | |
| return result | |
| def subtr(objects): | |
| """Subtract faces from the first one.""" | |
| faces = [] | |
| done_list = [] | |
| for obj in objects: | |
| if obj.Shape.Faces: | |
| faces.extend(obj.Shape.Faces) | |
| done_list.append(obj) | |
| if not faces: | |
| return False | |
| main_face = faces.pop(0) | |
| for face in faces: | |
| main_face = main_face.cut(face) | |
| if main_face.isNull(): | |
| return False | |
| newobj = doc.addObject("Part::Feature", "Subtraction") | |
| newobj.Shape = main_face | |
| format(done_list[0], [newobj]) | |
| add_to_parent(done_list[0], [newobj]) | |
| add_list.append(newobj) | |
| delete_list.extend(done_list) | |
| return True | |
| def getWire(obj): | |
| """Get the wire from a face object.""" | |
| if not obj.Shape.Faces: | |
| return False | |
| new_list = [] | |
| for wire in obj.Shape.Faces[0].Wires: | |
| newobj = doc.addObject("Part::Feature", "Wire") | |
| newobj.Shape = wire | |
| new_list.append(newobj) | |
| format(obj, new_list) | |
| add_to_parent(obj, new_list) | |
| add_list.extend(new_list) | |
| delete_list.append(obj) | |
| return True | |
| def splitWires(objects): | |
| """Split the wires contained in objects into edges.""" | |
| result = False | |
| for obj in objects: | |
| if not obj.Shape.Edges: | |
| continue | |
| new_list = [] | |
| for edge in obj.Shape.Edges: | |
| newobj = doc.addObject("Part::Feature", "Edge") | |
| newobj.Shape = edge | |
| new_list.append(newobj) | |
| format(obj, new_list) | |
| add_to_parent(obj, new_list) | |
| add_list.extend(new_list) | |
| delete_list.append(obj) | |
| result = True | |
| return result | |
| def _shapify(obj): | |
| """Wrapper for utils.shapify.""" | |
| newobj = utils.shapify(obj, delete=False) | |
| if newobj: | |
| format(obj, [newobj]) | |
| add_to_parent(obj, [newobj]) | |
| add_list.append(newobj) | |
| delete_list.append(obj) | |
| return True | |
| return False | |
| # helper functions (same as in upgrade.py) | |
| def get_parent(obj): | |
| # Problem with obj.getParent(): | |
| # https://github.com/FreeCAD/FreeCAD/issues/19600 | |
| parent = obj.getParentGroup() | |
| if parent is not None: | |
| return parent | |
| return obj.getParentGeoFeatureGroup() | |
| def can_be_deleted(obj): | |
| if not obj.InList: | |
| return True | |
| for other in obj.InList: | |
| if is_group(other): | |
| continue | |
| if other.TypeId == "App::Part": | |
| continue | |
| return False | |
| return True | |
| def delete_object(obj): | |
| if utils.is_deleted(obj): | |
| return | |
| parent = get_parent(obj) | |
| if parent is not None and parent.TypeId == "PartDesign::Body": | |
| obj = parent | |
| if not can_be_deleted(obj): | |
| # Make obj invisible instead: | |
| obj.Visibility = False | |
| return | |
| if obj.TypeId == "PartDesign::Body": | |
| obj.removeObjectsFromDocument() | |
| doc.removeObject(obj.Name) | |
| def add_to_parent(obj, new_list): | |
| parent = get_parent(obj) | |
| if parent is None: | |
| if doc.getObject("Draft_Construction"): | |
| # This cludge is required because the make_* commands may | |
| # put new objects in the construction group. | |
| constr_group = doc.getObject("Draft_Construction") | |
| for newobj in new_list: | |
| constr_group.removeObject(newobj) | |
| return | |
| if parent.TypeId == "PartDesign::Body": | |
| # We don't add to a PD Body. We process its placement and | |
| # add to its parent instead. | |
| for newobj in new_list: | |
| newobj.Placement = parent.Placement.multiply(newobj.Placement) | |
| add_to_parent(parent, new_list) | |
| return | |
| for newobj in new_list: | |
| # Using addObject is different from just changing the Group property. | |
| # With addObject the object will be added to the parent group, but if | |
| # that is a normal group, also to that group's parent GeoFeatureGroup, | |
| # if available. | |
| parent.addObject(newobj) | |
| def format(obj, new_list): | |
| for newobj in new_list: | |
| gui_utils.format_object(newobj, obj, ignore_construction=True) | |
| doc = App.ActiveDocument | |
| add_list = [] | |
| delete_list = [] | |
| result = False | |
| if not isinstance(objects, list): | |
| objects = [objects] | |
| if not objects: | |
| return add_list, delete_list | |
| # analyzing objects | |
| solids = [] | |
| faces = [] | |
| edges = [] | |
| onlyedges = True | |
| parts = [] | |
| for obj in objects: | |
| if hasattr(obj, "Shape"): | |
| for solid in obj.Shape.Solids: | |
| solids.append(False) | |
| for face in obj.Shape.Faces: | |
| faces.append(face) | |
| for edge in obj.Shape.Edges: | |
| edges.append(edge) | |
| if obj.Shape.ShapeType != "Edge": | |
| onlyedges = False | |
| parts.append(obj) | |
| objects = parts | |
| if not objects: | |
| result = False | |
| elif force: | |
| # functions that work on a single object: | |
| single_funcs = {"explode": explode, "getWire": getWire, "shapify": _shapify} | |
| # functions that work on multiple objects: | |
| multi_funcs = { | |
| "cut2": cut2, | |
| "splitCompounds": splitCompounds, | |
| "splitFaces": splitFaces, | |
| "splitWires": splitWires, | |
| "subtr": subtr, | |
| } | |
| if force in single_funcs: | |
| result = any([single_funcs[force](obj) for obj in objects]) | |
| elif force in multi_funcs: | |
| result = multi_funcs[force](objects) | |
| else: | |
| _msg(translate("draft", "Downgrade: Unknown force method:") + " " + force) | |
| result = False | |
| else: | |
| parent = get_parent(objects[0]) | |
| same_parent = True | |
| same_parent_type = getattr(parent, "TypeId", "") # "" for global space. | |
| if len(objects) > 1: | |
| for obj in objects[1:]: | |
| if get_parent(obj) != parent: | |
| same_parent = False | |
| same_parent_type = None | |
| break | |
| # we have a block, we explode it | |
| if len(objects) == 1 and utils.get_type(objects[0]) == "Block": | |
| result = explode(objects[0]) | |
| if result: | |
| _msg(translate("draft", "Found 1 block: exploding it")) | |
| # we have an array, we explode it | |
| elif len(objects) == 1 and "Array" in utils.get_type(objects[0]): | |
| result = explode(objects[0]) | |
| if result: | |
| _msg(translate("draft", "Found 1 array: exploding it")) | |
| # special case, we have one parametric object: we "de-parametrize" it | |
| elif ( | |
| len(objects) == 1 | |
| and hasattr(objects[0], "Shape") | |
| and ( | |
| hasattr(objects[0], "Base") | |
| or hasattr(objects[0], "Profile") | |
| or hasattr(objects[0], "Sections") | |
| ) | |
| ): | |
| result = _shapify(objects[0]) | |
| if result: | |
| _msg(translate("draft", "Found 1 parametric object: breaking its dependencies")) | |
| # we have one multi-solids compound object: extract its solids | |
| elif len(objects) == 1 and hasattr(objects[0], "Shape") and len(solids) > 1: | |
| result = splitCompounds(objects) | |
| if result: | |
| _msg(translate("draft", "Found 1 multi-solids compound: exploding it")) | |
| # we have only 2 objects: cut 2nd from 1st | |
| elif len(objects) == 2 and same_parent and same_parent_type != "PartDesign::Body": | |
| result = cut2(objects) | |
| if result: | |
| _msg(translate("draft", "Found 2 objects: subtracting them")) | |
| elif len(faces) > 1: | |
| # one object with several faces: split it | |
| if len(objects) == 1: | |
| result = splitFaces(objects) | |
| if result: | |
| _msg(translate("draft", "Found several faces: splitting them")) | |
| # several objects: remove all the faces from the first one | |
| elif same_parent and same_parent_type != "PartDesign::Body": | |
| result = subtr(objects) | |
| if result: | |
| _msg( | |
| translate( | |
| "draft", "Found several faces: subtracting them from the first one" | |
| ) | |
| ) | |
| # only one face: we extract its wires | |
| elif len(faces) > 0: | |
| result = getWire(objects[0]) | |
| if result: | |
| _msg(translate("draft", "Found 1 face: extracting its wires")) | |
| # no faces: split wire into single edges | |
| elif not onlyedges: | |
| result = splitWires(objects) | |
| if result: | |
| _msg(translate("draft", "Found only wires: extracting their edges")) | |
| # no result has been obtained | |
| if not result: | |
| _msg(translate("draft", "Unable to downgrade these objects")) | |
| if delete: | |
| for obj in delete_list: | |
| delete_object(obj) | |
| gui_utils.select(add_list) | |
| return add_list, delete_list | |
| ## @} | |