Exposing the Core

Banate CAD strives to follow the design cliche “Make that which is simple, simple, make that which is hard possible”, or something to that effect.  To that end, Banate CAD is structured in nicely composable layers, or at least in my mind.  This can be seen in one area, the import of mesh files.

Here, I have used the simplest of import commands:

import_stl("bowl_12.stl")


translate({100, 0, 0})
import_stl("bowl_12.stl")

The import_stl() function will simply read in the .stl, and add the mesh to the scene automatically. In this case, the second import_stl() call will load the same .stl again, crating another instance of the mesh, and add that to the scene as well.

This is pretty straight forward, and works just fine. For limited numbers of meshes, the fact that you’re replicating meshes, doesn’t really have that much of an effect. Where this is a pain though is when you want to load multiple instances of the same mesh, perhaps 10s or hundreds of them.

Here for displaying 16 of the bowls, I’d actually like to load the bowl once, and simply use that same mesh to perform many operations. So, instead of using the import_stl(), I use the import_stl_mesh() function like the following:

function test_instancing(rows, columns)
local amesh = import_stl_mesh("bowl_12.stl")

for row = 1,rows do
for col = 1,columns do
color({row/rows, (row+col)/(rows+columns), col/columns, 1})
translate({col*100,row*100,0})
addmesh(amesh)
end
end
end

In particular, it is this call at the beginning of the function that does the magic.

local amesh = import_stl_mesh("bowl_12.stl")

Here, we are loading a single instance of the mesh. Then later, we can use the addmesh() function to actually add the mesh to the scene. From then on out, it will be displayed just like in the first case, but it won’t have to replicate the mesh to do it.

It really shines when you have a whole bunch of things:

In this case, with 100 instances of the bowl, it doesn’t take much longer to load/render than simply displaying a single bowl.

What does this have to do with layering? When you call ‘import_stl’, that code is essentially doing import_stl_mesh(); addmesh(). Rather than hiding that, it is made readily available to the user. The casual user who’s not concerned with hundreds of instances, or optimizations, can simply use the import_stl() call. For those who want to get into the nitty gritty, and deal with their own instances, they can use the import_stl_mesh() call. The other benefit of doing the version where you get back the mesh, is that you can then perform operations on the mesh itself if you so choose. Let’s say for example you have some sort of smoothing routines, or something that fixes vertex normals, or an exporter, or any manner of things, you can add them easily because you have the mesh in hand, and not just a graphic representation of it on the screen.

It might be possible to write code like this:


local amesh = import_stl_mesh("file.stl")
export_dxf("file.dxf")

Banate CAD does not currently have any DXF support, but when it does show up, you’ll be able to easily do this. And while you’re at it, since you can do: export_stl(amesh, "newfile.stl"), you could write code within your file to generate multiple .stl files, one per mesh in your scene if you feel like it. The default “Export STL” from the File menu simply iterates through all the meshes in the scene (simple) and exports them all. They access and layering in Banate CAD allows anyone to do the same from within your script.

So, the system is very flexible, and gives the scripter the exact same capabilities as the guy who wrote Banate CAD in the first place (me). So, if you want to stick with the simple, you can do that. If you want to go down to the metal, from within your scripts, you can do that as well.


Banate CAD Second Release

There is a new consolidated package available here: Banate CAD 20111128

I wonder if a weekly distribution is a realistic thing to expect?

Banate CAD is maintained “Live” on GitHub, so changes that are going on in the code are reflected in GitHub fairly regularly. But, since it is live, things like the Examples get out of sync regularly as well. I am updating the Examples weekly to match the current state of the software for that week. When I bring the two in sync, I package it up in a convenient .zip and make it available for download.

This past week saw quite a lot of change in Banate CAD. Primarily, after the first release, I have been focused on reducing the amount of code that’s in the package. At the end of the day, I want the codesize to remain within about 256K. If it remains within that budget, then I have something that could possibly be hosted on a microcontroller, or other small compute devices. So far, on disk size is 133K, so I still have some room to add more functions.

