diff --git a/cadquery/cq.py b/cadquery/cq.py index 943f1fc..38d9b41 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -1331,6 +1331,41 @@ def threePointArc(self, point1, point2, forConstruction=False): return self.newObject([arc]) + def sagittaArc(self, endPoint, sag, forConstruction=False): + """ + Draw an arc from the current point to endPoint with an arc defined by the sag (sagitta). + + :param endPoint: end point for the arc + :type endPoint: 2-tuple, in workplane coordinates + :param sag: the sagitta of the arc + :type sag: float, perpendicular distance from arc center to arc baseline. + :return: a workplane with the current point at the end of the arc + + The sagitta is the distance from the center of the arc to the arc base. + Given that a closed contour is drawn clockwise; + A positive sagitta means convex arc and negative sagitta means concave arc. + See "https://en.wikipedia.org/wiki/Sagitta_(geometry)" for more information. + """ + + startPoint = self._findFromPoint(False) + endPoint = self.plane.toWorldCoords(endPoint) + midPoint = endPoint.add(startPoint).multiply(0.5) + + sagVector = endPoint.sub(startPoint).normalized().multiply(abs(sag)) + if(sag > 0): + sagVector.x, sagVector.y = -sagVector.y, sagVector.x # Rotate sagVector +90 deg + else: + sagVector.x, sagVector.y = sagVector.y, -sagVector.x # Rotate sagVector -90 deg + + sagPoint = midPoint.add(sagVector) + + arc = Edge.makeThreePointArc(startPoint, sagPoint, endPoint) + + if not forConstruction: + self._addPendingEdge(arc) + + return self.newObject([arc]) + def rotateAndCopy(self, matrix): """ Makes a copy of all edges on the stack, rotates them according to the @@ -2080,7 +2115,7 @@ def sweep(self, path, sweepAlongWires=False, makeSolid=True, isFrenet=False, com :param path: A wire along which the pending wires will be swept :param boolean sweepAlongWires: False to create mutliple swept from wires on the chain along path - True to create only one solid swept along path with shape following the list of wires on the chain + True to create only one solid swept along path with shape following the list of wires on the chain :param boolean combine: True to combine the resulting solid with parent solids if found. :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape :return: a CQ object with the resulting solid selected. @@ -2407,7 +2442,7 @@ def _sweep(self, path, sweepAlongWires=False, makeSolid=True, isFrenet=False): :param path: A wire along which the pending wires will be swept :param boolean sweepAlongWires: False to create mutliple swept from wires on the chain along path - True to create only one solid swept along path with shape following the list of wires on the chain + True to create only one solid swept along path with shape following the list of wires on the chain :return:a FreeCAD solid, suitable for boolean operations """ diff --git a/examples/FreeCAD/Ex005_Extruded_Lines_and_Arcs.py b/examples/FreeCAD/Ex005_Extruded_Lines_and_Arcs.py index 62b51a4..6470fd3 100644 --- a/examples/FreeCAD/Ex005_Extruded_Lines_and_Arcs.py +++ b/examples/FreeCAD/Ex005_Extruded_Lines_and_Arcs.py @@ -18,15 +18,23 @@ # half-way back to the origin in the X direction and 0.5 mm above where # the last line ended at. The arc then ends at (0.0, 1.0), which is 1.0 mm # above (in the Y direction) where our first line started from. -# 5. close() is called to automatically draw the last line for us and close +# 5. An arc is drawn from the last point that ends on (0.3, 0.2), the sag of +# the curve 0.1 determines that the curve is concave with the midpoint 0.1 mm +# from the arc baseline. If the sag was -0.1 the arc would be convex. +# This convention is valid when the profile is drawn counterclockwise. +# The reverse is true if the profile is drawn clockwise. +# Clockwise: +sag => convex, -sag => concave +# Counterclockwise: +sag => concave, -sag => convex +# 6. close() is called to automatically draw the last line for us and close # the sketch so that it can be extruded. -# 5a. Without the close(), the 2D sketch will be left open and the extrude +# 6a. Without the close(), the 2D sketch will be left open and the extrude # operation will provide unpredictable results. -# 6. The 2D sketch is extruded into a solid object of the specified thickness. +# 7. The 2D sketch is extruded into a solid object of the specified thickness. result = cq.Workplane("front").lineTo(width, 0) \ .lineTo(width, 1.0) \ - .threePointArc((1.0, 1.5), (0.0, 1.0)) \ + .threePointArc((1.0, 2.5), (0.2, 2.6)) \ + .sagittaArc((0.3, 0.2), 0.1) \ .close().extrude(thickness) - + # Displays the result of this script show_object(result) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index db329dc..5b16a7f 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -867,6 +867,10 @@ def test2DDrawing(self): r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0)))\ .first().val().Y)) + # Test the sagittaArc functions + s = Workplane(Plane.YZ()) + r = s.sagittaArc((10, 8), 1).close() + def testLargestDimension(self): """ Tests the largestDimension function when no solids are on the stack and when there are