Skip to content

Commit 1b3469c

Browse files
committed
Updating the CadQuery library again for a mistaken reversion of the sagitta arc changes.
1 parent fc4a18d commit 1b3469c

File tree

5 files changed

+140
-13
lines changed

5 files changed

+140
-13
lines changed

Libs/cadquery/.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ before_install:
1414
conda config --set always_yes yes --set changeps1 no;
1515
conda update -q conda;
1616
conda info -a;
17-
conda create -y -q -n freecad_cq3 -c freecad -c freecad/label/broken -c conda-forge freecad=0.17=py36_11 occt=7.2.0=occt7.2.0_0 python=3.6 pyparsing conda mock coverage codecov;
17+
conda create -y -q -n freecad_cq3 -c cadquery -c conda-forge freecad=0.17 python=3.6 pyparsing conda mock coverage codecov;
1818
source ~/miniconda/bin/activate freecad_cq3;
1919
else
2020
sudo add-apt-repository -y ppa:freecad-maintainers/freecad-stable;
2121
sudo apt-get update -qq;
22-
sudo apt-get install -y freecad freecad-doc;
22+
sudo apt-get install -y freecad;
2323
pip install -r requirements-dev.txt;
2424
pip install travis-sphinx;
2525
pip install sphinx_rtd_theme;

Libs/cadquery/appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ install:
1212
- set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
1313
- conda config --set always_yes yes
1414
- conda update -q conda
15-
- conda create --quiet --name cqtest -c freecad -c freecad/label/broken -c conda-forge python=%PYTHON_VERSION% freecad=0.17=py36_vc14_13 occt=7.2.0 python=3.6 pyparsing mock coverage codecov
15+
- conda create --quiet --name cqtest -c cadquery -c conda-forge python=%PYTHON_VERSION% freecad=0.17 python=3.6 pyparsing mock coverage codecov
1616
- activate cqtest
1717
- python setup.py install
1818

Libs/cadquery/cadquery/cq.py

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Libs/cadquery/examples/FreeCAD/Ex005_Extruded_Lines_and_Arcs.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,27 @@
1818
# half-way back to the origin in the X direction and 0.5 mm above where
1919
# the last line ended at. The arc then ends at (0.0, 1.0), which is 1.0 mm
2020
# above (in the Y direction) where our first line started from.
21-
# 5. close() is called to automatically draw the last line for us and close
21+
# 5. An arc is drawn from the last point that ends on (-0.5, 1.0), the sag of
22+
# the curve 0.2 determines that the curve is concave with the midpoint 0.1 mm
23+
# from the arc baseline. If the sag was -0.2 the arc would be convex.
24+
# This convention is valid when the profile is drawn counterclockwise.
25+
# The reverse is true if the profile is drawn clockwise.
26+
# Clockwise: +sag => convex, -sag => concave
27+
# Counterclockwise: +sag => concave, -sag => convex
28+
# 6. An arc is drawn from the last point that ends on (-0.7, -0.2), the arc is
29+
# determined by the radius of -1.5 mm.
30+
# Clockwise: +radius => convex, -radius => concave
31+
# Counterclockwise: +radius => concave, -radius => convex
32+
# 7. close() is called to automatically draw the last line for us and close
2233
# the sketch so that it can be extruded.
23-
# 5a. Without the close(), the 2D sketch will be left open and the extrude
34+
# 7a. Without the close(), the 2D sketch will be left open and the extrude
2435
# operation will provide unpredictable results.
25-
# 6. The 2D sketch is extruded into a solid object of the specified thickness.
36+
# 8. The 2D sketch is extruded into a solid object of the specified thickness.
2637
result = cq.Workplane("front").lineTo(width, 0) \
2738
.lineTo(width, 1.0) \
2839
.threePointArc((1.0, 1.5), (0.0, 1.0)) \
40+
.sagittaArc((-0.5, 1.0), 0.2) \
41+
.radiusArc((-0.7, -0.2), -1.5) \
2942
.close().extrude(thickness)
3043

3144
# Displays the result of this script

Libs/cadquery/tests/TestCadQuery.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,20 @@ def test2DDrawing(self):
867867
r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0)))\
868868
.first().val().Y))
869869

870+
# Test the sagittaArc and radiusArc functions
871+
a1 = Workplane(Plane.YZ()).threePointArc((5, 1), (10, 0))
872+
a2 = Workplane(Plane.YZ()).sagittaArc((10, 0), -1)
873+
a3 = Workplane(Plane.YZ()).threePointArc((6, 2), (12, 0))
874+
a4 = Workplane(Plane.YZ()).radiusArc((12, 0), -10)
875+
876+
assert(a1.edges().first().val().geomType() == "CIRCLE")
877+
assert(a2.edges().first().val().geomType() == "CIRCLE")
878+
assert(a3.edges().first().val().geomType() == "CIRCLE")
879+
assert(a4.edges().first().val().geomType() == "CIRCLE")
880+
881+
assert(a1.edges().first().val().Length() == a2.edges().first().val().Length())
882+
assert(a3.edges().first().val().Length() == a4.edges().first().val().Length())
883+
870884
def testLargestDimension(self):
871885
"""
872886
Tests the largestDimension function when no solids are on the stack and when there are
@@ -1591,3 +1605,36 @@ def testExtrude(self):
15911605
self.assertTupleAlmostEquals(delta.toTuple(),
15921606
(0.,0.,2.*h),
15931607
decimal_places)
1608+
1609+
def testClose(self):
1610+
# Close without endPoint and startPoint coincide.
1611+
# Create a half-circle
1612+
a = Workplane(Plane.XY()).sagittaArc((10, 0), 2).close().extrude(2)
1613+
1614+
# Close when endPoint and startPoint coincide.
1615+
# Create a double half-circle
1616+
b = Workplane(Plane.XY()).sagittaArc((10, 0), 2).sagittaArc((0, 0), 2).close().extrude(2)
1617+
1618+
# The b shape shall have twice the volume of the a shape.
1619+
self.assertAlmostEqual(a.val().wrapped.Volume * 2.0, b.val().wrapped.Volume)
1620+
1621+
# Testcase 3 from issue #238
1622+
thickness = 3.0
1623+
length = 10.0
1624+
width = 5.0
1625+
1626+
obj1 = Workplane('XY', origin=(0, 0, -thickness / 2)) \
1627+
.moveTo(length / 2, 0).threePointArc((0, width / 2), (-length / 2, 0)) \
1628+
.threePointArc((0, -width / 2), (length / 2, 0)) \
1629+
.close().extrude(thickness)
1630+
1631+
os_x = 8.0 # Offset in X
1632+
os_y = -19.5 # Offset in Y
1633+
1634+
obj2 = Workplane('YZ', origin=(os_x, os_y, -thickness / 2)) \
1635+
.moveTo(os_x + length / 2, os_y).sagittaArc((os_x -length / 2, os_y), width / 2) \
1636+
.sagittaArc((os_x + length / 2, os_y), width / 2) \
1637+
.close().extrude(thickness)
1638+
1639+
# The obj1 shape shall have the same volume as the obj2 shape.
1640+
self.assertAlmostEqual(obj1.val().wrapped.Volume, obj2.val().wrapped.Volume)

0 commit comments

Comments
 (0)