One big form of reduction for this past week was further simplification and reuse of the core BiParametric object. It can be used to do anything from produce a sphere, to a Bezier surface. I also introduced the concept of “Sampler”, which is essentially just a function that you can ask “for this parametric position, what’s your value?”.

The MoonShot picture above is the epitomy of using these samplers. I use a sampler for the offsets of vertices, to do height mapping, and I use the same exact sampler to determine the color on a per facet basis. That makes for some very convenient reuse. It also demonstrates how easily samplers can be combined into super samplers.

Another usage of a functor has to do with the Torus object. The base torus on its own will only generate a circular profile. But, you can hand it a functor which will determine the outline of that profile. In this case, I’ve used the SuperEllipse formula to give shape to the profile. In this way, you can pretty much select any cross section profile you like. The core of the Torus routine remains the same, go around a ‘circle’. At some point, even that part will become a functor, and you’ll be able to generate very strange torii indeed.

As Banate CAD is meant to do ‘CAD’, I finally worked on getting the export to .stl function working properly.  Now, most models, if they are solids, can actually be exported.  The cheat at the moment is that they will always export to a file with the name “file.stl”.  There’s no reason for this other than I need to change file handling to be more robust in general, so I didn’t bother to make any attempt to improve it at the moment.  At the moment, although the .stl files will work, they certainly benefit from going through a tool like MeshLab to clean them up and remove bits and pieces, depending on the model.  Mainly removing duplicate vertices, and triangles of zero area, like at the poles of a sphere.

So, there you have it.  One week’s worth of work, and a whole bunch of new functionality.


And Height Maps for all…

How can there possibly be 3D modeling and visualization without height maps?

Of course, Height maps have been a staple of 3D graphics for quite some time now.  Why not extent this to 3D modeling for printing?  In this case,  I have created this nice generic RubberSheet object.  A RubberSheet is a BiParametric with a size.  So, it’s just a convenience wrapper on the BiParametric.  It assumes a basic X,Y grid, but then things get really strange.

Of course you can give it a number of steps to iterate in each of the axes, and you can give it a color sampler as well, and you can give it a VertexSampler.  What?  Yah, of course, you can just hand it a routine which will calculate the vertex at the particular grid point.  On its own, it will just lay out a flat grid, with nothing interesting on it.

This is using the standard ImageSampler.

local colorSampler = ImageSampler.new({  Filename='firstheightmap.png',  Size = size,  Resolution = res,  MaxHeight=64, })

rubbersheet({  Size=size,  Resolution=res,  ColorSampler = colorSampler   })

But, what if I used the color sampler for both the color at a position, and for the x,y,z value as well? After all, it’s pretty easy to calculate the x and y, values. And the z could be calculated by taking the luminace value (gray) of the color pixel, and using that as the height. In fact the code is as simple as this:


function ImageSampler.GetVertex(self, u, w)
local col = self:GetColor(u,w)


-- Turn it to a grayscale value
local height = luminance(col)


-- Multiply by max height
local x = u*self.Size[1]/self.Resolution[1]
local y = w*self.Size[2]/self.Resolution[2]
local z = height*self.MaxHeight
local vert = {x,y,z}


return vert, {0,0,1}
end

The ImageSampler is a functor. That is, it’s an object that has some state, and it can have simple functions called. At any rate, since I’ve implemented ‘GetVertex’, and the BiParametric object calls GetVertex if you tell it what VertexSampler to use, you can now do the following:


rubbersheet({
Size=size,
Resolution=res,
ColorSampler = colorSampler,
VertexSampler = colorSampler,
Thickness = -10,
})

As an added bonus, you can set the Thickness property, and automagically get a rubber sheet with the specified thickness, which makes it printable. This will create a rubber sheet that will follow the contours up and down. If you want a base plane, then you’d alter the lengths of the normals to be a reciprocal relationship to the height, but that’s a silly way of doing things, even though it would work.

And, there you have it. Height maps with ease. And, since it’s integral with the color Sampler, you could easily add colors based on the height value, for instance to have snow at the tops, brown dirt below that, then greenery at the bottom.


