| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """Provides functions to upgrade objects by different methods. |
| | |
| | See also the `downgrade` function. |
| | """ |
| | |
| | |
| | |
| |
|
| | import math |
| | import re |
| | import lazy_loader.lazy_loader as lz |
| |
|
| | import FreeCAD as App |
| | from draftfunctions import draftify |
| | from draftgeoutils.geometry import is_straight_line |
| | from draftmake import make_block |
| | from draftmake import make_wire |
| | from draftutils import gui_utils |
| | from draftutils import params |
| | from draftutils import utils |
| | from draftutils.groups import is_group |
| | from draftutils.messages import _msg |
| | from draftutils.translate import translate |
| |
|
| | |
| | Part = lz.LazyLoader("Part", globals(), "Part") |
| | DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") |
| | Arch = lz.LazyLoader("Arch", globals(), "Arch") |
| |
|
| | _DEBUG = False |
| |
|
| | |
| | |
| |
|
| |
|
| | def upgrade(objects, delete=False, force=None): |
| | """Upgrade the given objects. |
| | |
| | This is a counterpart to `downgrade`. |
| | |
| | Parameters |
| | ---------- |
| | objects: Part::Feature or list |
| | A single object to upgrade 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 upgrading. |
| | It can be any of: `'makeCompound'`, `'closeGroupWires'`, |
| | `'makeSolid'`, `'closeWire'`, `'turnToParts'`, `'makeFusion'`, |
| | `'makeShell'`, `'makeFaces'`, `'draftify'`, `'joinFaces'`, |
| | `'makeSketchFace'`, `'makeWires'`. |
| | |
| | Returns |
| | ------- |
| | tuple |
| | A tuple containing two lists, a list of new objects |
| | and a list of objects to be deleted. |
| | |
| | See Also |
| | -------- |
| | downgrade |
| | """ |
| |
|
| | |
| |
|
| | def makeCompound(objects): |
| | """Return a compound object made from the given objects.""" |
| | newobj = make_block.make_block(objects) |
| | format(objects[0], [newobj]) |
| | add_to_parent(objects[0], [newobj]) |
| | add_list.append(newobj) |
| | return True |
| |
|
| | def closeGroupWires(groups): |
| | """Close every open wire in the given groups.""" |
| | result = False |
| | for grp in groups: |
| | if any([closeWire(obj) for obj in grp.Group]): |
| | result = True |
| | return result |
| |
|
| | def makeSolid(obj): |
| | """Turn an object into a solid, if possible.""" |
| | if obj.Shape.Solids: |
| | return False |
| | try: |
| | solid = Part.makeSolid(obj.Shape) |
| | except Part.OCCError: |
| | return False |
| | if not solid.isClosed(): |
| | return False |
| | newobj = doc.addObject("Part::Feature", "Solid") |
| | newobj.Shape = solid |
| | format(obj, [newobj]) |
| | add_to_parent(obj, [newobj]) |
| | add_list.append(newobj) |
| | delete_list.append(obj) |
| | return True |
| |
|
| | def closeWire(obj): |
| | """Close a wire object, if possible.""" |
| | if obj.Shape.Faces: |
| | return False |
| | if len(obj.Shape.Wires) != 1: |
| | return False |
| | if len(obj.Shape.Edges) == 1: |
| | return False |
| | if is_straight_line(obj.Shape): |
| | return False |
| | if utils.get_type(obj) == "Wire": |
| | obj.Closed = True |
| | return True |
| | wire = obj.Shape.Wires[0] |
| | if wire.isClosed(): |
| | return False |
| | verts = wire.OrderedVertexes |
| | p0 = verts[0].Point |
| | p1 = verts[-1].Point |
| | edges = wire.Edges |
| | edges.append(Part.LineSegment(p1, p0).toShape()) |
| | wire = Part.Wire(Part.__sortEdges__(edges)) |
| | newobj = doc.addObject("Part::Feature", "Wire") |
| | newobj.Shape = wire |
| | format(obj, [newobj]) |
| | add_to_parent(obj, [newobj]) |
| | add_list.append(newobj) |
| | delete_list.append(obj) |
| | return True |
| |
|
| | def turnToParts(meshes): |
| | """Turn given meshes to parts.""" |
| | result = False |
| | for mesh in meshes: |
| | shp = Arch.getShapeFromMesh(mesh.Mesh) |
| | if shp: |
| | newobj = doc.addObject("Part::Feature", shp.ShapeType) |
| | newobj.Shape = shp |
| | format(mesh, [newobj]) |
| | add_to_parent(mesh, [newobj]) |
| | add_list.append(newobj) |
| | delete_list.append(mesh) |
| | result = True |
| | return result |
| |
|
| | def makeFusion(objects): |
| | """Make a Draft or Part fusion between 2 given objects.""" |
| | newobj = doc.addObject("Part::Fuse", "Fusion") |
| | 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 makeShell(objects): |
| | """Make a shell or compound with the given objects.""" |
| | faces = [] |
| | done_list = [] |
| | for obj in objects: |
| | if obj.Shape.Faces: |
| | faces.extend(obj.Shape.Faces) |
| | done_list.append(obj) |
| | if not faces: |
| | return None |
| | shp = Part.makeShell(faces) |
| | if shp.isNull(): |
| | return None |
| | newobj = doc.addObject("Part::Feature", shp.ShapeType) |
| | newobj.Shape = shp |
| | |
| | format(done_list[0], [newobj]) |
| | add_to_parent(done_list[0], [newobj]) |
| | add_list.append(newobj) |
| | delete_list.extend(done_list) |
| |
|
| | if App.GuiUp and params.get_param("preserveFaceColor"): |
| | |
| | colors = gui_utils.get_diffuse_color(done_list) |
| | if len(faces) != len(colors): |
| | newobj.ViewObject.DiffuseColor = [colors[0]] |
| | else: |
| | |
| | |
| | |
| | old_data = [] |
| | for face, color in zip(faces, colors): |
| | old_data.append([face.Area, face.CenterOfMass, color]) |
| | new_colors = [] |
| | for new_face in shp.Faces: |
| | new_area = new_face.Area |
| | new_cen = new_face.CenterOfMass |
| | for old_area, old_cen, old_color in old_data: |
| | if math.isclose(new_area, old_area, abs_tol=1e-7) and new_cen.isEqual( |
| | old_cen, 1e-7 |
| | ): |
| | new_colors.append(old_color) |
| | break |
| | newobj.ViewObject.DiffuseColor = new_colors |
| |
|
| | if params.get_param("preserveFaceNames"): |
| | firstName = done_list[0].Label |
| | nameNoTrailNumbers = re.sub(r"\d+$", "", firstName) |
| | newobj.Label = "{} {}".format(newobj.Label, nameNoTrailNumbers) |
| |
|
| | return newobj |
| |
|
| | def joinFaces(objects): |
| | """Make one big face from the given objects, if possible.""" |
| | 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 |
| | if not DraftGeomUtils.is_coplanar(faces, 1e-3): |
| | return False |
| | fuse_face = faces.pop(0) |
| | for face in faces: |
| | fuse_face = fuse_face.fuse(face) |
| | face = DraftGeomUtils.concatenate(fuse_face) |
| | |
| | if face.isEqual(fuse_face): |
| | return False |
| | |
| | if len(face.Wires) == 1 and not DraftGeomUtils.hasCurves(face): |
| | newobj = make_wire.make_wire(face.Wires[0], closed=True, face=True) |
| | |
| | else: |
| | newobj = doc.addObject("Part::Feature", "Union") |
| | newobj.Shape = 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 makeSketchFace(obj): |
| | """Make a face from a sketch.""" |
| | face = Part.makeFace(obj.Shape.Wires, "Part::FaceMakerBullseye") |
| | if not face: |
| | return False |
| | newobj = doc.addObject("Part::Feature", "Face") |
| | newobj.Shape = face |
| | format(obj, [newobj]) |
| | add_to_parent(obj, [newobj]) |
| | add_list.append(newobj) |
| | delete_list.append(obj) |
| | return True |
| |
|
| | def makeFaces(objects): |
| | """Make a face from every closed wire in the given objects.""" |
| | result = False |
| | for obj in objects: |
| | new_list = [] |
| | for wire in obj.Shape.Wires: |
| | try: |
| | face = Part.Face(wire) |
| | except Part.OCCError: |
| | continue |
| | newobj = doc.addObject("Part::Feature", "Face") |
| | newobj.Shape = face |
| | new_list.append(newobj) |
| | if not new_list: |
| | continue |
| | format(obj, new_list) |
| | add_to_parent(obj, new_list) |
| | add_list.extend(new_list) |
| | delete_list.append(obj) |
| | result = True |
| | return result |
| |
|
| | def makeWires(objects): |
| | """Join edges in the given objects into wires.""" |
| | edges = [] |
| | done_list = [] |
| | for obj in objects: |
| | if obj.Shape.Edges: |
| | edges.extend(obj.Shape.Edges) |
| | done_list.append(obj) |
| | if not edges: |
| | return False |
| | try: |
| | sorted_edges = Part.sortEdges(edges) |
| | if _DEBUG: |
| | for cluster in sorted_edges: |
| | for edge in cluster: |
| | print("Curve: {}".format(edge.Curve)) |
| | print( |
| | "first: {}, last: {}".format( |
| | edge.Vertexes[0].Point, edge.Vertexes[-1].Point |
| | ) |
| | ) |
| | wires = [Part.Wire(cluster) for cluster in sorted_edges] |
| | except Part.OCCError: |
| | return False |
| | if len(objects) > 1 and len(wires) == len(objects): |
| | |
| | return False |
| | new_list = [] |
| | for wire in wires: |
| | newobj = doc.addObject("Part::Feature", "Wire") |
| | newobj.Shape = wire |
| | new_list.append(newobj) |
| | |
| | format(done_list[0], new_list) |
| | add_to_parent(done_list[0], new_list) |
| | add_list.extend(new_list) |
| | delete_list.extend(done_list) |
| | return True |
| |
|
| | def _draftify(obj): |
| | """Wrapper for draftify.""" |
| | new_list = draftify.draftify(obj, delete=False) |
| | if not new_list: |
| | return False |
| | if not isinstance(new_list, list): |
| | new_list = [new_list] |
| | format(obj, new_list) |
| | add_to_parent(obj, new_list) |
| | add_list.extend(new_list) |
| | delete_list.append(obj) |
| | return True |
| |
|
| | |
| |
|
| | def get_parent(obj): |
| | |
| | |
| | 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): |
| | |
| | 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"): |
| | |
| | |
| | constr_group = doc.getObject("Draft_Construction") |
| | for newobj in new_list: |
| | constr_group.removeObject(newobj) |
| | return |
| | if parent.TypeId == "PartDesign::Body": |
| | |
| | |
| | for newobj in new_list: |
| | newobj.Placement = parent.Placement.multiply(newobj.Placement) |
| | add_to_parent(parent, new_list) |
| | return |
| | for newobj in new_list: |
| | |
| | |
| | |
| | |
| | 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 |
| |
|
| | |
| | faces = [] |
| | wires = [] |
| | openwires = [] |
| | facewires = [] |
| | edges = [] |
| | loneedges = [] |
| | groups = [] |
| | meshes = [] |
| | parts = [] |
| |
|
| | for obj in objects: |
| | if obj.TypeId == "App::DocumentObjectGroup": |
| | groups.append(obj) |
| | elif hasattr(obj, "Shape"): |
| | parts.append(obj) |
| | faces.extend(obj.Shape.Faces) |
| | wires.extend(obj.Shape.Wires) |
| | edges.extend(obj.Shape.Edges) |
| | for face in obj.Shape.Faces: |
| | facewires.extend(face.Wires) |
| | wirededges = [] |
| | for wire in obj.Shape.Wires: |
| | if len(wire.Edges) > 1: |
| | for edge in wire.Edges: |
| | wirededges.append(edge.hashCode()) |
| | if not wire.isClosed(): |
| | openwires.append(wire) |
| | for edge in obj.Shape.Edges: |
| | if not edge.hashCode() in wirededges and not edge.isClosed(): |
| | loneedges.append(edge) |
| | elif obj.isDerivedFrom("Mesh::Feature"): |
| | meshes.append(obj) |
| | objects = parts |
| |
|
| | if _DEBUG: |
| | print("objects: {}, edges: {}".format(objects, edges)) |
| | print("wires: {}, openwires: {}".format(wires, openwires)) |
| | print("faces: {}".format(faces)) |
| | print("groups: {}".format(groups)) |
| | print("facewires: {}, loneedges: {}".format(facewires, loneedges)) |
| |
|
| | if not (groups or objects or meshes): |
| | result = False |
| |
|
| | elif force: |
| | if force == "closeGroupWires": |
| | result = closeGroupWires(groups) |
| | elif force == "turnToParts": |
| | result = turnToParts(meshes) |
| | else: |
| | |
| | single_funcs = { |
| | "closeWire": closeWire, |
| | "draftify": _draftify, |
| | "makeSketchFace": makeSketchFace, |
| | "makeSolid": makeSolid, |
| | } |
| | |
| | multi_funcs = { |
| | "joinFaces": joinFaces, |
| | "makeCompound": makeCompound, |
| | "makeFaces": makeFaces, |
| | "makeFusion": makeFusion, |
| | "makeShell": makeShell, |
| | "makeWires": makeWires, |
| | } |
| | 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", "Upgrade: Unknown force method:") + " " + force) |
| | result = False |
| |
|
| | |
| | elif groups: |
| | result = closeGroupWires(groups) |
| | if result: |
| | _msg(translate("draft", "Found groups: closing open wires inside")) |
| |
|
| | |
| | elif meshes: |
| | result = turnToParts(meshes) |
| | if result: |
| | _msg(translate("draft", "Found meshes: turning them into Part shapes")) |
| |
|
| | else: |
| | |
| | |
| | |
| | |
| | if faces: |
| | faces_coplanarity = DraftGeomUtils.is_coplanar(faces, 1e-3) |
| |
|
| | parent = get_parent(objects[0]) |
| | same_parent = True |
| | same_parent_type = getattr(parent, "TypeId", "") |
| | if len(objects) > 1: |
| | for obj in objects[1:]: |
| | if get_parent(obj) != parent: |
| | same_parent = False |
| | same_parent_type = None |
| | break |
| |
|
| | |
| | if faces and len(facewires) == len(wires) and not openwires and not loneedges: |
| |
|
| | |
| | |
| | if len(objects) == 1 and len(faces) > 3 and not faces_coplanarity: |
| | result = makeSolid(objects[0]) |
| | if result: |
| | _msg(translate("draft", "Found 1 solidifiable object: solidifying it")) |
| |
|
| | |
| | elif ( |
| | len(objects) == 2 |
| | and not faces_coplanarity |
| | and same_parent |
| | and same_parent_type != "PartDesign::Body" |
| | ): |
| | result = makeFusion(objects) |
| | if result: |
| | _msg(translate("draft", "Found 2 objects: fusing them")) |
| |
|
| | |
| | elif ( |
| | len(objects) > 1 |
| | and len(faces) > 1 |
| | and same_parent |
| | and same_parent_type != "PartDesign::Body" |
| | ): |
| | result = makeShell(objects) |
| | if result: |
| | _msg( |
| | translate( |
| | "draft", "Found several objects: creating a " + result.Shape.ShapeType |
| | ) |
| | ) |
| |
|
| | |
| | elif len(objects) == 1 and len(faces) > 1 and faces_coplanarity: |
| | result = joinFaces(objects) |
| | if result: |
| | _msg( |
| | translate( |
| | "draft", "Found object with several coplanar faces: refining them" |
| | ) |
| | ) |
| |
|
| | |
| | elif ( |
| | len(objects) == 1 |
| | and not objects[0].isDerivedFrom("Part::Part2DObjectPython") |
| | and not utils.get_type(objects[0]) in ["BezCurve", "BSpline", "Wire"] |
| | ): |
| | result = _draftify(objects[0]) |
| | if result: |
| | _msg( |
| | translate( |
| | "draft", |
| | "Found 1 non-parametric object: replacing it with a Draft object", |
| | ) |
| | ) |
| |
|
| | |
| | elif not faces: |
| |
|
| | |
| | if wires and not openwires and not loneedges: |
| | |
| | if len(objects) == 1 and objects[0].isDerivedFrom("Sketcher::SketchObject"): |
| | result = makeSketchFace(objects[0]) |
| | if result: |
| | _msg( |
| | translate( |
| | "draft", "Found 1 closed sketch object: creating a face from it" |
| | ) |
| | ) |
| | |
| | else: |
| | result = makeFaces(objects) |
| | if result: |
| | _msg(translate("draft", "Found closed wires: creating faces")) |
| |
|
| | |
| | elif len(objects) > 1 and len(edges) > 1 and same_parent: |
| | result = makeWires(objects) |
| | if result: |
| | _msg(translate("draft", "Found several wires or edges: wiring them")) |
| | else: |
| | result = makeCompound(objects) |
| | if result: |
| | _msg( |
| | translate( |
| | "draft", "Found several non-treatable objects: creating compound" |
| | ) |
| | ) |
| |
|
| | |
| | elif len(objects) == 1 and len(openwires) == 1: |
| | result = closeWire(objects[0]) |
| | if result: |
| | _msg(translate("draft", "Found 1 open wire: closing it")) |
| |
|
| | |
| | elif ( |
| | len(objects) == 1 |
| | and len(edges) == 1 |
| | and not objects[0].isDerivedFrom("Part::Part2DObjectPython") |
| | and not utils.get_type(objects[0]) in ["BezCurve", "BSpline", "Wire"] |
| | ): |
| | edge_type = DraftGeomUtils.geomType(objects[0].Shape.Edges[0]) |
| | |
| | if edge_type in ("Line", "Circle"): |
| | result = _draftify(objects[0]) |
| | if result: |
| | _msg( |
| | translate( |
| | "draft", |
| | "Found 1 non-parametric object: replacing it with a Draft object", |
| | ) |
| | ) |
| |
|
| | |
| | elif not edges and len(objects) > 1: |
| | result = makeCompound(objects) |
| | if result: |
| | _msg(translate("draft", "Found points: creating compound")) |
| |
|
| | |
| | elif len(objects) > 1: |
| | result = makeCompound(objects) |
| | if result: |
| | _msg(translate("draft", "Found several non-treatable objects: creating compound")) |
| |
|
| | |
| | if not result: |
| | _msg(translate("draft", "Unable to upgrade these objects")) |
| |
|
| | if delete: |
| | for obj in delete_list: |
| | delete_object(obj) |
| |
|
| | gui_utils.select(add_list) |
| | return add_list, delete_list |
| |
|
| |
|
| | |
| |
|