Skip to content

Commit

Permalink
Added Shell.sweep Issue #622
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Sep 7, 2024
1 parent ba348d5 commit 88a4057
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 26 deletions.
5 changes: 2 additions & 3 deletions src/build123d/operations_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
Part,
Shape,
ShapeList,
Shell,
Sketch,
Solid,
Vertex,
Expand Down Expand Up @@ -1045,9 +1046,7 @@ def sweep(
new_faces = []
if edge_list:
for sec in section_list:
swept = Face.sweep(
sec, path_wire, transition
) # Could generate a shell here
swept = Shell.sweep(sec, path_wire, transition)
new_faces.extend(swept.faces())

if context is not None:
Expand Down
74 changes: 52 additions & 22 deletions src/build123d/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -1997,7 +1997,9 @@ def bounding_box(self, tolerance: float = None, optimal: bool = True) -> BoundBo
Returns:
BoundBox: A box sized to contain this Shape
"""
return BoundBox._from_topo_ds(self.wrapped, tolerance=tolerance, optimal=optimal)
return BoundBox._from_topo_ds(
self.wrapped, tolerance=tolerance, optimal=optimal
)

def mirror(self, mirror_plane: Plane = None) -> Self:
"""
Expand Down Expand Up @@ -5856,39 +5858,39 @@ def sew_faces(cls, faces: Iterable[Face]) -> list[ShapeList[Face]]:

return sewn_faces

# @classmethod
# def sweep(cls, profile: Edge, path: Union[Edge, Wire]) -> Face:
# """Sweep a 1D profile along a 1D path"""
# if isinstance(path, Edge):
# path = Wire([path])
# # Ensure the edges in the path are ordered correctly
# path = Wire(path.order_edges())
# pipe_sweep = BRepOffsetAPI_MakePipe(path.wrapped, profile.wrapped)
# pipe_sweep.Build()
# return Face(pipe_sweep.Shape())

@classmethod
def sweep(
cls,
profile: Union[Edge, Wire],
path: Union[Edge, Wire],
transition=Transition.RIGHT,
profile: Union[Curve, Edge, Wire],
path: Union[Curve, Edge, Wire],
transition=Transition.TRANSFORMED,
) -> Face:
"""sweep
Sweep a 1D profile along a 1D path
Sweep a 1D profile along a 1D path. Both the profile and path must be composed
of only 1 Edge.
Args:
profile (Union[Edge, Wire]): the object to sweep
path (Union[Wire, Edge]): the path to follow when sweeping
profile (Union[Curve,Edge,Wire]): the object to sweep
path (Union[Curve,Edge,Wire]): the path to follow when sweeping
transition (Transition, optional): handling of profile orientation at C1 path
discontinuities. Defaults to Transition.RIGHT.
discontinuities. Defaults to Transition.TRANSFORMED.
Raises:
ValueError: Only 1 Edge allowed in profile & path
Returns:
Face: resulting face, may be non-planar
"""
profile = profile.to_wire()
path = Wire(Wire(path).order_edges())
# Note: BRepOffsetAPI_MakePipe is an option here
# pipe_sweep = BRepOffsetAPI_MakePipe(path.wrapped, profile.wrapped)
# pipe_sweep.Build()
# return Face(pipe_sweep.Shape())

if len(profile.edges()) != 1 or len(path.edges()) != 1:
raise ValueError("Use Shell.sweep for multi Edge objects")
profile = Wire([profile.edge()])
path = Wire([path.edge()])
builder = BRepOffsetAPI_MakePipeShell(path.wrapped)
builder.Add(profile.wrapped, False, False)
builder.SetTransitionMode(Solid._transModeDict[transition])
Expand Down Expand Up @@ -6658,6 +6660,34 @@ def center(self) -> Vector:
BRepGProp.LinearProperties_s(self.wrapped, properties)
return Vector(properties.CentreOfMass())

@classmethod
def sweep(
cls,
profile: Union[Curve, Edge, Wire],
path: Union[Curve, Edge, Wire],
transition=Transition.TRANSFORMED,
) -> Shell:
"""sweep
Sweep a 1D profile along a 1D path
Args:
profile (Union[Curve, Edge, Wire]): the object to sweep
path (Union[Curve, Edge, Wire]): the path to follow when sweeping
transition (Transition, optional): handling of profile orientation at C1 path
discontinuities. Defaults to Transition.TRANSFORMED.
Returns:
Shell: resulting Shell, may be non-planar
"""
profile = Wire(profile.edges())
path = Wire(Wire(path.edges()).order_edges())
builder = BRepOffsetAPI_MakePipeShell(path.wrapped)
builder.Add(profile.wrapped, False, False)
builder.SetTransitionMode(Solid._transModeDict[transition])
builder.Build()
return Shape.cast(builder.Shape())


class Solid(Mixin3D, Shape):
"""A Solid in build123d represents a three-dimensional solid geometry
Expand Down Expand Up @@ -8591,7 +8621,7 @@ def isclose_b(a: float, b: float, rel_tol=1e-9, abs_tol=1e-14) -> bool:
magnitude of the input values. Defaults to 1e-9.
abs_tol (float, optional): Maximum difference for being considered "close", regardless of the
magnitude of the input values. Defaults to 1e-14 (unlike math.isclose which defaults to zero).
Returns: True if a is close in value to b, and False otherwise.
"""
return isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol)
Expand Down
25 changes: 24 additions & 1 deletion tests/test_direct_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
from build123d.operations_sketch import make_face
from build123d.operations_generic import fillet, add, sweep
from build123d.objects_part import Box, Cylinder
from build123d.objects_curve import Polyline
from build123d.objects_curve import JernArc, Polyline
from build123d.build_sketch import BuildSketch
from build123d.build_line import BuildLine
from build123d.objects_curve import Spline
Expand Down Expand Up @@ -1420,6 +1420,8 @@ def test_sweep(self):
circle_with_hole = Face.sweep(edge, path)
self.assertTrue(isinstance(circle_with_hole, Face))
self.assertAlmostEqual(circle_with_hole.area, math.pi * (2**2 - 1**1), 5)
with self.assertRaises(ValueError):
Face.sweep(edge, Polyline((0, 0), (0.1, 0), (0.2, 0.1)))