Tactile Math Explorations

Although I took math in high school and college, my math skills have atrophied over time due to lack of usage.  How can I possibly do 3D graphics if I can’t remember the math?  It takes a lot of referencing back to books, and code I wrote years ago.  So, perhaps there’s a better way of teaching some math skills which will make the lessons stick?

Here we see 3 of the quadrics.  The cone, cylinder, and hyperboloid.  Each of them has their own bi-parametric equation that describes them.  In this particular case though, I’ve used only one formula to describe three different shapes.  I have used the Hyperboloid, because by slightly changing various parameters, you can get all three shapes.  The Banate CAD code looks like this:


local usteps = 180
local wsteps = 80
local radius = 5
local height = 10


local con = {{radius, 0, 0}, {0, 0, height}}
local cyl = {{radius, 0, 0}, {radius, 0, height}}
local hyp = {{radius, 0, 0}, {0, radius, height}}


function hyper(params)
hyperboloid({
USteps = usteps,
WSteps = wsteps,
StartPoint = params[1],
EndPoint = params[2],
--ColorSampler = checkerboard:new({Columns=usteps, Rows=wsteps})
})
end


color("Red")
hyper(con)


color("Green")
translate({12, 0, 0})
hyper(cyl)


color("Blue")
translate({24, 0, 0})
hyper(hyp)

In the beginning there, I setup the radius, height, and resolution I’m going to generate the curves.  Now, the hyperboloid takes some explanation.  Of course you can look it up online, but the thing to realize is that you take a line, any line, and sweep it around in a circle.  for the simplest case, the cone, just take a stick, stand it straight up on your desk, some distance from the center of your circle, and move it around that center.  You have just traced out a cylinder in space.

Now, let’s say you want to make a cone.  Take that stick again, and instead of it standing straight up and down, lean it in towards the center.  Now as you sweep it around, the top part stays fixed, as the bottom traces out a circle.

And lastly, for the hyperboloid, tiltl the stick slightly back, and to the left, and again, trace it around.

This is kind of hard to describe in words, and some visual aids would sure make it easier.

I’ve done exactly the same thing here, execept I’ve changed the resolution to make the facets more obvious.  Now if you go back and read the explanation again, it might make a little more sense.

In each case, all I’ve done is change the starting and ending points for the stick to be traced around the circle.

local con = {{radius, 0, 0}, {0, 0, height}}
local cyl = {{radius, 0, 0}, {radius, 0, height}}
local hyp = {{radius, 0, 0}, {0, radius, height}}

Each of these little arrays defines a starting and ending point for the hyperboloid function.  The con (cone), has a bottom radius of ‘radius’ and a top radius of 0.  The cylinder maintains the same radius at the bottom and top.  The hyperboloid maintains the same radius, but with a twist as it shifts from the x-axis to the y-axis.

This is a nice revelation.  For the code itself, it means that I don’t actually have to write three different functors to represent these things.  I can write just one, the hyperboloid, and just change the parameters I feed it in order to get the others.  Same for ellipse/sphere.  A sphere is just an ellipse where the two axes are the same length.

How does this help me learn maths?  Well, I can write the hyperboloid equation on paper, or better yet, plug it into a graphing calculator, and play around with the results.  Better still, I can plug it into Banate CAD, and see it on the screen in real time.  And even bestest of all, I can lower the facet count, and print these out so I can play with them in my hands.  It’s this latter case that’s the most exciting to me.  If I can make maths a tangible physical thing, then I’m more likely to remember it.  A set of these hyperboloid relatives, printed out as play things, would really help me retain and understand the math behind them.

 


What the Functor can draw that?

I want to reduce the amount of code I write, and yet increase the power of the core system.

Here are three instances of a Bezier Surface.  What’s different about them?  Their geometry is the same, meaning the exact same control curve was used in all three cases.  Their coloring is obviously different though.

In the first case,  the code looks something like this:

color(“Yellow”)
addshape(lshape3)

