Skip to content

OEP1: Generalized extrusion module

Marius Kintel edited this page Jun 2, 2014 · 38 revisions

Goals

  • Make extrusions more general and flexible
  • Handle transformations along the extrusion
  • Allow extrusion along a path
  • Make use of functions to describe the paths

Status and References

Status as of 2014-05-26

  • Different example/prototype code exists
  • Open discussion about how to fit the functionality into the language
  • Discussions indicate that this should be split into sweep() and loft(), where the primary difference is that loft() offers morphing between different 2D shapes
  • Some user-space experiments: https://gist.github.com/kintel/4645e2c91eb02e577688

Github

Issue: https://github.com/openscad/openscad/issues/114

Mailinglist

API

This attempts to formalize an API based on discussions so far.

sweep(<path>) <2d shape>

This version sweeps the given 2D shape across the path. Different types of paths have been suggested:

Curve paths

In this case, a path is a list of 2D or 3D coordinates describing a curve in space. The user is thus responsible for discretizing the curve.

This is likely to be the easier way for users to describe paths. The downside is that we need to calculate reference frames along the path. As there are many ways of doing this, we should offer a reasonable default, and perhaps additional parameters for typical use-cases (e.g. frenet, rotation-minimizing and perhaps stuff like locking an axis to a direction or a point in space).

Transformation path

In this case, a path is a list of 3x2 or 4x4 transformation matrices describing a curve in space including its reference frame. This is a generalization of the above and would pass the responsibility to define reference frames to the user. To make this more manageable, we could supply a user-space library for building such frames.

Function

In this case, the curve is defined by a passed function reference. The function is parametrizing the path using a parameter from 0-1. The return value of the function is either a 2D/3D vector or a 3x3/4x4 transformation matrix (see above)

Open questions:

  • How do we decide on discretization of the function?
  • This requires us to extend the OpenSCAD language with function references. We could postpone this variant to a later point in time.

loft() <oriented 2d shapes>

loft is also called skin in the discussions.

The idea of loft is that it successively morphs one shape into the next. To be able to discretize shape or movement formulas, it's possible to use for loops with loft, e.g.:

loft() for(i=[0:10]) translate([0,0,i]) square(10*i);

Old Ideas

http://piratepad.net/LLU3j8QIJh

Look at e.g. Mathematica's ParametricPlot3D function for inspiration.

How to describe the 2D shapes

2D shapes can be described in two ways:

  • Use OpenSCAD's 2D subsystem to create a 2D shape using a tree of CSG operators
  • Describe 2D shapes using lists (e.g. through list comprehensions, concat and other functions)

The first way is likely to be more logic to average users, while the second might be used my more mathematically minded people. We'd likely have to support both ways. As lists of vertices can be fed directly to the polygon() module, supporting the first explicitly should be sufficient.

How to bind the function

The examples show functions specified as strings. This assumes that functions are looked up in the scope where loft() is instantiated. This way of binding functions cause some challenges wrt. caching of nodes, as we can no longer look at the expanded representation of a module instantiation and deduct how to render it.

Another option is to allow binding a function reference to a parameter value, or more generally, to a variable. This would also make it possible to pass functions as parameter to modules. To do this properly requires some thinking. The main issue right now appears to be that functions, modules and variables live in separate namespaces. This causes ambiguous lookup when a name could resolve both to a function and to a parameter holding a function reference. Perhaps we need to deprecate the possibility of functions, modules and variables having the same name.

Impact of merging namespaces

I have done a quick study of around 1000 packages collected from Thingiverse with the script in statistics-scripts. Check.hs can be found here,

 $ ghc Check.hs
 $ ./Check files/* > results
 $ grep "^duplicates" results | wc -l
 27
 $ wc -l results
 954

That is to say that only 27 scripts out of the 1000 tested have identifiers that occur in more than one namespace (in all cases the overlap was between the module and variable namespaces). This seems like an acceptable breakage.

How to make the API general

Add a transformation() type node? That could even specify the range, or variable?

    transformation(function = "func3", variable = "t", range = [0:0.1:1]);

Require the shape to be the first node, all other nodes are transformation nodes?

loft() {
    union() {
        ... 2d child nodes ...
    }
    transformation(function = "func1");
    transformation(function = "func2");
    transformation(function = "func3");
}

Put the transformation into the tree?

loft() {
    transformation(function = "func1") {
        transformation(function = "func2") {
            ... 2d child nodes ...
        }
    }
}

Other ideas?...

  • Add a matrix parameter (or matrix2D and matrix3D) which can take functions as matrix elements.
  • Allow less mathematical ways of building extrusions by specifying a list of shapes, points and transforms to describe a turtle-graphics-like environment.

How to integrate the functions

The examples below show the functions as parameters to the loft() module. This is not very flexible and also does not allow to give a specific order for transformations.

How to call the functions

  • Always assume f(t) and sweep t between 0 and 1?
  • Introduce some kind of generator function to produce a list of points / values?

Examples of models that could be generated by this type of functionality

Extrusion with function based transformations

Function based transformations essentially extend the existing linear_extrude() by giving more control about the transformations. E.g. scale and twist are not fixed/linear across the whole extrusion but can vary according to the result of a function evaluation.

function func5(t) = 2 * PI * t * t * t;
function func6(t) = 3 - 2 * t;

loft(rotate = "func5", scale_x = "func6", scale_y = "func6", slices = 100, height = 100)
    square(size = [10, 10], center = true);

loft-3

Extrusion along a path

By extruding a shape along a path, quite complex models can be created with very little code.

function path(t) = [
        (t / 1.5 + 0.5) * 100 * cos(6 * 360 * t),
        (t / 1.5 + 0.5) * 100 * sin(6 * 360 * t),
        200 * (1 - t)
];

loft(path = "path", slices = 800, height = 100) {
        translate([-10, -6, 0]) square([3, 6]);
        square([20, 2], center = true);
        translate([7, -6, 0]) square([3, 6]);
}

loft-4

Combining things together: Teardrop

With extrusion along a path together with a scaling function, it's easily possible to model something based on the teardrop formula by Paul Bourke (http://paulbourke.net/geometry/teardrop/).

function drop(t) = 100 * 0.5 * (1 - cos(180 * t)) * sin(180 * t) + 1;
function path(t) = [0, 0, 80 + 80 * cos(180 * t)];
function rotate(t) = PI * pow((1 - t), 3);

loft(path="path", scale_x = "drop", scale_y="drop", rotate="rotate", slices=100) circle(r = 1, $fn = 12);

loft-5