FreeCAD

Форк
0
/
UtilsAssembly.py 
645 строк · 21.8 Кб
1
# SPDX-License-Identifier: LGPL-2.1-or-later
2
# /**************************************************************************
3
#                                                                           *
4
#    Copyright (c) 2023 Ondsel <development@ondsel.com>                     *
5
#                                                                           *
6
#    This file is part of FreeCAD.                                          *
7
#                                                                           *
8
#    FreeCAD is free software: you can redistribute it and/or modify it     *
9
#    under the terms of the GNU Lesser General Public License as            *
10
#    published by the Free Software Foundation, either version 2.1 of the   *
11
#    License, or (at your option) any later version.                        *
12
#                                                                           *
13
#    FreeCAD is distributed in the hope that it will be useful, but         *
14
#    WITHOUT ANY WARRANTY; without even the implied warranty of             *
15
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU       *
16
#    Lesser General Public License for more details.                        *
17
#                                                                           *
18
#    You should have received a copy of the GNU Lesser General Public       *
19
#    License along with FreeCAD. If not, see                                *
20
#    <https://www.gnu.org/licenses/>.                                       *
21
#                                                                           *
22
# **************************************************************************/
23

24
import FreeCAD as App
25
import Part
26

27
if App.GuiUp:
28
    import FreeCADGui as Gui
29

30
import PySide.QtCore as QtCore
31
import PySide.QtGui as QtGui
32

33

34
# translate = App.Qt.translate
35

36
__title__ = "Assembly utilitary functions"
37
__author__ = "Ondsel"
38
__url__ = "https://www.freecad.org"
39

40

41
def activeAssembly():
42
    doc = Gui.ActiveDocument
43

44
    if doc is None or doc.ActiveView is None:
45
        return None
46

47
    active_assembly = doc.ActiveView.getActiveObject("part")
48

49
    if active_assembly is not None and active_assembly.Type == "Assembly":
50
        return active_assembly
51

52
    return None
53

54

55
def activePart():
56
    doc = Gui.ActiveDocument
57

58
    if doc is None or doc.ActiveView is None:
59
        return None
60

61
    active_part = doc.ActiveView.getActiveObject("part")
62

63
    if active_part is not None and active_part.Type != "Assembly":
64
        return active_part
65

66
    return None
67

68

69
def isAssemblyCommandActive():
70
    return activeAssembly() is not None and not Gui.Control.activeDialog()
71

72

73
def isDocTemporary(doc):
74
    # Guard against older versions of FreeCad which don't have the Temporary attribute
75
    try:
76
        temp = doc.Temporary
77
    except AttributeError:
78
        temp = False
79
    return temp
80

81

82
def assembly_has_at_least_n_parts(n):
83
    assembly = activeAssembly()
84
    i = 0
85
    if not assembly:
86
        assembly = activePart()
87
        if not assembly:
88
            return False
89
    for obj in assembly.OutList:
90
        # note : groundedJoints comes in the outlist so we filter those out.
91
        if hasattr(obj, "Placement") and not hasattr(obj, "ObjectToGround"):
92
            i = i + 1
93
            if i == n:
94
                return True
95
    return False
96

97

98
def getObject(full_name):
99
    # full_name is "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBox.Edge16"
100
    # or           "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.pad.Edge16"
101
    # or           "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.Local_CS.X"
102
    # We want either LinkOrBody or LinkOrBox or Local_CS.
103
    names = full_name.split(".")
104
    doc = App.ActiveDocument
105

106
    if len(names) < 3:
107
        App.Console.PrintError(
108
            "getObject() in UtilsAssembly.py the object name is too short, at minimum it should be something like 'Assembly.Box.edge16'. It shouldn't be shorter"
109
        )
110
        return None
111

112
    prevObj = None
113

114
    for i, objName in enumerate(names):
115
        if i == 0:
116
            prevObj = doc.getObject(objName)
117
            if prevObj.TypeId == "App::Link":
118
                prevObj = prevObj.getLinkedObject()
119
            continue
120

121
        obj = None
122
        if prevObj.TypeId in {"App::Part", "Assembly::AssemblyObject", "App::DocumentObjectGroup"}:
123
            for obji in prevObj.OutList:
124
                if obji.Name == objName:
125
                    obj = obji
126
                    break
127

128
        if obj is None:
129
            return None
130

131
        # the last is the element name. So if we are at the last but one name, then it must be the selected
132
        if i == len(names) - 2:
133
            return obj
134

135
        if obj.TypeId == "App::Link":
136
            linked_obj = obj.getLinkedObject()
137
            if linked_obj.TypeId == "PartDesign::Body":
