iScriptDesign. Generating Designs

Automate and parameterise your design

Jeroen Dijkmeijer

Using SVG for materialization

jeroen <at> iscriptdesign <dot> com


Abstract


Abstract

SVG stands for Scalable Vector Graphics. iScriptDesign stands for what I do: I script Design.

HTML was originally about presentation however, it is legitimate to say that HTML's popularity is in great part due to the possibility of interaction. Interaction is hardly ever found in graphics, yet graphics and dynamic graphics like movies enjoy a tremendous popularity. Imagine what would happen if you allow interaction in graphics..

One application of interaction in graphics is the display of diagrams, by choosing between different formats or range of values. Another yet unknown use case are template models. Do you still remember the days that, as a youngster, you received paper models as birthday gifts from family and friends? Keep those paper models in mind and..

iScriptDesign has been developed considering all of the above bullets and is developed using open and license friendly frameworks and formats, leveraging the power of SVG in a not so traditional way. iScriptDesign has proven to be an indispensable tool while constructing my own parameterizable furniture out of simple sheets of plywood. I hope it will serve you as well!

I'm not hindered with any knowledge on historical decision making inside the SVG specification group, and I still consider myself a junior in the world of SVG. Therefore the final conclusion of this paper may be far reached and unimplementable but is nevertheless an advice to consider an addition to the specification. This addition must encourage a unified approach for scripted design while, at the same time, allowing programmers to apply programming paradigms and languages at their own insight.


Table of Contents

Overview
Current status
Alternative initiatives
Remarks
iScriptDesign: mixing Javascript and SVG
Directives
Examples
Artist's toolbox: iPath
iPath's commands
Examples
Redefined path operators
Recommendations and conclusions
Possible applications and future investigations
Conclusion
Acknowledgments
Bibliography

The paper you are about to read has mostly been fueled by the annoyances with SVG's path. SVG is great for designing, but is not very well suited for engineering and calculating blueprints. This is hard to understand as SVG has embedded a powerful scripting engine, or can borrow calculation capacity from its containers, when displayed.

The investigations in this paper will overcome the path's shortcomings and keep its assets. Therefore I will first explain my irritations with SVG's path. Next I will research ways to mitigate its liabilities. This research will focus on using the container's processing power and, on the other hand, investigate the possibility to transform this power to SVG's internals. While leveraging this script engine, I'll introduce parametric or customizable online SVG. Subsequently I will introduce in this paper an artist toolbox to script your design. This toolbox facilitates programmers and artists alike: programmers' power at an artist's disposal, or vice versa artistic tools available for programmers. Finally the last paragraph mentions possible applications of iScriptDesign. Together with the artist's toolbox, iScriptDesign is opening up new ways for online fabrication, customizable design and scripted sketches.

SVG is experiencing a slowly but steady increase in popularity, accelerated in the last few months by IE 9's incorporation of SVG, and SVG being part of the HTML5 standard. Unfortunately there is not yet an uniform way to include SVG inside the webbrowser. The <embed>, <object> and <svg> elements are all valid and may cause animated discussion among its proponents [SVG Primer]

To my knowledge it is not yet possible to include SVG as a background image in the webpage, and I'm missing the ease of embedding SVG like a jpg or gif image.

