Extrude Along Path

by gringer Nov 19, 2013
Download All Files

Thing Apps Enabled

Please Login to Comment

Not quite sure what happened to the comment by @tpchuckles, but it's a good question, so I'll answer it anyway:

it LOOKS like we construct the "end-caps" simply by declaring those additional faces out of the points in polypoints, but I assume those may not necessarily be planar (int precision from translating the 2D profile into 3D space?) and we really ought to be setting up a series of triangles to close off the end? (gross!) gringer: any thoughts here? is my understanding of how the "end-caps" are made correct, and do you have any other thoughts on a solution? (aside from "use a different version" which i'm not thrilled about).

The end caps are indeed treated as a single planar polygon, as they should be in a theoretical world where I don't need to care about floating point rounding. Just in case it wasn't obvious, this also applies to the faces between adjacent object copies, where the code assumes a planar trapezium.

As a simple fix for the end points, I'd create triangles between the polygon points and the origin, and let the users of the code deal with any intersection issues that might arise from that (as happens currently with the extrusion anyway). For the extruded trapezoids, I had the triangle version implemented in an earlier code version, but thought it was no longer necessary now that OpenSCAD supports non-triangles. The simpler implementation for the trapezoids is to split them down the diagonal, which shouldn't require too much change to the code.

These are probably the most frustrating issues for me (because it keeps giving errors in OpenSCAD), which I guess means I'm more likely to fix them first... but I'd also like to add on @halfshavedyak's scale fixes, and @kwx's end merge fix.

... when I have some time spare to think on this.

This is great, and my hats off to you for making this. But can you add some more detail? As it is, I don't believe that your description of the path extrusion is fully specified. When the 2D shape is extruded along the path, which 2D point gets pulled exactly along the specified 3D path- the first 2D point? the centroid (average)? the 2D origin?

The 2D origin is extruded along the path.

I have published my version of the older code with 2D scale, morph and other additions. https://www.thingiverse.com/thing:2845013

I have looked at the new version, it is quite beyond me without loads of unpicking, and I don't think I'll be hacking on it or backporting these changes to it, at least not anytime soon. It is a completely different algorithm and focused on twisting the shape as it extrudes which is not something I've needed to do.

The only rotation settings I have use for myself are the option of keeping the shape normal to the path, or not rotating it at all. Although I'd like a setting that makes the start shape normal to the path and then extrudes without further rotation.

Both new and old versions still give me errors on render (F6) when using newer versions of CGAL (ie newer versions of openscad) anyone else have this?

Extrude Along Path Remix with 2D scale and morph

You have path_extrude, path_extrude_v1 & path_extrude_v2, all updated in Feb 2018. There are significant differences between path_extrude and V2.

Which are we supposed to use as the latest version?

There are no comments I can find in the files to give a hint about which is truly the latest / best version. Some guidance would be appreciated.

From the summary text for this object:

See the script 'path_extrude.scad' for the most recent version, implementing the above algorithm.

I'm curious because the code below produces a trefoil knot, using an octagon with one concave facet, not a partial circle. I'd say path_extrude is busted.

If I use it with _v1, it works correctly (partial circle, respects the shape of the extruded object).
If I use it with _v2, it is back to the trefoil knot (but still doesn't respect the object shape).

Ignoring the obvious errors, what is the point of having all three versions without any explanation of why they are there or what the differences are? You talked about the perl script, why not explain these as well?


myPoints = [
for(t = [0:6:359]) [1.5cos(t-18),1.5sin(t-18)]
myPath = [
for(a=[45:6:335]) [10cos(a), 10sin(a), 0]

path_extrude(points=myPoints, path=myPath, merge=false);

I have multiple versions there because Thingiverse doesn't allow versioned files, and I prefer preserving version history.

Sorry, the path extrude syntax has changed; it should be path_extrude(exShape=myPoints, exPath=myPath, merge=false);

And also the rotation parameter. exRots. Appears to be the rotation angle (around Z from the original 2d object) for each point along the path.

So, the documentation, for those who are looking to know how to use this library (using path_extrude.scad), is as follows:

path_extrude(exShape=myPoints, exPath=myPath, exRots=[0], merge=false);`
  • exShape = list of 2D points (x,y) that define the shape of the object to be extruded on a flat surface with X moving left (negative) and right (positive) and Y moving up (positive) and down (negative).
  • exPath = list of 3D points (x,y,z) that define the path that the object will be extruded along.
  • exRots = list of rotations around Z to apply to the original 2D object as you move along the path. Each position in this array corresponds to the same position in the exPath array. These rotations follow the 'right hand rule'.
  • merge = a boolean (true, false) that tells the library to connect the start and end of the path if true.

Here's a working example that demonstrates all these parameters. Note that the shape is a pentagon with the top side elongated. The rotation list has the first element rotated 45 degrees so you can see which way positive numbers move the object.


myPoints = [
    for(t = [0:72:359]) [((t==288) ? 3 : 1.5)*cos(t-18),((t==288) ? 3 : 1.5)*sin(t-18)] 
myPath = [
    for(a=[45:(360/12):360-45]) [10*cos(a), 10*sin(a), 0]
myRots = [
    for(a=[0:len(myPath)-1]) (a==0) ? 45 : 0

path_extrude(exShape=myPoints, exPath=myPath, exRots=myRots, merge=false);

Yes, exRots is the big change between v1 and v2. I wouldn't recommend that people touch this value unless they want to do weird things with the twist of the structure (e.g. for creating screw threads).

The problem is that the standard "rotate an arrow to line up with another arrow" algorithm doesn't consider the initial Z rotation to be important, whereas it's very important for extruding along a path. By default it tries to be intelligent about setting these up to avoid large twists in the structure.

The code might currently be over-compensating, but as a consolation, this exRots is exposed to the outside world to allow for advanced uses where people to define their own pre-rotations.

The demonstration object in the path_extrude.scad code has a kink in it so that it's more obvious what the various parameters are doing, and includes an animation that demonstrates the weirdness that happens from modifying just the first value of exRots.

One thing I noticed was that without a full set of explicit rotation values, your pentagon example ends up slowly rotating 144 or so degrees within a full circle. You can see this in the attached image. Depending on which axis you extrude / rotate around, the amount of unexpected rotation changes.

If you want to actually just do a straight extrude along a path without the undesired rotation, you need to supply a rotation value for each step in the path.

Yes, I'm aware of this. If you have suggestions on how to fix this overcompensation, that would be greatly appreciated.

A "straight extrude along a path" doesn't work for all shapes. Any extrusion path components that are close to vertical will have excessive twists (which is what the modification fixes).

I've had an issue with using merge=true along with exRot rotations, where the end points didn't match up correctly. As a quick hack, I've disabled the spreadError code via an added optional parameter to get it working as expected. Here's the patch:

--- path_extrude.scad.orig   2019-03-27 13:14:41.932931102 -0700
+++ path_extrude.scad     2019-03-27 13:13:27.059513667 -0700
@@ -182,7 +182,7 @@
 myPointsChunk = [ for(t = [45:(360/8):136]) ((t==90)?1.5:1.9) * [cos(t+ofs1),sin(t+ofs1)]];
 //myPoints = [ for(t = [0:(360/8):359]) 2 * [cos(t+45),sin(t+45)]];

-module path_extrude(exPath = myPath, exShape = myPoints, exRots = [0], merge=false){
+module path_extrude(exPath = myPath, exShape = myPoints, exRots = [0], merge=false, spreadError=true){
     if((exShape == undef) || (exPath == undef)){
         echo("Path or shape not defined");
     } else {
@@ -200,7 +200,7 @@
                 myRotate([0,0,-rawPreRots[len(rawPreRots)-1]], c3D(exShape[0])));
         pt0 = project(p1=t0, c1=pm1, n1=rPlanes[len(rPlanes)-1]);
         lfAng = -getNPAngle(p1 = pt0, c1 = pm1, n1=rPlanes[len(rPlanes)-2], p2=tm1);
-        preRots = (merge) ? spreadError(rawPreRots, -lfAng) : rawPreRots;
+        preRots = (merge && spreadError) ? spreadError(rawPreRots, -lfAng) : rawPreRots;
         polyPoints = flatten(makePolyPoints(polyPath=exPath, polyForm=exShape, 
             polyAngles=preRots, polyNormals=rPlanes, merge=merge));

If you render the attached file with the default spreadError=true, the start and end of the path are unexpectedly connected with straight segments due to the last slice being rotated to match the start slice. With the option disabled, I get the expected smooth join. See the attached pictures for comparison.

Thanks for making this library! This is very useful.

Edit: fixed the diff, I had gotten the patch order backwards.

I have a couple of suggestions / feature requests

  1. easy - allow the doScale function to accept a list of 2D vectors so that as the shape is extruded it can be scaled and squished separately in X and Y dimensions. This would allow simple object morphing and be really useful. I might even try to do this myself though I'm not sure if I'm up to it.

  2. what about some options/flags to determine if the object is centred on the path as now, or fully to a specific side of it. This would be very useful in combination with above scaling functions.

  3. harder. What about developing this code to add a simple loft function which connects two different shapes to make a solid. If the input shapes were required to have the same number of points and be in the correct orientation then I imagine it wouldn't be tooo hard.

actually I have implemented 1 and workflow to achieve 2, and I'll post the code when I've tested and cleaned up some more. 3 might still be a bit beyond me though,.

Hi, have you achieved an usable code? i was trying to implement the scale feature but I found you have worked it out. Could you please share your version?

I'm happy to share it, but my code is a mess with lots of other modifications in the file, and I think it is based on the older version of gringer's code pre feb 2018... so I will have a look soon and see what I can put together to share.

I've also implemented the feature of morphing from one shape to another along the path, however that is very tricky to use as it just interpolates on a point by point basis so it only works if the start and end shapes have the same number of points and you want each point connected to it's counterpart of the same point number in the other shape.

I've just finished a rewrite of the code, which uses a pre-calculated array and a "make it look like the last one" pre-rotation algorithm that uses a virtual unit vector (rather than a point on the polygon). I've added in options to rotate the polygon around the extrusion axis, scale the polygon, and tween / morph from one shape into another. The rotation and scaling is scalar values only at the moment, but there's no particular reason why it couldn't allow for arbitrary scaling and pre-rotation.

Scaling and morphing should be easier to hack in with the new code, but the internal implementation has changed quite a lot, so it might be a bit fiddly if you're more used to the older code. The way I would do it would be to change exShape into a pre-calculated / pre-populated array (with a default setting that is the initial shape duplicated), and pick the exShape array index based on the position in the path. This would allow scaling and any manner of intermediary shapes (as long as point counts were preserved). That's what I'd do if I didn't have most of my time taken up by trying to earn money by doing DNA sequencing and bioinformatics, or getting kids to bed.

This library is great I have used it for some things that would have been near impossible in openscad without it! In fact it is keeping me using openscad when I might otherwise have given up.

However I just noticed a problem, the latest version(s?) of CGAL refuse to render objects made with extrude along path, and returns the following error:

ERROR: CGAL error in CGAL_Nef_polyhedron3(): CGAL ERROR: assertion violation! Expr: ss_circle.has_on(sp) File: /usr/include/CGAL/Nef_3/polygon_mesh_to_nef_3.h Line: 263

So running an installed version of openscad on Fedora 27, or the latest appimage from the openscad website, causes the problem, but setups with an older version of CGAL, such as the appimage from 2017.03.02 which I have, or an installed version running on Fedora 25 are fine.

Anyway I am hoping you might be able to find a workaround in your code, or, if appropriate, file a bug report with CGAL, I don't have the expertise to do either!

Not really any answers here, just a bit more info:
I'm running into the same error, and I noticed it only happens when i have merge=false (or defaulted to false) AND when I have an linear_extrude'd 2D shape (like text). (I'm on xubuntu 18.04, OpenSCAD 2015.03-2, GCAL 4.11)
it seems the polyhedrons we're creating are incompatible (why?) with other objects.
I also found this forum, which may be related? http://forum.openscad.org/F5-works-but-F6-gives-ERROR-CGAL-error-in-CGAL-Nef-polyhedron3-td22982.html
I tried the "hull" workaround they suggested but that shortcuts concave surfaces of the polyhedron. (a U macaroni shape becomes half a cheese wheel). However it all seems to work with the nightly openscad (2019.01.23, GCAL 4.7).
I still can't tell if this is some issue with how the polygon is constructed (and different versions of openscad/GCAL are more robust than others, although it looks fine to me) or "bugs" with different versions.

(That all said, while looking into this I also "noticed" that the way this code works (take your profile as a series of points, construct these into a polyhedron constructed of sets of points/surfaces) is incompatible (I think?) with using 2D shapes (can't path_extrude text, need to use points=[for....] instead of circle(r=...), etc) as your profile input. as a "nice to have", i wonder if we could instead create a union of a series of linear_extrudes between each point in our path? a profile from points could still be passed through polygon(), so you could use either)

I've also noticed this same error ("ss_circle.has_on(sv_prev->point())" or "ss_circle.has_on(sp)") when I try doing booleans with other shapes that intersect with the end of the non-merged path_extrude.
here's an example:

use < path_extrude.scad >;
profile=[ for(t=[0:10:360]) [ cos(t)r, sin(t)r]];

path_extrude(exShape=profile, exPath=path, merge=false);

I suspect this is related to how the "end-caps" are constructed, where certain versions of the compile throw a fit with non-planar faces? : https://github.com/openscad/openscad/issues/2412
it LOOKS like we construct the "end-caps" simply by declaring those additional faces out of the points in polypoints, but I assume those may not necessarily be planar (int precision from translating the 2D profile into 3D space?) and we really ought to be setting up a series of triangles to close off the end? (gross!)
gringer: any thoughts here? is my understanding of how the "end-caps" are made correct, and do you have any other thoughts on a solution? (aside from "use a different version" which i'm not thrilled about).

edit: it looks like this affects several sweep implementations too (where folks expect the profiles (sweep from-to) to be planar). i ended up writing a quick hacky method of closing the extrude profile with triangles instead, which fixed the issue. check out attached, with function radialClose. eventually i'll be uploading this to my own "fork" of this path_extrude. it works with fully-concave profiles, but will fail with more complex profiles.

CGAL errors usually happen when the turns between different path points are too tight, resulting in bits of the extruded path intersecting with itself. If that happens, a possible solution could be to break the object up into multiple sub-paths, and 'union' those components.

As far as I'm aware, it's not possible to expose the geometry of 2D objects using pure OpenSCAD code. If it were, the code for path extrusion would be made a lot simpler (I wouldn't need to re-implement rotation, for example).

Yeah, I played around with a rewrite of this code that basically rotates/transforms a 2D shape input into place for each "slice" in the pathextrude, and then hull()s between them for each segment. it sort of worked (aside from hull() closing off concavities).

I had one more thought about the rotation too; could you define a second path that a specific point of your profile is "locked" to?

edit: after re-reading your comments and a bit more playing around with it, I decided i don't really care too much about the rotation issues. they only seem to really present themselves when the path has a vertical, so for my purposes, I can get away with making the loop on the X-Y plane and rotating it, instead of making the loop in Y-Z or something.

This is really awesome.
But I noticed there is a problem when a part of the path is parallel to Z-axis.

Here is a small example code which produces a ring. If you render this in OpenScad you may notice the weird twist it makes on the two places where it cuts XY-level

use <path_extrude.scad>;
myPoints = [ for(t = [0:6:359]) [cos(t),sin(t)] ];
myPath = [ for(t = [0:3.6:359]) [
    ] ];
path_extrude(points=myPoints, path=myPath, merge=true);

Any ideas how to solve this?

Sorted. I've created some OpenSCAD code that demonstrates I can create an extruded polygon with minimal jitter between adjacent repeats of the polygon.

Now I just need to bolt the edges onto the duplicated polygons, hopefully mostly just requiring me to copy code from the old version of the extrusion code.

I think I've worked out how to iron out most of the kinks that were caused by this bug, and just need to implement it in the code. Along the plane with a normal in the direction from the last to the next point, The idea is to compare the actual rotation with an ideal rotation that minimises rotation from the previous point. This requires me to add in an additional pre-rotation argument at each point, which might be useful to others (it's a bit like the 'twist' argument in the current linear_extrude, but applied on a per-point basis).

What's happening is that that X/Y angle is given ultimate priority for the twist, so when there's not much movement in the X/Y direction (and a lot in the Z), the twist can be extreme. This is easier to see when looking at models from the top down.

The twist is a known problem, brought about by needing to decide on a rotation (around the axis perpendicular to the plane of the polygon) for every point, and getting it wrong. Unfortunately, I don't know of an easy way to fix it such that it works in all (or most) cases.

But it is interesting that the axis matters, and that could lead me to a fix. The following (for example) is an equivalent construction, but doesn't have the twist problem:

use <path_extrude.scad>;
myPoints = [ for(t = [0:6:359]) [cos(t),sin(t)] ];
myPath = [ for(t = [0:3.6:359]) [
    ] ];
    path_extrude(points=myPoints, path=myPath, merge=true);

My guess is that it's related to something like trying to calculate an angle of something that has no height.

differencing the output of this from a block doesn't make a nice tube like I'm hoping for. Instead, it looks like a skin cut through the block. Is there any way to make the output solid?

The output of this should be fully solid if you have sufficient space between points in the 3D path, no big twists, and the resultant object is enclosed (merge=false for "free" ends, merge=true if ends are joined, but don't duplicate the first/last point).

Can you give the code for what you're trying to make so that I can help troubleshoot?

Can't appreciate this enough. Great work!

Can't appreciate this enough. Great work!

This is cool! Just FYI, I'm using Windows with Strawberry Perl, and I had to modify the command slightly to get it to work. Here is what worked for me:

perl http://path_extrude_2014-Jan-21.plpath_extrude_2014-Jan-21.pl -polygon "(cos($t),sin($t))" -path "(5(.41cos($t) - .18sin($t) - .83cos(2$t) - .83sin(2$t) - .11cos(3$t) + .27sin(3$t)),5(.36cos($t) + .27sin($t) - 1.13cos(2$t) + .30sin(2$t) + .11cos(3$t) - .27sin(3$t)),5(.45sin($t) - .30cos(2$t) +1.13sin(2$t) - .11cos(3$t) + .27sin(3$t)))" -fn 100 -pn 20 > foo_test_4.scad

Very nice! Been trying to come up with a way to convert my saved GPX files of memorable running paths to 3D meshes, but haven't found any good way yet. Perhaps this could be used as a way to visualize my data?