138
                if i + 1 < len(names):
139
                    obj2 = None
140
                    for obji in linked_obj.OutList:
141
                        if obji.Name == names[i + 1]:
142
                            obj2 = obji
143
                            break
144
                    if obj2 and isBodySubObject(obj2.TypeId):
145
                        return obj2
146
                return obj
147
            elif linked_obj.isDerivedFrom("Part::Feature"):
148
                return obj
149
            else:
150
                prevObj = linked_obj
151
                continue
152

153
        elif obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "App::DocumentObjectGroup"}:
154
            prevObj = obj
155
            continue
156

157
        elif obj.TypeId == "PartDesign::Body":
158
            if i + 1 < len(names):
159
                obj2 = None
160
                for obji in obj.OutList:
161
                    if obji.Name == names[i + 1]:
162
                        obj2 = obji
163
                        break
164
                if obj2 and isBodySubObject(obj2.TypeId):
165
                    return obj2
166
            return obj
167

168
        elif obj.isDerivedFrom("Part::Feature"):
169
            # primitive, fastener, gear ...
170
            return obj
171

172
    return None
173

174

175
def isBodySubObject(typeId):
176
    return (
177
        typeId == "Sketcher::SketchObject"
178
        or typeId == "PartDesign::Point"
179
        or typeId == "PartDesign::Line"
180
        or typeId == "PartDesign::Plane"
181
        or typeId == "PartDesign::CoordinateSystem"
182
    )
183

184

185
def getContainingPart(full_name, selected_object, activeAssemblyOrPart=None):
186
    # full_name is "Assembly.Assembly1.LinkOrPart1.LinkOrBox.Edge16" -> LinkOrPart1
187
    # or           "Assembly.Assembly1.LinkOrPart1.LinkOrBody.pad.Edge16" -> LinkOrPart1
188
    # or           "Assembly.Assembly1.LinkOrPart1.LinkOrBody.Sketch.Edge1" -> LinkOrBody
189

190
    if selected_object is None:
191
        App.Console.PrintError("getContainingPart() in UtilsAssembly.py selected_object is None")
192
        return None
193

194
    names = full_name.split(".")
195
    doc = App.ActiveDocument
196
    if len(names) < 3:
197
        App.Console.PrintError(
198
            "getContainingPart() in UtilsAssembly.py the object name is too short, at minimum it should be something like 'Assembly.Box.edge16'. It shouldn't be shorter"
199
        )
200
        return None
201

202
    for objName in names:
203
        obj = doc.getObject(objName)
204

205
        if not obj:
206
            continue
207

208
        if obj == selected_object:
209
            return selected_object
210

211
        if obj.TypeId == "PartDesign::Body" and isBodySubObject(selected_object.TypeId):
212
            if selected_object in obj.OutListRecursive:
213
                return obj
214

215
        # Note here we may want to specify a specific behavior for Assembly::AssemblyObject.
216
        if obj.TypeId == "App::Part":
217
            if selected_object in obj.OutListRecursive:
218
                if not activeAssemblyOrPart:
219
                    return obj
220
                elif activeAssemblyOrPart in obj.OutListRecursive or obj == activeAssemblyOrPart:
221
                    continue
222
                else:
223
                    return obj
224

225
        elif obj.TypeId == "App::Link":
226
            linked_obj = obj.getLinkedObject()
227
            if linked_obj.TypeId == "PartDesign::Body" and isBodySubObject(selected_object.TypeId):
228
                if selected_object in linked_obj.OutListRecursive:
229
                    return obj
230
            if linked_obj.TypeId == "App::Part":
231
                # linked_obj_doc = linked_obj.Document
232
                # selected_obj_in_doc = doc.getObject(selected_object.Name)
233
                if selected_object in linked_obj.OutListRecursive:
234
                    if not activeAssemblyOrPart:
235
                        return obj
236
                    elif (linked_obj.Document == activeAssemblyOrPart.Document) and (
237
                        activeAssemblyOrPart in linked_obj.OutListRecursive
238
                        or linked_obj == activeAssemblyOrPart
239
                    ):
240
                        continue
241
                    else:
242
                        return obj
243

244
    # no container found so we return the object itself.
245
    return selected_object
246

247

248
def getObjectInPart(objName, part):
249
    if part.Name == objName:
250
        return part
251

252
    if part.TypeId == "App::Link":
253
        part = part.getLinkedObject()
254

255
    if part.TypeId in {
256
        "App::Part",
257
        "Assembly::AssemblyObject",
258
        "App::DocumentObjectGroup",
259
        "PartDesign::Body",
260
    }:
261
        for obji in part.OutListRecursive:
262
            if obji.Name == objName:
263
                return obji
264

265
    return None
266

267

268
# get the placement of Obj relative to its containing Part
269
# Example : assembly.part1.part2.partn.body1 : placement of Obj relative to part1
270
def getObjPlcRelativeToPart(objName, part):
271
    obj = getObjectInPart(objName, part)
272

273
    # we need plc to be relative to the containing part
274
    obj_global_plc = getGlobalPlacement(obj, part)
275
    part_global_plc = getGlobalPlacement(part)
276

277
    return part_global_plc.inverse() * obj_global_plc
278

279

280
# Example : assembly.part1.part2.partn.body1 : jcsPlc is relative to body1
281
# This function returns jcsPlc relative to part1
282
def getJcsPlcRelativeToPart(jcsPlc, objName, part):
283
    obj_relative_plc = getObjPlcRelativeToPart(objName, part)
284
    return obj_relative_plc * jcsPlc
285

286

287
# Return the jcs global placement
288
def getJcsGlobalPlc(jcsPlc, objName, part):
289
    obj = getObjectInPart(objName, part)
290

291
    obj_global_plc = getGlobalPlacement(obj, part)
292
    return obj_global_plc * jcsPlc
293

294

295
# The container is used to support cases where the same object appears at several places
296
# which happens when you have a link to a part.
297
def getGlobalPlacement(targetObj, container=None):
298
    if targetObj is None:
299
        return App.Placement()
300

301
    inContainerBranch = container is None
302
    for rootObj in App.activeDocument().RootObjects:
303
        foundPlacement = getTargetPlacementRelativeTo(
304
            targetObj, rootObj, container, inContainerBranch
305
        )
306
        if foundPlacement is not None:
307
            return foundPlacement
308

309
    return App.Placement()
310

311

312
def isThereOneRootAssembly():
313
    for part in App.activeDocument().RootObjects:
314
        if part.TypeId == "Assembly::AssemblyObject":
315
            return True
316
    return False
317

318

319
def getTargetPlacementRelativeTo(
320
    targetObj, part, container, inContainerBranch, ignorePlacement=False
321
):
322
    inContainerBranch = inContainerBranch or (not ignorePlacement and part == container)
323

324
    if targetObj == part and inContainerBranch and not ignorePlacement:
325
        return targetObj.Placement
326

327
    if part.TypeId == "App::DocumentObjectGroup":
328
        for obj in part.OutList:
329
            foundPlacement = getTargetPlacementRelativeTo(
330
                targetObj, obj, container, inContainerBranch, ignorePlacement
331
            )
332
            if foundPlacement is not None:
333
                return foundPlacement
334

335
    elif part.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}:
336
        for obj in part.OutList:
337
            foundPlacement = getTargetPlacementRelativeTo(
338
                targetObj, obj, container, inContainerBranch
339
            )
340
            if foundPlacement is None:
341
                continue
342

343
            # If we were called from a link then we need to ignore this placement as we use the link placement instead.
344
            if not ignorePlacement:
345
                foundPlacement = part.Placement * foundPlacement
346

347
            return foundPlacement
348

349
    elif part.TypeId == "App::Link":
350
        linked_obj = part.getLinkedObject()
351
        if part == linked_obj or linked_obj is None:
352
            return None  # upon loading this can happen for external links.
353

354
        if linked_obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}:
355
            for obj in linked_obj.OutList:
356
                foundPlacement = getTargetPlacementRelativeTo(
357
                    targetObj, obj, container, inContainerBranch
358
                )
359
                if foundPlacement is None:
360
                    continue
361

362
                foundPlacement = part.Placement * foundPlacement
363
                return foundPlacement
364

365
        foundPlacement = getTargetPlacementRelativeTo(
366
            targetObj, linked_obj, container, inContainerBranch, True
367
        )
368

369
        if foundPlacement is not None and not ignorePlacement:
370
            foundPlacement = part.Placement * foundPlacement
371

372
        return foundPlacement
373

374
    return None
375

376

377
def getElementName(full_name):
378
    # full_name is "Assembly.Assembly1.Assembly2.Assembly3.Box.Edge16"
379
    # We want either Edge16.
380
    parts = full_name.split(".")
381

382
    if len(parts) < 3:
383
        # At minimum "Assembly.Box.edge16". It shouldn't be shorter
384
        return ""
385

386
    # case of PartDesign datums : CoordinateSystem, point, line, plane
387
    if parts[-1] in {"X", "Y", "Z", "Point", "Line", "Plane"}:
388
        return ""
389

390
    return parts[-1]
