diff --git a/docs/assets/tech_drawing.svg b/docs/assets/tech_drawing.svg index 7279403..5038000 100644 --- a/docs/assets/tech_drawing.svg +++ b/docs/assets/tech_drawing.svg @@ -52,12 +52,12 @@ - + - + @@ -68,8 +68,8 @@ - + @@ -85,8 +85,8 @@ - + @@ -103,8 +103,8 @@ - + @@ -121,8 +121,8 @@ - + @@ -133,8 +133,8 @@ - + \ No newline at end of file diff --git a/docs/objects_2d.py b/docs/objects_2d.py index ac1a8b6..8901e0c 100644 --- a/docs/objects_2d.py +++ b/docs/objects_2d.py @@ -290,10 +290,13 @@ svg.add_shape(tech_drawing.sketch) svg.write("assets/tech_drawing.svg") # [ArrowHead] -arrow_head = ArrowHead(10) -s = 100 / max(*arrow_head.bounding_box().size) +arrow_head_types = [HeadType.CURVED, HeadType.STRAIGHT, HeadType.FILLETED] +arrow_heads = [ArrowHead(50, a_type) for a_type in arrow_head_types] +s = 100 / max(*arrow_heads[0].bounding_box().size) svg = ExportSVG(scale=s) -svg.add_shape(arrow_head) +for i, arrow_head in enumerate(arrow_heads): + svg.add_shape(arrow_head.moved(Location((0, -i * 40)))) + svg.add_shape(Text(arrow_head_types[i].name, 5).moved(Location((-25, -i * 40)))) svg.write("assets/arrow_head.svg") # [Arrow] diff --git a/src/build123d/build_common.py b/src/build123d/build_common.py index 9764135..dfe0853 100644 --- a/src/build123d/build_common.py +++ b/src/build123d/build_common.py @@ -103,7 +103,7 @@ LB = 453.59237 * G operations_apply_to = { "add": ["BuildPart", "BuildSketch", "BuildLine"], "bounding_box": ["BuildPart", "BuildSketch", "BuildLine"], - "chamfer": ["BuildPart", "BuildSketch"], + "chamfer": ["BuildPart", "BuildSketch", "BuildLine"], "extrude": ["BuildPart"], "fillet": ["BuildPart", "BuildSketch", "BuildLine"], "loft": ["BuildPart"], diff --git a/src/build123d/drafting.py b/src/build123d/drafting.py index 2a141ee..4eff61d 100644 --- a/src/build123d/drafting.py +++ b/src/build123d/drafting.py @@ -135,7 +135,7 @@ class Arrow(BaseSketchObject): shaft_path = shaft_path.trim(0.0, 1.0 - trim_amount) # Create a perpendicular line to sweep the tail path - shaft_pen = shaft_path.perpendicular_line(shaft_width) + shaft_pen = shaft_path.perpendicular_line(shaft_width, 0) shaft = sweep(shaft_pen, shaft_path, mode=Mode.PRIVATE) arrow = arrow_head.fuse(shaft).clean() @@ -284,6 +284,8 @@ class Draft: processed_path = Wire.make_polygon(pnts, close=False) else: raise ValueError("Unsupported patch descriptor") + # processed_path = Plane.XY.to_local_coords(processed_path) + return processed_path def _label_to_str( @@ -547,7 +549,7 @@ class ExtensionLine(BaseSketchObject): # Build the extension line sketch e_lines = [] for extension_line in extension_lines: - line_pen = extension_line.perpendicular_line(draft.line_width) + line_pen = extension_line.perpendicular_line(draft.line_width, 0) e_line_shape = sweep(line_pen, extension_line, mode=Mode.PRIVATE) e_lines.append(e_line_shape) d_line = DimensionLine( diff --git a/src/build123d/operations_generic.py b/src/build123d/operations_generic.py index 3a66d93..f5b599b 100644 --- a/src/build123d/operations_generic.py +++ b/src/build123d/operations_generic.py @@ -274,6 +274,7 @@ def chamfer( ValueError: objects must be Vertices """ context: Builder = Builder._get_context("chamfer") + length2 = length if length2 is None else length2 if (objects is None and context is None) or ( objects is None and context is not None and context._obj is None @@ -309,14 +310,13 @@ def chamfer( target = ( Sketch(target.wrapped) if isinstance(target, BaseSketchObject) else target ) - if not all([isinstance(obj, Vertex) for obj in object_list]): raise ValueError("2D chamfer operation takes only Vertices") new_faces = [] for face in target.faces(): vertices_in_face = [v for v in face.vertices() if v in object_list] if vertices_in_face: - new_faces.append(face.chamfer_2d(length, vertices_in_face)) + new_faces.append(face.chamfer_2d(length, length2, vertices_in_face)) else: new_faces.append(face) new_sketch = Sketch(Compound.make_compound(new_faces).wrapped) @@ -325,6 +325,28 @@ def chamfer( context._add_to_context(new_sketch, mode=Mode.REPLACE) return new_sketch + elif target._dim == 1: + target = ( + Wire(target.wrapped) + if isinstance(target, BaseLineObject) + else target.wires()[0] + ) + if not all([isinstance(obj, Vertex) for obj in object_list]): + raise ValueError("1D fillet operation takes only Vertices") + # Remove any end vertices as these can't be filleted + if not target.is_closed(): + object_list = filter( + lambda v: not ( + (Vector(*v.to_tuple()) - target.position_at(0)).length == 0 + or (Vector(*v.to_tuple()) - target.position_at(1)).length == 0 + ), + object_list, + ) + new_wire = target.chamfer_2d(length, length2, object_list) + if context is not None: + context._add_to_context(new_wire, mode=Mode.REPLACE) + return new_wire + def fillet( objects: Union[ChamferFilletType, Iterable[ChamferFilletType]], diff --git a/src/build123d/operations_sketch.py b/src/build123d/operations_sketch.py index 750057c..6f466bc 100644 --- a/src/build123d/operations_sketch.py +++ b/src/build123d/operations_sketch.py @@ -140,7 +140,7 @@ def trace( new_faces = [] for edge in trace_edges: - trace_pen = edge.perpendicular_line(line_width) + trace_pen = edge.perpendicular_line(line_width, 0) new_faces.extend(Face.sweep(trace_pen, edge).faces()) if context is not None: context._add_to_context(*new_faces, mode=mode) diff --git a/src/build123d/topology.py b/src/build123d/topology.py index 70d2bc7..0ca4bef 100644 --- a/src/build123d/topology.py +++ b/src/build123d/topology.py @@ -835,20 +835,25 @@ class Mixin1D: else: return offset_wire - def perpendicular_line(self, length: float, plane: Plane = Plane.XY) -> Edge: + def perpendicular_line( + self, length: float, u_value: float, plane: Plane = Plane.XY + ) -> Edge: """perpendicular_line Create a line on the given plane perpendicular to and centered on beginning of self Args: length (float): line length + u_value (float): position along line between 0.0 and 1.0 plane (Plane, optional): plane containing perpendicular line. Defaults to Plane.XY. Returns: Edge: perpendicular line """ - start = self.position_at(0) - local_plane = Plane(origin=start, x_dir=self.tangent_at(0), z_dir=plane.z_dir) + start = self.position_at(u_value) + local_plane = Plane( + origin=start, x_dir=self.tangent_at(u_value), z_dir=plane.z_dir + ) line = Edge.make_line( start + local_plane.y_dir * length / 2, start - local_plane.y_dir * length / 2, @@ -5136,11 +5141,14 @@ class Face(Shape): return self.__class__(fillet_builder.Shape()) - def chamfer_2d(self, distance: float, vertices: Iterable[Vertex]) -> Face: + def chamfer_2d( + self, distance: float, distance2: float, vertices: Iterable[Vertex] + ) -> Face: """Apply 2D chamfer to a face Args: distance: float: + distance2: float: vertices: Iterable[Vertex]: Returns: @@ -5161,7 +5169,7 @@ class Face(Shape): TopoDS.Edge_s(edge1.wrapped), TopoDS.Edge_s(edge2.wrapped), distance, - distance, + distance2, ) chamfer_builder.Build() @@ -6629,19 +6637,26 @@ class Wire(Shape, Mixin1D): """ return Face.make_from_wires(self).fillet_2d(radius, vertices).outer_wire() - def chamfer_2d(self, distance: float, vertices: Iterable[Vertex]) -> Wire: + def chamfer_2d( + self, distance: float, distance2: float, vertices: Iterable[Vertex] + ) -> Wire: """chamfer_2d Apply 2D chamfer to a wire Args: distance (float): chamfer length + distance2 (float): chamfer length vertices (Iterable[Vertex]): vertices to chamfer Returns: Wire: chamfered wire """ - return Face.make_from_wires(self).chamfer_2d(distance, vertices).outer_wire() + return ( + Face.make_from_wires(self) + .chamfer_2d(distance, distance2, vertices) + .outer_wire() + ) @classmethod def make_rect( diff --git a/tests/test_direct_api.py b/tests/test_direct_api.py index b3a65b1..3beb4f6 100644 --- a/tests/test_direct_api.py +++ b/tests/test_direct_api.py @@ -3039,7 +3039,7 @@ class TestWire(DirectApiTestCase): def test_chamfer_2d(self): square = Wire.make_rect(1, 1) - squaroid = square.chamfer_2d(0.1, square.vertices()) + squaroid = square.chamfer_2d(0.1, 0.1, square.vertices()) self.assertAlmostEqual( squaroid.length, 4 * (1 - 2 * 0.1 + 0.1 * math.sqrt(2)), 5 )