Parametric Modeling
Source: Makerbot
Objectives:
Software Needed: Processing, Netfabb Studio Basic or MeshLab, ReplicatorG
Background
To get up to speed using Processing review Processing Basics and Processing and 3D space.
Many established artists, architects and designers have integrated software into their process. According to Casey Reas "Learning to program and to engage the computer more directly with code opens the possibility to create not only tools, but systems, environments, and new modes of expression. It is here that the computer ceases to be a tool and becomes a medium."
Parametric modeling uses parameters to define a model. Parameters may be set and as they are modified the model will update to reflect the modifications. This type of modeling allows you to explore the effects of different feature sizes, without recreating model geometry.
"Generative art describes a strategy for artistic practice, not a style or genre of work. The artist describes a rule-based system external to him/herself that either produces works of art or is itself a work of art."
Objectives:
- To answer why a designer or artist would want (or need) to write software?
- To foster creativity.
- To be able to modify parameters in order to generate unique forms.
- To be able to create interactive Processing sketches that generate 3D printable forms.
Software Needed: Processing, Netfabb Studio Basic or MeshLab, ReplicatorG
Background
To get up to speed using Processing review Processing Basics and Processing and 3D space.
Many established artists, architects and designers have integrated software into their process. According to Casey Reas "Learning to program and to engage the computer more directly with code opens the possibility to create not only tools, but systems, environments, and new modes of expression. It is here that the computer ceases to be a tool and becomes a medium."
Parametric modeling uses parameters to define a model. Parameters may be set and as they are modified the model will update to reflect the modifications. This type of modeling allows you to explore the effects of different feature sizes, without recreating model geometry.
Introduction to Modelbuilder
Probability Lattice | Object #1 |
Probability Lattice | Form Studies (MakerBot) |
Work by Marius Watz
Modelbuilder is a Processing library written by artist Marius Watz. Watz's first iteration of the library was created as part of his artist-in-residence project at MakerBot.
ModelBuilder focuses on functions useful for digital fabrication, such as creating meshes, centering models around the origin, scaling to given dimensions, outputting to STL format, etc. Built in functions emulate Processing's beginShape(), vertex() and endShape() logic to build mesh geometry which can then be manipulated and transformed. The UVertexList class allows the user to build paths of vertices. Vertex lists are then fed into various UGeometry.quadStrip() functions to construct quad strip meshes, with the intention of eliminating the need for dozens of loops. See the ModelBuilder JavaDoc for a more complete overview.
If you use the library to output models for 3D printing you need to be aware of face orientation (see Wikipedia: Handedness) so that face normals are generated correctly. Always check models to see if the normals are correct before attempting to 3D print them. You can use Netfabb's free Studio Basic, MeshLab, or any other program you like to preview and check your meshes.
Download and install the latest version of modelbuilder.
There are two core functions to modelbuilder:
- UVertex
- UGeometry
Using Modelbuilder
Tutorial derived from work by Marius Watz- Open Processing and create a new sketch.
-
Import Modelbuilder and OpenGL libraries.
To import a library select Sketch>Import Library and select the library from the drop-down menu:
- Create setup() and draw() methods.
-
Create an instance variable to hold a vertex list. This variable will be an of type UVertexList.
- Create a method named build(). This method will not return anything (void)
- Inside the method
- Initialize the vertex list variable by setting the variable to a new UVertexList()
- You want to add vertex points to the vertex list— one in the upper left corner and one in the lower right corner.
vl.add(0,0,0); vl.add(width,height,0);
- Initialize the vertex list variable by setting the variable to a new UVertexList()
- Navigate to the setup() method and set the size of the sketch and the type of renderer you want Processing to use.
- In the setup() method call your build() method.
- Inside the draw() method:
- Update the background.
- Set the stroke to a color.
- Create reference to self:
your_vertex_list_variable.draw(this);
- Update the background.
- Test.
- Add another vertex point.
- Test.
- Delete the last two points you added and replace that code with a for loop to add 20 random points
between the upper left and lower right corner.
- Save and test.
- Now you need a way to manipulate the points in 3D space.
- Add an instance variable of type UNav3D named nav
- In the setup() method initialize nav by setting it to new UNav3D(this).
- Also in setup() you want to center the 3D space. You can do that with this line of code:
nav.setTranslation(width/2, height/2, 0);
- In the draw() method call doTransforms() on the nav instance of UNav3D.
- Add an instance variable of type UNav3D named nav
- Save and test.
By adding doTransforms() you will be able to Right click to scroll and left mouse button to manipulate the object. - To add a third dimension to your shape navigate to your for loop where you create vertices. Set the z point to a random number between two numbers. Outside of the for loop call translate() on the vertex list and pass it -width/2,-height/2, and some negative number
- Save and test.
- Now it's time to add some dimensionality to your lines.
- Add an instance variable of type UGeometrynamed geo.
- In the build() method initialize geo by setting it to new UGeometry()
- In the build() method you want to iterate over the vertex list starting with the first point.
vertex_list.n will give you the number of vertex points in the vertex list.
vertex_list.v[0] will give you the x, y and z coordinates for the the first vertex point in the vertex list.
You want to create boxes along the edges of your lines. To do that you will use java class UPrimitive which contains generators for 3D objects and can calculate the heading angles.
Inside your for loop create the boxes:geo.add(UPrimitive.box2Points(vl.v[i], vl.v[i+1], offset));
- You won't be able to see the boxes yet. Navigate to the draw() method and add a call to lights() so that you can see your geometry. Call fill() and pass in a low alpha number so that you add color to your boxes, but still will be able to see through them. Then draw the boxes by calling draw() on your geo instance and pass in this to the method.
- Add an instance variable of type UGeometrynamed geo.
- Save and test.
Using an array of UVertexLists
Tutorial derived from work by Marius Watz- Open Processing and create a new sketch.
-
Import Modelbuilder and OpenGL libraries.
- Create setup() and draw() methods.
-
Create an instance variable to hold an array of UVertexLists. This array variable will be of type UVertexList. Name the array vl
To create an array use this syntax:UVertexList[] vl;
- Add an instance variable of type UNav3D named nav.
- Add an instance variable of type UGeometry. Name the variable geo.
- Create a method named build(). This method will not return anything (void).
- Inside the method:
- Initialize the array of UVertexLists to hold 12 elements by setting the array variable to UVertexList.getVertexLists(12)
- You want to add 3 vertex points that will determine the x and y offsets
//give it an x and y offset vl[0].add(200,0,0); vl[0].add(random(50,200), random (10, 190),0); vl[0].add(200,200,0);
- Now you are going to iterate over the array of UVertexLists starting at 1 and continuing until the variable is less than or equal to vl.length-1
- Inside the loop. add this next line of code which copies the first 3 points into the list
vl[i]=new UVertexList(vl[0]);
- This next line of code moves the points all the way around the Y axis:
vl[i].rotateY(map(i, 0, vl.length-1, 0, TWO_PI));
- Outside of the loop initialize the geo instance by setting it to new UGeometry()
- In order to connect all the points, you need to call quadStrip(vl) on the geo instance.
- Initialize the array of UVertexLists to hold 12 elements by setting the array variable to UVertexList.getVertexLists(12)
- Navigate to the setup() method:
- Set the size of the sketch and the renderer you want to use.
- Call your build() method.
- Initialize nav by setting it to new UNav3D(this)
- You want to center the 3D space:
nav.setTranslation(width/2, height/2, 0);
- Set the size of the sketch and the renderer you want to use.
- Inside the draw() method:
- Update the background.
- Add lights.
- Set the stroke.
- Call doTransforms() on nav
- Set the fill color and pass in a low alpha value for transparency.
-
Call draw() on geo with a reference to self:
geo.draw(this);
- Update the background.
- Save and test.
-
Add another random point to the first array in the UVertexList array. This will create a line with four points.
- Save and test.
- Navigate back to build() and add this line of code to connect the last vertex list with the first.
geo.quadStrip(vl[vl.length-1], vl[0]);
- Now you need to add a top and a top to close the shape:
You'll use a
triangle fan and you can
extract a new vertex list from the vertex list array:
Outside of the loop:
//defines edge of the bottom UVertexList top=UVertexList.extractFromArray(0, vl);
- This is the method definition for UGeometry triangleFan(UVertexList vl,boolean useCentroid,boolean reverseOrder)
This code connects the vertex points, but doesn't close the mesh.
You may need to reverse the true and false parameters later, but try to create a triangleFan after the line that created the first quadStrip:geo.triangleFan(top, true, false);
- Add this line of code to close the vertex list by adding a copy of the first vertex.
bottom.close();
- Save and test.
- Extract a new vertex list from the vertex list array to define the bottom edge. The parameters this time are vl[0].n-1, vl
- Create a triangleFan for the bottom.
- Close the bottom by closing the vertex list by adding a copy of the first vertex.
-
Add geo.writeSTL(this, "test.stl"); at end of build() to create an stl file.
- Open NetFabb Studio Basic, Meshlab or an application of your choice and open the stl file to check the mesh.
- If you see something like this in NetFabb:
you'll need to go back to your sketch and switch the true and falses in either your geo.triangleFan(top or geo.triangleFan(bottom statement. You want something like this: - Save and test.
Adding a GUI
Tutorial derived from work by Marius Watz- Navigate to the line of code that creates an stl and comment it out.
- Import the controlP5 library. This library allow you to create GUI elements. The modelbuilder library has been written with convenience functions to make building your gui even easier.
- Create an instance variable of type USimpleGUI. Name it
gui.
- Create an instance variable of type int. Name it
meshRes. This variable will control the resolution of the mesh. In the original sketch this value was the number of elements in your UVertexList array or 12.
- Create an instance variables to hold your x and y vertices for the first, second, third and fourth points in your UVertexList. You could name them firstX, firstYsecondX, secondY, thirdX, thirdY, fourthX, fourthY for clarity.
- Create an instance variable of type boolean. Name it
doRebuild. This will help control when you want to update the sketch.
- Navigate to the draw() method and surround the doTransforms(), fill() and geo.draw(this) statements with calls to pushMatrix() and popMatrix().
- Create a conditional to test if doRebuild is true. If the condition is met, call build() and set doRebuild to false.
- Create a new method called initGUI(). This method does not return anything (void).
- Inside the initGUI() method
-
Initialize the instance of gui by setting it to new USimpleGUI(this).
- Set meshRes to 12.
- Set the other variables:
firstX=50; firstY=0; secondX=50; secondY=10; thirdX=50; thirdY=10; fourthX=50; fourthY=200;
- Add a slider to the gui instance by calling addSlider("Label", value, min, max) on the instance of gui. Set the Label to "meshRes" and set the value to meshRes. Set the min to 4 and the max to 50.
- Add another slider to the instance of gui. Call it "firstX" and set the value to firstX. Set the min to 50 and the max to 200
- Add another slider to the instance of gui. Call it "firstY" and set the value to firstY. Set the min to -150 and the max to 9
- Add another slider to the instance of gui. Call it "secondX" and set the value to secondX. Set the min to 50 and the max to 200
- Add another slider to the instance of gui. Call it "secondY" and set the value to secondY. Set the min to 10 and the max to 190
- Continue for all x and y points. Just remember that the fourthY should range between 191 and something above 200.
-
Initialize the instance of gui by setting it to new USimpleGUI(this).
- Create a new method called controlEvent. This method does not return anything, but takes ControlEvent ev as a parameter.
- Inside the method set doRebuild to true.
- Navigate to the build() method and replace the random numbers with the appropriate variables
- Navigate to the setup() method and add
- initGUI(); before build()
- nav.setGUI(gui);
- Navigate to the draw() method and call draw() on the instance of your gui object.
- Save and test
Saving STL
- Navigate to the initGUI() method. Inside the method add a button to the GUI. The syntax for adding a button is this:
Name your button saveSTL
gui.addButton("button/method_name);
- Create a saveSTL() method.
- Inside the method add this code so that every time you click on the button you will create an stl file with a unique name.
geo.writeSTL(this, UIO.getIncrementalFilename( this.getClass().getSimpleName()+" ###.stl", sketchPath) );
- Save and test.
- Add a fifth point to the UVertexList().
Terrain Builder
Terrain Builder
Tutorial derived from work by Marius Watz- Open Processing and create a new sketch.
-
Import Modelbuilder and OpenGL libraries.
- Create setup() and draw() methods.
-
Create an instance variable to hold an array of UVertexlists. This variable will be of type UVertexList. Name the array variable vl.
To create an array use this syntax:UVertexList[] vl;
- Add an instance variable of type UNav3D. Name the variable nav.
- Add an instance variable of type UGeometry. Name the variable geo.
- Navigate to the setup() method:
- Set the size of your sketch and select the renderer you want to use.
- Call your build() method.
- Initialize nav by setting it to new UNav3D(this).
- You want to center the 3D space:
nav.setTranslation(width/2, height/2, 0);
- Set the size of your sketch and select the renderer you want to use.
- Inside the draw() method:
- Update the background.
- Add lights.
- Set the stroke.
- Set the fill.
- Call doTransforms() on nav so you can manipulate 3D space.
- Call draw() on geo and pass the method
a reference to self:
geo.draw(this);
- Update the background.
- Create a method named build(). This method does not return anything (void)
- Inside the build() method:
- Initialize the array of UVertexLists to hold 20 elements. You can do this by setting the array variable to UVertexList.getVertexLists(20).
- Now you want to create a for loop to iterate over the UVertexList array.
- Inside the loop you just constructed, create a nested loop that iterates over the UVertexList array again.
- Inside the inner loop you want to add an x, y and z value to each element in the array (vl[i]).
The first point will be map(outer_loop_var, 0,vl.length-1, -1,1),
the second point will be a random number between 0.2 and 1
and the third point will be map(inner_loop_var, 0,vl.length-1, -1,1)
The x will take the outer loop variable and map it, changing the min of 0 and the max of the 11 to a new min of -1 and a new max of 1.
The y will be a random number between .2 and 1.
The z will take the inner loop variable and map it, changing the min of 0 and the max of the 11 to a new min of -1 and a new max of 1. - Initialize the geo instance by setting it to new UGeometry().
- In order to connect all the points, call quadStrip(vl) on the instance of geo.
- If you ran this now, you wouldn't see anything. Why?
- Call setDimensions(600) on the instance of geo. The parameter is the number in millimeters that you want width and length of the terrain to be.
- Initialize the array of UVertexLists to hold 20 elements. You can do this by setting the array variable to UVertexList.getVertexLists(20).
- Save and test.
Adding a GUI
Tutorial derived from work by Marius Watz- Open the terrain sketch.
- Import the controlP5 library.
- Create an instance variable of type USimpleGUI. Name it
gui.
- Create an instance variable of type int. Name it
meshRes. This variable will control the resolution of the mesh. In the original terrain sketch this value was the number of elements in your UVertexList array.
- Create an instance variable of type float. Name it
noiseStep. This will control the randomness of the form. Set it to 0.1.
- Create an instance variable of type boolean. Name it
doRebuild. This will help control when you want to update the sketch.
- Navigate to the draw() method and surround the doTransforms(), fill() and geo.draw(this) statements with calls to pushMatrix() and popMatrix().
- Create a conditional to test if doRebuild is true. If the condition is met, call build() and set doRebuild to false.
- Create a new method called initGUI(). This method does not return anything (void).
- Inside the initGUI() method
-
Initialize the instance of gui by setting it to new USimpleGUI(this).
- Set meshRes to 20.
- Add a slider to the gui instance by calling addSlider("Label", value, min, max) on the instance of gui. Set the Label to "meshRes" and set the value to meshRes. Set the min to 4 and the max to 50.
- Add another slider to the instance of gui. Call it "noiseStep" and set the value to noiseStep. Set the min to 0.001 and the max to 0.5
-
Initialize the instance of gui by setting it to new USimpleGUI(this).
- Create a new method called controlEvent. This method does not return anything, but takes ControlEvent ev as a parameter.
- Inside the method set doRebuild to true.
- Navigate to the setup() method and add
- initGUI();
- nav.setGUI(gui);
- Navigate to the build() method.
- Instead of hard-coding the number of elements in the array set this value to meshRes.
- Navigate to inside the inner loop of your nested loops and before you add points add this line:
float y=noise((float)outer_loop_var*noiseStep,(float)inner_loop_var*noiseStep);
- Add y to the second point.
- Navigate to the draw() method and call draw() on the instance of your gui object.
- Save and test.
Making the terrain solid
Tutorial derived from work by Marius Watz- Open the terrain sketch.
- Navigate to the setup() method and call setRotation() on the instance nav and pass it PI,0,0
- Move build() to the end of the draw() method.
- Navigate to the build() method
- You want to build the left edge of the terrain mesh. To do that, place this block of code before geo.setDimensions(600);:
UVertexList bottom=new UVertexList(vl[0]); for(int i=0; i<bottom.n; i++){ bottom.v[i].y=0; } // create edge as a quadstrip between bottom and // the first edge of the terrain geo.quadStrip(bottom, vl[0]);
- This will build the right edge:
// right edge bottom=new UVertexList(vl[meshRes-1]); for(int i=0; i<bottom.n; i++){ bottom.v[i].y=0; } // reverse order to get correct face normals geo.quadStrip(vl[meshRes-1], bottom);
- This will build the front wall:
// get edge using extractFromArray() to get // all the first points of the vertex lists. // this is the front edge UVertexList top=UVertexList.extractFromArray(0, vl); bottom=new UVertexList(top); for(int i=0; i<bottom.n; i++){ bottom.v[i].y=0; } geo.quadStrip(top, bottom);
- This will build the back wall:
// get edge using extractFromArray() to get // all the first points of the vertex lists. // this is the front edge top=UVertexList.extractFromArray(meshRes-1, vl); bottom=new UVertexList(top); for(int i=0; i<bottom.n; i++) { bottom.v[i].y=0; } geo.quadStrip(bottom, top);
- To create the ground plane you need to use UVec3:
// create ground plane as quad. // use new UVec3(vl[0].v[0]).scale(1,0,1) // to get a copy of the vertex with y=0 geo.beginShape(QUAD_STRIP); geo.vertex( new UVec3(vl[0].v[0]).mult(1,0,1)); geo.vertex( new UVec3(vl[0].v[meshRes-1]).mult(1,0,1)); geo.vertex( new UVec3(vl[meshRes-1].v[0]).mult(1,0,1)); // geo.vertex( new UVec3(vl[meshRes-1].v[meshRes-1]).mult(1,0,1)); geo.endShape();
- Navigate to the initGUI() method. Inside the method add a button to the GUI. The syntax for adding a button is this:
Name your button saveSTL
gui.addButton("button/method_name);
- Create a saveSTL() method.
- Inside the method add this code so that every time you click on the button you will create an stl file with a unique name.
geo.writeSTL(this, UIO.getIncrementalFilename( this.getClass().getSimpleName()+" ###.stl", sketchPath) );
- Save and test.
- You'll need to repair this mesh. I suggest using NetFabb
Images and Terrain Builder
Tutorial derived from work by Marius Watz- Open the Terrain Builder sketch from the previous tutorial.
- Find a suitable image to work with and add the file to your sketch.
- Create an instance variable of type float. Name the variable yScale
- Add another instance variable. This time of type PImage and name the variable img.
- Navigate to the initGUI() method.
- Inside the initGUI() method
- Comment out the slider and statements in the build() method that deal with noise.
- Set yScale to 0.05
- Add a slider to scale the Y axis. The syntax for adding a slider is this:
Name your slider yScale. Set the value to yScale, set min to 0.02 and max to .1.
gui.addSlider("slider name", value, min, max);
- Change meshRes to 50
- Change the meshRes slider values to range between 50 and 200.
- Comment out the slider and statements in the build() method that deal with noise.
- Navigate to the setup() method and load the image:
img=loadImage("name_of_image.png");
- Change the setRotation() to PI/2,0,0
- Navigate to the build() method
- Inside the nested loop comment out anything to do with noiseStep
- Now you are going calculate y as pixel brightness. Create a variable named y that is of type float. Set the variable to
1-(float)brightness(img.get( (int)map(i, 0,vl.length-1, 0,img.width-1), (int)map(j, 0,vl.length-1, img.height-1,0)))/255
- Set y to itself multiplied by yScale.
- Modify the statement where you add the points to the UVertexList:
vl[i].add( map(i, 0,vl.length-1, -1,1), y, map(j, 0,vl.length-1, -1,1) );
- Change the fill() to a gray , background() to black and stroke()
to noStroke().
- Save and test.
Standards
STANDARD 1Analysis, Inquiry, and Design: ENGINEERING DESIGN
Key Idea 1: details
Engineering design is an iterative process involving modeling and optimization (finding the best solution within given constraints); this process is used to develop technological solutions to problems within given constraints. (Note: The design process could apply to activities from simple investigations to long-term projects.)
STANDARD 5
Technology: Computer Technology
Key Idea 3: details
Computers, as tools for design, modeling, information processing, communication, and system control, have greatly increased human productivity and knowledge. Standard 2: Integrated Learning details Students will demonstrate how academic knowledge and skills are applied in the workplace and other settings.
Integrated learning encourages students to use essential academic concepts, facts, and procedures in applications related to life skills and the world of work. This approach allows students to see the usefulness of the concepts that they are being asked to learn and to understand their potential application in the world of work.
Projects
This exercise introduces using a programming language as a tool that allows you to solve problems. It uses numbers, math and logic to solve a design problem.Algorithms
from Casey Reas's WORKSHOP 1: CONDITIONAL DRAWINGAlgorithms are the foundation of all programmed graphics, but of course algorithms exist outside of computer code. When applied to drawing, some algorithms are the basis for extraordinary interactions between people, pencils, and paper. Based on the Conditional Design Manifesto by Luna Maurer, Edo Paulus, Jonathan Puckey, and Roel Wouters, you'll explore a range of drawing systems and instructions and then invent one of our own.
Each member of the class should design a set of drawing instructions for one person to make a drawing. Design the instructions so that the drawing time will be approximately ten minutes.
Come to class with your instructions printed on a letter-size sheet of paper and two drawings that you've made using the instructions. Also bring four sheets of letter-size paper you want people to draw on and the drawing tools you'd like them to use. Your instructions must not exceed one page.
Toolbar
from Casey Reas's WORKSHOP 3: VARIABLES, RESPONSEUsing the ControlP5 library create a drawing tool that can be manipulated by the user.
Art & Code
from Casey Reas's WORKSHOP 2 & 3: DRAWING WITH CODE and VARIABLES, RESPONSESelect a crop of this Sarah Morris painting and draw it within Processing as a 640 x 360 pixel program. First select an interesting/ambitious crop, then draw it on graph paper and/or load it into a program such as Photoshop or Illustrator to read the color and coordinate data. Use integer values for coordinates and only use the following functions for geometry: line(), triangle(), quad(), rect(), ellipse(), arc(), beginShape(),,endShape()and vertex() . The goal is to translate the image into code.
Modify your code from above to make the image respond to the mouse. Do this by adding variables to your program and controlling your custom variables with the built-in Processing variables mouseX, mouseY, and mousePressed. Think about how the quality of motion from the mouse (direction and speed) should aesthetically affect the lines and shapes. The program should remain 640 x 360 pixels.
Add the 3rd dimension.
Encoding compositional logic into the generator
from Casey Reas's WORKSHOP 4: MEDIACreate a sketch so that elements have relationships. First, think about what this means and how you want to utilize the idea. (For example, if the photo of the moom is large, then the photo of the space station is small and vice versa.) Then, develop a precise plan for how your images relate and implement these relationships in code. Use only your own media or media that you have permission to use. Copy and modify the Art & Code sketch to start.
Objects
from Casey Reas's WORKSHOP 7: OBJECTSWrite a function to draw the simple object (cat, face, building,etc). Add parameters to the function so the object can be drawn differently based on the selected parameters. Draw three or more objects on the screen to show the differences possible by changing the parameters. Next, create a Object class using the code from your function as a foundation. Add methods to give the object motion (behavior). Draw three or more objects on the screen to show the differences possible by changing the parameters to the class and its methods.
Arrays
from Casey Reas's WORKSHOP 8: ARRAYSUse the pixels array of an image as data for something other than literally displaying the image. Use common techniques such as slitscanning and height mapping, or invent something new.
Reading List
- Getting Started with Processing
- Form+Code in Design, Art, and Architecture
- Processing: A Programming Handbook for Visual Designers and Artists
Saving STL files
This assumes that you have created a UGeometry geo object and a USimpleGUI gui object with a saveSTL button:- Create a saveSTL() method that does not return anything and does not take any arguments.
- Inside the method add this code so that every time you click on the button you will create an stl file with a unique name.
geo.writeSTL(this, UIO.getIncrementalFilename( this.getClass().getSimpleName()+" ###.stl", sketchPath) );