Skip to content

Commit

Permalink
Improving test coverage of three_d.py to 97%
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Jan 1, 2025
1 parent ae15a95 commit 1611ca8
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 23 deletions.
45 changes: 22 additions & 23 deletions src/build123d/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ def topods_dim(topods: TopoDS_Shape) -> int | None:
sub_dims = {topods_dim(s) for s in get_top_level_topods_shapes(topods)}
return sub_dims.pop() if len(sub_dims) == 1 else None

return None


HASH_CODE_MAX = 2147483647 # max 32bit signed int, required by OCC.Core.HashCode

Expand Down Expand Up @@ -2143,7 +2145,7 @@ def to_splines(
params,
)

return self.__class__(result)
return self.__class__.cast(result)

def to_vtk_poly_data(
self,
Expand Down Expand Up @@ -4148,11 +4150,8 @@ def center(self, center_of: CenterOf = CenterOf.MASS) -> Vector:
if center_of == CenterOf.MASS:
properties = GProp_GProps()
calc_function = Shape.shape_properties_LUT[shapetype(self.wrapped)]
if calc_function:
calc_function(self.wrapped, properties)
middle = Vector(properties.CentreOfMass())
else:
raise NotImplementedError
calc_function(self.wrapped, properties)
middle = Vector(properties.CentreOfMass())
elif center_of == CenterOf.BOUNDING_BOX:
middle = self.bounding_box().center()
return middle
Expand Down Expand Up @@ -4207,10 +4206,10 @@ def hollow(
shell_builder.Build()

if faces:
return_value = self.__class__(shell_builder.Shape())
return_value = self.__class__.cast(shell_builder.Shape())

else: # if no faces provided a watertight solid will be constructed
shell1 = self.__class__(shell_builder.Shape()).shells()[0].wrapped
shell1 = self.__class__.cast(shell_builder.Shape()).shells()[0].wrapped
shell2 = self.shells()[0].wrapped

# s1 can be outer or inner shell depending on the thickness sign
Expand All @@ -4220,7 +4219,7 @@ def hollow(
sol = BRepBuilderAPI_MakeSolid(shell2, shell1)

# fix needed for the orientations
return_value = self.__class__(sol.Shape()).fix()
return_value = self.__class__.cast(sol.Shape()).fix()

return return_value

Expand Down Expand Up @@ -4281,7 +4280,7 @@ def offset_3d(
"offset Error, an alternative kind may resolve this error"
) from err

offset_solid = self.__class__(offset_occt_solid)
offset_solid = self.__class__.cast(offset_occt_solid)
assert offset_solid.wrapped is not None

# The Solid can be inverted, if so reverse
Expand Down Expand Up @@ -6738,7 +6737,7 @@ def fillet_2d(self, radius: float, vertices: Iterable[Vertex]) -> Face:

fillet_builder.Build()

return self.__class__(fillet_builder.Shape())
return self.__class__.cast(fillet_builder.Shape())

def chamfer_2d(
self,
Expand Down Expand Up @@ -6794,7 +6793,7 @@ def chamfer_2d(
)

chamfer_builder.Build()
return self.__class__(chamfer_builder.Shape()).fix()
return self.__class__.cast(chamfer_builder.Shape()).fix()

def is_coplanar(self, plane: Plane) -> bool:
"""Is this planar face coplanar with the provided plane"""
Expand Down Expand Up @@ -6943,7 +6942,7 @@ def to_arcs(self, tolerance: float = 1e-3) -> Face:
if self.wrapped is None:
raise ValueError("Cannot approximate an empty shape")

return self.__class__(BRepAlgo.ConvertFace_s(self.wrapped, tolerance))
return self.__class__.cast(BRepAlgo.ConvertFace_s(self.wrapped, tolerance))


class Shell(Mixin2D, Shape[TopoDS_Shell]):
Expand Down Expand Up @@ -7693,18 +7692,18 @@ def _set_sweep_mode(
cls,
builder: BRepOffsetAPI_MakePipeShell,
path: Union[Wire, Edge],
mode: Union[Vector, Wire, Edge],
binormal: Union[Vector, Wire, Edge],
) -> bool:
rotate = False

if isinstance(mode, Vector):
if isinstance(binormal, Vector):
coordinate_system = gp_Ax2()
coordinate_system.SetLocation(path.start_point().to_pnt())
coordinate_system.SetDirection(mode.to_dir())
coordinate_system.SetDirection(binormal.to_dir())
builder.SetMode(coordinate_system)
rotate = True
elif isinstance(mode, (Wire, Edge)):
builder.SetMode(mode.to_wire().wrapped, True)
elif isinstance(binormal, (Wire, Edge)):
builder.SetMode(binormal.to_wire().wrapped, True)

return rotate

Expand Down Expand Up @@ -7782,7 +7781,7 @@ def sweep_multi(
path: Union[Wire, Edge],
make_solid: bool = True,
is_frenet: bool = False,
mode: Union[Vector, Wire, Edge, None] = None,
binormal: Union[Vector, Wire, Edge, None] = None,
) -> Solid:
"""Multi section sweep
Expand All @@ -7793,7 +7792,7 @@ def sweep_multi(
path (Union[Wire, Edge]): The wire to sweep the face resulting from the wires over
make_solid (bool, optional): Solid or Shell. Defaults to True.
is_frenet (bool, optional): Select frenet mode. Defaults to False.
mode (Union[Vector, Wire, Edge, None], optional): additional sweep mode parameters.
binormal (Union[Vector, Wire, Edge, None], optional): additional sweep mode parameters.
Defaults to None.
Returns:
Expand All @@ -7806,8 +7805,8 @@ def sweep_multi(
translate = False
rotate = False

if mode:
rotate = cls._set_sweep_mode(builder, path, mode)
if binormal:
rotate = cls._set_sweep_mode(builder, path, binormal)
else:
builder.SetMode(is_frenet)

Expand Down Expand Up @@ -8641,7 +8640,7 @@ def stitch(self, other: Wire) -> Wire:
wire_builder.Add(TopoDS.Wire_s(other.wrapped))
wire_builder.Build()

return self.__class__(wire_builder.Wire())
return self.__class__.cast(wire_builder.Wire())

def fillet_2d(self, radius: float, vertices: Iterable[Vertex]) -> Wire:
"""fillet_2d
Expand Down
72 changes: 72 additions & 0 deletions tests/test_direct_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2497,9 +2497,38 @@ def test_chamfer_edge_not_part_of_face(self):
face = box.faces().sort_by(Axis.Z)[0]
self.assertRaises(ValueError, box.chamfer, 0.1, None, edge, face=face)

@patch.object(Shape, "is_valid", return_value=False)
def test_chamfer_invalid_shape_raises_error(self, mock_is_valid):
box = Solid.make_box(1, 1, 1)

# Assert that ValueError is raised
with self.assertRaises(ValueError) as chamfer_context:
max = box.chamfer(0.1, None, box.edges())

# Check the error message
self.assertEqual(
str(chamfer_context.exception),
"Failed creating a chamfer, try a smaller length value(s)",
)

# Verify is_valid was called
mock_is_valid.assert_called_once()

def test_hollow(self):
shell_box = Solid.make_box(1, 1, 1).hollow([], thickness=-0.1)
self.assertAlmostEqual(shell_box.volume, 1 - 0.8**3, 5)

shell_box = Solid.make_box(1, 1, 1)
shell_box = shell_box.hollow(
shell_box.faces().filter_by(Axis.Z), thickness=0.1, kind=Kind.INTERSECTION
)
self.assertAlmostEqual(shell_box.volume, 1 * 1.2**2 - 1**3, 5)

shell_box = Solid.make_box(1, 1, 1).hollow(
[], thickness=0.1, kind=Kind.INTERSECTION
)
self.assertAlmostEqual(shell_box.volume, 1.2**3 - 1**3, 5)

with self.assertRaises(ValueError):
Solid.make_box(1, 1, 1).hollow([], thickness=0.1, kind=Kind.TANGENT)

Expand Down Expand Up @@ -3252,6 +3281,20 @@ def test_max_fillet(self):
# invalid_object = box.fillet(0.75, box.edges())
# invalid_object.max_fillet(invalid_object.edges())

@patch.object(Shape, "is_valid", return_value=False)
def test_max_fillet_invalid_shape_raises_error(self, mock_is_valid):
box = Solid.make_box(1, 1, 1)

# Assert that ValueError is raised
with self.assertRaises(ValueError) as max_fillet_context:
max = box.max_fillet(box.edges())

# Check the error message
self.assertEqual(str(max_fillet_context.exception), "Invalid Shape")

# Verify is_valid was called
mock_is_valid.assert_called_once()

def test_locate_bb(self):
bounding_box = Solid.make_cone(1, 2, 1).bounding_box()
relocated_bounding_box = Plane.XZ.from_local_coords(bounding_box)
Expand Down Expand Up @@ -4146,6 +4189,11 @@ def test_extrude_until(self):
extrusion = Solid.extrude_until(square, box, (0, 0, 1), Until.LAST)
self.assertAlmostEqual(extrusion.volume, 4, 5)

square = Face.make_rect(1, 1)
box = Solid.make_box(4, 4, 1, Plane((-2, -2, -3)))
extrusion = Solid.extrude_until(square, box, (0, 0, 1), Until.PREVIOUS)
self.assertAlmostEqual(extrusion.volume, 2, 5)

def test_sweep(self):
path = Edge.make_spline([(0, 0), (3, 5), (7, -2)])
section = Wire.make_circle(1, Plane(path @ 0, z_dir=path % 0))
Expand All @@ -4159,10 +4207,34 @@ def test_hollow_sweep(self):
swept = Solid.sweep(section, path)
self.assertAlmostEqual(swept.volume, 5 * (1 - 0.1**2), 5)

def test_sweep_multi(self):
f0 = Face.make_rect(1, 1)
f1 = Pos(X=10) * Circle(1).face()
path = Spline((0, 0), (10, 0), tangents=((0, 0, 1), (0, 0, -1)))
binormal = Edge.make_line((0, 1), (10, 1))
swept = Solid.sweep_multi([f0, f1], path, is_frenet=True, binormal=binormal)
self.assertAlmostEqual(swept.volume, 23.78, 2)

path = Spline((0, 0), (10, 0), tangents=((0, 0, 1), (1, 0, 0)))
swept = Solid.sweep_multi(
[f0, f1], path, is_frenet=True, binormal=Vector(5, 0, 1)
)
self.assertAlmostEqual(swept.volume, 20.75, 2)

def test_constructor(self):
with self.assertRaises(TypeError):
Solid(foo="bar")

def test_offset_3d(self):
with self.assertRaises(ValueError):
Solid.make_box(1, 1, 1).offset_3d(None, 0.1, kind=Kind.TANGENT)

def test_revolve(self):
r = Solid.revolve(
Face.make_rect(1, 1, Plane((10, 0, 0))).wire(), 180, axis=Axis.Y
)
self.assertEqual(len(r.faces()), 6)


class TestSkipClean(unittest.TestCase):
def setUp(self):
Expand Down

0 comments on commit 1611ca8

Please sign in to comment.