391

392

393
def getObjsNamesAndElement(obj_name, sub_name):
394
    # if obj_name = "Assembly" and sub_name = "Assembly1.Assembly2.Assembly3.Box.Edge16"
395
    # this will return ["Assembly","Assembly1","Assembly2","Assembly3","Box"] and "Edge16"
396

397
    parts = sub_name.split(".")
398

399
    # The last part is always the element name even if empty
400
    element_name = parts[-1]
401

402
    # The remaining parts are object names
403
    obj_names = parts[:-1]
404
    obj_names.insert(0, obj_name)
405

406
    return obj_names, element_name
407

408

409
def getFullObjName(obj_name, sub_name):
410
    # if obj_name = "Assembly" and sub_name = "Assembly1.Assembly2.Assembly3.Box.Edge16"
411
    # this will return "Assembly.Assembly1.Assembly2.Assembly3.Box"
412
    objs_names, element_name = getObjsNamesAndElement(obj_name, sub_name)
413
    return ".".join(objs_names)
414

415

416
def getFullElementName(obj_name, sub_name):
417
    # if obj_name = "Assembly" and sub_name = "Assembly1.Assembly2.Assembly3.Box.Edge16"
418
    # this will return "Assembly.Assembly1.Assembly2.Assembly3.Box.Edge16"
419
    return obj_name + "." + sub_name
420

421

422
def extract_type_and_number(element_name):
423
    element_type = ""
424
    element_number = ""
425

426
    for char in element_name:
427
        if char.isalpha():
428
            # If the character is a letter, it's part of the type
429
            element_type += char
430
        elif char.isdigit():
431
            # If the character is a digit, it's part of the number
432
            element_number += char
433
        else:
434
            break
435

436
    if element_type and element_number:
437
        element_number = int(element_number)
438
        return element_type, element_number
439
    else:
440
        return None, None
441

442

443
def findElementClosestVertex(selection_dict):
444
    obj = selection_dict["object"]
445

446
    mousePos = selection_dict["mouse_pos"]
447

448
    # We need mousePos to be relative to the part containing obj global placement
449
    if selection_dict["object"] != selection_dict["part"]:
450
        plc = App.Placement()
451
        plc.Base = mousePos
452
        global_plc = getGlobalPlacement(selection_dict["part"])
453
        plc = global_plc.inverse() * plc
454
        mousePos = plc.Base
455

456
    elt_type, elt_index = extract_type_and_number(selection_dict["element_name"])
457

458
    if elt_type == "Vertex":
459
        return selection_dict["element_name"]
460

461
    elif elt_type == "Edge":
462
        edge = obj.Shape.Edges[elt_index - 1]
463
        curve = edge.Curve
464
        if curve.TypeId == "Part::GeomCircle":
465
            # For centers, as they are not shape vertexes, we return the element name.
466
            # For now we only allow selecting the center of arcs / circles.
467
            return selection_dict["element_name"]
468

469
        edge_points = getPointsFromVertexes(edge.Vertexes)
470

471
        if curve.TypeId == "Part::GeomLine":
472
            # For lines we allow users to select the middle of lines as well.
473
            line_middle = (edge_points[0] + edge_points[1]) * 0.5
474
            edge_points.append(line_middle)
475

476
        closest_vertex_index, _ = findClosestPointToMousePos(edge_points, mousePos)
477

478
        if curve.TypeId == "Part::GeomLine" and closest_vertex_index == 2:
479
            # If line center is closest then we have no vertex name to set so we put element name
480
            return selection_dict["element_name"]
481

482
        vertex_name = findVertexNameInObject(edge.Vertexes[closest_vertex_index], obj)
483

484
        return vertex_name
485

486
    elif elt_type == "Face":
487
        face = obj.Shape.Faces[elt_index - 1]
488
        surface = face.Surface
489
        _type = surface.TypeId
490
        if _type == "Part::GeomSphere" or _type == "Part::GeomTorus":
491
            return selection_dict["element_name"]
492

493
        # Handle the circle/arc edges for their centers
494
        center_points = []
495
        center_points_edge_indexes = []
496
        edges = face.Edges
497

498
        for i, edge in enumerate(edges):
499
            curve = edge.Curve
500
            if curve.TypeId == "Part::GeomCircle" or curve.TypeId == "Part::GeomEllipse":
501
                center_points.append(curve.Location)
502
                center_points_edge_indexes.append(i)
503

504
            elif _type == "Part::GeomCylinder" and curve.TypeId == "Part::GeomBSplineCurve":
505
                # handle special case of 2 cylinder intersecting.
506
                for j, facej in enumerate(obj.Shape.Faces):
507
                    surfacej = facej.Surface
508
                    if (elt_index - 1) != j and surfacej.TypeId == "Part::GeomCylinder":
509
                        for edgej in facej.Edges:
510
                            if edgej.Curve.TypeId == "Part::GeomBSplineCurve":
511
                                if (
512
                                    edgej.CenterOfGravity == edge.CenterOfGravity
513
                                    and edgej.Length == edge.Length
514
                                ):
515
                                    center_points.append(edgej.CenterOfGravity)
516
                                    center_points_edge_indexes.append(i)
517

518
        if len(center_points) > 0:
519
            closest_center_index, closest_center_distance = findClosestPointToMousePos(
520
                center_points, mousePos
521
            )
522

523
        # Handle the face vertexes
524
        face_points = []
525

526
        if _type != "Part::GeomCylinder" and _type != "Part::GeomCone":
527
            face_points = getPointsFromVertexes(face.Vertexes)
528

529
        # We also allow users to select the center of gravity.
530
        if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
531
            centerOfG = face.CenterOfGravity - surface.Center
532
            centerPoint = surface.Center + centerOfG
533
            centerPoint = centerPoint + App.Vector().projectToLine(centerOfG, surface.Axis)
534
            face_points.append(centerPoint)
535
        else:
536
            face_points.append(face.CenterOfGravity)
537

538
        closest_vertex_index, closest_vertex_distance = findClosestPointToMousePos(
539
            face_points, mousePos
540
        )
541

542
        if len(center_points) > 0:
543
            if closest_center_distance < closest_vertex_distance:
544
                # Note the index here is the index within the face! Not the object.
545
                index = center_points_edge_indexes[closest_center_index] + 1
546
                return "Edge" + str(index)
547

548
        if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
549
            return selection_dict["element_name"]
550

551
        if closest_vertex_index == len(face.Vertexes):
552
            # If center of gravity then we have no vertex name to set so we put element name
553
            return selection_dict["element_name"]
554

555
        vertex_name = findVertexNameInObject(face.Vertexes[closest_vertex_index], obj)
556

557
        return vertex_name
558

559
    return ""
560

561

562
def getPointsFromVertexes(vertexes):
563
    points = []
564
    for vtx in vertexes:
565
        points.append(vtx.Point)
566
    return points
567

568

569
def findClosestPointToMousePos(candidates_points, mousePos):
570
    closest_point_index = None
571
    point_min_length = None
572

573
    for i, point in enumerate(candidates_points):
574
        length = (mousePos - point).Length
575
        if closest_point_index is None or length < point_min_length:
576
            closest_point_index = i
577
            point_min_length = length
578

579
    return closest_point_index, point_min_length
580

581

582
def findVertexNameInObject(vertex, obj):
583
    for i, vtx in enumerate(obj.Shape.Vertexes):
584
        if vtx.Point == vertex.Point:
585
            return "Vertex" + str(i + 1)
586
    return ""
587

588

589
def color_from_unsigned(c):
590
    return [
591
        float(int((c >> 24) & 0xFF) / 255),
592
        float(int((c >> 16) & 0xFF) / 255),
593
        float(int((c >> 8) & 0xFF) / 255),
594
    ]
595

596

597
def getJointGroup(assembly):
598
    joint_group = None
599

600
    for obj in assembly.OutList:
601
        if obj.TypeId == "Assembly::JointGroup":
602
            joint_group = obj
603
            break
604

605
    if not joint_group:
606
        joint_group = assembly.newObject("Assembly::JointGroup", "Joints")
607

608
    return joint_group
609

610

611
def isAssemblyGrounded():
612
    assembly = activeAssembly()
613
    if not assembly:
614
        return False
615

616
    jointGroup = getJointGroup(assembly)
617

618
    for joint in jointGroup.Group:
619
        if hasattr(joint, "ObjectToGround"):
620
            return True
621

622
    return False
623

624

625
def removeObjAndChilds(obj):
626
    removeObjsAndChilds([obj])
627

628

629
def removeObjsAndChilds(objs):
630
    def addsubobjs(obj, toremoveset):
631
        if obj.TypeId == "App::Origin":  # Origins are already handled
632
            return
633

634
        toremoveset.add(obj)
635
        if obj.TypeId != "App::Link":
636
            for subobj in obj.OutList:
637
                addsubobjs(subobj, toremoveset)
638

639
    toremove = set()
640
    for obj in objs:
641
        addsubobjs(obj, toremove)
642

643
    for obj in toremove:
644
        if obj:
645
            obj.Document.removeObject(obj.Name)
646

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.