Skip to content

Commit c996b98

Browse files
committed
Type hints and docstrings
1 parent b72be82 commit c996b98

File tree

2 files changed

+90
-13
lines changed

2 files changed

+90
-13
lines changed

plugins/apply_to_each_face/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@ both of which are callbacks
5151
- `WORLD_AXIS_PLANES_YZ_ZX_XY`
5252
- `WORLD_AXIS_PLANES_ZX_XY_YZ`
5353
- `WORLD_AXIS_PLANES_ZX_YZ_XY`
54-
2. `XAxisClosestTo` - a callable that chooses x axis perpendicular
55-
to face normal at face center as close as possible to one
56-
of user-specified unit vectors (usually world coordinate
57-
system axis unit vectors).
58-
Vectors are checked in the order they are provided and the first one
59-
that is not collinear with face normal is used.
54+
2. `XAxisClosestTo` - a callable that chooses x axis
55+
perpendicular to face normal at face center as
56+
close as possible to one of user-specified unit
57+
vectors (usually world coordinate system axis unit
58+
vectors).
59+
Vectors are checked in the order they are provided
60+
and the first one that is not collinear with face
61+
normal is used.
6062
The plugin provides the following vector lists
6163
- `WORLD_AXIS_UNIT_VECTORS_XYZ`
6264
- `WORLD_AXIS_UNIT_VECTORS_XZY`

plugins/apply_to_each_face/apply_to_each_face.py

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
1+
from typing import Callable, List, TypeVar
2+
13
import cadquery as cq
24

35

4-
def applyToEachFace(wp, f_workplane_selector, f_draw):
6+
def applyToEachFace(
7+
wp: cq.Workplane,
8+
f_workplane_selector: Callable[[cq.Face], cq.Workplane],
9+
f_draw: Callable[[cq.Workplane, cq.Face], cq.Workplane],
10+
) -> cq.Workplane:
11+
"""
12+
Basically equivalent to `Workplane.each(..)` but
13+
applicable only to faces and with tasks of face coordinate
14+
system selection and actually drawing in this coordinate
15+
system separated.
16+
17+
:param wp: Workplane with some faces selected
18+
:param f_workplane_selector: callback that accepts
19+
a face and returns a Workplane (a coordinate system to
20+
that is passed to `f_draw`). See `XAxisInPlane`
21+
and `XAxisClosestTo`
22+
:param f_draw: a callback that accepts a workplane and
23+
a face and draws something in that workplane
24+
"""
25+
526
def each_callback(face):
627
wp_face = f_workplane_selector(face)
728

@@ -47,16 +68,36 @@ def each_callback(face):
4768
WORLD_AXIS_PLANES_ZX_YZ_XY = [WORLD_ZX_NORMAL, WORLD_YZ_NORMAL, WORLD_XY_NORMAL]
4869

4970

50-
def _create_workplane(v_center, v_xaxis, v_zaxis):
71+
def _create_workplane(
72+
v_center: cq.Vector, v_xaxis: cq.Vector, v_zaxis: cq.Vector
73+
) -> cq.Workplane:
5174
return cq.Workplane(cq.Plane(v_center, v_xaxis, v_zaxis), origin=v_center)
5275

5376

5477
class XAxisInPlane:
55-
def __init__(self, plane_normals, tolerance=1e-3):
78+
"""
79+
Selects face center of origin at face center
80+
(`Face.Center()`) and face normal at face
81+
center as Z axis.
82+
83+
Selects X axis as intersection of face plane and one of
84+
user provided planes (specified by their unit normal vectors).
85+
86+
The first one of the list that is not too close to
87+
being parallel to face plane is chosen.
88+
89+
User-provided plane normals do not
90+
have to be linearly independent but their span
91+
(linear hull) should be all 3D vector space
92+
for XAxisInPlane to work on arbitrary faces.
93+
In some cases this requirement can be relaxed.
94+
"""
95+
96+
def __init__(self, plane_normals: List[cq.Vector], tolerance: float = 1e-3):
5697
self.__plane_normals = [x.normalized() for x in plane_normals]
5798
self.__tolerance = tolerance
5899

59-
def __call__(self, face):
100+
def __call__(self, face: cq.Face) -> cq.Workplane:
60101
v_zaxis = face.normalAt()
61102

62103
selected_plane_normal = None
@@ -69,20 +110,49 @@ def __call__(self, face):
69110
raise ValueError(
70111
"All plane normals are too close to face normal %s" % v_zaxis
71112
)
113+
72114
v_xaxis = selected_plane_normal.cross(v_zaxis)
73115

74116
return _create_workplane(face.Center(), v_xaxis, v_zaxis)
75117

76118

119+
T = TypeVar("T")
120+
121+
77122
class XAxisClosestTo:
78-
def __init__(self, candidate_vectors, tolerance=1e-3):
123+
"""
124+
Selects face center of origin at face center
125+
(`Face.Center()`) and face normal at face
126+
center as Z axis.
127+
128+
Selects one of provided vectors with the smallest
129+
projection on face normal and choses normalized
130+
projection of that vector onto face plane as X axis.
131+
132+
If two or more vectors have the same and smallest
133+
projection on face normal the one that comes first
134+
in provided list is chosen.
135+
136+
User-provided vectors do not
137+
have to be linearly independent but their span
138+
(linear hull) should be all 3D vector space
139+
for XAxisInPlane to work on arbitrary faces.
140+
In some cases this requirement can be relaxed.
141+
"""
142+
143+
def __init__(self, candidate_vectors: List[cq.Vector], tolerance: float = 1e-3):
79144

80145
self.__tolerance = tolerance
81146
self.__weighted_candidate_vectors = [
82147
(i, x.normalized()) for i, x in enumerate(candidate_vectors)
83148
]
84149

85-
def __get_best_candidate(self, objectlist, key_selector, cluster_sort_key):
150+
def __get_best_candidate(
151+
self,
152+
objectlist: List[T],
153+
key_selector: Callable[[T], float],
154+
cluster_sort_key: Callable[[T], float],
155+
):
86156
# idea borrowed from
87157
# https://github.com/CadQuery/cadquery/blob/a71a93ea274089ddbd48dbbd84d84710fc82a432/cadquery/selectors.py#L343
88158
key_and_obj = []
@@ -101,15 +171,20 @@ def __get_best_candidate(self, objectlist, key_selector, cluster_sort_key):
101171

102172
return first_cluster[0]
103173

104-
def __call__(self, face):
174+
def __call__(self, face: cq.Face) -> cq.Workplane:
105175
v_zaxis = face.normalAt()
106176

177+
# Choosing user-specified vector with minimum
178+
# face normal projection. If multiple vectors
179+
# have the same projection, the one that
180+
# comes first in the list is chosen
107181
best_xax_candidate = self.__get_best_candidate(
108182
self.__weighted_candidate_vectors,
109183
lambda x: abs(x[1].dot(v_zaxis)),
110184
lambda x: x[0],
111185
)[1]
112186

187+
# projecting onto face plane and normalizing
113188
v_xaxis = (
114189
best_xax_candidate - v_zaxis.multiply(best_xax_candidate.dot(v_zaxis))
115190
).normalized()

0 commit comments

Comments
 (0)