Above are some drawbacks when embedding SVG inside an HTML5 page, however SVG itself also has some unpolished spots:

  1. SVG offers ways to interact with its internal data structure: the Document Object Model (DOM). Unfortunately SVG's path, the general Swiss army knife of SVG sketching, is not well (if not completely unequipped) to interact with or to modify it. Currently best way to modify an existing path so it can react to interactions is to retrieve its structure and split the contents of the "d" attribute on spaces and change the value at the designated index of the array (the result of the split). The dPath's (dPath is my term for indicating the string of the path element's d-attribute) DOM equivalent, the pathSegList, has the same shortcomings as there is no possibility to label elements, which should be modified when triggered by certain events.
  2. The location driven meaning of parameters (in general even indexed numbers for x and uneven indexed numbers for y) makes all parameters mandatory.
  3. SVG's path knows only the Cartesian coordinate system whereas the polar system may offer significant increase in usability during design phase.
  4. SVG's path looks like a relic from the past: verbosity, clarity and readability sacrificed for performance and storage. It doesn't allow for comments nor does it give any hooks for substituting values. Invalid code is not displayed, nor are the relevant error messages. The term graphical Assembler suits it best.

The intended audience has some working knowledge of Javascript and SVG and in particular SVG's XML definition. Therefore the audience may recognize the same annoyances and appreciate the solutions this paper will offer. To fully understand the shown solutions, one can save and upload the example code snippets as a single file (with .jsvg extension) into iScriptDesign by either clicking the right part (map) of the drop spot, or simply dragging the file into the drop spot (verified for latest releases of Firefox and Chrome). Apart from the upload or drop functionality, iScriptDesign will work for all up to date browsers except for any version of Microsoft Internet Explorer.

iScriptDesign's code is available on google code. The website is an almost exact copy of the HEAD of the SVN repository at google code.

All SVG and the path's shortcomings mentioned in the overviews' current status sections aside, the path is also your one stop shop for most of your sketching needs: lines, curves, arcs, regions etc. Considered the moving head or stylus-like notion of the path, the path and it's trail can be used to drive 2D or 2.5D cnc devices like laser cutters, water cutters or cnc routers, which also have a moving "active" head.

iScriptDesign offers a more modern approach to dPath's functionality by adding Javascript to the well known stack of pencil tools. The addition of javascrip makes iScriptDesign to a certain extend comparable with a spreadsheet: modification of a cell may lead to a cascade of changes in your worksheet. Likewise a change in a variable in iScriptDesign ignites a new evaluation of coordinates and a redraw of your picture. In order to behave like a spreadsheet and evaluate all the Javascript code the SVG file needs enhancements. To distinguish between SVG and "enhanced" SVG, iScriptDesign files have been called "jsvg" (Javascript SVG). The jsvg file contains normal SVG enhanced with tags to define "cells", to remain within the spreadsheet analogy. People with a background in shell scripting or HTML templates will not have difficulties finding similarities between iScriptDesign's tags and directives in aforementioned technologies.

Below some less formal small examples. Although the main focus for iScriptDesign is the path element, listing 1 will demonstrate iScriptDesign's concepts for a basic SVG element, the rectangle. The height and the width of the rectangle are the same (making it a square) and the value is the evaluation of 'size'. The variable 'size' is defined inside the first directive (the control directive), which does also display a slider. The sliders' state controls the value of the variable size and a change to the slider will redraw the whole image.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100mm" height="600mm"> 
@{ var:size, label:size, defaultvalue:150, size:3, type:slider, max:300, min:20 step:1 } 
<rect x="50" y="50" width="#{size}" height="#{size}"/> 
</svg> 

--{listing 1. a rectangle}

Instead of the XML element "<rect...>" the dPath (listing 2) can be used. As you will see however the greater flexibility is at cost of readability. The dPath generally describes a line (l) or movement (m) across the x (uneven indices) and y (even indices) axis. Lowercase characters denote a relative movement (dx and dy), uppercase characters denote an absolute movement (x,y). Listing 2 not only introduces the path and its famous d-attribute, it also borrows the browser's Javascript engine to calculate the coordinates of the triangle corners.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100mm" height="600mm"> 
@{ var:tri.size, label:size, defaultvalue:150, size:3, type:slider, max:300, min:20 step:1 } 
<g fill="none" stroke="black">
<path d="M 70 50 l #{tri.size} 0 l #{Math.cos(2*Math.PI/3) * tri.size} #{Math.sin(2*Math.PI/3) * tri.size} 
l #{Math.cos(4*Math.PI/3) * tri.size} #{Math.sin(4*Math.PI/3) * tri.size}"/> 
</g> 
</svg> 

--{listing 2, an equilateral triangle}

The triangle above is introduced to display the use of Javascript's math functionality. You may verify that the code above displays a "customizable" triangle in iScriptDesign, however it is not very readable and what to do when you want to display an arbitrary number of triangles? Of course the code above can be copied and pasted x times, however this will not contribute to readability or flexibility. Listing 3 shows a more feasible approach by introducing a function.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100mm" height="600mm"> 
@{ var:tri.size,label:size, defaultvalue:150, size:3, type:slider, max:300, min:20 step:1 }
${ triangle = function r(rsize) {                                                              --{ #1}
    return "l " + rsize + " 0 l " + Math.cos(2*Math.PI/3) * rsize + " " 
        + Math.sin(2*Math.PI/3) * rsize + " l " + Math.cos(4*Math.PI/3) * rsize 
        + " " + Math.sin(4*Math.PI/3) * rsize; 
    }; 
}$
<g fill="none" stroke="black"> 
<path d="M #{90 + 2*tri.size} 50 #{triangle(tri.size);}"/> 
</g>
</svg>

--{listing 3. a triangle function}

A few things to note about the examples:

  • In listing 3 the function triangle (at #1) is defined embedded inside jsvg but could as well have been defined inside an external library, well documented and tested.

  • Also note the difference for parameters in listing 1 and 2 versus 3: an an ordinary parameter versus an object with attributes. The object with attributes will prevent name collisions and keeps your code cleaner, it is therefore by far more recommended. It is possible to use semi endless nested object attribute definitions for your variable declarations.

  • Currently the curly bracket matching is not the most sophisticated routine and is incapable of matching nested curly brackets
    (#{ function(parameter1, parameter2, { att1: "value", att2: 3 }} This will fail because the first closing curly bracket is matched against the first opening bracket). Using nested curly brackets (for additional settings) can only be done by escaping the inner nested closing curly bracket (#{ function(parameter1, parameter2, { att1: "value", att2: 3 \}})

  • Experience with iScriptDesign taught me that absolute movement is hardly used within functions. Most common was to point the pen to a certain position and from there start "sketching" either with functions or with plain path code.

  • The premature performance optimization pitfall has been very successfully circumvented

  • While the introduction of a function in listing 3 may look like a successful exercise of tackling at least some of the shortcomings of the path, it is still very close to the original path. Path codes are moved inside functions where they are as unreadable as outside the function. Logic inside directives is hard to debug and there is no clean separation of your code and the view.

Especially the last bullet point is the reason why iScriptDesign was taken back to the drawing board. Having all the programming power at your hands, why not leverage its usage even more and create a path object which offers the same capabilities as SVG's own original path, enhanced with more verbose functionality and features often needed by designers and developers?

While iScriptDesign has served me well in creating real materialized artifacts, its deficiencies became more and more apparent. Mainly because dPath appears as a relic from times long past when readability was a sacrifice to performance and memory. Replacing strings within dPath with directives is fighting symptoms and it remains a suboptimal solution: obscure paths remain, errors are easily made and debugging is difficult.

After working more thoroughly with iScriptDesign, best practice appeared to be taking out long strings of dPath coordinates and replace them with functions, instead of mixing in few directives between dPath's coordinates. Reusability of those functions is a big advantage and putting those functions in test sets (generally called test suites) secured outcome when modifying them for functionality or performance.

Above described difficulties became more and apparent and iScriptDesign became more and more a can of worms while scripting my design. With the gained insights, I assembled a wish-list for iScriptDesign++.
It must (be):

  • Verbose

  • Easy to implement for me

  • Easy for you to use

  • Make unspecified variables default to zero

  • Incorporate turtle graphics, a well kept child hood memory

  • In combination with turtle graphics, allow polar coordinates

  • Offer an intuitive way for common tasks like reversing, mirroring (over an arbitrary vector), rotating, scaling and skewing

  • Allow redefinition of dPath's functionality like the cubic bezier, and the "smooth" operator

  • Not tied to SVG alone, support vector dependent devices like cnc routers, water/laser cutters, embroidery machines etc

  • Derived from above: path orientated

  • Have a new name. iScriptDesign is not very catching

Order is not correlated to priority, but last 2 points were easiest to solve: it's called iPath. iPath does not replace iScriptDesign. Instead it is a new module which can be used inside iScriptDesign, but can also be inside plain svg itself, as listing 5b shows.

Positive experience with jQuery's method chained programming paradigm and earlier experiences with arrays led to the core of iPath: a wrapper object around the array. Lines, moves and bezier curves are pushed on the array as Javascript objects with methods which support method chaining. Predefined methods make it also easier for Javascript editors to allow for code completion.

Let's get back to our first example in listing 1 (our rectangle) and rewrite it to iPath, to get a basic idea:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100mm" height="600mm"> 
@{ var:rect.size, label:size, defaultvalue:150, size:3, type:slider, max:300, min:20 step:1 }
<g fill="none" stroke="black"> 
<path d="M #{90 + 3*rsize} 50 #{myPathRect.dPath();}"/> --{ #1 } 
</g>
${ myPathRect = new iPath() -- { #2 } 
    .line(rect.size,0) 
    .line(0,rect.size) 
    .line({x:-1*rect.size})
    .line({y:-1*rect.size}); }$

--{ Listing 5a. rectangle with iPath. }

At --{ #2} a new iPath is constructed leaving us with an new iPath object. Immediately after the construction we can invoke methods on the same object. In listing 5a the line method is called 4 times. A more graphical interpretation commands an imaginary pen or cursor to draw a line, from its starting position, east, south, west and north. Parameters are passed in a way which should feel familiar by now. Also pay attention to the way arguments are passed in, as ordered x,y pair or as a data object (with x and y attributes). The latter offering the option of defaulting unspecified parameters to 0 (zero). To get a dPath representation (and a the actual square) the initial position of the cursor is set at --{ #1 } and the call dPath(significance) at the same line suffices to create the contents of the path's d-attribute. It is still my intention to create, besides the dPath method, a gcode or dxf method to drive directly cnc devices.

Listing 5a shows how to use the code in plain (x)html pages with a svg element.

var svg  = document.getElementsByTagName('svg')[0];
var dPath = svg.getElementsByTagName('path')[0];
dPath.setAttribute("d", "M303, 282" + myPathRect.dPath);

--{ Listing 5b. rectangle with iPath. }

Considering the Javascript object myPathRect: all movements are pushed as objects on the array and objects for movements all need to have x & y attributes in a Cartesian context, or a (alpha) and r (radius) attributes in a polar context. While traversing and rendering the path, the location and the direction (heading) of cursor is tracked. Below is a list of the functionality currently available in iPath:

  1. iPath.move(x,y) | move({x:p1, y:p2}): adds a relative movement to the iPath;

  2. iPath.Move(x,y) | Move({x:p1, y:p2}): adds an absolute movement to the iPath

  3. iPath.line(x,y) | line({x:p1, y:p2}): adds a relative line to the iPath

  4. iPath.Line(x,y) | Line({x:p1, y:p2}): adds an absolute line to the iPath

  5. iPath.smooth(weight, cp2x, cp2y, x, y) : adds an bezier curve to the path, which takes directions of last element, the length is specified by weight

  6. iPath.bezier(cp1x, cp1y, cp2x, cp2y, x, y) | iPath.bezier(cp1x, cp1y, x, y) : adds a cubic or quadratic curve to the iPath.

  7. iPath.reverse() : returns the reversed iPath (modifies iPath)

  8. iPath.reverse(arg) : returns the reversed the arg (if arg is of type iPath, arg and iPath are untouched)

  9. iPath.skew(skewfactor) : returns the skewed iPath (modifies iPath)

  10. iPath.skew(skewfactor, arg) : returns the skewed the arg (if arg is of type iPath, arg and iPath are untouched)

  11. iPath.reflect({x:0, y:0}) : returns the reflected iPath in the passed in vector (modifies iPath)

  12. iPath.reflect({x:0, y:0}, arg) : returns the reflected arg (if arg is of type iPath, arg and iPath are untouched)

  13. iPath.rotate(angle) : returns the angle radians rotated iPath (modifies iPath)

  14. iPath.rotate(angle, arg) : returns the rotated arg (if arg is of type iPath, arg and iPath are untouched)

  15. iPath.concat(other) : returns an iPath concatenated with another iPath

  16. iPath.turtleLine({a: alpha, r:radius}): adds a polar line to the iPath

  17. iPath.turtleMove({a: alpha, r:radius}): adds a polar movement to the iPath

  18. iPath.turtleBezier({a:angle, r:radius, cp1:{a:angle, r:radius}, cp2:{a:angle, r:radius}})

  19. iPath.repeat(nrOfTimes)

  20. iPath.dPath(significance) : returns the stringified version of the iPath

The transformation methods (rotate, skew, reflect, reverse) are in 2 flavors, one accepting only transformation arguments and the second the same method accepting an extra iPath argument. The first version modifies the object self. The second leaves the argument and the main object untouched and returns a cloned copy of the passed in argument with the transformation applied. The unit test below shows that the original path indeed has been modified.

  it ("should test a modified iPath", function() { 
       var rotateTest = new iPath().line(5,5).line(5,-5);
       expect(rotateTest.rotate(Math.PI/4).dPath(3)) .toEqual(" l 0 7.071 7.071 0");
       expect(rotateTest.dPath(3)).toEqual(" l 0 7.071 7.071 0"); }); 

// Listing 6a testing a modified Path.
Next unit test shows that the original rotateTest object is untouched

  it ("should test an unmodified iPath", function() { 
       var rotateTest = new iPath().line(5,5).line(5,-5);
       expect(rotateTest.rotate(Math.PI/4, rotateTest).dPath(3)) .toEqual(" l 0 7.071 7.071 0");
       expect(rotateTest.dPath(3)).toEqual(" l 5 5 5 -5"); }); 

// Listing 6b testing a unmodified Path.

For mor information on unittests and to verify proper behavior of iScriptDesign on your preferred browser, please check www.iScriptDesign.com/test

Below some examples leveraging iPath's functionality:
a heart (a symmetrical form) can be created with code like:

<svg  xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100mm" height="600mm">
${ (heart = new iPath()).bezier(0, -50, -150, -200, 0, -120)
     .concat(heart.reverse(heart).reflect({y:1})); 
}$
<g stroke="black" stroke-width="2.8" fill="red" transform="scale(0.8)"> 
<path id="heart" d="M700 300 #{heart.dPath(3); }"/> 
</g> 
</svg>

--{ listing 7. Heart.}

At first sight it looks like a cumbersome exercise, however you may feel more at ease once you realize that only the hearts' left part is designed as a bezier curve and the rest of the heart is a mirrored (in the y axis) concatenation of the first part. Changing the left parts' bezier curve will update the right part without any effort, keeping the symmetry in place. Also worth noting is the fact that the drawing of the heart above is cnc friendly: the pen (or operating head) does not need to be taken from the material during action. Of course arguments can still be passed to iPath method's arguments, so that they will respond to user generated events like sliders or input fields.

The next example will introduce Turtle Graphics and polar coordinates. Turtle graphics have been introduced as a part of the logo programming language [Logo]

, a language aimed at children and capable of visualizing a stateful cursor (the turtle) which listens to very simple instructions like forward, turn, penUp and penDown. With repetition and conditional statements it was a fun way of creating impressive looking graphics. Turtle graphics are a synonym for polar coordinates, which may be more appealing to the professional audience. A typical use case for turtle graphics (or polar graphics) is a regular polygon with n sides and a variable radius:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100" height="600"> 
@{ var:polygon.n,label:corners, defaultvalue:8, type:slider, max:60, min:3, step:1, size:3 } 
@{ var:polygon.r, label:radius, defaultvalue:8, type:slider, max:300, min:10, step:10, size:3 } 
${ polygon.iPath = new iPath()
     .turtleLine({a:Math.PI*2 / polygon.n, r: polygon.r * Math.sin(Math.PI/polygon.n) * 2 }).repeat(polygon.n); }$ 
<g stroke="red" stroke-width="0.8" fill="none" transform="scale(0.6)"> 
<path id="loop1" d="M400 400
#{polygon.iPath.dPath();}"/> 
</g> 
</svg> 

--{ listing 8 regular polygon }

The last repeat statement is subject to change: it is more logical to introduce the repeat statement upfront. This way it can be used as a conditional statement (0-n) instead of an mandatory "at least one time execution" and it will be possible to use a counter while repeating the statements. Also note the way for directing the turtle (opposed to the x and y coordinates): {a: for angle and r: radius distance}. First the turtle turns "a" radians (a turtle initial heading is 0 radians corresponding to 3 o'clock) and then the turtle moves r steps forward.

When introducing a new idiom as above, it allows me to "modify" some of SVG's less understood features, as the capabilities to transform it back to the original format are close at hand. One of the most counter intuitive format in SVG (in my opinion) has always been the cubic bezier curve, where the second control point is relative to the start and not to the end point. This behaviour has been corrected in the turtleBezier function. The second control point is relative to the endpoint and copies the heading, which is set after the first control point, so an angle of 0 in cp2 makes the endpoint's tangent parallel to the tangent of the start point. Test listing 9 to get a feeling.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1100" height="600"> 
@{ var:angie, label:angle, defaultvalue:0, type:slider, max:3.141592, min:-3.1415921, step:0.05, size:3 } 
@{ var:cp1Angie, label:angle cp1, defaultvalue:0, type:slider, max:3.141592, min:-3.1415921, step:0.05, size:3 } 
@{ var:cp2Angie, label:angle cp2, defaultvalue:0, type:slider, max:3.142592, min:-3.1415921, step:0.05, size:3 } 
${
turtleBezier = new iPath().Move(400, 200).line(200, 0)
    .turtleBezier({ a: angie, r: 200, cp1: { a:cp1Angie, r:300 }, cp2: {a:cp2Angie, r:300} }); 
}$ 
<g stroke="blue" stroke-width="0.8" fill="none" transform="scale(0.6)"> 
<path id="bult2" d="#{ turtleBezier.dPath(3) }"/> 
</g> 
</svg> 

--{ listing 9 turtle bezier }

A second example is the "smooth" operator which allows bezier curves to continue smoothly. This is very useful, but there is room for improvement: SVG's smooth operator only allows continuation with a mirrored controlpoint. It is useful to have the next controlpoint on the same line as the previous controlpoint but an exact mirror is a serious constraint. iPath's smooth method introduces a weight parameter which is relative to the length of the previous controlline. A weight of 1 is the same as SVG's smooth method and a weight of -1 forces the line back in the same direction it came from.

This feature has helped me in implementing svg handwriting, see figure 6.

The close reader must have noticed that almost all items in the wish list, introduced at the beginning of this section, have been ticked off. The ones still open are:

  • it must be easy to implement for me: Yes it was (I learned some Javascript as well)
  • It must be verbose
  • It must be easy for you to use

Last two open bullets are left as judgement to the audience.

The concepts of iPath and iScriptDesign should be clear by now. This paragraph is dedicated to speculation about future possibilities and applications for iScriptDesign. iScriptDesign's original target was an online sketch spreadsheet for designing real life artifacts. But while working on and with iScriptDesign, I realized that there are many more applications for it. Below a selection:

  • Potentially iPath can return more than SVG's dPath strings, for example direct generation of a pathSegList. Outside SVG, iPath may also produce: gcode, DXF, DST (a format used for embroidery) or other vector like formats. Extra settings like milling radius correction, material thickness speed and other typical cnc parameters can be fed in as Javascript objects and if necessary can be added as meta-data to movements or complete iPath's.

  • Reverse engineer an existing dPath into a Cartesian or polar iPath object by reading in a dPath string

  • iPath is now only capable of reacting to user input (via input fields or sliders), but imagine other inputs, like audio streams captured from a microphone, or a music clip within the web page.

  • In addition to the possibility above, animation of iScriptDesign and iPath can be realized when values of parameters can be controlled by predefined time lines, creating simple yet effective animated sketches.

  • Currently iScriptDesign is only available as a big page size panel. A arbitrary sized panel like GIF images (but now with a script as image source and user input controls attached to it) may serve many new usecases.

  • Use of complimentary and uncomplimentary parameters. A business case for customizable design is to distinguish between free and priced parameters. A blueprint can be made available free of costs but one is charged to have additional parameters (height or width) available for modification.

  • Visually programming graphics has been very rewarding for me during my childhood. I do expect that iScriptDesign will be a big asset in education especially when the programmed objects can be visualized with the aid of SVG and materialized with CNC equipment.