-
Notifications
You must be signed in to change notification settings - Fork 391
Ellipse #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ellipse #265
Conversation
Codecov Report
@@ Coverage Diff @@
## master #265 +/- ##
==========================================
+ Coverage 94.98% 95.16% +0.17%
==========================================
Files 18 18
Lines 4411 4567 +156
==========================================
+ Hits 4190 4346 +156
Misses 221 221
Continue to review full report at Codecov.
|
|
Adresses issue #261 |
|
@bernhard-42 Thanks for putting the work into this. Is this ready for review? |
|
@jmwright yes, it is ready for review. The only unresolved issue I know of is the OCCT bug about center of mass of an ellipse - which I cannot do anything. Impact: concentric ellipses need a |
cadquery/cq.py
Outdated
| 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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't angle1=0 be more logical?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
absolutely, however, makeCircle (and hence makeEllipse) uses 360 and I decided to keep that up to the top level. And from the later logic perspective, angle1 needs to be equal angle2 to get a full circle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... or full ellipse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that circle does not expose those options at all - @jmwright what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just looked at Workplane.spline which follows a different approach than Workplane.circle:
It does only have Edge.makeSpline and then applies the following logic
Lines 1441 to 1450 in 74573fc
| e = Edge.makeSpline(allPoints, tangents=tangents, periodic=periodic) | |
| if makeWire: | |
| rv = Wire.assembleEdges([e]) | |
| if not forConstruction: | |
| self._addPendingWire(rv) | |
| else: | |
| rv = e | |
| if not forConstruction: | |
| self._addPendingEdge(e) |
This allows splines to be used when building 2D paths.
If we expose angle1 and angle2 with ellipse shouldn't we change it like spline to allow ellipse arcs being used in building 2D paths? That was my original intention with exposing both angles, but I missed the point that circle doesn't add itself to pendingWires/pendingEdges (and hence my ellipse code doesn't either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't assume that the code in circle is the correct way to do it. I don't think that code has been touched since the early days of CQ, and may not represent our current understanding of best practices.
|
The more I look into the cadquery source code the more I understand from the overall approach. After analysing def ellipseArc(self, x_radius, y_radius, angle1=360, angle2=360, rotation_angle=0.0, sense=1,
forConstruction=False, startAtCurrent=True, makeWire=False):
# start building the ellipse with the current point as center ...
center = self._findFromPoint(useLocalCoords=True)
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)
# and 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)
print(rv)
return self.newObject([rv])This would be analogous to circle:
Note, I had to add a sense parameter to control whether the arc goes clockwise or not. x = (cq.Workplane("XY")
.lineTo(0, 1)
.ellipseArc(5, 4, -10, 190, 45, sense=-1)
)
ep = x.val().endPoint()
x = (x
.hLineTo(ep.x + 2)
.ellipseArc(5, 4, -10, 190, -45, sense=-1)
.vLineTo(0)
.mirrorX()
)Would that be an enhancement you would like to add to cadquery? |
|
I think the approach you're suggesting makes sense. If you want to put the work in, I'm all for having the increased functionality. |
|
@bernhard-42 I also think that keeping |
|
I have built I then added a As a side note, since no point is included in the construction, I had to find the last point with |
cadquery/cq.py
Outdated
| # 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if center is in global coords the Vector(0,0,1) is NOK I think. You might want to try: self.plane.zDir
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it is wrong. I will change and add a test case for a transformed plane
cadquery/cq.py
Outdated
|
|
||
| # Rotate if necessary | ||
| if rotation_angle != 0.0: | ||
| e = e.rotate(center, center.add(Vector(0,0,1)), rotation_angle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same potential issue here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above
cadquery/cq.py
Outdated
| 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): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Open question: do we have a usecase for startAtCurrent=False?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I found a similar construct with spline (includeCurrent) where I would also not see an immediate use case. So having seen this pattern in cq, I decided to add the same approach to ellipseArc and let others find out use cases.
But I am also happy to remove if someone has arguments against it.
|
I tried For this example: def sample(angle):
return(
cq.Workplane("XY")
.transformed(offset=(20, 5, 10), rotate=(angle, 0, 0))
.rect(10,10)
.workplane()
.ellipseArc(20, 10, 90, 180, sense=-1, startAtCurrent=False))calling while calling Why does changing the rotation angle from 45° to 46° change the orientation in which OCC creates ellipse arcs? and then flip again 136° |
|
I've opened an issue in pythonocc tpaviot/pythonocc-core#773 |
|
I went for the ellipses1 = [cq.Workplane("XY").transformed(offset=Rx(i, (0,0,20)), rotate=(i,0,0)).ellipseArc(3, 5, 0, 180, startAtCurrent=False).close().extrude(1) for i in range(0, 360, 30)]
ellipses2 = [cq.Workplane("XY").transformed(offset=Ry(i, (0,0,20)), rotate=(0,i,0)).ellipseArc(3, 5, 0, 180, startAtCurrent=False).close().extrude(1) for i in range(0, 360, 30)]I get arcs of ellipses nicely aligned with the sphere surface: |
|
@bernhard-42 @adam-urbanczyk Where are we at with this pull request? Is pythonocc-core issue #773 a blocker for merging this? |
adam-urbanczyk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor docstring remarks
|
@bernhard-42 @jmwright two minor comments on the docstrings. Otherwise I think it can be merged. Using |
Co-Authored-By: Adam Urbańczyk <adam-urbanczyk@users.noreply.github.com>
Co-Authored-By: Adam Urbańczyk <adam-urbanczyk@users.noreply.github.com>
|
@adam-urbanczyk Thought committing your proposals would be sufficient, however there is now a conflict. Shall I rebase my branch and push or can you resolve that? |
|
I will resolve the conflict. |
|
Alright, merging. Thanks for the contribution @bernhard-42 ! |






I have implemented ellipses following the implementation of circles, however, having some more flexibility. Compared to circle the following parameters are avaiable:
x_radius,y_radius: While OCC needsmajor_radius > minor_radius, the implementation allows for arbitraryxandyradiuses. Ifx_radius < y_radius, the implementation swaps both and rotates the resulting ellipse by+90°. This was done to simplify building arbitrary ellipses in cadqueryangle1(default=360), angle2 (default=360): allow to return an arc betweenangle1andangle2. This actually just makesGC_MakeArcOfEllipseavailableclosed(default=False): If an arc, return a closed wire ifTruerotation_angle(default=0.0): rotate the resulting ellipse or arc around its center ifrotation_angle != 0.0