I simply use the general purpose ‘color()’ function to give it a nice Crayola Yellow coloring.  Nice and uniform, no fuss no muss.

In the second case, the actual rendering part is exactly the same, but the setup is a bit different.  To setup the surface in the first place, I do the following:


local lshape1 = shape_bicubicsurface.new({
M=cubic_bezier_M(),
UMult=1,
Mesh = mesh,
Thickness = thickness,
USteps = usteps,
WSteps = wsteps,
ColorSampler = colorSampler1,
})
addshape(lshape1)

The various parameters here are all very interesting, but they all have reasonable defaults.  The key here is the ‘ColorSampler = colorSampler1’, so what is colorSampler1?
local colorSampler1 = checkerboard:new({Columns=usteps, Rows=wsteps})

In this case, the checkerboard ‘object’ is a “Functor”, which can procedurally generate a color, at a given point.  It has a function:

rgba GetColor(self, u, v)

This function will be called every time the underlying machinery wants to get a color value.  Those ‘u’ and ‘v’ values will vary from 0 to 1, across the entire surface being rendered.  So, the Checkerboard pattern just needs to determine what value it wants to show, based on the ‘uv’ combination.

This is normally called Procedural Texture Mapping, in the graphics world.  It’s a fairly powerful technique, which will allow you to quickly and easily create all sorts of textures for objects, without having to deal with bitmaps.  At least for textures that can be computed, rather than being static images.  It will get really interesting when the animation system is operative.

In this case, I am using the same checkerboard procedural texture, but it’s setup a bit differently.  I can specify a “HighColor” and a “LowColor”.


torus({Offset=offset,
Size=size,
USteps = usteps,
WSteps = wsteps,
ColorSampler = checkerboard:new({Columns=usteps, Rows=wsteps, LowColor={0,0,1,1}, HighColor={1,1,1,0}})
})

Here, the checkerboard procedural texture is setup with a LowColor == blue, and a HighColor, which is white, but transparent, so nothing shows.  Why would you want to do this?  Well, let’s consider the case where you are trying to print with multiple materials.  You could easily take the exact same mesh, render it twice.  The first instance would have the HighColor set to something, and lowcolor transparent.  The second instance would have things reversed.  Then, as you generate the .stl, you could look at the color, and drop out objects with transparency, or generate a file format that would actually support changing materials like that.

So, what is a function?  It’s just a small object, that typically contains some retained parameters, and can execute a simple function.  In the case of the checkerboard pattern, the retained data is the size of the grid, and the high and low colors.  The function is “GetColor”.

This is great because you can pass this function to a core object, like the BiParametric thing, and it will handily just call your function at the right times.  You don’t have to worry about doing any of the coding to come up with the individual vertices for your mesh.

In this final case, I am using the Image ImageSampler object.  You construct it, supplying a filename, and it will deal with handing out pixels based on the ‘uv’ parameters being passed into its ‘GetColor’ function.  So, great fun.  You could of course implement filters and all manner of things within this sampler.

So, that’s what a functor can do.  Execute a simple function.  When you have a base object like BiParametric, it becomes fairly easy to do everything from determining vertices, to normals, to coloring, using these simple functors.  That makes it extremely fast to experiment and extend without having to really mess with the base class.

 


Rapid Prototyping and Improvement

Putting Banate CAD out has been a nice rewarding experience.  Responding to user feedback, making improvements and the like, is ongoing fun.  The great fun of Banate CAD for me is how quickly I can make progress.  The compile/try cycle is very fast.  I use the SciTE editor/environment to do my development.  It’s pretty good about showing errors, and helping you zero in on what’s wrong.  The fact that Lua is such a basic minimalist language actually makes things much easier.

As far as actually using Banate CAD is concerned, today on Thingiverse, I saw an entry for a Mobius Heart.  I thought, hmmm, how hard would it be for me to model that in Banate CAD?

To break this problem down, I first thought about the various things that are operating.  In the Mobius version, there are essentially 3 parametric equations operating at once.

