Merge branch 'master' into marcus7070/tangentArc

This commit is contained in:
Adam Urbańczyk
2020-03-19 08:46:22 +01:00
committed by GitHub
6 changed files with 501 additions and 5 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ dist/*
.idea/* .idea/*
cadquery.egg-info cadquery.egg-info
target/* target/*
.vscode

View File

@ -1557,7 +1557,10 @@ class Workplane(CQ):
if tangents: if tangents:
t1, t2 = tangents t1, t2 = tangents
tangents = (self.plane.toWorldCoords(t1), self.plane.toWorldCoords(t2)) tangents = (
self.plane.toWorldCoords(t1) - self.plane.origin,
self.plane.toWorldCoords(t2) - self.plane.origin,
)
e = Edge.makeSpline(allPoints, tangents=tangents, periodic=periodic) e = Edge.makeSpline(allPoints, tangents=tangents, periodic=periodic)
@ -1589,6 +1592,65 @@ class Workplane(CQ):
return self.spline(allPoints, includeCurrent=False, makeWire=True) 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
center = self._findFromPoint(useLocalCoords=False)
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)
# 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): def threePointArc(self, point1, point2, forConstruction=False):
""" """
Draw an arc from the current point, through point1, and ending at point2 Draw an arc from the current point, through point1, and ending at point2
@ -2073,6 +2135,42 @@ class Workplane(CQ):
return self.eachpoint(makeCircleWire, useLocalCoordinates=True) return self.eachpoint(makeCircleWire, useLocalCoordinates=True)
# ellipse from current point
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
: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),
Vector(1, 0, 0),
rotation_angle=rotation_angle,
)
elip.forConstruction = forConstruction
return elip
return self.eachpoint(makeEllipseWire, useLocalCoordinates=True)
def polygon(self, nSides, diameter, forConstruction=False): def polygon(self, nSides, diameter, forConstruction=False):
""" """
Creates a polygon inscribed in a circle of the specified diameter for each point on Creates a polygon inscribed in a circle of the specified diameter for each point on

View File

@ -11,6 +11,7 @@ from OCC.Core.gp import (
gp_Ax3, gp_Ax3,
gp_Dir, gp_Dir,
gp_Circ, gp_Circ,
gp_Elips,
gp_Trsf, gp_Trsf,
gp_Pln, gp_Pln,
gp_GTrsf, gp_GTrsf,
@ -76,7 +77,7 @@ from OCC.Core.TopoDS import (
from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder 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.GCE2d import GCE2d_MakeSegment
from OCC.Core.GeomAPI import GeomAPI_Interpolate, GeomAPI_ProjectPointOnSurf from OCC.Core.GeomAPI import GeomAPI_Interpolate, GeomAPI_ProjectPointOnSurf
@ -751,6 +752,62 @@ class Edge(Shape, Mixin1D):
).Value() ).Value()
return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge()) 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,
):
"""
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)
: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)
:param sense: clockwise (-1) or counter clockwise (1)
:return: an Edge
"""
pnt = Vector(pnt).toPnt()
dir = Vector(dir).toDir()
xdir = Vector(xdir).toDir()
ax1 = gp_Ax1(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
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,
sense == 1,
).Value()
ellipse = cls(BRepBuilderAPI_MakeEdge(ellipse_geom).Edge())
return ellipse
@classmethod @classmethod
def makeSpline(cls, listOfVector, tangents=None, periodic=False, tol=1e-6): def makeSpline(cls, listOfVector, tangents=None, periodic=False, tol=1e-6):
""" """
@ -884,6 +941,46 @@ class Wire(Shape, Mixin1D):
w = cls.assembleEdges([circle_edge]) w = cls.assembleEdges([circle_edge])
return w 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,
):
"""
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: Wire
"""
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())
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 @classmethod
def makePolygon(cls, listOfVertices, forConstruction=False): def makePolygon(cls, listOfVertices, forConstruction=False):
# convert list of tuples into Vectors. # convert list of tuples into Vectors.

View File

@ -1,5 +1,5 @@
from cadquery import * from cadquery import *
from OCC.gp import gp_Vec from OCC.Core.gp import gp_Vec
import unittest import unittest
import sys import sys
import os import os

View File

@ -1,18 +1,21 @@
# system modules # system modules
import math
import sys import sys
import unittest import unittest
from tests import BaseTest 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 ( from OCC.BRepBuilderAPI import (
BRepBuilderAPI_MakeVertex, BRepBuilderAPI_MakeVertex,
BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeEdge,
BRepBuilderAPI_MakeFace, BRepBuilderAPI_MakeFace,
) )
from OCC.GC import GC_MakeCircle from OCC.Core.GC import GC_MakeCircle
from cadquery import * from cadquery import *
DEG2RAD = 2 * math.pi / 360
class TestCadObjects(BaseTest): class TestCadObjects(BaseTest):
def _make_circle(self): def _make_circle(self):
@ -20,6 +23,11 @@ class TestCadObjects(BaseTest):
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()), 2.0) circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()), 2.0)
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge()) return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
def _make_ellipse(self):
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): def testVectorConstructors(self):
v1 = Vector(1, 2, 3) v1 = Vector(1, 2, 3)
v2 = Vector((1, 2, 3)) v2 = Vector((1, 2, 3))
@ -69,6 +77,13 @@ class TestCadObjects(BaseTest):
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3) 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): def testEdgeWrapperMakeCircle(self):
halfCircleEdge = Edge.makeCircle( halfCircleEdge = Edge.makeCircle(
radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180 radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180
@ -100,6 +115,84 @@ class TestCadObjects(BaseTest):
(0, -1, 0), tangent_arc.tangentAt(locationParam=1).toTuple(), 3 (0, -1, 0), tangent_arc.tangentAt(locationParam=1).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): def testFaceWrapperMakePlane(self):
mplane = Face.makePlane(10, 10) mplane = Face.makePlane(10, 10)

View File

@ -545,6 +545,206 @@ class TestCadQuery(BaseTest):
path1 = Workplane("XZ").spline(pts[1:], includeCurrent=True).val() path1 = Workplane("XZ").spline(pts[1:], includeCurrent=True).val()
self.assertAlmostEqual(path.Length(), path1.Length()) 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)]
path2 = Workplane("XY", (0, 0, 10)).spline(pts, tangents=tangents)
self.assertAlmostEqual(path2.val().tangentAt(0).z, 0)
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,
makeWire=True,
)
)
self.assertEqual(len(ellipseArc4.ctx.pendingWires), 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 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())
self.assertTrue(el.isValid())
def testSweep(self): def testSweep(self):
""" """
Tests the operation of sweeping a wire(s) along a path Tests the operation of sweeping a wire(s) along a path
@ -802,6 +1002,13 @@ class TestCadQuery(BaseTest):
self.saveModel(s) self.saveModel(s)
self.assertEqual(14, s.faces().size()) 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 testLegoBrick(self): def testLegoBrick(self):
# test making a simple lego brick # test making a simple lego brick
# which of the below # which of the below