Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist/*
.idea/*
cadquery.egg-info
target/*
.vscode
95 changes: 95 additions & 0 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,65 @@ 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
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):
"""
Draw an arc from the current point, through point1, and ending at point2
Expand Down Expand Up @@ -2023,6 +2082,42 @@ def makeCircleWire(obj):

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):
"""
Creates a polygon inscribed in a circle of the specified diameter for each point on
Expand Down
99 changes: 98 additions & 1 deletion cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
gp_Ax3,
gp_Dir,
gp_Circ,
gp_Elips,
gp_Trsf,
gp_Pln,
gp_GTrsf,
Expand Down Expand Up @@ -76,7 +77,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

Expand Down Expand Up @@ -751,6 +752,62 @@ def makeCircle(
).Value()
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
def makeSpline(cls, listOfVector, tangents=None, periodic=False, tol=1e-6):
"""
Expand Down Expand Up @@ -869,6 +926,46 @@ def makeCircle(cls, radius, center, normal):
w = cls.assembleEdges([circle_edge])
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
def makePolygon(cls, listOfVertices, forConstruction=False):
# convert list of tuples into Vectors.
Expand Down
2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
97 changes: 95 additions & 2 deletions tests/test_cad_objects.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
# 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):

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.0, 2.0)
return Shape.cast(BRepBuilderAPI_MakeEdge(ellipse).Edge())

def testVectorConstructors(self):
v1 = Vector(1, 2, 3)
v2 = Vector((1, 2, 3))
Expand Down Expand Up @@ -69,6 +77,13 @@ 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
Expand All @@ -82,6 +97,84 @@ 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)

Expand Down
Loading