-
Notifications
You must be signed in to change notification settings - Fork 56
Feature Request: Sweep Operation #33
Comments
This is the next feature in my sights. FreeCAD has separate sweep and extrude operations, but there's that note in the CQ source about extending the extrude operation to extrude along a path. This is a pretty significant philosophical/design difference. Is there one of those ways that would be more CQ-ic? |
hah! funny you ask this. This is one area i explored quite deeply, and The use case I was working on was creating a herringbone gear: to create this geometry, you need to extrude over a piecewise linear path, This is definitely a situation where, though the actual operation you do is I think it woudl be most CQ-esque to implement this as a single, general
in this case, curve coudl be a line or a spline or whatever. in cq, we would still want .extrude( distance, rotateAngle=0.0 ) which we also probably want .sweep(curve,rotateAngle=0.0 ), which is conceptually On Tue, Oct 21, 2014 at 9:14 PM, Jeremy Wright notifications@github.com
|
In the source, the current extrude implementation doesn't take a rotation angle. def extrude(self,distance,combine=True): There's also a twistExtrude function. def twistExtrude(self,distance,angleDegrees,combine=True): If I'm understanding you correctly, we would implement the extrudeSweepTwist function and then combine the extrude and twistExtrude functions. def extrude(self, distance, angleDegrees=0.0, combine=True): That would not break any of the examples that currently use the extrude function, but would add the necessary functionality to do the twist. Then there would also be sweep, which as you mentioned would take a curve. Is there any way to use the forConstruction boolean to avoid having to pass a curve to the function? Just taking a stab at it, some thing resembling the rect example here s = Workplane().spline(pnts, forConstruction=True).curve().circle(0.25) That's a rough stab at it that won't stand up under much scrutiny, but hopefully it gets my line of thinking across. It seems very CQ-esque to have some form that works that way if possible. |
if memory serves, i think twistExtrude might have some problems. i'd first i like the direction you're heading with constructing the spline to avoid a i guess it is kind of true that the curve starts on the workplane though... On Thu, Oct 23, 2014 at 9:57 PM, Jeremy Wright notifications@github.com
|
|
CQ doesnt currently support creating wires in 3d space, but it should be A wire need not be closed. Here's how the topology formally works: A curve is a geometric construct. curves include splines, circles, lines, So you would have a wire that has an edge that uses some or all of a spline Regarding sweeping, i do not actually know offhand whether you'd need a Finally, regarding 3d operations, when the user is working with a
Workplane("XZ").circle(0.2).spline((0,.4,2),(0,4,3)...).sweep(shell=True) this is simple and clean syntactically. And, it has the advantage that the Notice that the example i used is using XZ as the workplane. That means By extension, a three-tuple in this context would be (x,z,y), not (x,y,z). One way we could solve this is to add a CQ operation that allows the user
Workplane("XZ").circle(0.2).globalCoords().spline((0,.4,2),(0,4,3)...).sweep(shell=True) in this example, after the call to globalCoords(), 3-tuples are x-y-z, not On Fri, Oct 24, 2014 at 11:28 PM, Jeremy Wright notifications@github.com
|
It sounds to me like we should start with defining a "curve" (or set of curves) in 3D space. We can expand from there to wires if the techniques we try work well. If we decided to add this functionality I would probably treat it as a separate feature request that supports the Sweep request. Above, you gave the following example form. wp = Workplane("XZ").circle(0.2).spline((0,.4,2),(0,4,3)...).sweep(shell=True) Would you want to use a shell boolean that way, or control shelling by allowing the user to draw an inner outline to define the wall? If we did use the shell boolean, wouldn't the user also need to define a wall thickness? Also, should the order of the circle and spline be reversed, with a forConstruction boolean on the spline? That way it would be consistent with the way things seem to currently work. This is from one of the CQ examples: result = cadquery.Workplane(cadquery.Plane.XY()).box(4, 2, 0.5).faces(">Z").workplane().rect(3.5, 1.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82.0, depth=None) So maybe... wp = Workplane("XZ").spline((0,.4,2),(0,4,3)..., forConstruction=True).circle(0.2).sweep(shell=True, wallThickness=1.0) Regarding the user having to shift their frame of reference in 3 dimensions: wp = Workplane("XZ").circle(0.2).globalCoords().spline((0,.4,2),(0,4,3)...).sweep(shell=True) It seems like it would be reasonable to expect more of the user if they want a more advanced operation like this. The first form above (without globalCoords) would be the most intuitive to me if I was working with the operation (I think). You just have to look at the first two letters in your workplane designation to get your bearings. The user already has to keep track of the fact that Workplane("XZ").lineTo(0,1) will draw a line straight up the Z axis from the origin. I know that simply adding that 3rd dimension causes a lot of people problems, but to be fair, they are attempting to do something beyond what I think most people will try (a sweep operation in 3 dimensions). There's more discussion to be had on what the right answer is, but I'm leaning towards my modification of your first form (with spline first and the thickness added for the shell). |
wp = Workplane("XZ").spline((0,.4,2),(0,4,3)...,
wallThickness would be needed for a shell. there's also, now that you the order for the spline and circle are tricky. the question becomes: what Regarding the user having to shift their frame of reference in 3 dimensions:
yes you are right. One other thing to consider here is that, in this case, Here's one other datapoint to consider-- Solidworks. I use Solidworks a In Solidworks ( albeit a very dated version i got as a student), they have In practice, this separation is quite helpful. I use 2-d coordinates for my spine = 3dSketch().spline((0,0,0),(0,.1,.2)....) sketch = Workplane("XY").circle(0.2).sweep(spine) though this is more typing, im beginning to think that pounding all of this on top of that, i think there's the possibility that the person wants to spine = Workplane("YZ").arcTo((0,2)).lineTo(2,2)) sketch = Workplane("XY").circle(0.2).sweep(spine) in this case, i was able to work in 2d both times-- but in different On Tue, Oct 28, 2014 at 10:58 AM, Jeremy Wright notifications@github.com
|
Yes, good point.
Correct, I was thinking that the spline call would not change anything. Everything would stay grounded in/on the workplane. It seems like that would give the user a safe haven to always retreat to when things got confusing. They could always count on the workplane being the stable basis for the coordinate system and origin. |
Is sweeping going to make possible something like this? That would be cool.
|
yes those both describe sweeping operations. for use case #1, splines there are also a couple of other common parameters with a sweep operation: (1) twist. sometimes, you would like to "twist" the profile being swept as (2). tube/solid. this normally controls whether you get a solid or a tube. (3) shell thickness. if you want a tube, you also normally specify a On Mon, Nov 24, 2014 at 6:19 AM, eudoxos notifications@github.com wrote:
|
The following FreeCAD script will give a solid coil, which I'm using as an approximation of a spring in an assembly. It gives a reference of how CQ might handle sweeping a pipe or coil under the hood. We can already make a helix in CQ and the section to sweep. What we lack is the ability to sweep the section over the path. import Part
from FreeCAD import Base
helix = Part.makeHelix(1.016, 8.89, 10.795, 0.0)
section = Part.Wire(Part.makeCircle(0.4445, Base.Vector(10.795, 0, 0), Base.Vector(0, 1, 0)))
makeSolid = True
isFrenet = True
pipe = Part.Wire(helix).makePipeShell([section], makeSolid, isFrenet)
Part.show(pipe) It's also interesting to note that this script runs in the CQ module for FreeCAD without modification, and prevents me from having to run it in the stock scripting window or in the Python console. Any valid Python should run fine in the module because of how it executes the code. |
Here's a resource that I used to figure out the code above. |
On things like this I normally start hacking around with FreeCAD scripting in the CadQuery FreeCAD module, and then try to figure out how to make that mesh with how we want it to work in CadQuery. Below are a couple of my experiments. I've been trying to figure out how to set shell wall thickness, but everything I've tried so far ends up crashing FreeCAD. Straight wire path and a solid result: import Part
from FreeCAD import Base
l=Part.Line()
l.StartPoint=(0.0,0.0,0.0)
l.EndPoint=(0.0,1.0,0.0)
section = Part.Wire(Part.makeCircle(0.4445, Base.Vector(0, 0, 0), Base.Vector(0, 1, 0)))
makeSolid = True
isFrenet = True
wire = Part.Wire([l.toShape()])
pipe = wire.makePipeShell([section], makeSolid, isFrenet)
Part.show(wire)
Part.show(section)
Part.show(pipe) Curved wire path and a solid result: import Part
from FreeCAD import Base
# Circular section to sweep
section = Part.Wire(Part.makeCircle(0.4445, Base.Vector(0, 0, 0), Base.Vector(0, 1, 0)))
# Three point arc
arc = Part.Arc(Base.Vector(0, 0, 0), Base.Vector(-10, 0, 0), Base.Vector(0, -10, 0))
makeSolid = True
isFrenet = True
# Create a wire from the line/edge
wire = Part.Wire([arc.toShape()])
# Doc the actual sweep
pipe = wire.makePipeShell([section], makeSolid, isFrenet)
Part.show(wire)
Part.show(section)
Part.show(pipe) |
Latest iteration, which can be switched between a linear path and an arc path: import Part
from FreeCAD import Base
import cadquery as cq
S = cq.selectors.StringSyntaxSelector
# Circular section to sweep
section = Part.Wire(Part.makeCircle(0.4445, Base.Vector(0, 0, 0), Base.Vector(0, 1, 0)))
l=Part.Line()
l.StartPoint=(0.0,0.0,0.0)
l.EndPoint=(0.0,10.0,0.0)
# Three point arc
arc = Part.Arc(Base.Vector(0, 0, 0), Base.Vector(-10, 0, 0), Base.Vector(0, -10, 0))
# arc = l
makeSolid = True
isFrenet = True
# Create a wire from the line/edge
wire = Part.Wire([arc.toShape()])
# Doc the actual sweep
pipe = wire.makePipeShell([section], makeSolid, isFrenet)
# Part.show(wire)
# Part.show(section)
# Part.show(pipe)
# CQ object conversion
cqObj = cq.CQ(cq.Shape.cast(pipe))
tube = cqObj.newObject([cq.Shape.cast(pipe)])
# Shell operation
hollow_tube = tube.faces((S('|X') + S('|Z'))).shell(0.1)
# hollow_tube = tube.faces((S('|Y') + S('|Z'))).shell(0.1)
Part.show(hollow_tube.val().wrapped) There's a parallel discussion going on here. |
I have a rough implementation of this operation in the sweep branch. Here's sample code to sweep over a polyline. import cadquery as cq
from Helpers import show
pts = [
(0, 1),
(1, 2),
(2, 4)
]
path = cq.Workplane("XZ").polyline(pts)
sweep = cq.Workplane("XY").circle(1.0).sweep(path)
show(path)
show(sweep) I need to do some cleanup and a lot of manual testing before I even consider adding unit tests, but I think it's a decent start. You pass it a path (the polyline) as an argument. I'm not totally crazy about that implementation, but I feel it's the best option we have. |
Will it accept a spline path? I think that would also be a common case to
|
It should accept anything that is, or can be, converted to a wire. I
|
Ok, I had the isFrenet option of makePipeShell set to True, and that was causing the problem. With that set to False I get a clean sweep. I'm not sure if I should just always leave frenet as False, or if I should expose that option to the user. I don't really want to complicate the API with options like that, but I suppose there are cases where Frenet might make sense. |
Here's some additional info on Frenet. http://www.freecadweb.org/wiki/index.php?title=Part_Sweep#Frenet I think I'm going to have to expose that option in the CadQuery API. I know that a user is going to ask for that eventually. |
Making a shell is tricky with these sweeps. It's very easy to end up with a face that's difficult (or impossible) to grab with CadQuery's selectors, which means that the shell function doesn't work quite right. In that case, the following solution isn't ideal, but works. import cadquery as cq
from Helpers import show
pts = [
(0, 1),
(1, 2),
(2, 4)
]
cutpts = [
(0, 1),
(1, 2),
(2, 4.02)
]
path = cq.Workplane("XZ").spline(pts)
section = cq.Workplane("XY").circle(1.0)
sweep = section.sweep(path)
cutPath = cq.Workplane("XZ").spline(cutpts)
cutSection = cq.Workplane("XY").circle(0.9)
cutSweep = cutSection.sweep(cutPath).translate((0, 0, -0.01))
# show(path)
# show(cutPath)
# show(section)
# show(cutSection)
# show(cutSweep)
show(sweep.cut(cutSweep)) |
Nice! combined with filters by operation id also, we could select the end faces On Tue, Apr 26, 2016 at 10:26 AM, Jeremy Wright notifications@github.com
|
@dcowden Awesome, and a leading reason for CadQuery 2.0. It seems like this is about as good as it's going to get in CadQuery v0.x. Users will be able to do what they need to do (I think), but it just won't be as easy/convenient as we want things to be with CadQuery. Is that your take on this also? |
yes i agree. And, it is worth noting that the 'parent of' selectors are On Tue, Apr 26, 2016 at 10:50 AM, Jeremy Wright notifications@github.com
|
I'll expose the isFrenet option and do the unit tests tonight then. We'll call this one good for now, and look forward to the good things coming with CadQuery 2.0. |
yep, sounds great! thanks for adding this! On Tue, Apr 26, 2016 at 11:34 AM, Jeremy Wright notifications@github.com
|
After thinking through the implementation again, I do think it's better to call val() when calling the freecad_impl.Solid.sweep function rather than calling it inside freecad_impl. It keeps the objects at the same level as the other functions in that class. |
Created PR #145 to implement this operation. |
I think this is fine.
|
Yeah that's good logic. You are right, consistent level of abstraction is
|
The source code makes note that this functionality might be added to the extrude function at some point. While I don't see this being as important as the revolve operation, I think it's something that will be needed.
I'll need to think a little bit about how this could most cleanly be merged with the extrude function.
The text was updated successfully, but these errors were encountered: