File size: 25,520 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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
# 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 upgrade objects by different methods.

See also the `downgrade` function.
"""
## @package downgrade
# \ingroup draftfunctions
# \brief Provides functions to upgrade objects by different methods.

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

# Delay import of module until first use because it is heavy
Part = lz.LazyLoader("Part", globals(), "Part")
DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils")
Arch = lz.LazyLoader("Arch", globals(), "Arch")

_DEBUG = False

## \addtogroup draftfunctions
# @{


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
    """

    # definitions of actions to perform

    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 before applying diffuse color:
        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"):
            # Must happen after add_to_parent for correct CenterOfMass.
            colors = gui_utils.get_diffuse_color(done_list)
            if len(faces) != len(colors):
                newobj.ViewObject.DiffuseColor = [colors[0]]
            else:
                # The ordering of shp.Faces may be different. Since we cannot
                # compare via hashCode(), we have to iterate and use different
                # criteria to find the correct color.
                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)
        # check if concatenate failed
        if face.isEqual(fuse_face):
            return False
        # several coplanar and non-curved faces, they can become a Draft Wire
        if len(face.Wires) == 1 and not DraftGeomUtils.hasCurves(face):
            newobj = make_wire.make_wire(face.Wires[0], closed=True, face=True)
        # if not possible, we do a non-parametric union
        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):
            # we still have the same number of objects, we actually didn't join anything!
            return False
        new_list = []
        for wire in wires:
            newobj = doc.addObject("Part::Feature", "Wire")
            newobj.Shape = wire
            new_list.append(newobj)
        # We don't know which wire came from which obj, we format them the same:
        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

    # helper functions (same as in downgrade.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
    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:
            # functions that work on a single object:
            single_funcs = {
                "closeWire": closeWire,
                "draftify": _draftify,
                "makeSketchFace": makeSketchFace,
                "makeSolid": makeSolid,
            }
            # functions that work on multiple objects:
            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

    # if we have a group: close each wire inside
    elif groups:
        result = closeGroupWires(groups)
        if result:
            _msg(translate("draft", "Found groups: closing open wires inside"))

    # if we have meshes, we try to turn them into shapes
    elif meshes:
        result = turnToParts(meshes)
        if result:
            _msg(translate("draft", "Found meshes: turning them into Part shapes"))

    else:
        # checking faces coplanarity
        # The precision needed in Part.makeFace is 1e-7. Here we use a
        # higher value to let that function throw the exception when
        # joinFaces is called if the precision is insufficient.
        if faces:
            faces_coplanarity = DraftGeomUtils.is_coplanar(faces, 1e-3)

        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 only faces
        if faces and len(facewires) == len(wires) and not openwires and not loneedges:

            # we have one shell: we try to make a solid
            # this also handles PD Bodies and PD features with solids (result will be False)
            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"))

            # we have exactly 2 objects: we fuse them
            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"))

            # we have many separate faces: we try to make a shell or compound
            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
                        )
                    )

            # we have faces: we try to join them if they are coplanar
            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"
                        )
                    )

            # only one object: if not parametric, we "draftify" it
            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",
                        )
                    )

        # in the following cases there are no faces
        elif not faces:

            # we have only closed wires
            if wires and not openwires and not loneedges:
                # we have a sketch: extract a face
                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"
                            )
                        )
                # only closed wires
                else:
                    result = makeFaces(objects)
                    if result:
                        _msg(translate("draft", "Found closed wires: creating faces"))

            # wires or edges: we try to join them
            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"
                            )
                        )

            # special case, we have only one open wire. We close it, unless it has only 1 edge!
            elif len(objects) == 1 and len(openwires) == 1:
                result = closeWire(objects[0])
                if result:
                    _msg(translate("draft", "Found 1 open wire: closing it"))

            # only one object: if not parametric, we "draftify" 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])
                # currently only support Line and Circle
                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",
                            )
                        )

            # only points, no edges
            elif not edges and len(objects) > 1:
                result = makeCompound(objects)
                if result:
                    _msg(translate("draft", "Found points: creating compound"))

        # all other cases, if more than 1 object, make a compound
        elif len(objects) > 1:
            result = makeCompound(objects)
            if result:
                _msg(translate("draft", "Found several non-treatable objects: creating compound"))

    # no result has been obtained
    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


## @}