I’ve been using openSCAD almost to the exclusion of other design tools, and although it has substantial limitations (something like a c “struct” would be peachy, for instance) its advantages are more than enough to make it a keeper.
I’ve opened this topic to provide a place where we can trade ideas and code snippets in openSCAD.
openSCAD plays well with source code management.
Having opened the topic, I feel obliged to toss in the first post.
One of the biggest reasons that I prefer working in openSCAD is just the fact that it is a source-code environment.
Anyone who’s used to coding will immediately feel at home. (I’ve been programming since about 1976)
Since you work by writing source code, it’s a very natural fit to manage the version history of your designs using a source code management system such as git or subversion. (Anyone else remember SCCS?).
You don’t have to accumulate blocks of commented-out code to be able to refer back to previous iterations of your designs - simply look back to the corresponding version in your repository.
A tutorial on source code management (git is a nice choice) is beyond the scope of a “3D Printing Space” thread, but no matter which one you choose, it’ll make it easy to keep track of your work.
Epsilon!
Here’s a pretty basic technique. Once you start using openSCAD, it won’t take long before you create a model that won’t render.
Here’s a very simple example:
cube ([10,10,10], center=true);
translate([10,0,0]) cylinder (h=10, d=10, center=true);
If you cut/paste this snippet into openSCAD and try to render it, you’ll get this message:
UI-WARNING: Object may not be a valid 2-manifold and may need repair!
The problem is that the cube and cylinder intersect along a line - that they touch but do not overlap. The same thing will happen at point intersections as well, and when adjacent holes just touch.
The solution is to define a special variable - I always call it “epsilon” that is small enough that when you use it it does not damage your model, and use it to force the objects to overlap or to be separate. (Just how to include it can involve some thought if your model is complex).
Here’s the same bit of code, but using epsilon to avoid the geometric ambiguity.
epsilon=0.001;
cube ([10,10,10], center=true);
translate([10-epsilon,0,0]) cylinder (h=10, d=10, center=true);
Another technique.
Making sockets that fit.
If you’re designing things that plug together, you have to account for a couple of issues.
This little article contains code snippets that let you deal with these issues, at least for cases where the cross-section of your socket and post lie in the XY plane.
The first issue is filament sag. If you print a very narrow vertical slot, you will find that it is slightly narrower in real life than in your model. This is because the cross-section of a line of extruded filament isn’t exactly rectangular, but bulges at the sides. You have to leave a little extra space in the X and Y dimensions of the socket to allow for this non-ideality in both it and the matching post.
The second issue has to do with imperfect extrusion. Even in a CoreXY machine, but much more so in the more common “moving-bed” style printers, objects of considerable mass have to be decelerated and accelerated again to get the extruded bead of filament to go around a corner. As the mechanism slows to a stop in one direction and them accelerates in another, filament continues to be extruded, so it “bunches up” at the corner.
If your control board has enough storage and computational grunt to enable linear advance in the firmware, you can tune this behaviour, but there will always be some residual imperfection to deal with, and in any case, many of us are running stock boards or have other reasons not to fuss with the firmware.
The solution is to model a little alcove at each corner of the socket, to provide space for this bunching-up to happen without rounding off the corner you’re trying to create. The technique isn’t restricted to openSCAD, of course; in fact I first used it in TinkerCAD, in a piece I had printed at the local library before I got my own printer. God love local libraries!
What follows is a few snippets of code, that you’re welcome to cut/paste into your own creations.
As the nozzle approaches within a certain distance ( CornerEaseA ) of a corner, it veers to the outside by a smaller distence (CornerEaseB), and past the corner (also CornerEaseB). It then reverses this deviation as it begins printing in the new direction. I’ve found that by tweaking these two values, I can come very very close to an exactly square interior corner.
(Cut/paste the rest into an openSCAD file)
epsilon=0.001; // to remove geometric ambiguities
Dummy=15;
PostX=10;
PostY=6;
PostZ=8;
// Clearances to compensate for nonidealities
// These values are a decent starting point for 0.4mm line width.
SocketExtraXY=0.35;
SocketExtraDepth=.8; // I do this to leave room for glue.
CornerEaseA=1.0;
CornerEaseB=0.3;
SocketX=(PostX+SocketExtraXY)/2;
SocketY=(PostY+SocketExtraXY)/2;
SocketDepth=PostZ+SocketExtraDepth;
SocketPoly=
[
[SocketX-CornerEaseA,-SocketY],
[SocketX+CornerEaseB,-(SocketY+CornerEaseB)],
[SocketX,-(SocketY-CornerEaseA)],
[SocketX,SocketY-CornerEaseA],
[SocketX+CornerEaseB,SocketY+CornerEaseB],
[SocketX-CornerEaseA,SocketY],
[-(SocketX-CornerEaseA),SocketY],
[-(SocketX+CornerEaseB),SocketY+CornerEaseB],
[-SocketX,SocketY-CornerEaseA],
[-SocketX,-(SocketY-CornerEaseA)],
[-(SocketX+CornerEaseB),-(SocketY+CornerEaseB)],
[-(SocketX-CornerEaseA),-SocketY],
];
module SocketPiece ()
{
difference ()
{
translate ([0,0,Dummy/2])
cube ([Dummy,Dummy,Dummy],center=true);
translate ([0,0,epsilon+Dummy-SocketDepth])
linear_extrude(height=SocketDepth)
polygon (points=SocketPoly);
}
}
module PlugPiece ()
{
translate ([0,0,Dummy/2])
cube ([Dummy,Dummy,Dummy], center=true);
translate ([0,0,Dummy+PostZ/2-epsilon])
cube ([PostX,PostY,PostZ], center=true);
}
/*
- Un-comment each of the following in turn to create test objects
*/
//PlugPiece();
//SocketPiece();
A Handy little filament clip and a trio of openSCAD tips
I decided to model a little clip that keeps the loose end of a spool of filament from wandering off.
While I was writing the code, it occurred to me that it’s a nice, compact illustration of a couple of
openSCAD techniques. … So here goes!
First: Connecting dots!
If the shape you’re trying to create can be represented as an extrusion of a line drawn through a set of points, then it’s pretty easy to whip up. For each adjacent pair of points in the set, all you have to do is drop a cylinder at each point (diameter = line width, length = extrusion length), and then wrap a hull() around both.
Using other techniques, the filament clip could be tricky to describe, but with this trick, it’s only a few lines of code.
Second: Late Binding!
I’m decades older than I’d like to admit - and I’ve been coding since about 1976. Back then, the latest
thing was called Structured Programming, and it came with Strong Types. Before you could use any entity, you had to declare it - that is, describe how it was to be used.
This can keep you out of trouble, because it forces you to think through every detail of your processing.
If you’re calling a subroutine (in openSCAD terminology, a module) and the first argument is declared as an integer, then you will get an error at compile time if you try to call it with anything else.
On the other hand, you do wind up spending a lot of time dealing with error messages that are the computer equivalent of ‘You didn’t say “Simon Says”.’ Everything would just have worked if it would just shut up about the types not matching!
openSCAD takes exactly the opposite approach. It delays having to care about the internals of an entity you’re using until it’s actually about to use it. The downside is that, when it can’t convert the thing you’ve passed into the form that’s expected, the error message you get might not be as informative as you’d like.
The upside is that it can simplify your code a very great deal.
Here’s a snippet from the filament-clip code
module 3DLine ( lnH, lnW, PointA, PointB) {
hull() {
translate(PointA) cylinder(h=lnH, d=lnW, $fn=16);
translate(PointB) cylinder(h=lnH, d=lnW, $fn=16);
}
}
It’s only when it’s getting around to processing the translate() operations that it examines what you’ve passed as PointA or PointB. If PointA can be converted into an array of two or three numbers, then it can be understood as a set of coordinates, and everything is okay, without you ever having had to declare that it’s an array.
Similarly, there’s the following snippet.
for(ii=[0:len(PointList)-2]) {
3DLine (Ht,Thickness,PointList[ii],PointList[ii+1]);
}
You don’t have to have told it in advance that Pointlist is an array of arrays of two or three numbers. As long as “len(Pontlist)” translates into an integer that’s >= 2, and as long as each element of Pointlist[] represents a coordinate set, the code will work fine.
Third: “for” vs “for”
They’re really very different things, and both are used in the code for the filament clip.
OpenSCAD will never get them confused, but humans may not be so lucky.
The “for” loop we just looked at will be familiar if you’ve done any coding at all. It pretty much reads in English. For (this list of instances) {do this stuff}
The other “for” is what the Cheat Sheet calls a “List Comprehension”. Instead of describing some {stuff} to do, it describes some list entries to create. In the spirit of late binding, as long as the list entries it describes are syntactically correct, and as long as they can be converted into whatever form is needed when they’re used, everything will be fine.
In this case, we’re creating a list of points to connect with straight lines. (from the first to the second, then the second to the third, and so on)
PointList=[ [-Da,-Db], // arm end
[0,-Db], // start of arc
for (angl=[Step:Step:Theta]) // This describes the set of instances to use...
[Db*cos(angl-90),Db*sin(angl-90)], // ... to generate lines of this form.
[-Dc,Dd],//Start of pinch
[-(Dc+De),Dd],//End of pinch
[-(Dc+De+Df),Dd+Dg] ];//Tip of fingernail catch
I think it’s easiest to think of this kind of “for” as a fancy text processor, that generates the code that you describe and substitutes that code for itself.
cut/paste the rest of this into a text file with the filename extension “.scad” and feed it to openSCAD to make yourself some filament clips.
Thickness=2.5;
Ht=10;
Da=16; //Origin to arm end
Db=5; //Radius of arc
Theta=225; //Total arc angle
arcsteps=20;
Dc=8; //Origin to pinch point X
Dd=-.5; // Origin to pinch point Y
De=3; // Pinch Length
Df=2.7; // Fingernail Catch length
Dg=2; // Fingernail Catch height
FLoc=[-2,-6,0];// location of filament hole
FCD=4.5;// filament carrier diameter
FHD=2.5;// filament hole diameter
Step=Theta/arcsteps;
epsilon=.005;
// Path for line defining cross-section
PointList=[ [-Da,-Db], // arm end
[0,-Db], // start of arc
for (angl=[Step:Step:Theta]) //the arc
[Db*cos(angl-90),Db*sin(angl-90)],
[-Dc,Dd],//Start of pinch
[-(Dc+De),Dd],//End of pinch
[-(Dc+De+Df),Dd+Dg] ];//Tip of fingernail catch
module 3DLine ( lnH, lnW, PointA, PointB) {
hull() {
translate(PointA) cylinder(h=lnH, d=lnW, $fn=16);
translate(PointB) cylinder(h=lnH, d=lnW, $fn=16);
}
}
difference () {
union() {
for(ii=[0:len(PointList)-2]) {
3DLine (Ht,Thickness,PointList[ii],PointList[ii+1]);
}
translate (FLoc) cylinder(h=Ht,d=FCD,$fn=90);
}
translate ([0,0,-epsilon]) translate (FLoc) cylinder(h=Ht+2*epsilon,d=FHD,$fn=90);
}
I haven’t used pointlists very often, mostly because I find them tedious. I never realised you could put a for loop within a point list. (and presumably other code? I’ll have to experiment)
I also wouldn’t have thought of using cylinders. I think I would have gravitated to using circles and then linear_extruding the final result.
Last week, in response to a facebook post, I made a “brick” chimney 1000 bricks tall and made from individual bricks which took 58 minutes to render.
Someone else came along and did the same thing by producing the brick layers as 2D objects and linear_extruding each layer as a whole. It took 1/4 the time.
Curious, I just took your collection of 3D cylenders and converted them to 2D circles and linear_extruded it, but it was actually marginally slower.
I, in turn, never realized you could do hull() in 2D.
Chances are that the extruded chimney was faster because it didn’t have to spend time rendering the brick faces inside the structure.
In general, I prefer to avoid code that renders “buried” structures that render inside other structures, but the code simplification from using hull() in this case is massive.
It’s not always easy to predict what’ll be efficient. I’m sure the openSCAD developers have spent time on optimization, but that’s effort-intensive coding, and needs rigorous testing - also hard to do in an open source environment. (let’s see - you could do a first-order approximation of the object intersections and use that to identify regions of geometry that you can ignore…)
A while back, I converted some code for a spiral into an approximation using a series of arc segments, and the rendering time dropped to 10% of what it had been
Im a little lost on this subject, but love hearing the expierence you guys have on this. Keep up the great work and input.