cadquery icon indicating copy to clipboard operation
cadquery copied to clipboard

Cutting a cylinder with helix sweep result in "Compound is empty"

Open realfun opened this issue 1 year ago • 4 comments

Hi I am trying to create an auger-shaped marble run lift with the following code, but when running it with latest cadquery it always yield an error: ValueError: Compound is empty

The code:

import cadquery as cq
from ocp_vscode import *

r = 18 # Radius of the helix
p = 18 # Pitch of the helix
h = 30 # Height of the helix

wire = cq.Wire.makeHelix(pitch=p, height=h, radius=r)
helix = cq.Workplane(obj=wire)
marble_track = (
    cq.Workplane('XZ')
    .center(r, 0)
    .circle(8)
    .sweep(helix)
)

post = cq.Workplane().cylinder(h, 22)

result = post.cut(marble_track)

show(result)

I visually inspected the marble_track & the post individually, they seem to be correct, but don't know why cutting the marble_track from the post would return empty compound.

I tried to cut the other way around to figure out what's going on, I.E. result = marble_track.cut(post), this time it yields something really weird.

image

After getting some help from anderson.pa in discord #general channel, change the post to be z-flush would make it render: post = cq.Workplane().cylinder(h, 22, centered=(True, True, False)), but change wire to a bigger height or change the post to smaller radius(18) would cause it to fail.

So it seems to be a bug particular to the helix sweep.

realfun avatar Nov 06 '23 20:11 realfun

I find the cut is successful for a range of rotation angles of the post cylinder around its center.

fail: 0 to ~79 success: ~80 to 360

import cadquery as cq
from cadquery.vis import show

r = 18  # Radius of the helix
p = 18  # Pitch of the helix
h = 30  # Height of the helix

wire = cq.Wire.makeHelix(pitch=p, height=h, radius=r)
helix = cq.Workplane(obj=wire)
marble_track = (
    cq.Workplane('XZ')
    .center(r, 0)
    .circle(8)
    .sweep(helix)
)

post = cq.Workplane().cylinder(h, 22).rotateAboutCenter((0, 0, 1), 80)

result = post.cut(marble_track)

show(result)

Of course I'd rather not consider the rotation of the cylinder in order for a successful cut. It appears to be an OCCT CAD kernel issue.

lorenzncode avatar Nov 10 '23 19:11 lorenzncode

Thanks for looking into this! Curious how do you ever think of trying rotateAboutCenter? This never crossed my mind.

Indeed it works after rotateAboutCenter 80, though it breaks again if h is 31, giving no error and just a cylinder without the coil cut. But if the cylinder is created Z-flush with centered=(1, 1, 0), then it starts working again! Weird, and cumbersome, but it works now. Really appreciated the help. Here is the updated code as a successful work around.

import cadquery as cq
from ocp_vscode import *

r = 18  # Radius of the helix
p = 18  # Pitch of the helix
h = 200  # Height of the helix

wire = cq.Wire.makeHelix(pitch=p, height=h, radius=r)
helix = cq.Workplane(obj=wire)
marble_track = (
    cq.Workplane('XZ')
    .center(r, 0)
    .circle(8)
    .sweep(helix)
)

post = cq.Workplane().cylinder(h, 22, centered=(1, 1, 0)).rotateAboutCenter((0, 0, 1), 80)
result = post.cut(marble_track)

show(result)

I noticed there are some weird line every several rounds: image

realfun avatar Nov 11 '23 04:11 realfun

Curious how do you ever think of trying rotateAboutCenter?

The boundary representation (BREP) of the cylinder includes a seam. The cylinder is not only defined by its origin, radius, height at the kernel level so I experimented with rotation to see if it affected the result.

Take a circle for example. https://dev.opencascade.org/doc/refman/html/classgp___circ.html "Describes a circle in 3D space. A circle is defined by its radius and positioned in space with a coordinate system"

The resulting edge or wire contains a vertex as shown here depending on the defined coordinate system.

import cadquery as cq
from cadquery.vis import show

circle = cq.Workplane().circle(10)
# mark the vertex
mark = cq.Workplane(origin=circle.vertices().val().Center()).sphere(1.0)

show(circle, mark)

Screenshot from 2023-11-11 20-12-27

though it breaks again if h is 31

Yes, modifying h or centered changes the problem and I find I had to play with the angle again (89 to 360 in case of h=31).

there are some weird line every several rounds:

That is from the intersection of the seam of the marble_track. See here:

Screenshot from 2023-11-11 21-24-20

lorenzncode avatar Nov 12 '23 02:11 lorenzncode

Thanks for the detailed explaination, today I learnt :)

For anyone who searched to here, notice that in the original code a vertical circle is used to create the coil sweep, which is slightly off. To be truly perpendicular, it needs to be in a special angle that has to do with pitch and radius, updated code below:

import cadquery as cq
from ocp_vscode import *
from math import pi, atan, degrees

r = 18  # Radius of the helix
p = 18  # Pitch of the helix
h = 200  # Height of the helix

wire = cq.Wire.makeHelix(pitch=p, height=h, radius=r)
helix = cq.Workplane(obj=wire)
c = p / (pi * 2)
angle = -degrees(atan(r/c))  # Helix starting angle, with help of Wolfram Alpha
# print(angle)
marble_track = (
    cq.Workplane()
    .center(r, 0)
    .transformed(rotate=(angle, 0, 0))
    .circle(8)
    .sweep(helix)
)

post = cq.Workplane().cylinder(h, 22, centered=(1, 1, 0)).rotateAboutCenter((0, 0, 1), 80)
result = post.cut(marble_track)

show(result)

realfun avatar Nov 12 '23 04:11 realfun