Open mbway opened 3 years ago
a more complex hypothetical example:
import cadquery as cq
obj = (cq.Workplane('XY')
.circle(10)
.workplane(20).rect(5, 7)
.loft()
)
show_object(obj, options={'alpha': 0.5})
selector = (cq.Workplane('XY')
.box(15, 20, 20, centered=(False, False, False))
.rotate((0, 0, 0), (0, 1, 0), -70)
.translate((10, -10, 10))
)
show_object(selector, options={'alpha': 0.9})
path = obj.intersect(selector).faces('<Z').edges()
tube = cq.Workplane('XZ').circle(0.5).sweep(path)
show_object(path, options={'alpha': 0.5})
show_object(tube, options={'alpha': 0.5})
So I've gotten a bit closer, but I've got an error I don't understand in the final sweep, perhaps because the path has some sharp corners?
import cadquery as cq
obj = (cq.Workplane('XY')
.circle(10)
.workplane(20).rect(5, 7)
.loft()
)
show_object(obj, "obj", options={'alpha': 0.5})
selector = (cq.Workplane('XY')
.box(15, 20, 20, centered=(False, False, False))
.rotate((0, 0, 0), (0, 1, 0), -70)
.translate((10, -10, 10))
)
# show_object(selector, options={'alpha': 0.9})
path = obj.intersect(selector).faces('<Z').edges() #.toPending().wire()
show_object(path, "path", options={'alpha': 0.5})
plane = cq.Plane(
path.val().startPoint(),
cq.Vector(0, 0, 1),
path.val().tangentAt(0),
)
tube = cq.Workplane(plane).circle(0.5).sweep(path)
show_object(tube, "tube", options={'alpha': 0.5})
Uncommenting that toPending().wire()
results in Standard_RangeError:NCollection_Array1::Create
.
That error goes away if you loft between two circles.
import cadquery as cq
obj = (cq.Workplane('XY')
.circle(10)
.workplane(20).circle(5) #.rect(5, 7)
.loft()
)
show_object(obj, "obj", options={'alpha': 0.5})
selector = (cq.Workplane('XY')
.box(15, 20, 20, centered=(False, False, False))
.rotate((0, 0, 0), (0, 1, 0), -70)
.translate((10, -10, 10))
)
# show_object(selector, options={'alpha': 0.9})
path = obj.intersect(selector).faces('<Z').edges().toPending()
show_object(path, "path", options={'alpha': 0.5})
plane = cq.Plane(
path.val().startPoint(),
cq.Vector(0, 0, 1),
path.val().tangentAt(0),
)
tube = cq.Workplane(plane).circle(0.5).sweep(path, clean=False)
show_object(tube, "tube", options={'alpha': 0.5})
Still not sure if it's a kernel error or if I'm not sweeping a multiple edge wire correctly.
I had another go, still got the same error. Here is as close as I could get:
import cadquery as cq
obj = (cq.Workplane('XY')
.circle(10)
.workplane(20).rect(5, 7)
.loft()
)
selector = (cq.Workplane('XY')
.box(15, 20, 20, centered=(False, False, False))
.rotate((0, 0, 0), (0, 1, 0), -70)
.translate((10, -10, 10))
)
# show_object(selector, options={'alpha': 0.9})
path = obj.intersect(selector).faces('<Z').edges()
show_object(path, "path", options={'alpha': 0.5})
paths = path.vals()
# check that these edges are in order and all join to the next edge
path_check_list = paths.copy()
path_check_list.append(path_check_list[0])
for p0, p1 in zip(path_check_list[:-1], path_check_list[1:]):
assert p0.endPoint() == p1.startPoint()
# despite the wire looking reasonable, the sweep still errors
# so do each cut individually
for p in paths:
plane_z_dir = p.tangentAt(0)
plane_x_dir = plane_z_dir.cross(cq.Vector(0, 1, 0))
assert plane_z_dir.dot(plane_x_dir) < 1e-4 # ie. orthogonal
plane = cq.Plane(
p.positionAt(0),
plane_x_dir,
plane_z_dir,
)
tube = cq.Workplane(plane).circle(0.5).sweep(cq.Workplane(p).toPending())
# show_object(tube)
obj = obj.cut(tube)
show_object(obj, "obj", options={'alpha': 0.5})
This looks fine I think:
import cadquery as cq
obj = (cq.Workplane('XY')
.circle(10)
.workplane(20).rect(5, 7)
.loft()
)
show_object(obj, "obj", options={'alpha': 0.5})
selector = (cq.Workplane('XY')
.box(15, 20, 20, centered=(False, False, False))
.rotate((0, 0, 0), (0, 1, 0), -70)
.translate((10, -10, 10))
)
# show_object(selector, options={'alpha': 0.9})
path = obj.intersect(selector).faces('<Z').wires()
show_object(path, "path", options={'alpha': 0.5})
plane = cq.Plane(
path.val().startPoint(),
cq.Vector(0, 0, 1),
path.val().tangentAt(0),
)
tube = cq.Workplane(plane).circle(0.5).sweep(path,transition='round')
show_object(tube, "tube", options={'alpha': 0.5})
result = obj.cut(tube)
show_object(result, "result")
Though I had issues with bigger radii.
Shorter version of the code, note the usage of section. The final cut is not working properly - I tag it for now as a kernel issue. Maybe it'll be solved in OCCT 7.5.
import cadquery as cq
obj = (cq.Workplane('XY')
.circle(10)
.workplane(20).rect(5, 7)
.loft()
)
path = obj.transformed((10,0,0),(0,0,-10)).section().wires()
param=0.0
plane = cq.Plane(
path.val().positionAt(param),
cq.Vector(0, 0, 1),
path.val().tangentAt(param),
)
tube = cq.Workplane(plane).circle(1).sweep(path,transition='round')
result = obj.cut(tube)
show_object(result, "result")
Thank you both for helping me with this. It's hard to know what is 'best practice' and techniques from only looking at the small number of examples and the API reference. It might be easier coming from interactive CAD software where cadquery simply performs operations you are already used to but I have no experience there either.
I didn't realize that when sweeping the workplane origin should be at the start point of the path. I expected that it would translate to where it needed to be. I also thought that the shape wouldn't stay tangent to the path because it didn't during my testing of a simple case(I was probably doing something wrong). Am I correct in thinking that the expected behavior is that the angle between the workplane and the tangent at the start of the path is maintained throughout the sweep?
I have experimented some more and found that for the code in the last comment, the cut sometimes does work if the plane origin is translated slight in y or z.
also I found that translating the swept shape has a different effect to positioning it at the origin and moving the workplane origin. The docs don't go into these sorts of details as far as I can see.
this might be a separate issue but I think section()
isn't working on lofts (I tried for rects, circles and ellipses):
when the sphere line is uncommented it prints 1 (section succeeded) but when calling section on the loft it prints 0
import cadquery as cq
obj = (cq.Workplane('XY')
.rect(5, 7)
.workplane(20)
.rect(5, 7)
.loft()
)
#obj = cq.Workplane('XY').sphere(10)
show_object(obj, options={'alpha': 0.6})
path = obj.section(2).edges()
print(len(path.objects))
show_object(path)
I think your workplane origin is not where you think it is. I'll comment your code below:
import cadquery as cq
obj = (cq.Workplane('XY')
# workplane origin is at (0, 0, 0)
.rect(5, 7)
.workplane(20)
# workplane orign now at (0, 0, 20)
.rect(5, 7)
.loft()
)
#obj = cq.Workplane('XY').sphere(10)
show_object(obj, options={'alpha': 0.6})
path = (
obj
.section(2)
# take a section through (0, 0, 20 + 2), which is above the object
.edges()
)
print(len(path.objects))
show_object(path)
.section()
or a negative offset works in your code.
ok, that was stupid. I see what I did wrong.
Is there a way to reset the origin of the workplane while keeping the current objects in place without keeping track of the current height and doing .workplane(offset=-height)
? I tried doing .workplane(origin=(0, 0, 0)).section(2)
but that seems to put the origin in the middle of the loft for some reason.
ok, that was stupid. I see what I did wrong.
Don't worry about it. I can only spot that mistake so quickly because I've made it myself so many times.
Is there a way to reset the origin of the workplane
Once #464 gets done, I expect you'll be able to do something like:
path = obj.workplane('XY').section(2).edges()
In the meantime I would suggest referring back to a tag, like so:
import cadquery as cq
obj = (cq.Workplane('XY')
# workplane origin is at (0, 0, 0)
.tag('base')
.rect(5, 7)
.workplane(20)
# workplane orign now at (0, 0, 20)
.rect(5, 7)
.loft()
)
#obj = cq.Workplane('XY').sphere(10)
show_object(obj, options={'alpha': 0.6})
path = (
obj
.workplaneFromTagged('base')
# workplane is now the same as when base was tagged, (0, 0, 0)
.section(2)
# take a section through (0, 0, 0 + 2), which is in the object
.edges()
)
print(len(path.objects))
show_object(path)
If you need even more flexibility in where you place the workplane, you could create a cq.Plane
like we do in the previous comments.
The final cut is not working properly - I tag it for now as a kernel issue. Maybe it'll be solved in OCCT 7.5.
@adam-urbanczyk, that wire starts and ends at a sharp corner. So the start and end of the swept solid self-intersect. Even though .isValid()
returns True, I think that shape isn't correct and that's where the problem is.
edit: removed a link to fuzzy cuts and intersect issue, that's got nothing to do with this
I tried calling clean()
before cutting but that didn't help. As a work around, assuming that self-intersection was the issue I created a very thin box at the start/end point of the path and cut away at the tube before using it to cut and that did work (but obviously leaves a sliver un-cut).
I tried intersecting a shape around the seam then unioning that shape back in in the hopes that the geometry would be cleaned up in the process, but I can't even intersect the tube with anything around the seam because I get:
tube.intersect(cutter)
File "/home/matthew/anaconda3/envs/cadquery/lib/python3.8/site-packages/cadquery/cq.py", line 3029, in intersect
newS = newS.clean()
File "/home/matthew/anaconda3/envs/cadquery/lib/python3.8/site-packages/cadquery/occ_impl/shapes.py", line 347, in clean
upgrader.Build()
OCP.Standard.Standard_NullObject: BRep_Tool:: TopoDS_Vertex hasn't gp_Pnt
here cutter
is a box encompassing the seam and tube
is a sweep in the shape of an ellipse with a single seam.
Are there any workarounds I can use to get cutting working properly without waiting for a new release?
My first thoughts were to try splitting that path wire along a continuous edge (eg. where the wire crosses the x-axis) and rotate the order ofthe edges so the path wire starts and ends at that point. That way the tangent at the start and end of the wire is identical and the shape won't intersect.
Oh, check out the way this person unions a sphere at the corners. Perhaps that might be a quick fix? https://stackoverflow.com/a/48491990
Sorry, it appears I was wrong about the path wire starting and ending at a sharp corner. I was looking closely at the tube solid and could see a good corner:
and a bad corner:
and jumped to a conclusion.
The path wire already starts and ends on a continuous edge, so that's not the problem. Here I highlight the start point and also assert that the tangents are equal between the start and end point, so there should be no self intersection:
import cadquery as cq
obj = (cq.Workplane('XY')
.circle(10)
.workplane(20).rect(5, 7)
.loft()
)
path = obj.transformed((10,0,0),(0,0,-10)).section().wires()
param=0.0
plane = cq.Plane(
path.val().positionAt(param),
cq.Vector(0, 0, 1),
path.val().tangentAt(param),
)
show_object(cq.Workplane(
origin=path.val().positionAt(param)).sphere(1),
'path start',
options={'color': 'red'}
)
assert path.val().tangentAt(0) == path.val().tangentAt(1)
tube = cq.Workplane(plane).circle(1).sweep(path,transition='round')
result = obj.cut(tube)
show_object(result, "result")
It does look like a kernel error.
However, cutting with a sphere and individual edges is working pretty well, I hope this is a suitable workaround for you:
import cadquery as cq
groove_radius = 1
obj = (cq.Workplane('XY')
.circle(10)
.workplane(20).rect(5, 7)
.loft()
)
selector = (cq.Workplane('XY')
.box(15, 20, 20, centered=(False, False, False))
.rotate((0, 0, 0), (0, 1, 0), -70)
.translate((10, -10, 10))
)
path = obj.intersect(selector).faces('<Z').edges()
show_object(path, "path", options={'alpha': 0.5})
paths = path.vals()
for p in paths:
plane_z_dir = p.tangentAt(0)
plane_x_dir = plane_z_dir.cross(cq.Vector(0, 1, 0))
assert plane_z_dir.dot(plane_x_dir) < 1e-4 # ie. orthogonal
plane = cq.Plane(
p.positionAt(0),
plane_x_dir,
plane_z_dir,
)
tube = cq.Workplane(plane).circle(groove_radius).sweep(cq.Workplane(p).toPending())
sphere = cq.Workplane('XY', origin = p.endPoint()).sphere(groove_radius)
tube = tube.union(sphere)
obj = obj.cut(tube)
show_object(obj, "obj", options={'alpha': 0.5})
unfortunately I had to make the sphere slightly larger than the tube for it to work (and the same additional radius would stop working if I changed the loft slightly) so still really buggy, but it's good enough. Thanks again for your help
NB: I opened #627 - it should to make the original approach of @marcus7070 work as intended.
What would be the best way to go about cutting a groove into an arbitrary solid? (to create an annular snap-fit joint)
My first thought was to create a copy of the solid, slice it in half along the desired groove location by intersecting it with a large box which contains one half of the solid and selecting the edge of the bottom face of the intersection. Now I have a wire that perfectly follows the contour of the solid. What I would like to do now is to sweep a sphere along this path and cut away from the original solid with it.
I've looked through all the API documentation and examined all the examples but I can't find anything that is similar to what I'm trying to do. Creating a sphere and then calling
sweep
simply giveslist index out of range
. I know this probably means it's looking for 2d shapes on the stack and not finding any, but this error message is not particularly useful as it's too generic.I tried to sweep a circle rather than a sphere the result takes a very long time to compute and is not correct. I think that right now that the swept shape doesn't stay tangent/parallel to the curve being swept through and I can't find any way to achieve this. I think it's possible to create a donut shape in cadquery by revolving a circle but I'm not looking for a circular donut shape.
for now I think another approach could be to intersect a thin box and the solid then create a shell of the intersection and cut that away from the original solid but I don't like this approach as much.