Closed fragmuffin closed 5 years ago
@dcowden
Your improvement list on the gist is good!... to repeat it:
It's funny to think that one of the oldest staples of mechanical engineering is so complex! But the fact is, a thread is a very complex structure; with the exception of the top and bottom face, there isn't a single flat, cylindrical, conical, or spherical face on a thread!
With regard to the cadquery direct API (#167) This highlights the need to:
I agree with your enhancements!, many will be quite simple to implement... the ones I'm more concerned with are:
Taper thread entry and exit The cross-section solution above doesn't translate easily to a helical sweep. In order to do this with a sweep, the cross section's scale would need to be altered throughout the sweep, something I don't see in the existing FreeCAD or OCC interfaces.
Although this article depicts a scale-varied thread (or impeller), but it's not clear how that was achieved.
Ball Screw It's not in your list, but still relevant. All thread profiles comprised of linear lines translate to a helical edge on the 2d profile, but a circular profile will be something else... my guess is an arc (which is actually simpler than a helix... hmm)
Yes, sadly threads are complex. Have a look at this:
https://forum.onshape.com/discussion/4867/new-featurescript-thread-creator
I created something with most of these features for Onshape. It was hard, and even hard to get to perform well. A later version supported buttress threads as well ( which are really good for 3-d printing since they do not have overhangs)
I found in that work that a common use of arbitrary threadforms tend to be users prototyping medical devices. For that problem, there are a lot of odd threads.
I was also reminded looking back at that: i created a flag that allowed creating more or less accurate threads. The inaccurate versions creates 'fully sharp' threads. The even worse-performing version creates the actual correct fillets at the thread root and tip.
Ball screws are definitely in the list too. I ended up not creating them because they are highly dependent on the specific manufacturer ( IE, there didnt appear to be any applicable standards for their geometries )
@fragmuffin As you and @dcowden have mentioned, threads are expensive computationally. However, I'd still be curious how the same thread created manually in FreeCAD (point-and-click GUI) does. I implemented the sweep operation quite awhile ago, but it's one of our newer operations and hasn't been used extensively yet, as far as I know. There could be some inefficiencies that have been introduced inside the CadQuery API.
If you want to include this thread generator in the official examples let me know. I'm in the process of cleaning up the examples, converting them to be compliant with the CadQuery Gateway Interface, and merging them all into the main library. Right now we've got examples in multiple places and you can't copy them between environments. After this update is done we'll be able to copy a script from the FreeCAD module into a Jupyter notebook and have it run without modifications.
@jmwright @fragmuffin one other thing to note regarding threads.
In Onshape land, I implemented two features-- one is a thread creator, which creates arbitrary threads. The other is a hole/countersink creator, which allows you to pick BASED on standard thread sizes, but actually creates the necessary tap, clear, and csk holes.
Since Onshape tracks how many users reference each one, its a good way to see which ones are most popular. These two features are used about equally.
The interesting thing about the hole creator is that most of the code is actually python. Onshape uses weird lookup table constructs, so i actually forward generate their weird language using python. I solved the hard problem of computing the threaded length, socket dimensions, and all that other nasty stuff in python, so i can re-use that for CQ also.
I found something a bit puzzling...
import cadquery as cq
from Helpers import show
import Part as FreeCADPart
pitch = 2
depth = 1
height = 10
radius = 2
thread = cq.Workplane("XY").newObject([
cq.Shape.cast(FreeCADPart.makeThread(
pitch, depth, height, radius
))
])
show(thread, (204, 204, 204, 0.3))
there are so many things wrong with the result
pitch=2
and height=10
should result in 5 turns, but no matter what parameters I enter, there's always 2 turnsisFrenet=False
, but the shape starts and ends in an edge (a line)So I don't know how it's supposed to be called, or how it can be used. :confused:
@fragmuffin This code takes CadQuery out of the mix and gives the same result. I get the same behavior in both FreeCAD 0.16 and 0.17.
import Part
pitch = 2
depth = 1
height = 10
radius = 2
thread = Part.makeThread(pitch, depth, height, radius)
Part.show(thread)
When I check the height with the measure tool it's closer to 6mm than 10mm. I'm not sure what's going on yet, but at least we can eliminate CadQuery as the culprit.
This threadmaker macro is interesting. They talk about high CPU utilization too.
https://www.freecadweb.org/wiki/Macro_screw_maker1_2
Search for "def cutIsoThread"
@jmwright I checked out that plugin, it's very nice! But like everything else I've seen, those threads are made by sweeping a profile and cutting it from a cylinder. I couldn't shake the idea that threads could be made much more elegantly.
So I did it! I've been conversing with @tomate44 on the FreeCAD python forums about this (details in this thread, pun intended, but also unavoidable)
some examples
first working in this commit
@dcowden @jmwright do you think this has a place in the cadquery API? a function to convert a wire into a face so it may be swept into a thread?
Nice work @fragmuffin ! Where do you think the wire-to-face operation would fit into the existing API?
I don't know @jmwright
For the profile...
import cadquery
points = [(2, 0), (3, 1), (2, 2)]
profile = cadquery.Workplane("XZ").moveTo(*points[0]).polyline(points[1:]).wire()
Conversion Utility It's a conversion, rather than an operation...
"convert this wire into an equivalent cross-section face"
so perhaps it should be a utility on the side.
from cadquery import thread_profile_to_cross_section
cross_section = thread_profile_to_cross_section(profile)
Operation Or do you think it's an operation on a wire to turn it into a face, conceptually similar to a wire being extruded being "converted" into a solid.
cross_section = profile.threadProfile2CrossSection()
Regarding our old friend:
Part.makeThread(pitch, depth, height, radius)
I found this in the OCC docs, the thread in the tutorial looks a lot like the one pictured in our previous posts.
https://www.opencascade.com/doc/occt-7.2.0/overview/html/occt__tutorial.html#sec4
For what it is worth, when Parasolid is the kernel, i also found that it performed best when you revolve a profile along a helix, and then cut that from a cylinder. Making internal threads is much the same, you just flip your profile.
The nice thing about doing it that way is that you can make just about any thread profile using the same code-- the difference is the sketch you rotate.
I feel like I'm so close!
I've built a fasteners
basis with real threads so the following code: (using cqparts)
import cadquery
from Helpers import show
import cqparts
from cqparts.fasteners import Fastener
from cqparts.types import threads
screw_m3_4_5 = Fastener(
head=('pan', {
'diameter': 5.2,
'height': 2,
'fillet': 1,
}
),
drive=('phillips', {
'diameter': 3,
'depth': 2,
'width': 0.6,
}
),
thread = (threads.iso_262.ISO262Thread, {
'radius': 3.0 / 2,
#'pitch': 0.35, # build invalid shape
'pitch': 0.7,
}
),
length=4.5,
)
show(screw_m3_4_5.object, (200, 200, 200, 0.3))
renders this
based on the above code, you can select from a number of heads, and a number of screw drives.
However, threads are still very temperamental, and I can't see me solving it any time soon. the solutions in this forum discussion sometimes work, and sometimes don't. (and weirdly, sometimes they work when run from a FreeCAD console, and not from an ipython console on exactly the same object)
So I'm going to switch to a default of just rendering a cylinder instead of the real thread.
I still think accurate threads is a valuable asset to have, especially when it comes to 3d printable objects. but I think I need to put that on the back-burner.
If anybody feels like a challenge, check out https://github.com/fragmuffin/cqparts/issues/1 to see sample code to generate dud thread solids.
Holy cow that's incredible! Amazing work!
Most users I've talked to end up being fine with the cylinder, even if threads are fast for one model they are too slow when you have many in an assembly. There seem to be two distinct user popuations. (A) users using standard thread sizes. They just want the cylinder, but if anything want to automatically drill the clearance or tap hole I'm the mating piece. They want normal tolerancing selections.(iso or ansi thread classes) (B) users designing custom threads, for example medical. These users do want the threads, and are probably going to 3d print it. They want fine grained control on the tolerances and generally want absolute tolerancing vs iso because they are going to adjust based in their particular printer. (For example, I want to generate a 0.75" x 40 tpi thread, oversized by 0.020" )
@fragmuffin That's awsome. So the core issue is that if I tried to 3D print this screw right now it wouldn't be watertight, correct? Sounds like you and Dave have figured out a compromise. Could cylinder vs threads just be a parameter you pass to the generators?
For anyone wanting to try cqparts out, make sure that you grab the develop
branch, open the Python console in FreeCAD, and enter the following, setting the correct path to where you've downloaded/cloned cqparts.
import sys
sys.path.insert(0, "[/path/to/download]/cqparts/src")
@dcowden
(A) users using standard thread sizes. They just want the cylinder, but if anything want to automatically drill the clearance or tap hole I'm the mating piece
That's exactly what I'm designing in next... the user can create a fastener, then "apply" it to a work-piece. I'll let you know how that goes too ;)
(B) users designing custom threads, for example medical. These users do want the threads, and are probably going to 3d print it
well this is perfect for them too! this code defines a custom thread with any profile they want
import cadquery
from Helpers import show
from cqparts.params import *
from cqparts.types.threads import Thread, thread
@thread('my_thread') # registration is not mandatory, but recommended
class MyThread(Thread):
my_param = FloatRange(0, 5, 2.5) # for example
def build_profile(self):
# can be any shape
points = [
(self.my_param, 0),
(self.radius, self.pitch/2),
(self.my_param, self.pitch),
]
profile = cadquery.Workplane("XZ") \
.moveTo(*points[0]).polyline(points[1:])
return profile.wire()
screw_m3_4_5 = Fastener(
# ... as above
thread = ('my_thread', {
'radius': 3.0 / 2,
'pitch': 0.7,
'my_param': 2.1,
}
),
length=5,
)
@jmwright yes, you're right, not watertight :(
also, correct path can be universally set with this code. if you have folders side-by-side:
import os
import sys
import inspect
if 'MYSCRIPT_DIR' in os.environ:
# the cadquery plugin adds `MYSCRIPT_DIR` to the environment
_this_path = os.environ['MYSCRIPT_DIR']
else:
_this_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
# or just adjust this relative path to suit your needs
sys.path.insert(0, os.path.join(_this_path, '..', 'cqparts', 'src'))
Could cylinder vs threads just be a parameter you pass to the generators?
yes, I'm going to do that too.. to only have real threads possible seems like a silly move, as @dcowden said, it's slow to build and render when there are a lot of them.
This has been closed in favor of the CQ 2.0 implemention at https://github.com/cadquery/cadquery
A tutorial on how to make a solid geometry thread would be very helpful.
I've (sort of) succeeded by using a few back doors in this gist (more information on implementation there) However, it takes a very long time to generate (~15sec), and it's not perfect.
Any advice on the best way to generate a thread? Perhaps make it into one of the example scripts.