From 475b2b0246bf3e5fab43f527cfe3a6cf904c122e Mon Sep 17 00:00:00 2001 From: Bernhard Date: Fri, 3 Jan 2020 12:06:45 +0100 Subject: [PATCH 01/16] added ellipse --- cadquery/cq.py | 35 ++++++++++++++++ cadquery/occ_impl/shapes.py | 81 +++++++++++++++++++++++++++++++++++-- tests/__init__.py | 2 +- tests/test_cad_objects.py | 52 +++++++++++++++++++++++- tests/test_cadquery.py | 38 +++++++++++++++++ 5 files changed, 202 insertions(+), 6 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index 25dc1d3eb..209dd0cde 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -2020,6 +2020,41 @@ def makeCircleWire(obj): return self.eachpoint(makeCircleWire, useLocalCoordinates=True) + # ellipse from current point + def ellipse(self, x_radius, y_radius, angle1=360, angle2=360, rotation_angle=0.0, + closed=False, forConstruction=False): + """ + Make an ellipse for each item on the stack. + :param x_radius: x radius of the ellipse (x-axis of plane the ellipse should lie in) + :type x_radius: float > 0 + :param y_radius: y radius of the ellipse (y-axis of plane the ellipse should lie in) + :type y_radius: float > 0 + :param forConstruction: should the new wires be reference geometry only? + :type forConstruction: true if the wires are for reference, false if they are creating + part geometry + :param rotation_angle: angle to rotate the ellipse (0 = no rotation = default) + :type rotation_angle: float + :param angle1: start angle of arc + :type angle1: float + :param angle2: end angle of arc + :type angle2: float + :param rotation_angle: angle to rotate the created ellipse / arc + :type rotation_angle: float + :return: a new CQ object with the created wires on the stack + + *NOTE* Due to a bug in opencascade (https://tracker.dev.opencascade.org/view.php?id=31290) + the center of mass (equals center for next shape) is shifted. To create concentric ellipses + use Workplane("XY") + .center(10, 20).ellipse(100,10) + .center(0, 0).ellipse(50, 5) + """ + def makeEllipseWire(obj): + elip = Wire.makeEllipse(x_radius, y_radius, obj, Vector(0, 0, 1), angle1, angle2, rotation_angle, closed) + elip.forConstruction = forConstruction + return elip + + return self.eachpoint(makeEllipseWire, useLocalCoordinates=True) + def polygon(self, nSides, diameter, forConstruction=False): """ Creates a polygon inscribed in a circle of the specified diameter for each point on diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index caa83f697..6cf3ed2f3 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -3,6 +3,7 @@ import OCC.Core.TopAbs as ta # Tolopolgy type enum import OCC.Core.GeomAbs as ga # Geometry type enum +<<<<<<< HEAD from OCC.Core.gp import ( gp_Vec, gp_Pnt, @@ -17,6 +18,10 @@ gp_Pnt2d, gp_Dir2d, ) +======= +from OCC.Core.gp import (gp_Vec, gp_Pnt, gp_Ax1, gp_Ax2, gp_Ax3, gp_Dir, gp_Circ, gp_Elips, + gp_Trsf, gp_Pln, gp_GTrsf, gp_Pnt2d, gp_Dir2d) +>>>>>>> added ellipse # collection of pints (used for spline construction) from OCC.Core.TColgp import TColgp_HArray1OfPnt @@ -76,7 +81,7 @@ from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder -from OCC.Core.GC import GC_MakeArcOfCircle # geometry construction +from OCC.Core.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse # geometry construction from OCC.Core.GCE2d import GCE2d_MakeSegment from OCC.Core.GeomAPI import GeomAPI_Interpolate, GeomAPI_ProjectPointOnSurf @@ -127,7 +132,7 @@ from OCC.Core.BRepClass3d import BRepClass3d_SolidClassifier -from math import pi, sqrt +from math import pi, sqrt, tan, atan2 from functools import reduce import warnings @@ -744,7 +749,49 @@ def makeCircle( return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) @classmethod - def makeSpline(cls, listOfVector, tangents=None, periodic=False, tol=1e-6): + def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), + angle1=360.0, angle2=360.0): + """ + Interpolate a spline through the provided points. + :param cls: + :param x_radius: x radius of the ellipse (along the x-axis of plane the ellipse should lie in) + :param y_radius: y radius of the ellipse (along the y-axis of plane the ellipse should lie in) + :param pnt: vector representing the center of the ellipse + :param dir: vector representing the direction of the plane the ellipse should lie in + :param angle1: start angle of arc + :param angle2: end angle of arc (angle2 == angle1 return closed ellipse = default) + :return: an Edge + """ + + pnt = Vector(pnt).toPnt() + dir = Vector(dir).toDir() + + ax1 = gp_Ax1(pnt, dir) + ax2 = gp_Ax2(pnt, dir) + + if y_radius > x_radius: + # swap x and y radius and rotate by 90° afterwards to create an ellipse with x_radius < y_radius + correction_angle = 90.0 * DEG2RAD + ellipse_gp = gp_Elips(ax2, y_radius, x_radius).Rotated(ax1, correction_angle) + else: + correction_angle = 0.0 + ellipse_gp = gp_Elips(ax2, x_radius, y_radius) + + if angle1 == angle2: # full ellipse case + ellipse = cls(BRepBuilderAPI_MakeEdge(ellipse_gp).Edge()) + else: # arc case + # take correction_angle into account + ellipse_geom = GC_MakeArcOfEllipse(ellipse_gp, + angle1 * DEG2RAD - correction_angle, + angle2 * DEG2RAD - correction_angle, + True).Value() + ellipse = cls(BRepBuilderAPI_MakeEdge(ellipse_geom).Edge()) + + return ellipse + + @classmethod + def makeSpline(cls, listOfVector, tangents=None, periodic=False, + tol = 1e-6): """ Interpolate a spline through the provided points. :param cls: @@ -861,6 +908,34 @@ def makeCircle(cls, radius, center, normal): w = cls.assembleEdges([circle_edge]) return w + @classmethod + def makeEllipse(cls, x_radius, y_radius, center, normal, angle1=360.0, angle2=360.0, + rotation_angle=0.0, closed=True): + """ + Makes an Ellipse centered at the provided point, having normal in the provided direction + :param x_radius: floating point major radius of the ellipse (x-axis), must be > 0 + :param y_radius: floating point minor radius of the ellipse (y-axis), must be > 0 + :param center: vector representing the center of the circle + :param normal: vector representing the direction of the plane the circle should lie in + :param angle1: start angle of arc + :param angle2: end angle of arc + :param rotation_angle: angle to rotate the created ellipse / arc + :return: + """ + + ellipse_edge = Edge.makeEllipse(x_radius, y_radius, center, normal, angle1, angle2) + + if angle1 != angle2 and closed: + line = Edge.makeLine(ellipse_edge.endPoint(), ellipse_edge.startPoint()) + w = cls.assembleEdges([ellipse_edge, line]) + else: + w = cls.assembleEdges([ellipse_edge]) + + if rotation_angle != 0.0: + w = w.rotate(center, center + normal, rotation_angle) + + return w + @classmethod def makePolygon(cls, listOfVertices, forConstruction=False): # convert list of tuples into Vectors. diff --git a/tests/__init__.py b/tests/__init__.py index b36542e9e..487dc014b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,5 @@ from cadquery import * -from OCC.gp import gp_Vec +from OCC.Core.gp import gp_Vec import unittest import sys import os diff --git a/tests/test_cad_objects.py b/tests/test_cad_objects.py index c556678a6..4c84a233e 100644 --- a/tests/test_cad_objects.py +++ b/tests/test_cad_objects.py @@ -1,18 +1,20 @@ # system modules +import math import sys import unittest from tests import BaseTest -from OCC.gp import gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_DZ, gp_XYZ +from OCC.gp import gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_Elips, gp_DZ, gp_XYZ from OCC.BRepBuilderAPI import ( BRepBuilderAPI_MakeVertex, BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeFace, ) -from OCC.GC import GC_MakeCircle +from OCC.Core.GC import GC_MakeCircle from cadquery import * +DEG2RAD = 2 * math.pi / 360 class TestCadObjects(BaseTest): def _make_circle(self): @@ -20,6 +22,11 @@ def _make_circle(self): circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()), 2.0) return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge()) + def _make_ellipse(self): + + ellipse = gp_Elips(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()), 4., 2.) + return Shape.cast(BRepBuilderAPI_MakeEdge(ellipse).Edge()) + def testVectorConstructors(self): v1 = Vector(1, 2, 3) v2 = Vector((1, 2, 3)) @@ -69,6 +76,11 @@ def testEdgeWrapperCenter(self): self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3) + def testEdgeWrapperEllipseCenter(self): + e = self._make_ellipse() + w = Wire.assembleEdges([e]) + self.assertTupleAlmostEquals((1.0, 2.0, 3.0), Face.makeFromWires(w).Center().toTuple(), 3) + def testEdgeWrapperMakeCircle(self): halfCircleEdge = Edge.makeCircle( radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180 @@ -82,6 +94,42 @@ def testEdgeWrapperMakeCircle(self): (-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3 ) + def testEdgeWrapperMakeEllipse1(self): + # Check x_radius > y_radius + x_radius, y_radius = 20, 10 + angle1, angle2 = -75.0, 90.0 + arcEllipseEdge = Edge.makeEllipse( + x_radius=x_radius, y_radius=y_radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=angle1, angle2=angle2) + + start = (x_radius * math.cos(angle1 * DEG2RAD), y_radius * math.sin(angle1 * DEG2RAD), 0.0) + end = (x_radius * math.cos(angle2 * DEG2RAD), y_radius * math.sin(angle2 * DEG2RAD), 0.0) + self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3) + self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3) + + def testEdgeWrapperMakeEllipse2(self): + # Check x_radius < y_radius + x_radius, y_radius = 10, 20 + angle1, angle2 = 0.0, 45.0 + arcEllipseEdge = Edge.makeEllipse( + x_radius=x_radius, y_radius=y_radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=angle1, angle2=angle2) + + start = (x_radius * math.cos(angle1 * DEG2RAD), y_radius * math.sin(angle1 * DEG2RAD), 0.0) + end = (x_radius * math.cos(angle2 * DEG2RAD), y_radius * math.sin(angle2 * DEG2RAD), 0.0) + self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3) + self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3) + + def testEdgeWrapperMakeCircleWithEllipse(self): + # Check x_radius == y_radius + x_radius, y_radius = 20, 20 + angle1, angle2 = 15.0, 60.0 + arcEllipseEdge = Edge.makeEllipse( + x_radius=x_radius, y_radius=y_radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=angle1, angle2=angle2) + + start = (x_radius * math.cos(angle1 * DEG2RAD), y_radius * math.sin(angle1 * DEG2RAD), 0.0) + end = (x_radius * math.cos(angle2 * DEG2RAD), y_radius * math.sin(angle2 * DEG2RAD), 0.0) + self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3) + self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3) + def testFaceWrapperMakePlane(self): mplane = Face.makePlane(10, 10) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 2057e58fa..d479563f7 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -753,6 +753,44 @@ def testNestedCircle(self): self.saveModel(s) self.assertEqual(14, s.faces().size()) + def testConcentricEllipses(self): + concentricEllipses = (Workplane("XY") + .center(10, 20).ellipse(100,10) + .center(0, 0).ellipse(50, 5)) + v = concentricEllipses.vertices().objects[0] + self.assertTupleAlmostEquals((v.X, v.Y), (10 + 50, 20), 3) + + + def testClosedEllipseArc(self): + ellipseArc = Workplane("XY").ellipse(20, 10, angle1=30, angle2=-30, rotation_angle=0, closed=False) + self.assertFalse(ellipseArc.val().IsClosed()) + ellipseArc = Workplane("XY").ellipse(20, 10, angle1=30, angle2=-30, rotation_angle=0, closed=True) + self.assertTrue(ellipseArc.val().IsClosed()) + + def testRotatedEllipse(self): + DEG2RAD = math.pi / 180.0 + c = (5, 7) + a1, a2 = 30, -60 + r1, r2 = 20, 10 + ra = 25 + ellipseArc = Workplane("XY").center(*c).ellipse(r1, r2, angle1=a1, angle2=a2, rotation_angle=ra) + + start = ellipseArc.vertices().objects[0] + end = ellipseArc.vertices().objects[1] + + # rotation matrix + r = (( math.cos(ra*DEG2RAD), math.sin(ra*DEG2RAD)), + (-math.sin(ra*DEG2RAD), math.cos(ra*DEG2RAD))) + # points on ellipse around zero before rotation + sx, sy = r1 * math.cos(a1*DEG2RAD), r2 * math.sin(a1*DEG2RAD) + ex, ey = r1 * math.cos(a2*DEG2RAD), r2 * math.sin(a2*DEG2RAD) + # points on ellipse around zero after rotation + sx_rot, sy_rot = c[0] + (sx*r[0][0] + sy*r[1][0]), c[1] + (sx*r[0][1] + sy*r[1][1]) + ex_rot, ey_rot = c[0] + (ex*r[0][0] + ey*r[1][0]), c[1] + (ex*r[0][1] + ey*r[1][1]) + + self.assertTupleAlmostEquals((start.X, start.Y), (sx_rot, sy_rot), 3) + self.assertTupleAlmostEquals((end.X, end.Y), (ex_rot, ey_rot), 3) + def testLegoBrick(self): # test making a simple lego brick # which of the below From 6c72780393db76bd4e1c4c3a6c202b4f996c0dda Mon Sep 17 00:00:00 2001 From: Bernhard Date: Fri, 3 Jan 2020 12:18:02 +0100 Subject: [PATCH 02/16] removed unused math imports --- cadquery/occ_impl/shapes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 6cf3ed2f3..217cf4561 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -132,7 +132,7 @@ from OCC.Core.BRepClass3d import BRepClass3d_SolidClassifier -from math import pi, sqrt, tan, atan2 +from math import pi, sqrt from functools import reduce import warnings From 6e9116c604395272d1d154b6f02409c222a8b0fd Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 18 Jan 2020 14:35:33 +0100 Subject: [PATCH 03/16] added method ellipseArc and adapted method ellipse to circle --- cadquery/cq.py | 57 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index 209dd0cde..a40d48835 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -1567,6 +1567,48 @@ def parametricCurve(self, func, N=400, start=0, stop=1): return self.spline(allPoints, includeCurrent=False, makeWire=True) + def ellipseArc(self, x_radius, y_radius, angle1=360, angle2=360, rotation_angle=0.0, sense=1, + forConstruction=False, startAtCurrent=True, makeWire=False): + """Draw an elliptical arc with x and y radiuses either with start point at current point or + or current point being the center of the arc + + :param x_radius: x radius of the ellipse (along the x-axis of plane the ellipse should lie in) + :param y_radius: y radius of the ellipse (along the y-axis of plane the ellipse should lie in) + :param angle1: start angle of arc + :param angle2: end angle of arc (angle2 == angle1 return closed ellipse = default) + :param rotation_angle: angle to rotate the created ellipse / arc + :param sense: clockwise (-1) or counter clockwise (1) + :param startAtCurrent: True: start point of arc is moved to current point; False: center of + arc is on current point + :param makeWire: convert the resulting arc edge to a wire + """ + + + # Start building the ellipse with the current point as center + # to support strating as the result of center(x,y) or moveTo(x,y) do not use local coordinates + center = self._findFromPoint(useLocalCoords=False) + e = Edge.makeEllipse(x_radius, y_radius, center, Vector(0, 0, 1), angle1, angle2, sense==1) + + # Rotate if necessary + if rotation_angle != 0.0: + e = e.rotate(center, center.add(Vector(0,0,1)), rotation_angle) + + # Move the start point of the ellipse onto the last current point + if startAtCurrent: + startPoint = e.startPoint() + e = e.translate(center.sub(startPoint)) + + if makeWire: + rv = Wire.assembleEdges([e]) + if not forConstruction: + self._addPendingWire(rv) + else: + rv = e + if not forConstruction: + self._addPendingEdge(e) + + return self.newObject([rv]) + def threePointArc(self, point1, point2, forConstruction=False): """ Draw an arc from the current point, through point1, and ending at point2 @@ -2021,25 +2063,18 @@ def makeCircleWire(obj): return self.eachpoint(makeCircleWire, useLocalCoordinates=True) # ellipse from current point - def ellipse(self, x_radius, y_radius, angle1=360, angle2=360, rotation_angle=0.0, - closed=False, forConstruction=False): + def ellipse(self, x_radius, y_radius, rotation_angle=0.0, forConstruction=False): """ Make an ellipse for each item on the stack. :param x_radius: x radius of the ellipse (x-axis of plane the ellipse should lie in) :type x_radius: float > 0 :param y_radius: y radius of the ellipse (y-axis of plane the ellipse should lie in) :type y_radius: float > 0 + :param rotation_angle: angle to rotate the ellipse (0 = no rotation = default) + :type rotation_angle: float :param forConstruction: should the new wires be reference geometry only? :type forConstruction: true if the wires are for reference, false if they are creating part geometry - :param rotation_angle: angle to rotate the ellipse (0 = no rotation = default) - :type rotation_angle: float - :param angle1: start angle of arc - :type angle1: float - :param angle2: end angle of arc - :type angle2: float - :param rotation_angle: angle to rotate the created ellipse / arc - :type rotation_angle: float :return: a new CQ object with the created wires on the stack *NOTE* Due to a bug in opencascade (https://tracker.dev.opencascade.org/view.php?id=31290) @@ -2049,7 +2084,7 @@ def ellipse(self, x_radius, y_radius, angle1=360, angle2=360, rotation_angle=0.0 .center(0, 0).ellipse(50, 5) """ def makeEllipseWire(obj): - elip = Wire.makeEllipse(x_radius, y_radius, obj, Vector(0, 0, 1), angle1, angle2, rotation_angle, closed) + elip = Wire.makeEllipse(x_radius, y_radius, obj, Vector(0, 0, 1), rotation_angle=rotation_angle) elip.forConstruction = forConstruction return elip From 282eddf2b0db9aacac32a0c0579f43f1869dc381 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 18 Jan 2020 14:36:05 +0100 Subject: [PATCH 04/16] introduced sense for ellipse building --- cadquery/occ_impl/shapes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 217cf4561..83e39eab5 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -750,7 +750,7 @@ def makeCircle( @classmethod def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), - angle1=360.0, angle2=360.0): + angle1=360.0, angle2=360.0, sense=1): """ Interpolate a spline through the provided points. :param cls: @@ -760,6 +760,7 @@ def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1 :param dir: vector representing the direction of the plane the ellipse should lie in :param angle1: start angle of arc :param angle2: end angle of arc (angle2 == angle1 return closed ellipse = default) + :param sense: clockwise (-1) or counter clockwise (1) :return: an Edge """ @@ -784,7 +785,7 @@ def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1 ellipse_geom = GC_MakeArcOfEllipse(ellipse_gp, angle1 * DEG2RAD - correction_angle, angle2 * DEG2RAD - correction_angle, - True).Value() + sense==1).Value() ellipse = cls(BRepBuilderAPI_MakeEdge(ellipse_geom).Edge()) return ellipse From 55899a5ace469e080531f027d20c2f4c7522d204 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 18 Jan 2020 14:36:53 +0100 Subject: [PATCH 05/16] adapted ellipse test cases --- tests/test_cadquery.py | 139 ++++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 31 deletions(-) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index d479563f7..78534809a 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -496,6 +496,114 @@ def testSpline(self): path1 = Workplane("XZ").spline(pts[1:], includeCurrent=True).val() self.assertAlmostEqual(path.Length(), path1.Length()) + def testRotatedEllipse(self): + + def rotatePoint(x, y, alpha): + # rotation matrix + a = alpha * DEG2RAD + r = ((math.cos(a), math.sin(a)), (-math.sin(a), math.cos(a))) + return ((x * r[0][0] + y * r[1][0]), (x * r[0][1] + y * r[1][1])) + + def ellipsePoints(r1, r2, a): + return (r1 * math.cos(a * DEG2RAD), r2 * math.sin(a * DEG2RAD)) + + DEG2RAD = math.pi / 180.0 + p0 = (10, 20) + a1, a2 = 30, -60 + r1, r2 = 20, 10 + ra = 25 + + sx_rot, sy_rot = rotatePoint(*ellipsePoints(r1, r2, a1), ra) + ex_rot, ey_rot = rotatePoint(*ellipsePoints(r1, r2, a2), ra) + + # startAtCurrent=False, sense = 1 + ellipseArc1 = Workplane("XY").moveTo(*p0).ellipseArc( + r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra) + start = ellipseArc1.vertices().objects[0] + end = ellipseArc1.vertices().objects[1] + + self.assertTupleAlmostEquals((start.X, start.Y), (p0[0] + sx_rot, p0[1] + sy_rot), 3) + self.assertTupleAlmostEquals((end.X, end.Y), (p0[0] + ex_rot, p0[1] + ey_rot), 3) + + # startAtCurrent=True, sense = 1 + ellipseArc2 = Workplane("XY").moveTo(*p0).ellipseArc( + r1, r2, startAtCurrent=True, angle1=a1, angle2=a2, rotation_angle=ra) + start = ellipseArc2.vertices().objects[0] + end = ellipseArc2.vertices().objects[1] + + self.assertTupleAlmostEquals((start.X, start.Y), (p0[0] + sx_rot - sx_rot, p0[1] + sy_rot - sy_rot), 3) + self.assertTupleAlmostEquals((end.X, end.Y), (p0[0] + ex_rot - sx_rot, p0[1] + ey_rot - sy_rot), 3) + + # startAtCurrent=False, sense = -1 + ellipseArc3 = Workplane("XY").moveTo(*p0).ellipseArc( + r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra, sense=-1) + start = ellipseArc3.vertices().objects[0] + end = ellipseArc3.vertices().objects[1] + + # swap start and end points for coparison due to different sense + self.assertTupleAlmostEquals((start.X, start.Y), (p0[0] + ex_rot, p0[1] + ey_rot), 3) + self.assertTupleAlmostEquals((end.X, end.Y), (p0[0] + sx_rot, p0[1] + sy_rot), 3) + + # startAtCurrent=True, sense = -1 + ellipseArc4 = Workplane("XY").moveTo(*p0).ellipseArc( + r1, r2, startAtCurrent=True, angle1=a1, angle2=a2, rotation_angle=ra, sense=-1) + start = ellipseArc4.vertices().objects[0] + end = ellipseArc4.vertices().objects[1] + + # swap start and end points for coparison due to different sense + self.assertTupleAlmostEquals((start.X, start.Y), (p0[0] + ex_rot - ex_rot, p0[1] + ey_rot - ey_rot), 3) + self.assertTupleAlmostEquals((end.X, end.Y), (p0[0] + sx_rot - ex_rot, p0[1] + sy_rot - ey_rot), 3) + + def testEllipseArcsClockwise(self): + ellipseArc = Workplane("XY").moveTo(10,15).ellipseArc(5, 4, -10, 190, 45, sense=-1, startAtCurrent=False) + sp = ellipseArc.val().startPoint() + ep = ellipseArc.val().endPoint() + self.assertTupleAlmostEquals((sp.x, sp.y), ( 7.009330014275797, 11.027027582524015), 3) + self.assertTupleAlmostEquals((ep.x, ep.y), (13.972972417475985, 17.990669985724203), 3) + + ellipseArc = (ellipseArc + .ellipseArc(5, 4, -10, 190, 315, sense=-1) + .ellipseArc(5, 4, -10, 190, 225, sense=-1) + .ellipseArc(5, 4, -10, 190, 135, sense=-1) + ) + ep = ellipseArc.val().endPoint() + self.assertTupleAlmostEquals((sp.x, sp.y), (ep.x, ep.y), 3) + + def testEllipseArcsCounterClockwise(self): + ellipseArc = Workplane("XY").moveTo(10,15).ellipseArc(5, 4, -10, 190, 45, startAtCurrent=False) + sp = ellipseArc.val().startPoint() + ep = ellipseArc.val().endPoint() + self.assertTupleAlmostEquals((sp.x, sp.y), (13.972972417475985, 17.990669985724203), 3) + self.assertTupleAlmostEquals((ep.x, ep.y), ( 7.009330014275797, 11.027027582524015), 3) + + ellipseArc = (ellipseArc + .ellipseArc(5, 4, -10, 190, 135) + .ellipseArc(5, 4, -10, 190, 225) + .ellipseArc(5, 4, -10, 190, 315) + ) + ep = ellipseArc.val().endPoint() + self.assertTupleAlmostEquals((sp.x, sp.y), (ep.x, ep.y), 3) + + def testEllipseCenterAndMoveTo(self): + # Whether we start from a center() call or a moveTo call, it should be the same ellipse Arc + p0 = (10, 20) + a1, a2 = 30, -60 + r1, r2 = 20, 10 + ra = 25 + + ellipseArc1 = Workplane("XY").moveTo(*p0).ellipseArc( + r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra) + sp1 = ellipseArc1.val().startPoint() + ep1 = ellipseArc1.val().endPoint() + + ellipseArc2 = Workplane("XY").moveTo(*p0).ellipseArc( + r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra) + sp2 = ellipseArc2.val().startPoint() + ep2 = ellipseArc2.val().endPoint() + + self.assertTupleAlmostEquals(sp1.toTuple(), sp2.toTuple(), 3) + self.assertTupleAlmostEquals(ep1.toTuple(), ep2.toTuple(), 3) + def testSweep(self): """ Tests the operation of sweeping a wire(s) along a path @@ -760,37 +868,6 @@ def testConcentricEllipses(self): v = concentricEllipses.vertices().objects[0] self.assertTupleAlmostEquals((v.X, v.Y), (10 + 50, 20), 3) - - def testClosedEllipseArc(self): - ellipseArc = Workplane("XY").ellipse(20, 10, angle1=30, angle2=-30, rotation_angle=0, closed=False) - self.assertFalse(ellipseArc.val().IsClosed()) - ellipseArc = Workplane("XY").ellipse(20, 10, angle1=30, angle2=-30, rotation_angle=0, closed=True) - self.assertTrue(ellipseArc.val().IsClosed()) - - def testRotatedEllipse(self): - DEG2RAD = math.pi / 180.0 - c = (5, 7) - a1, a2 = 30, -60 - r1, r2 = 20, 10 - ra = 25 - ellipseArc = Workplane("XY").center(*c).ellipse(r1, r2, angle1=a1, angle2=a2, rotation_angle=ra) - - start = ellipseArc.vertices().objects[0] - end = ellipseArc.vertices().objects[1] - - # rotation matrix - r = (( math.cos(ra*DEG2RAD), math.sin(ra*DEG2RAD)), - (-math.sin(ra*DEG2RAD), math.cos(ra*DEG2RAD))) - # points on ellipse around zero before rotation - sx, sy = r1 * math.cos(a1*DEG2RAD), r2 * math.sin(a1*DEG2RAD) - ex, ey = r1 * math.cos(a2*DEG2RAD), r2 * math.sin(a2*DEG2RAD) - # points on ellipse around zero after rotation - sx_rot, sy_rot = c[0] + (sx*r[0][0] + sy*r[1][0]), c[1] + (sx*r[0][1] + sy*r[1][1]) - ex_rot, ey_rot = c[0] + (ex*r[0][0] + ey*r[1][0]), c[1] + (ex*r[0][1] + ey*r[1][1]) - - self.assertTupleAlmostEquals((start.X, start.Y), (sx_rot, sy_rot), 3) - self.assertTupleAlmostEquals((end.X, end.Y), (ex_rot, ey_rot), 3) - def testLegoBrick(self): # test making a simple lego brick # which of the below From 69d1e1015a2df0c2c1580d8f9736d1accdc493d9 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 18 Jan 2020 14:37:43 +0100 Subject: [PATCH 06/16] exclude vscode config folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e7df8cfc4..8a19d8f0c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/* .idea/* cadquery.egg-info target/* +.vscode From 41c23439f88b8c9b92a86bc01815e5ba4ce4c6f2 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Tue, 11 Feb 2020 19:56:24 +0100 Subject: [PATCH 07/16] use gp_Ax2(p, zdir, xdir) for ellipse building --- cadquery/cq.py | 11 +++++------ cadquery/occ_impl/shapes.py | 15 ++++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index a40d48835..d7c1d4093 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -1585,13 +1585,12 @@ def ellipseArc(self, x_radius, y_radius, angle1=360, angle2=360, rotation_angle= # Start building the ellipse with the current point as center - # to support strating as the result of center(x,y) or moveTo(x,y) do not use local coordinates center = self._findFromPoint(useLocalCoords=False) - e = Edge.makeEllipse(x_radius, y_radius, center, Vector(0, 0, 1), angle1, angle2, sense==1) - + e = Edge.makeEllipse(x_radius, y_radius, center, self.plane.zDir, self.plane.xDir, angle1, angle2, sense==1) + # Rotate if necessary if rotation_angle != 0.0: - e = e.rotate(center, center.add(Vector(0,0,1)), rotation_angle) + e = e.rotate(center, center.add(self.plane.zDir), rotation_angle) # Move the start point of the ellipse onto the last current point if startAtCurrent: @@ -2076,7 +2075,7 @@ def ellipse(self, x_radius, y_radius, rotation_angle=0.0, forConstruction=False) :type forConstruction: true if the wires are for reference, false if they are creating part geometry :return: a new CQ object with the created wires on the stack - + *NOTE* Due to a bug in opencascade (https://tracker.dev.opencascade.org/view.php?id=31290) the center of mass (equals center for next shape) is shifted. To create concentric ellipses use Workplane("XY") @@ -2084,7 +2083,7 @@ def ellipse(self, x_radius, y_radius, rotation_angle=0.0, forConstruction=False) .center(0, 0).ellipse(50, 5) """ def makeEllipseWire(obj): - elip = Wire.makeEllipse(x_radius, y_radius, obj, Vector(0, 0, 1), rotation_angle=rotation_angle) + elip = Wire.makeEllipse(x_radius, y_radius, obj, Vector(0, 0, 1), Vector(1, 0, 0), rotation_angle=rotation_angle) elip.forConstruction = forConstruction return elip diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 83e39eab5..6b75c236e 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -3,7 +3,6 @@ import OCC.Core.TopAbs as ta # Tolopolgy type enum import OCC.Core.GeomAbs as ga # Geometry type enum -<<<<<<< HEAD from OCC.Core.gp import ( gp_Vec, gp_Pnt, @@ -12,16 +11,13 @@ gp_Ax3, gp_Dir, gp_Circ, + gp_Elips, gp_Trsf, gp_Pln, gp_GTrsf, gp_Pnt2d, gp_Dir2d, ) -======= -from OCC.Core.gp import (gp_Vec, gp_Pnt, gp_Ax1, gp_Ax2, gp_Ax3, gp_Dir, gp_Circ, gp_Elips, - gp_Trsf, gp_Pln, gp_GTrsf, gp_Pnt2d, gp_Dir2d) ->>>>>>> added ellipse # collection of pints (used for spline construction) from OCC.Core.TColgp import TColgp_HArray1OfPnt @@ -749,7 +745,7 @@ def makeCircle( return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) @classmethod - def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), + def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), xdir=Vector(1, 0, 0), angle1=360.0, angle2=360.0, sense=1): """ Interpolate a spline through the provided points. @@ -766,9 +762,10 @@ def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1 pnt = Vector(pnt).toPnt() dir = Vector(dir).toDir() + xdir = Vector(xdir).toDir() ax1 = gp_Ax1(pnt, dir) - ax2 = gp_Ax2(pnt, dir) + ax2 = gp_Ax2(pnt, dir, xdir) if y_radius > x_radius: # swap x and y radius and rotate by 90° afterwards to create an ellipse with x_radius < y_radius @@ -910,7 +907,7 @@ def makeCircle(cls, radius, center, normal): return w @classmethod - def makeEllipse(cls, x_radius, y_radius, center, normal, angle1=360.0, angle2=360.0, + def makeEllipse(cls, x_radius, y_radius, center, normal, xDir, angle1=360.0, angle2=360.0, rotation_angle=0.0, closed=True): """ Makes an Ellipse centered at the provided point, having normal in the provided direction @@ -924,7 +921,7 @@ def makeEllipse(cls, x_radius, y_radius, center, normal, angle1=360.0, angle2=36 :return: """ - ellipse_edge = Edge.makeEllipse(x_radius, y_radius, center, normal, angle1, angle2) + ellipse_edge = Edge.makeEllipse(x_radius, y_radius, center, normal, xDir, angle1, angle2) if angle1 != angle2 and closed: line = Edge.makeLine(ellipse_edge.endPoint(), ellipse_edge.startPoint()) From 914f75aabf6343e7024c4681f7563b5d481dce18 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Tue, 11 Feb 2020 21:22:57 +0100 Subject: [PATCH 08/16] ran black against the changes --- cadquery/cq.py | 38 ++++++++-- cadquery/occ_impl/shapes.py | 50 +++++++++---- tests/test_cad_objects.py | 67 ++++++++++++++--- tests/test_cadquery.py | 141 +++++++++++++++++++++++++++--------- 4 files changed, 232 insertions(+), 64 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index d7c1d4093..310968cb5 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -1567,8 +1567,18 @@ def parametricCurve(self, func, N=400, start=0, stop=1): return self.spline(allPoints, includeCurrent=False, makeWire=True) - def ellipseArc(self, x_radius, y_radius, angle1=360, angle2=360, rotation_angle=0.0, sense=1, - forConstruction=False, startAtCurrent=True, makeWire=False): + def ellipseArc( + self, + x_radius, + y_radius, + angle1=360, + angle2=360, + rotation_angle=0.0, + sense=1, + forConstruction=False, + startAtCurrent=True, + makeWire=False, + ): """Draw an elliptical arc with x and y radiuses either with start point at current point or or current point being the center of the arc @@ -1583,11 +1593,19 @@ def ellipseArc(self, x_radius, y_radius, angle1=360, angle2=360, rotation_angle= :param makeWire: convert the resulting arc edge to a wire """ - # Start building the ellipse with the current point as center center = self._findFromPoint(useLocalCoords=False) - e = Edge.makeEllipse(x_radius, y_radius, center, self.plane.zDir, self.plane.xDir, angle1, angle2, sense==1) - + e = Edge.makeEllipse( + x_radius, + y_radius, + center, + self.plane.zDir, + self.plane.xDir, + angle1, + angle2, + sense == 1, + ) + # Rotate if necessary if rotation_angle != 0.0: e = e.rotate(center, center.add(self.plane.zDir), rotation_angle) @@ -2082,8 +2100,16 @@ def ellipse(self, x_radius, y_radius, rotation_angle=0.0, forConstruction=False) .center(10, 20).ellipse(100,10) .center(0, 0).ellipse(50, 5) """ + def makeEllipseWire(obj): - elip = Wire.makeEllipse(x_radius, y_radius, obj, Vector(0, 0, 1), Vector(1, 0, 0), rotation_angle=rotation_angle) + elip = Wire.makeEllipse( + x_radius, + y_radius, + obj, + Vector(0, 0, 1), + Vector(1, 0, 0), + rotation_angle=rotation_angle, + ) elip.forConstruction = forConstruction return elip diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 6b75c236e..969433bee 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -77,7 +77,7 @@ from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder -from OCC.Core.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse # geometry construction +from OCC.Core.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse # geometry construction from OCC.Core.GCE2d import GCE2d_MakeSegment from OCC.Core.GeomAPI import GeomAPI_Interpolate, GeomAPI_ProjectPointOnSurf @@ -745,8 +745,17 @@ def makeCircle( return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) @classmethod - def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), xdir=Vector(1, 0, 0), - angle1=360.0, angle2=360.0, sense=1): + def makeEllipse( + cls, + x_radius, + y_radius, + pnt=Vector(0, 0, 0), + dir=Vector(0, 0, 1), + xdir=Vector(1, 0, 0), + angle1=360.0, + angle2=360.0, + sense=1, + ): """ Interpolate a spline through the provided points. :param cls: @@ -770,7 +779,9 @@ def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1 if y_radius > x_radius: # swap x and y radius and rotate by 90° afterwards to create an ellipse with x_radius < y_radius correction_angle = 90.0 * DEG2RAD - ellipse_gp = gp_Elips(ax2, y_radius, x_radius).Rotated(ax1, correction_angle) + ellipse_gp = gp_Elips(ax2, y_radius, x_radius).Rotated( + ax1, correction_angle + ) else: correction_angle = 0.0 ellipse_gp = gp_Elips(ax2, x_radius, y_radius) @@ -779,17 +790,18 @@ def makeEllipse(cls, x_radius, y_radius, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1 ellipse = cls(BRepBuilderAPI_MakeEdge(ellipse_gp).Edge()) else: # arc case # take correction_angle into account - ellipse_geom = GC_MakeArcOfEllipse(ellipse_gp, - angle1 * DEG2RAD - correction_angle, - angle2 * DEG2RAD - correction_angle, - sense==1).Value() + ellipse_geom = GC_MakeArcOfEllipse( + ellipse_gp, + angle1 * DEG2RAD - correction_angle, + angle2 * DEG2RAD - correction_angle, + sense == 1, + ).Value() ellipse = cls(BRepBuilderAPI_MakeEdge(ellipse_geom).Edge()) return ellipse @classmethod - def makeSpline(cls, listOfVector, tangents=None, periodic=False, - tol = 1e-6): + def makeSpline(cls, listOfVector, tangents=None, periodic=False, tol=1e-6): """ Interpolate a spline through the provided points. :param cls: @@ -907,8 +919,18 @@ def makeCircle(cls, radius, center, normal): return w @classmethod - def makeEllipse(cls, x_radius, y_radius, center, normal, xDir, angle1=360.0, angle2=360.0, - rotation_angle=0.0, closed=True): + def makeEllipse( + cls, + x_radius, + y_radius, + center, + normal, + xDir, + angle1=360.0, + angle2=360.0, + rotation_angle=0.0, + closed=True, + ): """ Makes an Ellipse centered at the provided point, having normal in the provided direction :param x_radius: floating point major radius of the ellipse (x-axis), must be > 0 @@ -921,7 +943,9 @@ def makeEllipse(cls, x_radius, y_radius, center, normal, xDir, angle1=360.0, ang :return: """ - ellipse_edge = Edge.makeEllipse(x_radius, y_radius, center, normal, xDir, angle1, angle2) + ellipse_edge = Edge.makeEllipse( + x_radius, y_radius, center, normal, xDir, angle1, angle2 + ) if angle1 != angle2 and closed: line = Edge.makeLine(ellipse_edge.endPoint(), ellipse_edge.startPoint()) diff --git a/tests/test_cad_objects.py b/tests/test_cad_objects.py index 4c84a233e..53cd3c7c8 100644 --- a/tests/test_cad_objects.py +++ b/tests/test_cad_objects.py @@ -16,6 +16,7 @@ DEG2RAD = 2 * math.pi / 360 + class TestCadObjects(BaseTest): def _make_circle(self): @@ -24,7 +25,7 @@ def _make_circle(self): def _make_ellipse(self): - ellipse = gp_Elips(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()), 4., 2.) + ellipse = gp_Elips(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()), 4.0, 2.0) return Shape.cast(BRepBuilderAPI_MakeEdge(ellipse).Edge()) def testVectorConstructors(self): @@ -79,7 +80,9 @@ def testEdgeWrapperCenter(self): def testEdgeWrapperEllipseCenter(self): e = self._make_ellipse() w = Wire.assembleEdges([e]) - self.assertTupleAlmostEquals((1.0, 2.0, 3.0), Face.makeFromWires(w).Center().toTuple(), 3) + self.assertTupleAlmostEquals( + (1.0, 2.0, 3.0), Face.makeFromWires(w).Center().toTuple(), 3 + ) def testEdgeWrapperMakeCircle(self): halfCircleEdge = Edge.makeCircle( @@ -99,10 +102,24 @@ def testEdgeWrapperMakeEllipse1(self): x_radius, y_radius = 20, 10 angle1, angle2 = -75.0, 90.0 arcEllipseEdge = Edge.makeEllipse( - x_radius=x_radius, y_radius=y_radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=angle1, angle2=angle2) + x_radius=x_radius, + y_radius=y_radius, + pnt=(0, 0, 0), + dir=(0, 0, 1), + angle1=angle1, + angle2=angle2, + ) - start = (x_radius * math.cos(angle1 * DEG2RAD), y_radius * math.sin(angle1 * DEG2RAD), 0.0) - end = (x_radius * math.cos(angle2 * DEG2RAD), y_radius * math.sin(angle2 * DEG2RAD), 0.0) + start = ( + x_radius * math.cos(angle1 * DEG2RAD), + y_radius * math.sin(angle1 * DEG2RAD), + 0.0, + ) + end = ( + x_radius * math.cos(angle2 * DEG2RAD), + y_radius * math.sin(angle2 * DEG2RAD), + 0.0, + ) self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3) self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3) @@ -111,10 +128,24 @@ def testEdgeWrapperMakeEllipse2(self): x_radius, y_radius = 10, 20 angle1, angle2 = 0.0, 45.0 arcEllipseEdge = Edge.makeEllipse( - x_radius=x_radius, y_radius=y_radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=angle1, angle2=angle2) + x_radius=x_radius, + y_radius=y_radius, + pnt=(0, 0, 0), + dir=(0, 0, 1), + angle1=angle1, + angle2=angle2, + ) - start = (x_radius * math.cos(angle1 * DEG2RAD), y_radius * math.sin(angle1 * DEG2RAD), 0.0) - end = (x_radius * math.cos(angle2 * DEG2RAD), y_radius * math.sin(angle2 * DEG2RAD), 0.0) + start = ( + x_radius * math.cos(angle1 * DEG2RAD), + y_radius * math.sin(angle1 * DEG2RAD), + 0.0, + ) + end = ( + x_radius * math.cos(angle2 * DEG2RAD), + y_radius * math.sin(angle2 * DEG2RAD), + 0.0, + ) self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3) self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3) @@ -123,10 +154,24 @@ def testEdgeWrapperMakeCircleWithEllipse(self): x_radius, y_radius = 20, 20 angle1, angle2 = 15.0, 60.0 arcEllipseEdge = Edge.makeEllipse( - x_radius=x_radius, y_radius=y_radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=angle1, angle2=angle2) + x_radius=x_radius, + y_radius=y_radius, + pnt=(0, 0, 0), + dir=(0, 0, 1), + angle1=angle1, + angle2=angle2, + ) - start = (x_radius * math.cos(angle1 * DEG2RAD), y_radius * math.sin(angle1 * DEG2RAD), 0.0) - end = (x_radius * math.cos(angle2 * DEG2RAD), y_radius * math.sin(angle2 * DEG2RAD), 0.0) + start = ( + x_radius * math.cos(angle1 * DEG2RAD), + y_radius * math.sin(angle1 * DEG2RAD), + 0.0, + ) + end = ( + x_radius * math.cos(angle2 * DEG2RAD), + y_radius * math.sin(angle2 * DEG2RAD), + 0.0, + ) self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3) self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 78534809a..ed0c63faa 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -497,7 +497,6 @@ def testSpline(self): self.assertAlmostEqual(path.Length(), path1.Length()) def testRotatedEllipse(self): - def rotatePoint(x, y, alpha): # rotation matrix a = alpha * DEG2RAD @@ -517,52 +516,108 @@ def ellipsePoints(r1, r2, a): ex_rot, ey_rot = rotatePoint(*ellipsePoints(r1, r2, a2), ra) # startAtCurrent=False, sense = 1 - ellipseArc1 = Workplane("XY").moveTo(*p0).ellipseArc( - r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra) + ellipseArc1 = ( + Workplane("XY") + .moveTo(*p0) + .ellipseArc( + r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra + ) + ) start = ellipseArc1.vertices().objects[0] end = ellipseArc1.vertices().objects[1] - self.assertTupleAlmostEquals((start.X, start.Y), (p0[0] + sx_rot, p0[1] + sy_rot), 3) - self.assertTupleAlmostEquals((end.X, end.Y), (p0[0] + ex_rot, p0[1] + ey_rot), 3) + self.assertTupleAlmostEquals( + (start.X, start.Y), (p0[0] + sx_rot, p0[1] + sy_rot), 3 + ) + self.assertTupleAlmostEquals( + (end.X, end.Y), (p0[0] + ex_rot, p0[1] + ey_rot), 3 + ) # startAtCurrent=True, sense = 1 - ellipseArc2 = Workplane("XY").moveTo(*p0).ellipseArc( - r1, r2, startAtCurrent=True, angle1=a1, angle2=a2, rotation_angle=ra) + ellipseArc2 = ( + Workplane("XY") + .moveTo(*p0) + .ellipseArc( + r1, r2, startAtCurrent=True, angle1=a1, angle2=a2, rotation_angle=ra + ) + ) start = ellipseArc2.vertices().objects[0] end = ellipseArc2.vertices().objects[1] - self.assertTupleAlmostEquals((start.X, start.Y), (p0[0] + sx_rot - sx_rot, p0[1] + sy_rot - sy_rot), 3) - self.assertTupleAlmostEquals((end.X, end.Y), (p0[0] + ex_rot - sx_rot, p0[1] + ey_rot - sy_rot), 3) + self.assertTupleAlmostEquals( + (start.X, start.Y), (p0[0] + sx_rot - sx_rot, p0[1] + sy_rot - sy_rot), 3 + ) + self.assertTupleAlmostEquals( + (end.X, end.Y), (p0[0] + ex_rot - sx_rot, p0[1] + ey_rot - sy_rot), 3 + ) # startAtCurrent=False, sense = -1 - ellipseArc3 = Workplane("XY").moveTo(*p0).ellipseArc( - r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra, sense=-1) + ellipseArc3 = ( + Workplane("XY") + .moveTo(*p0) + .ellipseArc( + r1, + r2, + startAtCurrent=False, + angle1=a1, + angle2=a2, + rotation_angle=ra, + sense=-1, + ) + ) start = ellipseArc3.vertices().objects[0] end = ellipseArc3.vertices().objects[1] # swap start and end points for coparison due to different sense - self.assertTupleAlmostEquals((start.X, start.Y), (p0[0] + ex_rot, p0[1] + ey_rot), 3) - self.assertTupleAlmostEquals((end.X, end.Y), (p0[0] + sx_rot, p0[1] + sy_rot), 3) + self.assertTupleAlmostEquals( + (start.X, start.Y), (p0[0] + ex_rot, p0[1] + ey_rot), 3 + ) + self.assertTupleAlmostEquals( + (end.X, end.Y), (p0[0] + sx_rot, p0[1] + sy_rot), 3 + ) # startAtCurrent=True, sense = -1 - ellipseArc4 = Workplane("XY").moveTo(*p0).ellipseArc( - r1, r2, startAtCurrent=True, angle1=a1, angle2=a2, rotation_angle=ra, sense=-1) + ellipseArc4 = ( + Workplane("XY") + .moveTo(*p0) + .ellipseArc( + r1, + r2, + startAtCurrent=True, + angle1=a1, + angle2=a2, + rotation_angle=ra, + sense=-1, + ) + ) start = ellipseArc4.vertices().objects[0] end = ellipseArc4.vertices().objects[1] # swap start and end points for coparison due to different sense - self.assertTupleAlmostEquals((start.X, start.Y), (p0[0] + ex_rot - ex_rot, p0[1] + ey_rot - ey_rot), 3) - self.assertTupleAlmostEquals((end.X, end.Y), (p0[0] + sx_rot - ex_rot, p0[1] + sy_rot - ey_rot), 3) + self.assertTupleAlmostEquals( + (start.X, start.Y), (p0[0] + ex_rot - ex_rot, p0[1] + ey_rot - ey_rot), 3 + ) + self.assertTupleAlmostEquals( + (end.X, end.Y), (p0[0] + sx_rot - ex_rot, p0[1] + sy_rot - ey_rot), 3 + ) def testEllipseArcsClockwise(self): - ellipseArc = Workplane("XY").moveTo(10,15).ellipseArc(5, 4, -10, 190, 45, sense=-1, startAtCurrent=False) + ellipseArc = ( + Workplane("XY") + .moveTo(10, 15) + .ellipseArc(5, 4, -10, 190, 45, sense=-1, startAtCurrent=False) + ) sp = ellipseArc.val().startPoint() ep = ellipseArc.val().endPoint() - self.assertTupleAlmostEquals((sp.x, sp.y), ( 7.009330014275797, 11.027027582524015), 3) - self.assertTupleAlmostEquals((ep.x, ep.y), (13.972972417475985, 17.990669985724203), 3) + self.assertTupleAlmostEquals( + (sp.x, sp.y), (7.009330014275797, 11.027027582524015), 3 + ) + self.assertTupleAlmostEquals( + (ep.x, ep.y), (13.972972417475985, 17.990669985724203), 3 + ) - ellipseArc = (ellipseArc - .ellipseArc(5, 4, -10, 190, 315, sense=-1) + ellipseArc = ( + ellipseArc.ellipseArc(5, 4, -10, 190, 315, sense=-1) .ellipseArc(5, 4, -10, 190, 225, sense=-1) .ellipseArc(5, 4, -10, 190, 135, sense=-1) ) @@ -570,14 +625,22 @@ def testEllipseArcsClockwise(self): self.assertTupleAlmostEquals((sp.x, sp.y), (ep.x, ep.y), 3) def testEllipseArcsCounterClockwise(self): - ellipseArc = Workplane("XY").moveTo(10,15).ellipseArc(5, 4, -10, 190, 45, startAtCurrent=False) + ellipseArc = ( + Workplane("XY") + .moveTo(10, 15) + .ellipseArc(5, 4, -10, 190, 45, startAtCurrent=False) + ) sp = ellipseArc.val().startPoint() ep = ellipseArc.val().endPoint() - self.assertTupleAlmostEquals((sp.x, sp.y), (13.972972417475985, 17.990669985724203), 3) - self.assertTupleAlmostEquals((ep.x, ep.y), ( 7.009330014275797, 11.027027582524015), 3) + self.assertTupleAlmostEquals( + (sp.x, sp.y), (13.972972417475985, 17.990669985724203), 3 + ) + self.assertTupleAlmostEquals( + (ep.x, ep.y), (7.009330014275797, 11.027027582524015), 3 + ) - ellipseArc = (ellipseArc - .ellipseArc(5, 4, -10, 190, 135) + ellipseArc = ( + ellipseArc.ellipseArc(5, 4, -10, 190, 135) .ellipseArc(5, 4, -10, 190, 225) .ellipseArc(5, 4, -10, 190, 315) ) @@ -591,13 +654,23 @@ def testEllipseCenterAndMoveTo(self): r1, r2 = 20, 10 ra = 25 - ellipseArc1 = Workplane("XY").moveTo(*p0).ellipseArc( - r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra) + ellipseArc1 = ( + Workplane("XY") + .moveTo(*p0) + .ellipseArc( + r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra + ) + ) sp1 = ellipseArc1.val().startPoint() ep1 = ellipseArc1.val().endPoint() - ellipseArc2 = Workplane("XY").moveTo(*p0).ellipseArc( - r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra) + ellipseArc2 = ( + Workplane("XY") + .moveTo(*p0) + .ellipseArc( + r1, r2, startAtCurrent=False, angle1=a1, angle2=a2, rotation_angle=ra + ) + ) sp2 = ellipseArc2.val().startPoint() ep2 = ellipseArc2.val().endPoint() @@ -862,9 +935,9 @@ def testNestedCircle(self): self.assertEqual(14, s.faces().size()) def testConcentricEllipses(self): - concentricEllipses = (Workplane("XY") - .center(10, 20).ellipse(100,10) - .center(0, 0).ellipse(50, 5)) + concentricEllipses = ( + Workplane("XY").center(10, 20).ellipse(100, 10).center(0, 0).ellipse(50, 5) + ) v = concentricEllipses.vertices().objects[0] self.assertTupleAlmostEquals((v.X, v.Y), (10 + 50, 20), 3) From c1a8e9a9dea27c51b76b43c4f0e941f7c211bdd5 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 14 Mar 2020 18:29:51 +0100 Subject: [PATCH 09/16] Fix docstring of makeEllipse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Adam Urbańczyk --- cadquery/occ_impl/shapes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 969433bee..c92c0d82e 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -757,7 +757,7 @@ def makeEllipse( sense=1, ): """ - Interpolate a spline through the provided points. + Makes an Ellipse centered at the provided point, having normal in the provided direction :param cls: :param x_radius: x radius of the ellipse (along the x-axis of plane the ellipse should lie in) :param y_radius: y radius of the ellipse (along the y-axis of plane the ellipse should lie in) From 1cb58b8ce2246ccc7314cd0437d2d51958fc9f52 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 14 Mar 2020 18:30:33 +0100 Subject: [PATCH 10/16] Fix return value in docstring of makeEllips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Adam Urbańczyk --- cadquery/occ_impl/shapes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index c92c0d82e..101f49e91 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -940,7 +940,7 @@ def makeEllipse( :param angle1: start angle of arc :param angle2: end angle of arc :param rotation_angle: angle to rotate the created ellipse / arc - :return: + :return: Wire """ ellipse_edge = Edge.makeEllipse( From e1729c5580ffca3a896da839e497a31941692726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Urba=C5=84czyk?= Date: Sun, 15 Mar 2020 12:11:44 +0100 Subject: [PATCH 11/16] Formatting fix --- tests/test_cadquery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index c2585ebca..89ce9e752 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -544,7 +544,7 @@ def testSpline(self): # test include current path1 = Workplane("XZ").spline(pts[1:], includeCurrent=True).val() self.assertAlmostEqual(path.Length(), path1.Length()) - + # test tangents and offset plane pts = [(0, 0), (-1, 1), (-2, 0), (-1, 0)] tangents = [(0, 1), (1, 0)] From 0ce5e5ea2e9992fbc1c17c5ad5b80d2c1347f38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Urba=C5=84czyk?= Date: Sun, 15 Mar 2020 12:21:34 +0100 Subject: [PATCH 12/16] Increase test coverage --- tests/test_cadquery.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 89ce9e752..1f6203297 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -644,8 +644,12 @@ def ellipsePoints(r1, r2, a): angle2=a2, rotation_angle=ra, sense=-1, + makeWire=True ) ) + + self.assertEqual(len(ellipseArc4.ctx.pendingWires),1) + start = ellipseArc4.vertices().objects[0] end = ellipseArc4.vertices().objects[1] From f7d873f8c3bb5013aede3f855a0a3c25943062da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Urba=C5=84czyk?= Date: Sun, 15 Mar 2020 12:37:02 +0100 Subject: [PATCH 13/16] Formatting fixes --- tests/test_cadquery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 1f6203297..a580aebcd 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -644,11 +644,11 @@ def ellipsePoints(r1, r2, a): angle2=a2, rotation_angle=ra, sense=-1, - makeWire=True + makeWire=True, ) ) - self.assertEqual(len(ellipseArc4.ctx.pendingWires),1) + self.assertEqual(len(ellipseArc4.ctx.pendingWires), 1) start = ellipseArc4.vertices().objects[0] end = ellipseArc4.vertices().objects[1] From 47580f6c854e1bd1eb1a066f69e1bbd79b126b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Urba=C5=84czyk?= Date: Sun, 15 Mar 2020 13:05:07 +0100 Subject: [PATCH 14/16] Add test for makeEllipse --- tests/test_cadquery.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index a580aebcd..f9f947eef 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -737,6 +737,22 @@ def testEllipseCenterAndMoveTo(self): self.assertTupleAlmostEquals(sp1.toTuple(), sp2.toTuple(), 3) self.assertTupleAlmostEquals(ep1.toTuple(), ep2.toTuple(), 3) + def testMakeEllipse(self): + el = cq.Wire.makeEllipse( + 1, + 2, + cq.Vector(0, 0, 0), + cq.Vector(0, 0, 1), + cq.Vector(1, 0, 0), + 0, + 90, + 45, + True, + ) + + self.assertTrue(el.isClosed()) + self.assertTrue(el.isValid()) + def testSweep(self): """ Tests the operation of sweeping a wire(s) along a path From a24b31fb2e4304797363ff61bbd0e37bc4b7ef5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Urba=C5=84czyk?= Date: Sun, 15 Mar 2020 13:15:45 +0100 Subject: [PATCH 15/16] Test fix --- tests/test_cadquery.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index f9f947eef..03a1657f0 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -738,12 +738,12 @@ def testEllipseCenterAndMoveTo(self): self.assertTupleAlmostEquals(ep1.toTuple(), ep2.toTuple(), 3) def testMakeEllipse(self): - el = cq.Wire.makeEllipse( + el = Wire.makeEllipse( 1, 2, - cq.Vector(0, 0, 0), - cq.Vector(0, 0, 1), - cq.Vector(1, 0, 0), + Vector(0, 0, 0), + Vector(0, 0, 1), + Vector(1, 0, 0), 0, 90, 45, From e3edecdc8305f98bd3fe5e24c66b812471151337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Urba=C5=84czyk?= Date: Sun, 15 Mar 2020 13:54:42 +0100 Subject: [PATCH 16/16] Formatting + typo fix --- tests/test_cadquery.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 03a1657f0..e41e3f733 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -739,18 +739,10 @@ def testEllipseCenterAndMoveTo(self): def testMakeEllipse(self): el = Wire.makeEllipse( - 1, - 2, - Vector(0, 0, 0), - Vector(0, 0, 1), - Vector(1, 0, 0), - 0, - 90, - 45, - True, - ) - - self.assertTrue(el.isClosed()) + 1, 2, Vector(0, 0, 0), Vector(0, 0, 1), Vector(1, 0, 0), 0, 90, 45, True, + ) + + self.assertTrue(el.IsClosed()) self.assertTrue(el.isValid()) def testSweep(self):