def test_to_arcs(self):
with BuildSketch() as bs:
Expand Down Expand Up @@ -3471,6 +3473,27 @@ def test_constructor(self):
single_face = Shell(surface.faces())
self.assertTrue(single_face.is_valid())

def test_sweep(self):
path_c1 = JernArc((0, 0), (-1, 0), 1, 180)
path_e = path_c1.edge()
path_c2 = JernArc((0, 0), (-1, 0), 1, 180) + JernArc((0, 0), (1, 0), 2, -90)
path_w = path_c2.wire()
section_e = Circle(0.5).edge()
section_c2 = Polyline((0, 0), (0.1, 0), (0.2, 0.1))
section_w = section_c2.wire()

sweep_e_w = Shell.sweep((path_w ^ 0) * section_e, path_w)
sweep_w_e = Shell.sweep((path_e ^ 0) * section_w, path_e)
sweep_w_w = Shell.sweep((path_w ^ 0) * section_w, path_w)
sweep_c2_c1 = Shell.sweep((path_c1 ^ 0) * section_c2, path_c1)
sweep_c2_c2 = Shell.sweep((path_c2 ^ 0) * section_c2, path_c2)

self.assertEqual(len(sweep_e_w.faces()), 2)
self.assertEqual(len(sweep_w_e.faces()), 2)
self.assertEqual(len(sweep_c2_c1.faces()), 2)
self.assertEqual(len(sweep_w_w.faces()), 4)
self.assertEqual(len(sweep_c2_c2.faces()), 4)


class TestSolid(DirectApiTestCase):
def test_make_solid(self):
Expand Down

0 comments on commit 88a4057

Please sign in to comment.