diff --git a/src/build123d/objects_curve.py b/src/build123d/objects_curve.py index c8904c86..0247ba91 100644 --- a/src/build123d/objects_curve.py +++ b/src/build123d/objects_curve.py @@ -40,35 +40,55 @@ from build123d.topology import Edge, Face, Wire, Curve -class BaseLineObject(Wire): - """BaseLineObject +def _add_curve_to_context(curve, mode: Mode): + """Helper function to add a curve to the context. + + Args: + curve (Union[Wire, Edge]): curve to add to the context (either a Wire or an Edge). + mode (Mode): combination mode. + """ + context: BuildLine = BuildLine._get_context(log=False) + + if context is not None and isinstance(context, BuildLine): + if isinstance(curve, Wire): + context._add_to_context(*curve.edges(), mode=mode) + elif isinstance(curve, Edge): + context._add_to_context(curve, mode=mode) - Base class for all BuildLine objects + +class BaseLineObject(Wire): + """BaseLineObject specialized for Wire. Args: - curve (Union[Edge,Wire]): edge to create + curve (Wire): wire to create. mode (Mode, optional): combination mode. Defaults to Mode.ADD. """ _applies_to = [BuildLine._tag] - def __init__( - self, - curve: Union[Edge, Wire], - mode: Mode = Mode.ADD, - ): - context: BuildLine = BuildLine._get_context(self, log=False) + def __init__(self, curve: Wire, mode: Mode = Mode.ADD): + # Use the helper function to handle adding the curve to the context + _add_curve_to_context(curve, mode) + super().__init__(curve.wrapped) - if context is not None and isinstance(context, BuildLine): - context._add_to_context(*curve.edges(), mode=mode) - if isinstance(curve, Edge): - super().__init__(Wire([curve]).wrapped) - else: - super().__init__(curve.wrapped) +class BaseEdgeObject(Edge): + """BaseEdgeObject specialized for Edge. + + Args: + curve (Edge): edge to create. + mode (Mode, optional): combination mode. Defaults to Mode.ADD. + """ + + _applies_to = [BuildLine._tag] + + def __init__(self, curve: Edge, mode: Mode = Mode.ADD): + # Use the helper function to handle adding the curve to the context + _add_curve_to_context(curve, mode) + super().__init__(curve.wrapped) -class Bezier(BaseLineObject): +class Bezier(BaseEdgeObject): """Line Object: Bezier Curve Create a rational (with weights) or non-rational bezier curve. The first and last @@ -99,7 +119,7 @@ def __init__( super().__init__(curve, mode=mode) -class CenterArc(BaseLineObject): +class CenterArc(BaseEdgeObject): """Line Object: Center Arc Add center arc to the line. @@ -150,7 +170,7 @@ def __init__( super().__init__(arc, mode=mode) -class DoubleTangentArc(BaseLineObject): +class DoubleTangentArc(BaseEdgeObject): """Line Object: Double Tangent Arc Create an arc defined by a point/tangent pair and another line which the other end @@ -249,7 +269,7 @@ def func(radius, perpendicular_bisector): super().__init__(double.wire(), mode=mode) -class EllipticalStartArc(BaseLineObject): +class EllipticalStartArc(BaseEdgeObject): """Line Object: Elliptical Start Arc Makes an arc of an ellipse from the start point. @@ -355,7 +375,7 @@ def __init__( # context: BuildLine = BuildLine._get_context(self) -class EllipticalCenterArc(BaseLineObject): +class EllipticalCenterArc(BaseEdgeObject): """Line Object: Elliptical Center Arc Makes an arc of an ellipse from a center point. @@ -409,7 +429,7 @@ def __init__( super().__init__(curve, mode=mode) -class Helix(BaseLineObject): +class Helix(BaseEdgeObject): """Line Object: Helix Add a helix to the line. @@ -545,7 +565,7 @@ def __init__( super().__init__(new_wire, mode=mode) -class JernArc(BaseLineObject): +class JernArc(BaseEdgeObject): """JernArc Circular tangent arc with given radius and arc_size @@ -605,7 +625,7 @@ def __init__( super().__init__(arc, mode=mode) -class Line(BaseLineObject): +class Line(BaseEdgeObject): """Line Object: Line Add a straight line defined by two end points. @@ -638,7 +658,7 @@ def __init__( super().__init__(new_edge, mode=mode) -class IntersectingLine(BaseLineObject): +class IntersectingLine(BaseEdgeObject): """Intersecting Line Object: Line Add a straight line that intersects another line at a given parameter and angle. @@ -679,7 +699,7 @@ def __init__( super().__init__(new_edge, mode=mode) -class PolarLine(BaseLineObject): +class PolarLine(BaseEdgeObject): """Line Object: Polar Line Add line defined by a start point, length and angle. @@ -780,7 +800,7 @@ def __init__( super().__init__(Wire.combine(new_edges)[0], mode=mode) -class RadiusArc(BaseLineObject): +class RadiusArc(BaseEdgeObject): """Line Object: Radius Arc Add an arc defined by two end points and a radius @@ -832,7 +852,7 @@ def __init__( super().__init__(arc, mode=mode) -class SagittaArc(BaseLineObject): +class SagittaArc(BaseEdgeObject): """Line Object: Sagitta Arc Add an arc defined by two points and the height of the arc (sagitta). @@ -874,7 +894,7 @@ def __init__( super().__init__(arc, mode=mode) -class Spline(BaseLineObject): +class Spline(BaseEdgeObject): """Line Object: Spline Add a spline through the provided points optionally constrained by tangents. @@ -932,7 +952,7 @@ def __init__( super().__init__(spline, mode=mode) -class TangentArc(BaseLineObject): +class TangentArc(BaseEdgeObject): """Line Object: Tangent Arc Add an arc defined by two points and a tangent. @@ -974,7 +994,7 @@ def __init__( super().__init__(arc, mode=mode) -class ThreePointArc(BaseLineObject): +class ThreePointArc(BaseEdgeObject): """Line Object: Three Point Arc Add an arc generated by three points. diff --git a/tests/test_build_line.py b/tests/test_build_line.py index e851ce2c..9211a26b 100644 --- a/tests/test_build_line.py +++ b/tests/test_build_line.py @@ -104,8 +104,9 @@ def test_bezier(self): pts = [(0, 0), (20, 20), (40, 0), (0, -40), (-60, 0), (0, 100), (100, 0)] wts = [1.0, 1.0, 2.0, 3.0, 4.0, 2.0, 1.0] with BuildLine() as bz: - Bezier(*pts, weights=wts) + b1 = Bezier(*pts, weights=wts) self.assertAlmostEqual(bz.wires()[0].length, 225.86389406824566, 5) + self.assertTrue(isinstance(b1, Edge)) def test_double_tangent_arc(self): l1 = Line((10, 0), (30, 20)) @@ -140,6 +141,7 @@ def test_double_tangent_arc(self): l9 = EllipticalCenterArc((15, 0), 10, 5, start_angle=90, end_angle=270) l10 = DoubleTangentArc((0, 0, 0), (1, 0, 0), l9, keep=Keep.BOTH) self.assertEqual(len(l10.edges()), 2) + self.assertTrue(isinstance(l10, Edge)) with self.assertRaises(ValueError): DoubleTangentArc((0, 0, 0), (0, 0, 1), l9) @@ -168,6 +170,7 @@ def test_elliptical_center_arc(self): self.assertGreaterEqual(bbox.min.Y, 0) self.assertLessEqual(bbox.max.X, 10) self.assertLessEqual(bbox.max.Y, 5) + self.assertTrue(isinstance(e1, Edge)) def test_filletpolyline(self): with BuildLine(Plane.YZ): @@ -185,6 +188,7 @@ def test_filletpolyline(self): self.assertEqual(len(p.edges()), 8) self.assertEqual(len(p.edges().filter_by(GeomType.CIRCLE)), 4) self.assertEqual(len(p.edges().filter_by(GeomType.LINE)), 4) + self.assertTrue(isinstance(p, Wire)) with self.assertRaises(ValueError): FilletPolyline((0, 0), radius=0.1) @@ -200,6 +204,7 @@ def test_intersecting_line(self): l3 = Line((0, 0), (10, 10)) l4 = IntersectingLine((0, 10), (1, -1), l3) self.assertTupleAlmostEquals((l4 @ 1).to_tuple(), (5, 5, 0), 5) + self.assertTrue(isinstance(l4, Edge)) with self.assertRaises(ValueError): IntersectingLine((0, 10), (1, 1), l3) @@ -209,33 +214,36 @@ def test_jern_arc(self): j1 = JernArc((1, 0), (0, 1), 1, 90) self.assertTupleAlmostEquals((jern.line @ 1).to_tuple(), (0, 1, 0), 5) self.assertAlmostEqual(j1.radius, 1) - self.assertAlmostEqual(j1.length, pi/2) + self.assertAlmostEqual(j1.length, pi / 2) with BuildLine(Plane.XY.offset(1)) as offset_l: off1 = JernArc((1, 0), (0, 1), 1, 90) self.assertTupleAlmostEquals((offset_l.line @ 1).to_tuple(), (0, 1, 1), 5) self.assertAlmostEqual(off1.radius, 1) - self.assertAlmostEqual(off1.length, pi/2) + self.assertAlmostEqual(off1.length, pi / 2) plane_iso = Plane(origin=(0, 0, 0), x_dir=(1, 1, 0), z_dir=(1, -1, 1)) with BuildLine(plane_iso) as iso_l: iso1 = JernArc((0, 0), (0, 1), 1, 180) - self.assertTupleAlmostEquals((iso_l.line @ 1).to_tuple(), (-sqrt(2), -sqrt(2), 0), 5) + self.assertTupleAlmostEquals( + (iso_l.line @ 1).to_tuple(), (-sqrt(2), -sqrt(2), 0), 5 + ) self.assertAlmostEqual(iso1.radius, 1) self.assertAlmostEqual(iso1.length, pi) - + with BuildLine() as full_l: l1 = JernArc(start=(0, 0, 0), tangent=(1, 0, 0), radius=1, arc_size=360) l2 = JernArc(start=(0, 0, 0), tangent=(1, 0, 0), radius=1, arc_size=300) self.assertTrue(l1.is_closed) self.assertFalse(l2.is_closed) - circle_face = Face(l1) + circle_face = Face(Wire([l1])) self.assertAlmostEqual(circle_face.area, pi, 5) self.assertTupleAlmostEquals(circle_face.center().to_tuple(), (0, 1, 0), 5) self.assertTupleAlmostEquals(l1.vertex().to_tuple(), l2.start.to_tuple(), 5) l1 = JernArc((0, 0), (1, 0), 1, 90) self.assertTupleAlmostEquals((l1 @ 1).to_tuple(), (1, 1, 0), 5) + self.assertTrue(isinstance(l1, Edge)) def test_polar_line(self): """Test 2D and 3D polar lines""" @@ -267,6 +275,7 @@ def test_polar_line(self): l1 = PolarLine((0, 0), 10, direction=(1, 1)) self.assertTupleAlmostEquals((l1 @ 1).to_tuple(), (10, 10, 0), 5) + self.assertTrue(isinstance(l1, Edge)) with self.assertRaises(ValueError): PolarLine((0, 0), 1) @@ -274,8 +283,9 @@ def test_polar_line(self): def test_spline(self): """Test spline with no tangents""" with BuildLine() as test: - Spline((0, 0), (1, 1), (2, 0)) + s1 = Spline((0, 0), (1, 1), (2, 0)) self.assertTupleAlmostEquals((test.edges()[0] @ 1).to_tuple(), (2, 0, 0), 5) + self.assertTrue(isinstance(s1, Edge)) def test_radius_arc(self): """Test center arc as arc and circle""" @@ -304,9 +314,12 @@ def test_radius_arc(self): self.assertAlmostEqual(arc4.length, 2 * r * pi * 0.6, 6) self.assertGreater(arc4.bounding_box().max.X, c.bounding_box().max.X) + self.assertTrue(isinstance(arc1, Edge)) + def test_sagitta_arc(self): l1 = SagittaArc((0, 0), (1, 0), 0.1) self.assertAlmostEqual((l1 @ 0.5).Y, 0.1, 5) + self.assertTrue(isinstance(l1, Edge)) def test_center_arc(self): """Test center arc as arc and circle""" @@ -327,18 +340,20 @@ def test_center_arc(self): self.assertTupleAlmostEquals((arc.edges()[0] @ 0.5).to_tuple(), (0, 0, 0), 5) arc = CenterArc((-100, 0), 100, 0, 360) - self.assertTrue(Face(arc.wires()[0]).is_coplanar(Plane.XY)) + self.assertTrue(Face(Wire([arc])).is_coplanar(Plane.XY)) self.assertTupleAlmostEquals(arc.bounding_box().max, (0, 100, 0), 5) + self.assertTrue(isinstance(arc, Edge)) def test_polyline(self): """Test edge generation and close""" with BuildLine() as test: - Polyline((0, 0), (1, 0), (1, 1), (0, 1), close=True) + p1 = Polyline((0, 0), (1, 0), (1, 1), (0, 1), close=True) self.assertAlmostEqual( (test.edges()[0] @ 0 - test.edges()[-1] @ 1).length, 0, 5 ) self.assertEqual(len(test.edges()), 4) self.assertAlmostEqual(test.wires()[0].length, 4) + self.assertTrue(isinstance(p1, Wire)) def test_polyline_with_list(self): """Test edge generation and close"""