Refactored sweep to generate faces within a sketch

This commit is contained in:
gumyr 2023-08-22 11:44:42 -04:00
parent 709e67cbc5
commit c858b10e8b
11 changed files with 248 additions and 205 deletions

View file

@ -151,7 +151,7 @@ BuildLine to BuildPart
********************** **********************
The other primary reasons to use BuildLine is to create paths for BuildPart The other primary reasons to use BuildLine is to create paths for BuildPart
:meth:`~operations_part.sweep` operations. Here some curved and straight segments :meth:`~operations_generic.sweep` operations. Here some curved and straight segments
define a path: define a path:
.. literalinclude:: objects_1d.py .. literalinclude:: objects_1d.py

View file

@ -48,14 +48,14 @@ implicitly - there are a couple things to consider when deciding how to proceed:
* Implicit parameters save some typing but hide some functionality - users have * Implicit parameters save some typing but hide some functionality - users have
to decide what works best for them. to decide what works best for them.
This tea cup example uses implicit parameters - note the :func:`~operations_part.sweep` This tea cup example uses implicit parameters - note the :func:`~operations_generic.sweep`
operation on the last line: operation on the last line:
.. literalinclude:: ../examples/tea_cup.py .. literalinclude:: ../examples/tea_cup.py
:lines: 25-74 :lines: 25-74
:emphasize-lines: 50 :emphasize-lines: 50
:func:`~operations_part.sweep` requires a 2D cross section - ``handle_cross_section`` - :func:`~operations_generic.sweep` requires a 2D cross section - ``handle_cross_section`` -
and a path - ``handle_path`` - which are both passed implicitly. and a path - ``handle_path`` - which are both passed implicitly.
.. image:: tea_cup.png .. image:: tea_cup.png

View file

@ -80,6 +80,7 @@ Cheat Sheet
| :func:`~operations_generic.offset` | :func:`~operations_generic.offset`
| :func:`~operations_generic.scale` | :func:`~operations_generic.scale`
| :func:`~operations_generic.split` | :func:`~operations_generic.split`
| :func:`~operations_generic.sweep`
.. grid-item-card:: 3D - BuildPart .. grid-item-card:: 3D - BuildPart
@ -94,7 +95,7 @@ Cheat Sheet
| :func:`~operations_generic.scale` | :func:`~operations_generic.scale`
| :func:`~operations_part.section` | :func:`~operations_part.section`
| :func:`~operations_generic.split` | :func:`~operations_generic.split`
| :func:`~operations_part.sweep` | :func:`~operations_generic.sweep`
.. card:: Selectors .. card:: Selectors

View file

@ -435,7 +435,7 @@ consuming, and more difficult to maintain.
* **Builder mode** * **Builder mode**
The :meth:`~operations_part.sweep` method takes any pending faces and sweeps them through the provided The :meth:`~operations_generic.sweep` method takes any pending faces and sweeps them through the provided
path (in this case the path is taken from the pending edges from ``ex14_ln``). path (in this case the path is taken from the pending edges from ``ex14_ln``).
:meth:`~operations_part.revolve` requires a single connected wire. The pending faces must lie on the :meth:`~operations_part.revolve` requires a single connected wire. The pending faces must lie on the
path. path.
@ -446,7 +446,7 @@ consuming, and more difficult to maintain.
* **Algebra mode** * **Algebra mode**
The :meth:`~operations_part.sweep` method takes any faces and sweeps them through the provided The :meth:`~operations_generic.sweep` method takes any faces and sweeps them through the provided
path (in this case the path is taken from the pending edges from ``ex14_ln``). path (in this case the path is taken from the pending edges from ``ex14_ln``).
.. literalinclude:: general_examples_algebra.py .. literalinclude:: general_examples_algebra.py

View file

