diff --git a/docs/assets/stepper_drawing.svg b/docs/assets/stepper_drawing.svg index 6504639..8b87d79 100644 --- a/docs/assets/stepper_drawing.svg +++ b/docs/assets/stepper_drawing.svg @@ -1,308 +1,308 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -319,453 +319,453 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/technical_drawing.py b/docs/technical_drawing.py index 55a6efc..0c45587 100644 --- a/docs/technical_drawing.py +++ b/docs/technical_drawing.py @@ -162,9 +162,13 @@ vis, _ = project_to_2d( ) visible_lines.extend(vis) side_bbox = Curve(vis).bounding_box() -perimeter = Pos(*side_bbox.center()) * Rectangle(side_bbox.size.X, side_bbox.size.Y) +shaft_top_corner = vis.edges().sort_by(Axis.Y)[-1].vertices().sort_by(Axis.X)[-1] +body_bottom_corner = (side_bbox.max.X, side_bbox.min.Y) d4 = ExtensionLine( - border=perimeter.edges().sort_by(Axis.X)[-1], offset=1 * CM, draft=drafting_options + border=(shaft_top_corner, body_bottom_corner), + offset=-(side_bbox.max.X - shaft_top_corner.X) - 1 * CM, # offset to outside view. + measurement_direction=(0, 1, 0), + draft=drafting_options, ) l3 = Text("Side Elevation", 6) l3.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM) diff --git a/src/build123d/drafting.py b/src/build123d/drafting.py index 415d33e..782c0d8 100644 --- a/src/build123d/drafting.py +++ b/src/build123d/drafting.py @@ -504,7 +504,8 @@ class ExtensionLine(BaseSketchObject): label_angle (bool, optional): a flag indicating that instead of an extracted length value, the size of the circular arc extracted from the path should be displayed in degrees. Defaults to False. - project_line (Vector, optional): Vector line which to project dimension against. + measurement_direction (VectorLike, optional): Vector line which to project the dimension + against. Offset start point is the position of the start of border. Defaults to None. mode (Mode, optional): combination mode. Defaults to Mode.ADD. @@ -520,7 +521,7 @@ class ExtensionLine(BaseSketchObject): arrows: tuple[bool, bool] = (True, True), tolerance: float | tuple[float, float] | None = None, label_angle: bool = False, - project_line: VectorLike | None = None, + measurement_direction: VectorLike | None = None, mode: Mode = Mode.ADD, ): # pylint: disable=too-many-locals @@ -528,24 +529,41 @@ class ExtensionLine(BaseSketchObject): context = BuildSketch._get_context(self) if sketch is None and not (context is None or context.sketch is None): sketch = context.sketch - if project_line is not None: - raise NotImplementedError("project_line is currently unsupported") + if offset == 0: + raise ValueError("A dimension line should be used if offset is 0") # Create a wire modelling the path of the dimension lines from a variety of input types object_to_measure = Draft._process_path(border) + if object_to_measure.position_at(0) == object_to_measure.position_at(1): + raise ValueError("Start and end points of border must be different.") + + if measurement_direction is not None: + if isinstance(measurement_direction, Iterable): + measurement_direction = Vector(measurement_direction) + measure_object_span = object_to_measure.position_at( + 1 + ) - object_to_measure.position_at(0) + extent_along_wire = measure_object_span.project_to_line( + measurement_direction + ) + object_to_dimension = Edge.make_line( + object_to_measure.position_at(0), + object_to_measure.position_at(0) + extent_along_wire, + ) + else: + object_to_dimension = object_to_measure side_lut = {1: Side.RIGHT, -1: Side.LEFT} - if offset == 0: - raise ValueError("A dimension line should be used if offset is 0") - dimension_path = object_to_measure.offset_2d( + dimension_path = object_to_dimension.offset_2d( distance=offset, side=side_lut[int(copysign(1, offset))], closed=False ) dimension_label_str = ( label if label is not None - else draft._label_to_str(label, object_to_measure, label_angle, tolerance) + else draft._label_to_str(label, object_to_dimension, label_angle, tolerance) ) + extension_lines = [ Edge.make_line( object_to_measure.position_at(e), dimension_path.position_at(e) diff --git a/tests/test_drafting.py b/tests/test_drafting.py index 2f1b301..6d4dae5 100644 --- a/tests/test_drafting.py +++ b/tests/test_drafting.py @@ -37,7 +37,9 @@ from build123d import ( Axis, BuildLine, BuildSketch, + CenterOf, Color, + Compound, Edge, Face, FontStyle, @@ -50,6 +52,7 @@ from build123d import ( Rectangle, Sketch, Unit, + Vector, add, make_face, offset, @@ -292,14 +295,150 @@ class ExtensionLineTestCase(unittest.TestCase): self.assertAlmostEqual(bbox.size.X, 30 + metric.line_width, 5) self.assertAlmostEqual(bbox.size.Y, 10, 5) - def test_not_implemented(self): - shape, outer, inner = create_test_sketch() - with self.assertRaises(NotImplementedError): + def test_vectorlike_in_extension_function(self): + diagonal_line = Edge.make_line((100, 100), (200, 200)) + ext = ExtensionLine( + border=diagonal_line, + offset=-10, + draft=metric, + measurement_direction=(0, 1, 0), + ) + self.assertIsNotNone(ext) + + def test_vertical_projection_with_dim_outside_shape(self): + diagonal_line = Edge.make_line((100, 100), (200, 200)) + ext = ExtensionLine( + border=diagonal_line, + offset=-10, + draft=metric, + measurement_direction=Vector(0, 1, 0), + ) + self.assertIsNotNone(ext) + self.assertGreater( + Compound(children=[diagonal_line, ext]).bounding_box().size.X, + diagonal_line.bounding_box().size.X, + ) # dimension should be outside shape. + self.assertEqual( + diagonal_line.bounding_box().size.Y + 0.25, # plus line_width + ext.bounding_box().size.Y, + ) + self.assertEqual( + diagonal_line.center(CenterOf.BOUNDING_BOX).Y, + ext.center(CenterOf.BOUNDING_BOX).Y, + ) + self.assertEqual(ext.dimension, 100) + + def test_vertical_projection_with_dim_inside_shape(self): + diagonal_line = Edge.make_line((100, 100), (200, 200)) + ext = ExtensionLine( + border=diagonal_line, + offset=10, + draft=metric, + measurement_direction=Vector(0, 1, 0), + ) + self.assertIsNotNone(ext) + self.assertEqual( + Compound(children=[diagonal_line, ext]).bounding_box().size.Y, + diagonal_line.bounding_box().size.Y + 0.25, + ) # plus line_width + self.assertEqual( + diagonal_line.center(CenterOf.BOUNDING_BOX).Y, + ext.center(CenterOf.BOUNDING_BOX).Y, + ) + self.assertEqual(ext.dimension, 100) + + def test_vertical_projection_with_dim_otherside(self): + diagonal_line = Edge.make_line((100, 100), (200, 200)) + x_size = diagonal_line.bounding_box().size.X + ext = ExtensionLine( + border=diagonal_line, + offset=x_size + 10, + draft=metric, + measurement_direction=Vector(0, 1, 0), + ) + self.assertIsNotNone(ext) + self.assertGreater( + Compound(children=[diagonal_line, ext]).bounding_box().size.Y, + diagonal_line.bounding_box().size.Y, + ) # plus line_width + self.assertEqual( + diagonal_line.center(CenterOf.BOUNDING_BOX).Y, + ext.center(CenterOf.BOUNDING_BOX).Y, + ) + self.assertEqual(ext.dimension, 100) + + def test_vertical_projection_with_vertical_line(self): + diagonal_line = Edge.make_line((100, 100), (100, 200)) + ext = ExtensionLine( + border=diagonal_line, + offset=10, + draft=metric, + measurement_direction=Vector(0, 1, 0), + ) + self.assertIsNotNone(ext) + self.assertEqual( + diagonal_line.center(CenterOf.BOUNDING_BOX).Y, + ext.center(CenterOf.BOUNDING_BOX).Y, + ) + self.assertEqual(ext.dimension, 100) + + def test_horizontal_projection_with_dim_outside_shape(self): + diagonal_line = Edge.make_line((100, 100), (200, 200)) + ext = ExtensionLine( + border=diagonal_line, + offset=10, + draft=metric, + measurement_direction=Vector(1, 0, 0), + ) + self.assertIsNotNone(ext) + self.assertGreater( + Compound(children=[diagonal_line, ext]).bounding_box().size.Y, + diagonal_line.bounding_box().size.Y, + ) # dimension should be outside shape. + self.assertEqual( + diagonal_line.bounding_box().size.X + 0.25, # plus line_width + ext.bounding_box().size.X, + ) + self.assertEqual( + diagonal_line.center(CenterOf.BOUNDING_BOX).X, + ext.center(CenterOf.BOUNDING_BOX).X, + ) + self.assertEqual(ext.dimension, 100) + + def test_angled_projection(self): + diagonal_line = Edge.make_line((100, 100), (200, 200)) + ext = ExtensionLine( + border=diagonal_line, + offset=10, + draft=metric, + measurement_direction=Vector(1, 1, 0), + ) + self.assertIsNotNone(ext) + self.assertAlmostEqual(ext.dimension, 141.421, places=2) + + def test_half_circle(self): + half_circle = Edge.make_circle(50, start_angle=0, end_angle=180) + ext = ExtensionLine( + border=half_circle, + offset=-10, + draft=metric, + measurement_direction=Vector(1, 0, 0), + ) + self.assertIsNotNone(ext) + self.assertEqual(ext.dimension, 100) + self.assertGreater( + Compound(children=[half_circle, ext]).bounding_box().size.Y, + half_circle.bounding_box().size.Y, + ) # dimension should be outside shape. + + def test_full_circle(self): + half_circle = Edge.make_circle(50) + with pytest.raises(ValueError): ExtensionLine( - outer.edges().sort_by(Axis.Y)[0], + border=half_circle, offset=10, - project_line=(1, 0, 0), draft=metric, + measurement_direction=Vector(0, 1, 0), )