@@ -1320,17 +1320,77 @@ def threePointArc(self, point1, point2, forConstruction=False):
13201320 provide tangent arcs
13211321 """
13221322
1323- gstartPoint = self ._findFromPoint (False )
1324- gpoint1 = self .plane .toWorldCoords (point1 )
1325- gpoint2 = self .plane .toWorldCoords (point2 )
1323+ startPoint = self ._findFromPoint (False )
1324+ point1 = self .plane .toWorldCoords (point1 )
1325+ point2 = self .plane .toWorldCoords (point2 )
13261326
1327- arc = Edge .makeThreePointArc (gstartPoint , gpoint1 , gpoint2 )
1327+ arc = Edge .makeThreePointArc (startPoint , point1 , point2 )
13281328
13291329 if not forConstruction :
13301330 self ._addPendingEdge (arc )
13311331
13321332 return self .newObject ([arc ])
13331333
1334+ def sagittaArc (self , endPoint , sag , forConstruction = False ):
1335+ """
1336+ Draw an arc from the current point to endPoint with an arc defined by the sag (sagitta).
1337+
1338+ :param endPoint: end point for the arc
1339+ :type endPoint: 2-tuple, in workplane coordinates
1340+ :param sag: the sagitta of the arc
1341+ :type sag: float, perpendicular distance from arc center to arc baseline.
1342+ :return: a workplane with the current point at the end of the arc
1343+
1344+ The sagitta is the distance from the center of the arc to the arc base.
1345+ Given that a closed contour is drawn clockwise;
1346+ A positive sagitta means convex arc and negative sagitta means concave arc.
1347+ See "https://en.wikipedia.org/wiki/Sagitta_(geometry)" for more information.
1348+ """
1349+
1350+ startPoint = self ._findFromPoint (useLocalCoords = True )
1351+ endPoint = Vector (endPoint )
1352+ midPoint = endPoint .add (startPoint ).multiply (0.5 )
1353+
1354+ sagVector = endPoint .sub (startPoint ).normalized ().multiply (abs (sag ))
1355+ if (sag > 0 ):
1356+ sagVector .x , sagVector .y = - sagVector .y , sagVector .x # Rotate sagVector +90 deg
1357+ else :
1358+ sagVector .x , sagVector .y = sagVector .y , - sagVector .x # Rotate sagVector -90 deg
1359+
1360+ sagPoint = midPoint .add (sagVector )
1361+
1362+ return self .threePointArc (sagPoint , endPoint , forConstruction )
1363+
1364+ def radiusArc (self , endPoint , radius , forConstruction = False ):
1365+ """
1366+ Draw an arc from the current point to endPoint with an arc defined by the sag (sagitta).
1367+
1368+ :param endPoint: end point for the arc
1369+ :type endPoint: 2-tuple, in workplane coordinates
1370+ :param radius: the radius of the arc
1371+ :type radius: float, the radius of the arc between start point and end point.
1372+ :return: a workplane with the current point at the end of the arc
1373+
1374+ Given that a closed contour is drawn clockwise;
1375+ A positive radius means convex arc and negative radius means concave arc.
1376+ """
1377+
1378+ startPoint = self ._findFromPoint (useLocalCoords = True )
1379+ endPoint = Vector (endPoint )
1380+
1381+ # Calculate the sagitta from the radius
1382+ length = endPoint .sub (startPoint ).Length / 2.0
1383+ try :
1384+ sag = abs (radius ) - math .sqrt (radius ** 2 - length ** 2 )
1385+ except ValueError :
1386+ raise ValueError ("Arc radius is not large enough to reach the end point." )
1387+
1388+ # Return a sagittaArc
1389+ if radius > 0 :
1390+ return self .sagittaArc (endPoint , sag , forConstruction )
1391+ else :
1392+ return self .sagittaArc (endPoint , - sag , forConstruction )
1393+
13341394 def rotateAndCopy (self , matrix ):
13351395 """
13361396 Makes a copy of all edges on the stack, rotates them according to the
@@ -1738,7 +1798,14 @@ def close(self):
17381798
17391799 s = Workplane().lineTo(1,0).lineTo(1,1).close().extrude(0.2)
17401800 """
1741- self .lineTo (self .ctx .firstPoint .x , self .ctx .firstPoint .y )
1801+ endPoint = self ._findFromPoint (True )
1802+ startPoint = self .ctx .firstPoint
1803+
1804+ # Check if there is a distance between startPoint and endPoint
1805+ # that is larger than what is considered a numerical error.
1806+ # If so; add a line segment between endPoint and startPoint
1807+ if endPoint .sub (startPoint ).Length > 1e-6 :
1808+ self .lineTo (self .ctx .firstPoint .x , self .ctx .firstPoint .y )
17421809
17431810 # Need to reset the first point after closing a wire
17441811 self .ctx .firstPoint = None
@@ -2080,7 +2147,7 @@ def sweep(self, path, sweepAlongWires=False, makeSolid=True, isFrenet=False, com
20802147 :param path: A wire along which the pending wires will be swept
20812148 :param boolean sweepAlongWires:
20822149 False to create mutliple swept from wires on the chain along path
2083- True to create only one solid swept along path with shape following the list of wires on the chain
2150+ True to create only one solid swept along path with shape following the list of wires on the chain
20842151 :param boolean combine: True to combine the resulting solid with parent solids if found.
20852152 :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
20862153 :return: a CQ object with the resulting solid selected.
@@ -2407,7 +2474,7 @@ def _sweep(self, path, sweepAlongWires=False, makeSolid=True, isFrenet=False):
24072474 :param path: A wire along which the pending wires will be swept
24082475 :param boolean sweepAlongWires:
24092476 False to create mutliple swept from wires on the chain along path
2410- True to create only one solid swept along path with shape following the list of wires on the chain
2477+ True to create only one solid swept along path with shape following the list of wires on the chain
24112478 :return:a FreeCAD solid, suitable for boolean operations
24122479 """
24132480
0 commit comments