One, determines the heart shape.  You can run this equation, like you would run a circle equation.  Go from 0 to 360, and it will tell you the x,y coordinates along that path.

In the above picture, I’ve essentially done exactly that.

The Second parametric curve would tell you the profile, or cross section of the thing in general.  This could be an ellipse, square, or anything (SuperEllipse).  As long as you could get a value for a parameter from 0..1.

The last parametric curve determines the twist, as you follow around the first curve.  This twist could modify the profile curve, and give you that mobius effect.

In this case, we essentially have a TriParametric thing.  As long as you can describe each of the curves independently, you could just plug them into something and get a mesh out of it.

Today I was adding  a BiParametric object.  This deals with everything from Ellipse, to Torus, and bicubic surfaces.  These shapes all have the same general thing going on.  The only thing that varies is when you ask for a value based on a u,v pair (parameters that vary from 0 to 1).

This is great fun because now that there is this BiParametric class, I can create an infinite variety of equations that can utilize it, without having to repeat that silly facet generation code all over the place.  This not only increases my speed in coding, but also reduces my errors, because all the hard stuff is concentrated into one simple place.

Not to allow the focus on 3D have all the fun, I’ve been adding various 2D objects as well.

Well, what good is that?  Isn’t this a CAD program?  Well, yes, but it’s more than just a CAD program, it is a visualization environment.  As such, I need to be able to do things like show dimension lines, bar graphs, and all that other stuff you see in typical 2D environments.  What Banate CAD does is understand the difference between a simple graphic, and a solid shape, which it could export as a .stl for example.   And of course, all good linear extrusions start with some 2D profiles, so this is the beginning of linear extrusions.

Besides all these fundamentals, one more major feature areas I need to add is the ability to do animations.  That doesn’t really add too much to the 3D capabilities, but it certainly gives some pizzazz to the visualization aspects of the thing.  And of course the trick is to do it all without creating such a huge bloated codebase that I can’t maintain over time.  And thus the introduction of the simple class mechanisms.

At any rate, Being able to rapidly lay out code is an extremely rewarding thing.  Being able to then leverage that code to create interesting designs is even more fun.

 


Improving the Examples

After making the initial release of Banate CAD yesterday, I went back and fixed up the various example scripts.  The code on GitHub is the most up to date.


Introducing BanateCAD

Hum Banate – We Make

Introducing BanateCAD.  I have spent the past year playing with OpenScad, doing 3D modeling, learning how to make interesting things to print on my printer.

Although I’ve been able to do a great many things with OpenScad, I finally came to the point where it made more sense for me to create my own 3D modeling package, rather than living within the constraints of OpenScad.

Banate CAD is the beginning of my effort to create what I will term as a 3D visualization and modeling package.  At the moment, it possesses many of the primitives that I have created in OpenScad over the past year, from cubic surfaces, to blobs.  I’ve thrown in some extras like being able to specify colors using Crayola color names.

There is NO CSG at the moment.  CSG operations will be great, and I will probably add them over time.  I want to implement the CSG operations as native Lua code, not simply bind to an existing library.  In that way, more of the codebase will be transferable to other platforms, without having to worry about porting monsterous libraries that I know nothing about.

Banate CAD is written completely in Lua.  I use the IUP library, and LuaGL.  As long as these two packages are available in your Lua distribution, it should work on your platform.  At the moment, it’s “Windows Only”, simply because I use the LuaForWindows package, and I haven’t tested with anything else.  I do have a Mac, and I’ll try it out there, and work out whatever differences may exist.

In creating Banate CAD, I am hoping to accelerate the advancement of this genre of CAD tools.  It is freely and easily modified/improved by anyone who has some basic Lua coding skills.  There should be a few people from the “World of Warcraft” universe who can apply some skills to making this better.

I do provide some basics, like being able to turn a triangle mesh into an OpenScad polyhedron().  That might make it relatively easy to move between environments.

So, there you have it.  On the one year anniversary of my first upload to Thingiverse, I am putting out a “Thing” that I think might be useful to some people.