Skip to content

Commit 58f4cbf

Browse files
Additions to cq.Face (#1752)
* Add Face paramAt * Fix method definition * Some tests for the new methods * Add params and positions * Add tests * Add closest * Add test * Adjust API for consistency * Fix typo Co-authored-by: Jeremy Wright <[email protected]> * Docstring fix --------- Co-authored-by: adam-urbanczyk <[email protected]> Co-authored-by: Jeremy Wright <[email protected]>
1 parent f13f607 commit 58f4cbf

File tree

4 files changed

+173
-5
lines changed

4 files changed

+173
-5
lines changed

cadquery/func.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@
4444
sweep,
4545
loft,
4646
check,
47+
closest,
4748
)

cadquery/occ_impl/shapes.py

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,11 @@
243243

244244
from OCP.TopAbs import TopAbs_ShapeEnum, TopAbs_Orientation
245245

246-
from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds, ShapeAnalysis_Wire
246+
from OCP.ShapeAnalysis import (
247+
ShapeAnalysis_FreeBounds,
248+
ShapeAnalysis_Wire,
249+
ShapeAnalysis_Surface,
250+
)
247251
from OCP.TopTools import TopTools_HSequenceOfShape
248252

249253
from OCP.GCPnts import (
@@ -2829,6 +2833,90 @@ def _uvBounds(self) -> Tuple[float, float, float, float]:
28292833

28302834
return BRepTools.UVBounds_s(self.wrapped)
28312835

2836+
def paramAt(self, pt: VectorLike) -> Tuple[float, float]:
2837+
"""
2838+
Computes the (u,v) pair closest to a given vector.
2839+
2840+
:returns: (u, v) tuple
2841+
:param pt: the location to compute the normal at.
2842+
:type pt: a vector that lies on or close to the surface.
2843+
"""
2844+
# get the geometry
2845+
surface = self._geomAdaptor()
2846+
2847+
# project point on surface
2848+
projector = GeomAPI_ProjectPointOnSurf(Vector(pt).toPnt(), surface)
2849+
2850+
u, v = projector.LowerDistanceParameters()
2851+
2852+
return u, v
2853+
2854+
def params(
2855+
self, pts: Iterable[VectorLike], tol: float = 1e-9
2856+
) -> Tuple[List[float], List[float]]:
2857+
"""
2858+
Computes (u,v) pairs closest to given vectors.
2859+
2860+
:returns: list of (u, v) tuples
2861+
:param pts: the points to compute the normals at.
2862+
:type pts: a list of vectors that lie on the surface.
2863+
"""
2864+
2865+
us = []
2866+
vs = []
2867+
2868+
# get the geometry
2869+
surface = self._geomAdaptor()
2870+
2871+
# construct the projector
2872+
projector = ShapeAnalysis_Surface(surface)
2873+
2874+
# get the first point
2875+
it = iter(pts)
2876+
pt = next(it)
2877+
2878+
uv = projector.ValueOfUV(Vector(pt).toPnt(), tol)
2879+
us.append(uv.X())
2880+
vs.append(uv.Y())
2881+
2882+
for pt in it:
2883+
uv = projector.NextValueOfUV(uv, Vector(pt).toPnt(), tol)
2884+
us.append(uv.X())
2885+
vs.append(uv.Y())
2886+
2887+
return us, vs
2888+
2889+
def positionAt(self, u: Real, v: Real) -> Vector:
2890+
"""
2891+
Computes the position vector at the desired location in the u,v parameter space.
2892+
2893+
:returns: a vector representing the position
2894+
:param u: the u parametric location to compute the normal at.
2895+
:param v: the v parametric location to compute the normal at.
2896+
"""
2897+
p = gp_Pnt()
2898+
vn = gp_Vec()
2899+
BRepGProp_Face(self.wrapped).Normal(u, v, p, vn)
2900+
2901+
return Vector(p)
2902+
2903+
def positions(self, uvs: Iterable[Tuple[Real, Real]]) -> List[Vector]:
2904+
"""
2905+
Computes position vectors at the desired locations in the u,v parameter space.
2906+
2907+
:returns: list of vectors corresponding to the requested u,v positions
2908+
:param uvs: iterable of u,v pairs.
2909+
"""
2910+
p = gp_Pnt()
2911+
vn = gp_Vec()
2912+
rv = []
2913+
2914+
for u, v in uvs:
2915+
BRepGProp_Face(self.wrapped).Normal(u, v, p, vn)
2916+
rv.append(Vector(p))
2917+
2918+
return rv
2919+
28322920
@multimethod
28332921
def normalAt(self, locationVector: Optional[VectorLike] = None) -> Vector:
28342922
"""
@@ -5805,3 +5893,15 @@ def check(s: Shape, results: Optional[List[Tuple[List[Shape], Any]]] = None) ->
58055893
)
58065894

58075895
return rv
5896+
5897+
5898+
#%% properties
5899+
5900+
5901+
def closest(s1: Shape, s2: Shape) -> Tuple[Vector, Vector]:
5902+
"""
5903+
Closest points between two shapes.
5904+
"""
5905+
ext = BRepExtrema_DistShapeShape(s1.wrapped, s2.wrapped)
5906+
5907+
return Vector(ext.PointOnShape1(1)), Vector(ext.PointOnShape2(1))

tests/test_free_functions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
_adaptor_curve_to_edge,
4545
check,
4646
Vector,
47+
closest,
4748
)
4849

4950
from OCP.BOPAlgo import BOPAlgo_CheckStatus
@@ -697,3 +698,14 @@ def test_check():
697698

698699
assert not check(s2, res)
699700
assert res[0][1] == BOPAlgo_CheckStatus.BOPAlgo_SelfIntersect
701+
702+
703+
# %% properties
704+
def test_closest():
705+
706+
s1 = plane(1, 1)
707+
s2 = plane(1, 1).moved(x=5, y=0.5)
708+
709+
p1, p2 = closest(s1, s2)
710+
711+
assert (p1 - p2).Length == approx(4)

tests/test_shapes.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
from cadquery.occ_impl.shapes import (
2+
Vector,
3+
Shape,
4+
Solid,
25
wire,
36
segment,
47
polyline,
5-
Vector,
68
box,
7-
Solid,
89
compound,
910
circle,
1011
plane,
1112
torus,
12-
Shape,
1313
cylinder,
1414
ellipse,
15+
closest,
1516
)
1617

1718
from pytest import approx, raises
1819

1920
from math import pi
2021

2122

22-
def test_paramAt():
23+
def test_edge_paramAt():
2324

2425
# paramAt for a segment
2526
e = segment((0, 0), (0, 1))
@@ -53,6 +54,60 @@ def test_paramAt():
5354
assert p8 == approx(w2.paramAt(0.1 / 2))
5455

5556

57+
def test_face_paramAt():
58+
59+
f = plane(1, 1)
60+
61+
u, v = f.paramAt((0.5, 0))
62+
63+
assert u == approx(0.5)
64+
assert v == approx(0.0)
65+
66+
67+
def test_face_params():
68+
69+
f = plane(1, 1)
70+
71+
us, vs = f.params([(0.49, 0.0), (0.5, 0)])
72+
73+
u1, u2 = us
74+
v1, v2 = vs
75+
76+
assert u1 == approx(0.49)
77+
assert v1 == approx(0.0)
78+
79+
assert u2 == approx(0.5)
80+
assert v2 == approx(0.0)
81+
82+
83+
def test_face_positionAt():
84+
85+
f = plane(1, 1)
86+
87+
p = f.positionAt(0.5, 0.5)
88+
89+
assert p.x == approx(0.5)
90+
assert p.y == approx(0.5)
91+
assert p.z == approx(0)
92+
93+
94+
def test_face_positions():
95+
96+
f = plane(1, 1)
97+
98+
ps = f.positions([(0, 0), (0.5, 0.5)])
99+
100+
p1, p2 = ps
101+
102+
assert p1.x == approx(0)
103+
assert p1.y == approx(0)
104+
assert p1.z == approx(0)
105+
106+
assert p2.x == approx(0.5)
107+
assert p2.y == approx(0.5)
108+
assert p2.z == approx(0)
109+
110+
56111
def test_isSolid():
57112

58113
s = box(1, 1, 1)

0 commit comments

Comments
 (0)