FreeCAD-macros
186 строк · 7.2 Кб
1"""Get the global placement of selected objects, respecting links."""
2# Usage: select some objects or some subelements, run and get the result in the
3# Report View.
4
5__Name__ = 'Get Global Placement'
6__Comment__ = 'Get the global placement, respecting links, link arrays and link scale'
7__Author__ = 'galou_breizh, Jolbas'
8__Version__ = '1.1.2'
9__Date__ = '2023-02-27'
10__License__ = 'LGPL-2.0-or-later'
11__Web__ = '' #'http://forum.freecadweb.org/viewtopic.php?f=?&t=????'
12__Wiki__ = '' #'http://www.freecadweb.org/wiki/Macro_Title_Of_macro'
13__Icon__ = ''
14__Help__ = 'Select some objects or some subelements and run'
15__Status__ = ''
16__Requires__ = 'FreeCAD >=0.21'
17__Communication__ = 'https://github.com/FreeCAD/FreeCAD-macros/issues/'
18__Files__ = 'GetGlobalPlacement.svg'
19
20from math import copysign, hypot
21from typing import Set, Tuple
22
23import FreeCAD as app
24import FreeCADGui as gui
25
26
27def strip_subelement(sub_fullpath: str) -> str:
28"""Return sub_fullpath without the last sub-element.
29
30A sub-element is a face, edge or vertex.
31
32Parameters
33----------
34- subobject_fullpath: SelectionObject.SubElementNames[i], where
35SelectionObject is obtained with gui.Selection.getSelectionEx('', 0)
36(i.e. not gui.Selection.getSelectionEx()).
37Examples:
38- 'Face6' if you select the top face of a cube solid made in Part.
39- 'Body.Box001.' if you select the tip of a Part->Body->"additive
40primitve" in PartDesign.
41- 'Body.Box001.Face6' if you select the top face of a Part->Body->
42"additive primitve" in PartDesign.
43
44"""
45return sub_fullpath.rpartition('.')[0]
46
47
48def get_global_placement_and_scale(
49object: app.DocumentObject,
50subobject_fullpath: str,
51) -> Tuple[app.Placement, app.Vector]:
52"""Return the global placement and the total scale, respecting links.
53Returns the placement and scale the objects content is related to,
54which means the properties LinkTransform and Scale is respected if
55path points to a link.
56
57This is in contrast with ``object.getGlobalPlacement()`` that returns
58the placement of the original object, not the linked one.
59
60Parameters
61----------
62- root_object: SelectionObject.Object, where SelectionObject is obtained
63with gui.Selection.getSelectionEx('', 0)
64(i.e. not gui.Selection.getSelectionEx()).
65- subobject_fullpath: SelectionObject.SubElementNames[i].
66Examples:
67- 'Face6' if you select the top face of a cube solid made in Part.
68- 'Body.Box001.' if you select the tip of a Part->Body->"additive
69primitve" in PartDesign.
70- 'Body.Box001.Face6' if you select the top face of a Part->Body->
71"additive primitve" in PartDesign.
72"""
73return_type_link_matrix = 6 # Cf. DocumentObjectPyImp.cpp::getSubObject (l.417).
74matrix = object.getSubObject(subobject_fullpath, return_type_link_matrix,
75transform=True)
76if matrix is None:
77return
78scale_type = matrix.hasScale(1e-5)
79if scale_type == app.ScaleType.NoScaling:
80return app.Placement(matrix), app.Vector(1.0, 1.0, 1.0)
81if scale_type != app.ScaleType.Uniform:
82app.Console.PrintWarning('Non-uniform scaling not supported\n')
83return
84app.Console.PrintWarning('Uniform scaling may give wrong results, use with care\n')
85# Find scale.
86# Works only if uniform?
87s_gen = (copysign(hypot(*matrix.col(i)), matrix.col(i)[i])
88for i in range(3))
89scale_vec = app.Vector(*s_gen)
90# Workaround for scale affecting rotation
91# see https://forum.freecad.org/viewtopic.php?t=75448
92# Remove the scale from the rotation.
93position = matrix.col(3)
94matrix.setCol(3, app.Vector())
95matrix.scale(*(1/s for s in scale_vec))
96matrix.setCol(3, position)
97return app.Placement(matrix), scale_vec
98
99
100def get_global_placement(
101object: app.DocumentObject,
102subobject_fullpath: str,
103) -> app.Placement:
104"""Return the global placement respecting links.
105Returns the placement the objects content is related to, which means
106the properties LinkTransform is respected if path points to a link.
107
108This is in contrast with ``object.getGlobalPlacement()`` that returns
109the placement of the original object, not the linked one.
110
111Parameters
112----------
113- root_object: SelectionObject.Object, where SelectionObject is obtained
114with gui.Selection.getSelectionEx('', 0)
115(i.e. not gui.Selection.getSelectionEx()).
116- subobject_fullpath: SelectionObject.SubElementNames[i].
117Examples:
118- 'Face6' if you select the top face of a cube solid made in Part.
119- 'Body.Box001.' if you select the tip of a Part->Body->"additive
120primitve" in PartDesign.
121- 'Body.Box001.Face6' if you select the top face of a Part->Body->
122"additive primitve" in PartDesign.
123"""
124p_and_s = get_global_placement_and_scale(object, subobject_fullpath)
125if p_and_s is None:
126return
127return p_and_s[0]
128
129
130def print_placement(object_name: str,
131sub_fullpath: str,
132placement: app.Placement,
133) -> None:
134"""Pretty-print a placement in the console."""
135dot = '.' if sub_fullpath else ''
136if placement:
137app.Console.PrintMessage(
138f'{object_name}{dot}{sub_fullpath}:'
139+ (' {b.x:.3f}, {b.y:.3f}, {b.z:.3f};'
140' {q[0]:.4f}, {q[1]:.4f}, {q[2]:.4f}, {q[3]:.4f};'
141' App.Placement(App.Vector({b.x:.3f}, {b.y:.3f}, {b.z:.3f}),'
142' App.Rotation({q[0]:.5f}, {q[1]:.5f}, {q[2]:.5f}, {q[3]:.5f}));'
143).format(
144b=placement.Base,
145q=placement.Rotation.Q)
146+ ' (rpy: {r[2]:.2f}, {r[1]:.2f}, {r[0]:.2f}) deg'.format(
147r=placement.Rotation.getYawPitchRoll())
148+ '\n')
149else:
150app.Console.PrintMessage(
151f'{object_name}{dot}{sub_fullpath}:'
152+ 'Couldn\'t find the placement\n')
153
154
155def get_and_print_selected_placements():
156"""Get selected objects and print their placement."""
157doc = app.activeDocument()
158if doc is None:
159return
160
161selection = gui.Selection.getSelectionEx('', 0)
162if not selection:
163app.Console.PrintWarning('Nothing selected, nothing to do\n')
164return
165
166shown_paths: Set[str] = set()
167for selection_object in selection:
168object_ = selection_object.Object
169sub_fullpaths = selection_object.SubElementNames
170if not sub_fullpaths:
171# An object is selected, not a face, edge, vertex.
172placement = get_global_placement(object_, '')
173print_placement(object_.Name, '', placement)
174continue
175for sub_fullpath in sub_fullpaths:
176# One or more subelements are selected.
177wo_subelement = strip_subelement(sub_fullpath)
178path = object_.Name + wo_subelement
179if not path in shown_paths:
180shown_paths.add(path)
181placement = get_global_placement(object_, sub_fullpath)
182print_placement(object_.Name, wo_subelement, placement)
183
184
185if __name__ == '__main__':
186get_and_print_selected_placements()
187