@ -21,47 +21,47 @@ The following table summarizes all of the available operations. Operations marke
applicable to BuildLine and Algebra Curve, 2D to BuildSketch and Algebra Sketch, 3D to applicable to BuildLine and Algebra Curve, 2D to BuildSketch and Algebra Sketch, 3D to
BuildPart and Algebra Part. BuildPart and Algebra Part.
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| Operation | Description | 0D | 1D | 2D | 3D | Example | | Operation | Description | 0D | 1D | 2D | 3D | Example |
+==============================================+==================================+====+====+====+====+========================+ +==============================================+====================================+====+====+====+====+========================+
| :func:`~operations_generic.add` | Add object to builder | | ✓ | ✓ | ✓ | :ref:`16 <ex 16>` | | :func:`~operations_generic.add` | Add object to builder | | ✓ | ✓ | ✓ | :ref:`16 <ex 16>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_generic.bounding_box` | Add bounding box as Shape | | ✓ | ✓ | ✓ | | | :func:`~operations_generic.bounding_box` | Add bounding box as Shape | | ✓ | ✓ | ✓ | |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_generic.chamfer` | Bevel Vertex or Edge | | | ✓ | ✓ | :ref:`9 <ex 9>` | | :func:`~operations_generic.chamfer` | Bevel Vertex or Edge | | | ✓ | ✓ | :ref:`9 <ex 9>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_part.extrude` | Draw 2D Shape into 3D | | | | ✓ | :ref:`3 <ex 3>` | | :func:`~operations_part.extrude` | Draw 2D Shape into 3D | | | | ✓ | :ref:`3 <ex 3>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_generic.fillet` | Radius Vertex or Edge | | | ✓ | ✓ | :ref:`9 <ex 9>` | | :func:`~operations_generic.fillet` | Radius Vertex or Edge | | | ✓ | ✓ | :ref:`9 <ex 9>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_part.loft` | Create 3D Shape from sections | | | | ✓ | :ref:`24 <ex 24>` | | :func:`~operations_part.loft` | Create 3D Shape from sections | | | | ✓ | :ref:`24 <ex 24>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_part.make_brake_formed` | Create sheet metal parts | | | | ✓ | | | :func:`~operations_part.make_brake_formed` | Create sheet metal parts | | | | ✓ | |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_sketch.make_face` | Create a Face from Edges | | | ✓ | | :ref:`4 <ex 4>` | | :func:`~operations_sketch.make_face` | Create a Face from Edges | | | ✓ | | :ref:`4 <ex 4>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_sketch.make_hull` | Create Convex Hull from Edges | | | ✓ | | | | :func:`~operations_sketch.make_hull` | Create Convex Hull from Edges | | | ✓ | | |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_generic.mirror` | Mirror about Plane | | ✓ | ✓ | ✓ | :ref:`15 <ex 15>` | | :func:`~operations_generic.mirror` | Mirror about Plane | | ✓ | ✓ | ✓ | :ref:`15 <ex 15>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_generic.offset` | Inset or outset Shape | | ✓ | ✓ | ✓ | :ref:`25 <ex 25>` | | :func:`~operations_generic.offset` | Inset or outset Shape | | ✓ | ✓ | ✓ | :ref:`25 <ex 25>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_generic.project` | Project points, lines or Faces | ✓ | ✓ | ✓ | | | | :func:`~operations_generic.project` | Project points, lines or Faces | ✓ | ✓ | ✓ | | |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_part.project_workplane` | Create workplane for projection | | | | | | | :func:`~operations_part.project_workplane` | Create workplane for projection | | | | | |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_part.revolve` | Swing 2D Shape about Axis | | | | ✓ | :ref:`23 <ex 23>` | | :func:`~operations_part.revolve` | Swing 2D Shape about Axis | | | | ✓ | :ref:`23 <ex 23>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_generic.scale` | Change size of Shape | | ✓ | ✓ | ✓ | | | :func:`~operations_generic.scale` | Change size of Shape | | ✓ | ✓ | ✓ | |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_part.section` | Generate 2D slices from 3D Shape | | | | ✓ | | | :func:`~operations_part.section` | Generate 2D slices from 3D Shape | | | | ✓ | |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_generic.split` | Divide object by Plane | | ✓ | ✓ | ✓ | :ref:`27 <ex 27>` | | :func:`~operations_generic.split` | Divide object by Plane | | ✓ | ✓ | ✓ | :ref:`27 <ex 27>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_part.sweep` | Extrude 2D section(s) along path | | | | ✓ | :ref:`14 <ex 14>` | | :func:`~operations_generic.sweep` | Extrude 1/2D section(s) along path | | | ✓ | ✓ | :ref:`14 <ex 14>` |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
| :func:`~operations_part.thicken` | Expand 2D section(s) | | | | ✓ | | | :func:`~operations_part.thicken` | Expand 2D section(s) | | | | ✓ | |
+----------------------------------------------+----------------------------------+----+----+----+----+------------------------+ +----------------------------------------------+------------------------------------+----+----+----+----+------------------------+
Reference Reference
^^^^^^^^^ ^^^^^^^^^
@ -82,5 +82,5 @@ Reference
.. autoclass:: operations_generic.scale .. autoclass:: operations_generic.scale
.. autoclass:: operations_part.section .. autoclass:: operations_part.section
.. autoclass:: operations_generic.split .. autoclass:: operations_generic.split
.. autoclass:: operations_part.sweep .. autoclass:: operations_generic.sweep
.. autoclass:: operations_part.thicken .. autoclass:: operations_part.thicken

View file

@ -13,7 +13,7 @@ Can't Get There from Here
Unfortunately, it's a reality that not all parts described using build123d can be Unfortunately, it's a reality that not all parts described using build123d can be
successfully constructed by the underlying CAD core. Designers may have to successfully constructed by the underlying CAD core. Designers may have to
explore different design approaches to get the OpenCascade CAD core to successfully explore different design approaches to get the OpenCascade CAD core to successfully
build the target object. For instance, if a multi-section :func:`~operations_part.sweep` build the target object. For instance, if a multi-section :func:`~operations_generic.sweep`
operation fails, a :func:`~operations_part.loft` operation may be a viable alternative operation fails, a :func:`~operations_part.loft` operation may be a viable alternative
in certain situations. It's crucial to remember that CAD is a complex field and in certain situations. It's crucial to remember that CAD is a complex field and
patience may be required to achieve the desired results. patience may be required to achieve the desired results.

View file

@ -111,7 +111,7 @@ operations_apply_to = {
"scale": ["BuildPart", "BuildSketch", "BuildLine"], "scale": ["BuildPart", "BuildSketch", "BuildLine"],
"section": ["BuildPart"], "section": ["BuildPart"],
"split": ["BuildPart", "BuildSketch", "BuildLine"], "split": ["BuildPart", "BuildSketch", "BuildLine"],
"sweep": ["BuildPart"], "sweep": ["BuildPart", "BuildSketch"],
"thicken": ["BuildPart"], "thicken": ["BuildPart"],
} }

View file

@ -31,7 +31,7 @@ import logging
from typing import Union, Iterable from typing import Union, Iterable
from build123d.build_common import Builder, LocationList, WorkplaneList, validate_inputs from build123d.build_common import Builder, LocationList, WorkplaneList, validate_inputs
from build123d.build_enums import Keep, Kind, Mode, Side from build123d.build_enums import Keep, Kind, Mode, Side, Transition
from build123d.build_line import BuildLine from build123d.build_line import BuildLine
from build123d.build_part import BuildPart from build123d.build_part import BuildPart
from build123d.build_sketch import BuildSketch from build123d.build_sketch import BuildSketch
@ -891,3 +891,117 @@ def split(
return Curve(split_compound.wrapped) return Curve(split_compound.wrapped)
else: else:
return split_compound return split_compound
#:TypeVar("SweepType"): Type of objects which can be swept
SweepType = Union[Compound, Edge, Wire, Face, Solid]
def sweep(
sections: Union[SweepType, Iterable[SweepType]] = None,
path: Union[Curve, Edge, Wire] = None,
multisection: bool = False,
is_frenet: bool = False,
transition: Transition = Transition.TRANSFORMED,
normal: VectorLike = None,
binormal: Union[Edge, Wire] = None,
clean: bool = True,
mode: Mode = Mode.ADD,
) -> Union[Part, Sketch]:
"""Generic Operation: sweep
Sweep pending 1D or 2D objects along path.
Args:
sections (Union[Compound, Edge, Wire, Face, Solid]): cross sections to sweep into object
path (Union[Curve, Edge, Wire], optional): path to follow.
Defaults to context pending_edges.
multisection (bool, optional): sweep multiple on path. Defaults to False.
is_frenet (bool, optional): use frenet algorithm. Defaults to False.
transition (Transition, optional): discontinuity handling option.
Defaults to Transition.RIGHT.
normal (VectorLike, optional): fixed normal. Defaults to None.
binormal (Union[Edge, Wire], optional): guide rotation along path. Defaults to None.
clean (bool, optional): Remove extraneous internal structure. Defaults to True.
mode (Mode, optional): combination. Defaults to Mode.ADD.
"""
context: Builder = Builder._get_context("sweep")
section_list = (
[*sections] if isinstance(sections, (list, tuple, filter)) else [sections]
)
section_list = [sec for sec in section_list if sec is not None]
validate_inputs(context, "sweep", section_list)
if path is None:
if context is None or context is not None and not context.pending_edges:
raise ValueError("path must be provided")
path_wire = Wire.make_wire(context.pending_edges)
context.pending_edges = []
else:
path_wire = Wire.make_wire(path.edges()) if not isinstance(path, Wire) else path
if not section_list:
if (
context is not None
and isinstance(context, BuildPart)
and context.pending_faces
):
section_list = context.pending_faces
context.pending_faces = []
context.pending_face_planes = []
else:
raise ValueError("No sections provided")
edge_list = []
face_list = []
for sec in section_list:
if isinstance(sec, (Curve, Wire, Edge)):
edge_list.extend(sec.edges())
else:
face_list.extend(sec.faces())
# sweep to create solids
new_solids = []
if face_list:
if binormal is None and normal is not None:
binormal_mode = Vector(normal)
elif isinstance(binormal, Edge):
binormal_mode = Wire.make_wire([binormal])
else:
binormal_mode = binormal
if multisection:
sections = [face.outer_wire() for face in face_list]
new_solid = Solid.sweep_multi(
sections, path_wire, True, is_frenet, binormal_mode
)
else:
for face in face_list:
new_solid = Solid.sweep(
section=face,
path=path_wire,
make_solid=True,
is_frenet=is_frenet,
mode=binormal_mode,
transition=transition,
)
new_solids.append(new_solid)
# sweep to create faces
new_faces = []
if edge_list:
for sec in section_list:
swept = Face.sweep(sec, path_wire) # Could generate a shell here
new_faces.extend(swept.faces())
if context is not None:
context._add_to_context(*(new_solids + new_faces), clean=clean, mode=mode)
elif clean:
new_solids = [solid.clean() for solid in new_solids]
new_faces = [face.clean() for face in new_faces]
if new_solids:
return Part(Compound.make_compound(new_solids).wrapped)
else:
return Sketch(Compound.make_compound(new_faces).wrapped)

View file

@ -502,93 +502,6 @@ def section(
return Part(Compound.make_compound(result).wrapped) return Part(Compound.make_compound(result).wrapped)
#:TypeVar("SweepType"): Type of objects which can be swept
SweepType = Union[Edge, Wire, Face, Solid]
def sweep(
sections: Union[SweepType, Iterable[SweepType]] = None,
path: Union[Edge, Wire] = None,
multisection: bool = False,
is_frenet: bool = False,
transition: Transition = Transition.TRANSFORMED,
normal: VectorLike = None,
binormal: Union[Edge, Wire] = None,
clean: bool = True,
mode: Mode = Mode.ADD,
) -> Part:
"""Part Operation: sweep
Sweep pending sketches/faces along path.
Args:
sections (Union[Face, Compound]): cross sections to sweep into object
path (Union[Edge, Wire], optional): path to follow.
Defaults to context pending_edges.
multisection (bool, optional): sweep multiple on path. Defaults to False.
is_frenet (bool, optional): use frenet algorithm. Defaults to False.
transition (Transition, optional): discontinuity handling option.
Defaults to Transition.RIGHT.
normal (VectorLike, optional): fixed normal. Defaults to None.
binormal (Union[Edge, Wire], optional): guide rotation along path. Defaults to None.
clean (bool, optional): Remove extraneous internal structure. Defaults to True.
mode (Mode, optional): combination. Defaults to Mode.ADD.
"""
context: BuildPart = BuildPart._get_context("sweep")
if path is None:
path_wire = context.pending_edges_as_wire
context.pending_edges = []
else:
path_wire = Wire.make_wire([path]) if isinstance(path, Edge) else path
section_list = (
[*sections] if isinstance(sections, (list, tuple, filter)) else [sections]
)
validate_inputs(context, "sweep", section_list)
if all([s is None for s in section_list]):
if context is None or (context is not None and not context.pending_faces):
raise ValueError("No sections provided")
section_list = context.pending_faces
context.pending_faces = []
context.pending_face_planes = []
else:
section_list = [face for section in sections for face in section.faces()]
if binormal is None and normal is not None:
binormal_mode = Vector(normal)
elif isinstance(binormal, Edge):
binormal_mode = Wire.make_wire([binormal])
else:
binormal_mode = binormal
new_solids = []
if multisection:
sections = [section.outer_wire() for section in section_list]
new_solid = Solid.sweep_multi(
sections, path_wire, True, is_frenet, binormal_mode
)
else:
for sec in section_list:
new_solid = Solid.sweep(
section=sec,
path=path_wire,
make_solid=True,
is_frenet=is_frenet,
mode=binormal_mode,
transition=transition,
)
new_solids.append(new_solid)
if context is not None:
context._add_to_context(*new_solids, clean=clean, mode=mode)
elif clean:
new_solids = [solid.clean() for solid in new_solids]
return Part(Compound.make_compound(new_solids).wrapped)
def thicken( def thicken(
to_thicken: Union[Face, Sketch] = None, to_thicken: Union[Face, Sketch] = None,
amount: float = None, amount: float = None,

View file

@ -614,5 +614,87 @@ class ScaleTests(unittest.TestCase):
scale(by="a") scale(by="a")
class TestSweep(unittest.TestCase):
def test_single_section(self):
with BuildPart() as test:
with BuildLine():
Line((0, 0, 0), (0, 0, 10))
with BuildSketch():
Rectangle(2, 2)
sweep()
self.assertAlmostEqual(test.part.volume, 40, 5)
def test_multi_section(self):
segment_count = 6
with BuildPart() as handle:
with BuildLine() as handle_center_line:
Spline(
(-10, 0, 0),
(0, 0, 5),
(10, 0, 0),
tangents=((0, 0, 1), (0, 0, -1)),
tangent_scalars=(1.5, 1.5),
)
handle_path = handle_center_line.wires()[0]
for i in range(segment_count + 1):
with BuildSketch(
Plane(
origin=handle_path @ (i / segment_count),
z_dir=handle_path % (i / segment_count),
)
) as section:
if i % segment_count == 0:
Circle(1)
else:
Rectangle(1, 2)
fillet(section.vertices(), radius=0.2)
# Create the handle by sweeping along the path
sweep(multisection=True)
self.assertAlmostEqual(handle.part.volume, 54.11246334691092, 5)
def test_passed_parameters(self):
with BuildLine() as path:
Line((0, 0, 0), (0, 0, 10))
with BuildSketch() as section:
Rectangle(2, 2)
with BuildPart() as test:
sweep(section.faces(), path=path.wires()[0])
self.assertAlmostEqual(test.part.volume, 40, 5)
def test_binormal(self):
with BuildPart() as sweep_binormal:
with BuildLine() as path:
Spline((0, 0, 0), (-12, 8, 10), tangents=[(0, 0, 1), (-1, 0, 0)])
with BuildLine(mode=Mode.PRIVATE) as binormal:
Line((-5, 5), (-8, 10))
with BuildSketch() as section:
Rectangle(4, 6)
sweep(binormal=binormal.edges()[0])
end_face: Face = (
sweep_binormal.faces().filter_by(GeomType.PLANE).sort_by(Axis.X)[0]
)
face_binormal_axis = Axis(
end_face.center(), binormal.edges()[0] @ 1 - end_face.center()
)
face_normal_axis = Axis(end_face.center(), end_face.normal_at())
self.assertTrue(face_normal_axis.is_normal(face_binormal_axis))
def test_sweep_edge(self):
arc = PolarLine((1, 0), 2, 135)
arc_path = PolarLine(arc @ 1, 10, 45)
swept = sweep(sections=arc, path=arc_path)
self.assertTrue(isinstance(swept, Sketch))
self.assertAlmostEqual(swept.area, 2 * 10, 5)
def test_no_path(self):
with self.assertRaises(ValueError):
sweep(PolarLine((1, 0), 2, 135))
def test_no_sections(self):
with self.assertRaises(ValueError):
sweep(path=PolarLine((1, 0), 2, 135))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -410,73 +410,6 @@ class TestSplit(unittest.TestCase):
self.assertAlmostEqual(test.part.volume, (2 / 3) * 1000 * pi, 5) self.assertAlmostEqual(test.part.volume, (2 / 3) * 1000 * pi, 5)
class TestSweep(unittest.TestCase):
def test_single_section(self):
with BuildPart() as test:
with BuildLine():
Line((0, 0, 0), (0, 0, 10))
with BuildSketch():
Rectangle(2, 2)
sweep()
self.assertAlmostEqual(test.part.volume, 40, 5)
def test_multi_section(self):
segment_count = 6
with BuildPart() as handle:
with BuildLine() as handle_center_line:
Spline(
(-10, 0, 0),
(0, 0, 5),
(10, 0, 0),
tangents=((0, 0, 1), (0, 0, -1)),
tangent_scalars=(1.5, 1.5),
)
handle_path = handle_center_line.wires()[0]
for i in range(segment_count + 1):
with BuildSketch(
Plane(
origin=handle_path @ (i / segment_count),
z_dir=handle_path % (i / segment_count),
)
) as section:
if i % segment_count == 0:
Circle(1)
else:
Rectangle(1, 2)
fillet(section.vertices(), radius=0.2)
# Create the handle by sweeping along the path
sweep(multisection=True)
self.assertAlmostEqual(handle.part.volume, 54.11246334691092, 5)
def test_passed_parameters(self):
with BuildLine() as path:
Line((0, 0, 0), (0, 0, 10))
with BuildSketch() as section:
Rectangle(2, 2)
with BuildPart() as test:
sweep(section.faces(), path=path.wires()[0])
self.assertAlmostEqual(test.part.volume, 40, 5)
def test_binormal(self):
with BuildPart() as sweep_binormal:
with BuildLine() as path:
Spline((0, 0, 0), (-12, 8, 10), tangents=[(0, 0, 1), (-1, 0, 0)])
with BuildLine(mode=Mode.PRIVATE) as binormal:
Line((-5, 5), (-8, 10))
with BuildSketch() as section:
Rectangle(4, 6)
sweep(binormal=binormal.edges()[0])
end_face: Face = (
sweep_binormal.faces().filter_by(GeomType.PLANE).sort_by(Axis.X)[0]
)
face_binormal_axis = Axis(
end_face.center(), binormal.edges()[0] @ 1 - end_face.center()
)
face_normal_axis = Axis(end_face.center(), end_face.normal_at())
self.assertTrue(face_normal_axis.is_normal(face_binormal_axis))
class TestThicken(unittest.TestCase): class TestThicken(unittest.TestCase):
def test_thicken(self): def test_thicken(self):
with BuildPart() as bp: with BuildPart() as bp: