FreeCAD-macros

Форк
0
/
DatumPlaneLocalAxis.FCMacro 
236 строк · 8.5 Кб
1
# -*- coding: utf-8 -*-
2
# view loacal axis of selected datum plane macro
3
# Author: Avinash Pudale
4
# License: LGPL v 2.1
5

6
from __future__ import annotations
7

8
__Name__ = 'Datum-Plane Local Axis'
9
__Comment__ = 'Select a datum plane in the 3D view then run, this macro will shows local axes for all selected planes or delete all created local axes if nothing is selected'
10
__Author__ = 'Avinash Pudale'
11
__Date__ = '2023-08-11'
12
__Version__ = '0.3.0'
13
__License__ = 'LGPL-2.1'
14
__Web__ = 'https://forum.freecad.org/viewtopic.php?t=79562'
15
__Wiki__ = ''
16
__Icon__ = 'DatumPlaneLocalAxis.svg'
17
__Xpm__ = ''
18
__Help__ = 'The macro will add small X-, Y-, and Z-axis representations on the selected datum planes in the 3D view. The X-axis is represented in red, the Y-axis in green, and the Z-axis in blue. To clear the axis representations, simply run the macro again without selecting anything.'
19
__Status__ = 'Stable'
20
__Requires__ = 'FreeCAD >=0.20'
21
__Communication__ = 'https://github.com/FabLabBaramati/freecadDatumLoacalAxisMacro/issues'
22
__Files__ = 'DatumPlaneLocalAxis.svg'
23

24

25
import FreeCAD as app
26
import FreeCADGui as gui
27

28
import Draft
29

30
# Typing hints.
31
DO = app.DocumentObject
32
PL = DO  # A `PartDesign::Plane` or a `Part::Link` to a `PartDesign::Plane`.
33

34
# The 3 axes representing a plane will be inside a group that
35
# starts with this label.
36
GROUP_LABEL = 'Datum_Plane_Axis'
37

38

39
def strip_subelement(sub_fullpath: str) -> str:
40
    """Return sub_fullpath without the last sub-element.
41

42
    A sub-element is a face, edge or vertex.
43
    Parameters
44
    ----------
45
    - subobject_fullpath: SelectionObject.SubElementNames[i], where
46
        SelectionObject is obtained with gui.Selection.getSelectionEx('', 0)
47
        (i.e. not gui.Selection.getSelectionEx()).
48
        Examples:
49
        - 'Face6' if you select the top face of a cube solid made in Part.
50
        - 'Body.Box001.' if you select the tip of a Part->Body->"additive
51
            primitve" in PartDesign.
52
        - 'Body.Box001.Face6' if you select the top face of a Part->Body->
53
            "additive primitve" in PartDesign.
54

55
    """
56
    if (not sub_fullpath) or ('.' not in sub_fullpath):
57
        return ''
58
    return sub_fullpath.rsplit('.', maxsplit=1)[0]
59

60

61
def get_subobject_by_name(object_: app.DocumentObject,
62
                          subobject_name: str,
63
                          ) -> app.DocumentObject:
64
    """Return the appropriate object from object_.OutListRecursive."""
65
    for o in object_.OutListRecursive:
66
        if o.Name == subobject_name:
67
            return o
68

69

70
def get_subobjects_by_full_name(
71
        root_object: app.DocumentObject,
72
        subobject_fullpath: str,
73
        ) -> list[app.DocumentObject]:
74
    """Return the list of objects from root_object excluded to the named object.
75

76
    The last part of ``subobject_fullpath`` is then a specific vertex, edge or
77
    face and is ignored.
78
    So, subobject_fullpath has the form 'name0.name1.Edge001', for example; in
79
    this case, the returned objects are
80
    [object_named_name0, object_named_name1].
81

82
    Parameters
83
    ----------
84
    - root_object: SelectionObject.Object, where SelectionObject is obtained
85
        with gui.Selection.getSelectionEx('', 0)
86
        (i.e. not gui.Selection.getSelectionEx()).
87
    - subobject_fullpath: SelectionObject.SubElementNames[i].
88
        Examples:
89
        - 'Face6' if you select the top face of a cube solid made in Part.
90
        - 'Body.Box001.' if you select the tip of a Part->Body->"additive
91
            primitve" in PartDesign.
92
        - 'Body.Box001.Face6' if you select the top face of a Part->Body->
93
            "additive primitve" in PartDesign.
94

95
    """
96
    objects = []
97
    names = strip_subelement(subobject_fullpath).split('.')
98
    subobject = root_object
99
    for name in names:
100
        subobject = get_subobject_by_name(subobject, name)
101
        if subobject is None:
102
            # This should not append.
103
            return []
104
        objects.append(subobject)
105
    return objects
106

107

108
def get_global_placement_expression(
109
        root_object: app.DocumentObject,
110
        subobject_fullpath: str,
111
        ) -> str:
112
    """Return the global placement by recursively going through parents.
113

114
    Return the expression to compute the global placement of `root_object`.
115

116
    Parameters
117
    ----------
118
    - root_object: SelectionObject.Object, where SelectionObject is obtained
119
        with gui.Selection.getSelectionEx('', 0)
120
        (i.e. not gui.Selection.getSelectionEx()).
121
    - subobject_fullpath: SelectionObject.SubElementNames[i].
122
        Examples:
123
        - 'Face6' if you select the top face of a cube solid made in Part.
124
        - 'Body.Box001.' if you select the tip of a Part->Body->"additive
125
            primitve" in PartDesign.
126
        - 'Body.Box001.Face6' if you select the top face of a Part->Body->
127
            "additive primitve" in PartDesign.
128

129
    """
130
    objects = get_subobjects_by_full_name(root_object, subobject_fullpath)
131
    expr = f'{root_object.Name}.Placement'
132
    for o in objects:
133
        expr += f' * {o.Name}.Placement'
134
    return expr
135

136

137
def _add_local_axis_to_plane(
138
        plane_or_link: app.DocumentObject,
139
        placement_expression: str,
140
        ) -> None:
141
    """Add the local axis to the plane or link."""
142
    doc = plane_or_link.Document
143
    # Implementation note: returns self for non-links.
144
    plane = plane_or_link.getLinkedObject()
145

146
    # Create a group to contain the axis representations.
147
    group = doc.addObject('App::DocumentObjectGroup',
148
                          f'{GROUP_LABEL}_{plane_or_link.Name}')
149

150
    # Size of the line representing an axis.
151
    size = 0.95 * min(plane.Length, plane.Width) / 2.0
152

153
    labels_points_colors = (
154
        ('x', app.Vector(size, 0.0, 0.0), (1.0, 0.0, 0.0)),  # Red.
155
        ('y', app.Vector(0.0, size, 0.0), (0.0, 1.0, 0.0)),  # Green.
156
        ('z', app.Vector(0.0, 0.0, size), (0.0, 0.0, 1.0)),  # Blue.
157
        )
158

159
    for label, point, color in labels_points_colors:
160
        # Create Draft objects for the axis.
161
        axis = Draft.makeLine(app.Vector(0.0, 0.0, 0.0), point)
162
        axis.Label = label
163

164
        # Set the placements for the axis representations
165
        axis.setExpression('Placement', placement_expression)
166
        axis.setPropertyStatus('Placement', ['ReadOnly'])
167

168
        axis.ViewObject.DisplayMode = 'Wireframe'
169

170
        axis.ViewObject.LineColor = color
171
        axis.ViewObject.PointColor = color
172

173
        # Add the axis representations as child objects to the group
174
        group.addObject(axis)
175

176

177
def add_local_axis():
178
    doc = app.activeDocument()
179

180
    if not doc:
181
        return
182

183
    # Check if a datum plane is selected in the tree view.
184
    selection = gui.Selection.getSelectionEx('', 0)
185

186
    if not selection:
187
        # Delete all axis representations if no object is selected.
188
        objs_to_delete: list[DO] = []
189
        for obj in doc.Objects:
190
            if (obj.isDerivedFrom('App::DocumentObjectGroup')
191
                    and obj.Label.startswith(GROUP_LABEL)):
192
                for child_obj in obj.Group:
193
                    objs_to_delete.append(child_obj)
194
                objs_to_delete.append(obj)
195
        # Delete the groups and lines.
196
        for obj in objs_to_delete:
197
            doc.removeObject(obj.Name)
198
        doc.recompute()
199
        return
200

201
    # The list of selected planes (or links to a plane).
202
    planes_or_links: set[PL] = set()
203
    # For each plane, the expressions to place the axis representations.
204
    placement_expression: dict[PL] = {}
205
    for sel_obj in selection:
206
        obj = sel_obj.Object
207
        sub_fullpaths = sel_obj.SubElementNames
208
        if not sub_fullpaths:
209
            # Cannot be a datum plane because the plane itself must be
210
            # the last element of `sub_fullpaths`.
211
            continue
212
        sub_fullpath = sub_fullpaths[0]
213
        objects = get_subobjects_by_full_name(obj, sub_fullpath)
214
        if ((not objects)
215
                or (not hasattr(objects[-1], 'isDerivedFrom'))
216
                or (not hasattr(objects[-1], 'getLinkedObject'))):
217
            # Cannot be a datum plane.
218
            continue
219
        plane_or_link = objects[-1]
220
        # Implementation note: returns self for non-links.
221
        plane = plane_or_link.getLinkedObject()
222
        if not plane.isDerivedFrom('PartDesign::Plane'):
223
            continue
224
        planes_or_links.add(plane_or_link)
225
        placement_expression[plane_or_link] = get_global_placement_expression(
226
                obj, sub_fullpath)
227

228
    for plane_or_link in planes_or_links:
229
        _add_local_axis_to_plane(plane_or_link,
230
                                 placement_expression[plane_or_link])
231

232
    doc.recompute()
233

234

235
if __name__ == '__main__':
236
    add_local_axis()
237

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

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

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

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