Skip to content

Commit

Permalink
Face.normal_at takes point or u,v Issue #410
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Mar 23, 2024
1 parent d82017b commit 78a30a7
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 2 deletions.
58 changes: 56 additions & 2 deletions src/build123d/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -5337,7 +5337,37 @@ def offset(self, amount: float) -> Face:
"""Return a copy of self moved along the normal by amount"""
return copy.deepcopy(self).moved(Location(self.normal_at() * amount))

@overload
def normal_at(self, surface_point: VectorLike = None) -> Vector:
"""normal_at point on surface
Args:
surface_point (VectorLike, optional): a point that lies on the surface where
the normal. Defaults to the center (None).
Returns:
Vector: surface normal direction
"""

@overload
def normal_at(self, u: float = None, v: float = None) -> Vector:
"""normal_at u, v values on Face
Args:
u (float, optional): the horizontal coordinate in the parameter space of the Face,
between 0.0 and 1.0
v (float, optional): the vertical coordinate in the parameter space of the Face,
between 0.0 and 1.0
Defaults to the center (None/None)
Raises:
ValueError: Either neither or both u v values must be provided
Returns:
Vector: surface normal direction
"""

def normal_at(self, *args, **kwargs) -> Vector:
"""normal_at
Computes the normal vector at the desired location on the face.
Expand All @@ -5349,13 +5379,37 @@ def normal_at(self, surface_point: VectorLike = None) -> Vector:
Returns:
Vector: surface normal direction
"""
surface_point, u, v = (None,) * 3

if args:
if isinstance(args[0], Iterable):
surface_point = args[0]
elif isinstance(args[0], (int, float)):
u = args[0]
if len(args) == 2 and isinstance(args[1], (int, float)):
v = args[1]

unknown_args = ", ".join(
set(kwargs.keys()).difference(["surface_point", "u", "v"])
)
if unknown_args:
raise ValueError(f"Unexpected argument(s) {unknown_args}")

surface_point = kwargs.get("surface_point", surface_point)
u = kwargs.get("u", u)
v = kwargs.get("v", v)
if surface_point is None and u is None and v is None:
u, v = 0.5, 0.5
elif surface_point is None and sum(i is None for i in [u, v]) == 1:
raise ValueError("Both u & v values must be specified")

# get the geometry
surface = self._geom_adaptor()

if surface_point is None:
u_val0, u_val1, v_val0, v_val1 = self._uv_bounds()
u_val = 0.5 * (u_val0 + u_val1)
v_val = 0.5 * (v_val0 + v_val1)
u_val = u * (u_val0 + u_val1)
v_val = v * (v_val0 + v_val1)
else:
# project point on surface
projector = GeomAPI_ProjectPointOnSurf(
Expand Down
13 changes: 13 additions & 0 deletions tests/test_direct_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,19 @@ def test_constructor(self):
with self.assertRaises(ValueError):
Face(bob="fred")

def test_normal_at(self):
face = Face.make_rect(1, 1)
self.assertVectorAlmostEquals(face.normal_at(0, 0), (0, 0, 1), 5)
self.assertVectorAlmostEquals(
face.normal_at(face.position_at(0, 0)), (0, 0, 1), 5
)
with self.assertRaises(ValueError):
face.normal_at(0)
with self.assertRaises(ValueError):
face.normal_at(center=(0, 0))
face = Cylinder(1, 1).faces().filter_by(GeomType.CYLINDER)[0]
self.assertVectorAlmostEquals(face.normal_at(0, 1), (1, 0, 0), 5)


class TestFunctions(unittest.TestCase):
def test_edges_to_wires(self):
Expand Down

0 comments on commit 78a30a7

Please sign in to comment.