Skip to content

OEP1: Generalized extrusion module

Ben Gamari edited this page Mar 10, 2014 · 38 revisions

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

Goals

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

API (see discussion below)

This lists the API shown in the examples. That needs to be amended to be more flexible regarding the specification of transformations. The working name for the module is loft(), but it could just as well be extrude() or any other suitable names.

loft(<parameters>) {
    <2d child nodes>
}

Parameters

height - height of the extrusion in case no path is used

path - extrusion path

Two options:

  1. f(t) = [X-Coordinate(t), Y-Coordinate(t), Z-Coordinate(t)] The given function will be called with a single argument ranging from 0.0-1.0. The function should return a 3D coordinate.
  2. list of coordinates

This produces points in 3d space to be used as anchor points for the shape.

slices - number of steps to call the functions

If a list of coordinates was given for path, this might be redundant.

rotate - 2D rotation

Three alternatives:

  1. f(t) = Rotation(t)
  2. f(t) = [X-Rotation(t), Y-Rotation(t), Z-Rotation(t)]
  3. List of rotations

Same as twist in today's linear_extrude(), but can be given as a function similar to the path parameter. The function will be called with a single argument from 0.0-1.0 and return a value in degrees rotation of the base 2D shape around the origin. The alternative form would specify a 3D transform as replacement for the default behavior which is to orient the shape to point in the direction of the next point in the path.

scale - 2D scale

Two variants:

  1. f(t) = Scale(t)
  2. f(t) = [X-Scale(t), Y-Scale(t)]
  3. List of scale factors

Same as scale in today's linear_extrude(), but can be given as a function similar to the path parameter. The function will be called with a single argument from 0.0-1.0 and return a value describing a scale factor of the base 2D shape. If the function returns a 2D vector, the X- and Y- axes will be scaled separately.

offset - 2D translation

  • f(t) = [X-Offset(t), Y-Offset(t)]
  1. List of offsets

The function will be called with a single argument from 0.0-1.0 and return a 2D vector describing an offset of the base 2D shape from its origin.

Discussion

Ideas

http://piratepad.net/LLU3j8QIJh

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

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