Category Archives: Uncategorized

What I Am Learning About Functional. Some Insights.

For years now I have been working on a replacement for OpenSCAD. The developers of OpenSCAD describe it as

a functional language that lacks both variables and assignment statements.

In various discussions nobody is entirely clear if OpenSCAD is functional or declarative. I’ll leave that argument to the wiki on functional programming.

In computer sciencefunctional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions[1]

Regardless what is true and useful to us is that you can unroll OpenSCAD operations in a way that is declarative and leaves nothing behind but a core subset of basic constructive solids geometry operations. All of which are constant. They take something. They return something. New state is always made. Old state is never altered. This gives us the opportunity to shave OpenSCAD with a sharp razor.

Shave The Nightmare

Following this I am not sure of the  formal proof but I also surmise that it is then true that you can go from a series of unrolled declarative statements to a functional outcome and indeed to any language you desire. The reasoning then follows that a functional representation is by far the best way to move OpenSCAD in to another language.

Why do we want to do this?

Well I will reiterate that we want to translate OpenSCAD in to a language that is formal and sensible. We want to do that for a whole lot of reasons but let us just start with the interesting translation part for now and how it leads us in to the land of functional.

Here is an example …

EXAMPLE : 
difference() {
for( x = [0:10:100] ) {
translate([x,0,0])
cube(size=1);
}
}

BECOMES :
stack.push(difference())
for ( i = 0; i < 100; i++ ) {
stack.push( translate([x,0,0]) .cube(size=1) )
}

UNROLLS :
difference()
this.translate( [ 0 , 0 , 0 ] )
this.cube(size=1)
this.translate( [ 10 , 0 , 0 ] )
this.cube(size=1)  }
this.translate( [ 20 , 0 , 0 ] )
this.cube(size=1)  }
this.translate( [ 30 , 0 , 0 ] )
this.cube(size=1)  }
this.translate( [ 40 , 0 , 0 ] )
this.cube(size=1)  }
this.translate( [ 50 , 0 , 0 ] )
this.cube(size=1)

*we build a push tree which we unwind as we traverse the stack.   

Note here that we applying a greatly simplified grammar set in order to do the bare minimum to the OpenSCAD script to move it over to a new descent tree for a new target. For example to attack the loop above we might have applied the following Ternary 

GRAMMAR:

|for ?  @NEXT : @COPY ?  “(”  ? @PROCESS_FORLOOPS : @COPY

I am referring to this as bare minimum grammar to achieve OpenSCAD shaving.

Now you might be wondering why we don’t use something like Antlr or YACC. Why don’t we take the existing grammar they have created for this purpose?

Having worked this out you are now up to where I am which is you could either implement your own version of the OpenSCAD grammar and parser thus making all the same mistakes again -or- write a translator that moves the quasi functional / declarative OpenSCAD language as it currently is in to a representation accessible in a broader richer more formalised language like Javascript.

Why would we want to do this. Because making up your own grammar and parser is a terrible idea. The heavy lifting has been done for you. Just pick one! They are all good! Just not Prolog. Prolog was that weird kid that sat at the back of the class and smelled funny. See him all alone over thar. Go give him a hug if your feeling sorry for him.

Pick One

Given that we know that at its most basic that the constructive geometry is a series of simple boolean operations it really isn’t a requirement to have written a whole enormous parser. We know that doing these operations well and quickly was the target problem.

What was NEVER the target problem was inventing your own parser. Avoid at all costs.

Whenever you can translate don’t spew out gigantic grammar files and bury us all in your YACC. YOU ARE NOT THAT SMART AND NEITHER ARE WE.

The answer is we could indeed do exactly that. However we have a burden of work which shifts incrementally to the right as the complexity of parsing increases. At a certain point the token consumer is so simple that the majority of our work is actually in the hooks to code. We then weigh up the extra weight and time consuming effort of making something like Antlr or YACC actually work. The OpenSCAD grammar is totally at the opposite end of the scale. I dare you to go read it.

So we reiterate once again we did not come here to write a parser. We came here to reshape as little as possible on the way to getting to a parser that ALREADY WORKS ..

It is particularly useful to understand that this is possible because it makes interpreting OpenSCAD fairly straight forward if you wish to separate the OpenSCAD language from its current parser and grammar and gigantic codebase of C++.

Those playing along may have seen a hole in the above reasoning. Because OpenSCAD is not entirely pure.

For example should a difference statement be cumulative over all children  or should it operate on an invisible but implied a,b argument. It is obvious once you have unrolled it but this is what happens in the existing parser. So this line of thinking is useful in that it makes us really thing about the language itself.

difference() {
this.translate( [ 0 , 0 , 0 ] )
this.cube(size=1)
difference() {
this.translate( [ 10 , 0 , 0 ] )
this.cube(size=1)
difference() {
this.translate( [ 20 , 0 , 0 ] )
this.cube(size=1)
difference() {
this.translate( [ 30 , 0 , 0 ] )
this.cube(size=1)
difference() {
this.translate( [ 40 , 0 , 0 ] )
this.cube(size=1)
difference() {
this.translate( [ 50 , 0 , 0 ] )
this.cube(size=1)
}}}}}}}

And that leads us to functional programming and a conversation about translating and parsing and what the code might look like in JS to get us from thar to over here ….

First some ugly code I put together in my first pass. Imperative. Bad. All bad.

// ------------------------------------------------------------
// Walk a tree
// ------------------------------------------------------------
this.walk = ( tree ) => {
let i = 0
let state = true
while ( state === true && i < tree.length ) {
if ( this.isInList(tree[i][0]) ) {
//tree[i][1]();
this.hooks(tree[i][1])
state = true
} else {
//tree[i][2]();
this.hooks(tree[i][2])
state = false
}
i++
}
}


let s = '|difference |intersection |union |minkowski @NEXT @COPY => |( @NEXT @COPY => |) @NEXT @COPY => |{ @PROCESS_BOOLEANS @COPY' // grammar
let tokens = " difference ( ) { difference ( ) { union ( ) { circle ( size = 5 ) ; } } }" // set of tokens to iterate over
// -------------------------------------------------
// Flow the grammar stream past the tokens stream
// -------------------------------------------------
let flowGrammar = g => t => {
// split each tern delimited by =>
const gram = g.split("=>").map((ternaryGroup)=> {
return ternaryGroup.split(" ").filter((a)=>{ return a.length!==0 ? true : false })
})
// build occurence table of each instruction descent tree in token stream
const set = gram.map((ternaryGroup)=> {
return lodash.flatten(ternaryGroup.map((oPeration) => {
return oPeration[0] === "|" ?
t.split(" ").map( (chnk,index) => {
return oPeration.slice(1,oPeration.length) === chnk ? index : 0
}).filter( (index)=>{ return index > 0 ? true : false })
: []
}))
})
// build call table
const instr = gram.map((ternaryGroup)=> {
return lodash.flatten(ternaryGroup.map((oPeration) => {
return oPeration[0] === "@" ? oPeration : []
}))
})
console.log( set )
console.log( instr )
}
flowGrammar(s)(tokens)

Tutorial 2 – Unions

INTERFACE CHANGES

Since the last tutorial the interface has had a significant update. You will now find that the editor and the main viewing area are all on the same page. You will see the interface now has two save buttons and that the editor button has been removed. The save button in the tool panel saves changes made to model through the visual interface. The save button in the editor saves changes made in the editor.

new_interface
UNIONS

The computational solids boolean operations in the Makertron support the following results of intersection subtraction and union.

booleanops

The type can be either “intersection” , “subtraction” , “union”. You may select only one root object.  Each boolean operation represents the application of one boolean statement on one root object. The boolean operation that is applied to the root object can have multiple children specified in the object field.

The resultant of the boolean operation will take the name in the name field.  If you wish to update an existing object you can specify that name in the name field and the previous state of that object will be over written.

In the example json script shown below both the ‘teapot_spout’ and the ‘teapot_handle’ are being unified with another object t named ‘clip_top’. The result of this operation will produce a new object named ‘unified_teapot’.

{
"type": "union",
"name": "unified_teapot",
"root": "clip_top",
"objects": "teapot_spout,teapot_handle",
"state": "teapot_body_enable"
}

For the teapot body in the body.jsn we have created two spheres. One named ‘teapot_body_outside’ and one named ‘teapot_body_inside’.

{
"type": "sphere",
"name": "teapot_body_outside",
"radius": "teapot_body_size",
"pos": "[0,0,0]",
"scale": "[1,1,1]",
"color": "teapot_body_color",
"resolution": "teapot_body_resolution",
"state": "1",
"group": "1"
}

{
"type": "sphere",
"name": "teapot_body_inside",
"radius": "teapot_body_size-teapot_body_thickness",
"pos": "[0,0,0]",
"scale": "[1,teapot_body_scale_y,1]",
"color": "teapot_body_color",
"resolution": "teapot_body_resolution",
"state": "1",
"group": "1"
}

For the radius of the inside sphere we set a a size that is the size of the outside sphere minus some thickness. It is important when doing boolean operations to consider what the results might be of your geometrical interactions.

The state of both these spheres could be true or  or false at this point. Once we have completed our operation with them they will be set to false. The state controls which objects will ultimately be included in our final scene for export. Note however that even if we set these objects to a state of false we can still act on them in the scene.   Likewise when we  get a new object as a result of our boolean operation we can set the state of that to. If the state of that boolean operation is set to true then this new object that is a result of the operation will be visible in the scene and exportable .  If it is set to false it will become invisible but we could still use this object as an invisible proxy to perform other operations on objects that were visible. For state you can enter true or false or 1 and 0 respectively. 1 being true and zero being false. Using an integer value for true or false is useful if you wish to set the state of something via a variable.

Now we will apply a boolean operation. This will result in a new object named ‘hollow_sphere’.

{
"type": "subtract",
"name": "hollow_sphere",
"root": "teapot_body_outside",
"objects": "teapot_body_inside",
"state": "1"
}

Next we will want to cut the top off the new object ‘hollow_sphere’ with another boolean operation.  To do this we will create a cube called ‘lid_line’.  This cube can be moved up and down on the y-axis to change how much of it will ultimately be subtracted from the ‘hollow_sphere’.

{
"type": "cube",
"name": "lid_line",
"radius": "[teapot_body_size*2,teapot_body_size*2,teapot_body_size*2]",
"pos": "[0,clip_height,0]",
"color": "teapot_body_color",
"state": "1",
"group": "1"
}

Then we will apply a subtraction between this cube called ‘lid_line’  and the ‘hollow_sphere’ object we made in the previous boolean operation. We are using the cube ‘lid_line’ as a proxy to alter our ‘hollow_sphere’ geometry. Note once again that it does not matter if it is invisible or visible in this context because after our boolean operation it will be set to false.

{
"type": "subtract",
"name": "clip_top",
"root": "hollow_sphere",
"objects": "lid_line",
"state": "1"
}

The result of this operation will be a new object named clip_top with the top sliced off that will be set to visible in our scene and exportable. This is the body of our teapot.

hollow_sphere

In the next tutorial I will cover Making the handle and the spout and the Bezier tool.

Makertron Tutorials

Makertron Teapot Tutorial 01

Interface Overview

Toolbar

toolbar

The tool bar contains the non dynamic parts of the scene that you cannot edit. The first button saves your complete project to server. Second exports your project out to a zip containing stl files. [ Third button takes you to the editor. – depreciated ] 

*Like most things in the Makertron how an stl is exported directly depends on how you configure your model. If you completely unifiy your model you will get a single STL. If you break your model up in to parts each of those will be contained with the zip file seperately. STL files are currently exported in the STL binary format. 

Main Display

display In the centre is the main display area which contains the tool bar  and the viewing area for your project. Below this is the debugging window which will tell you if you have any errors and if the Makertron has successfully managed to process your project. 

 

Panel Area

accordion Panels are dynamically generated at load time based on the code you have written. A project should always begin with an ‘About’ panel.  Each panel that is stacked in the accordion will load a set of components associated with that panel in to the menu space on the right hand side of the interface. In the  default project you begin with you will see that the ‘About’ panel loads three sliders in to the right hand interaction menu when selected. These are not currently connected to anything and have no descriptions. We will demonstrate in this tutorial how to get them to do something useful to your model.

In the editor where you access your code each panel entry is defined as a panel at the start of each module.  Each module that you write will typically begin with a panel.  If you include no panels you will have no interactivity aside from the main display and no interaction menus will be selectable. The very first line in the root of your project should always be a panel definition. 

Interaction Menus

menus_together

Interaction menus are also dynamically generated at load time based on the code you have written. Each interaction menu will typically contain a combination of sliders spinners and editing fields to let you tweak specific aspects of your model as you develop it. These changes can then be saved and in turn will be reflected in your code.

*Both the left hand panels and the right hand menu area are dynamically generated based on the code that you write when your model is loaded. This is what seperates the Makertron from pure computational solids generation systems like OpenSCAD. As you develop your project you are also developing an interface to interact with that project. In the coming update you wont need to switch between the editor and graphical display area. It will be in the same window.

 Associating A Slider With Code

All widgets in the right hand interaction menus are associated with code that you create in the editor. At the moment in the default teapot example you will see three sliders associated with the about panel when you select it ( select about panel interaction menu appears on right ) . They currently have no names and are not associated with anything.

menu_about

If you go in to the editor edit and look at the start of the first  module you will see three lines of code each of them associated with the about  panel.  We will edit the first one.

editor_slider

{ “type”: “set”, “panel”: “About”, “note”: “”, “value”: “teapot_body_enable”, “operation”:”range|0|1|1″ }

Set commands will typically intialize the variable and generate a widget.  The variable and widget can be defined even if you do not use them right away.

In this case we are initializing a variable called “teapot_body_enable” and associating it with the panel “About”. However we have not assigned it a note yet or associated it with anything in our project.  Change the code to the following

{ “type”: “set”, “panel”: “About”, “note”: “This Enables Teapot“, “value”: “teapot_body_enable”, “operation”:”range|0|1|1″ }

A range slider enables you to slide across a number range. This number range can either be  an interger range or floating point value. At the moment the range slider is set as a toggle between 0-1 and defaults to 1.   That is “range|0|1|1.

*The operation field in a set is where you define what sort of variable you want to set and also describes what widget will be defined for that variable. You can set spinners. Array fields. Bezier fields. Menu fields. Color Fields. More of these fields will become available as the interface develops. Controls for Chamfers. Complex organic surfaces. 

Now we want to get this slider to do something useful.  Scroll down to the bottom of the default. jsn module.  The final function is a union. It unifies together the body of the teapot and the handle. It currently has a state of 1 which means it is enabled.  Change that state to “teapot_body_enable”.

editor_union

Then click save. When you return to the main window from the editor you should now see the first slider in the interaction menu is labled.

slider_configured

Drag it to the left and the teapot and handle should both disable. Drag it to the right and it should be enabled again. You will also see that when you disable the unified teapot body and handle that it is no longer exported in the STL.

In the next tutorial I will demonstrate how to union the teapot body and handle with the spout.

 

Axis Problem

So I have an axis problem on the X-axis. Not sure if it is backlash / worn lead screw or a problem with the axis cable or controller. I swapped over the X/Y plugs on the controller and still got the same problem in the same cable. This leads me to think it is not the controller but either the axis leadscrew itself or the cable. Here is a picture of the part. Note that the walking back and fourth on the x-axis has happened in a thin part of the model aligned with the X-axis where it had to step quickly back and fourth over a short distance.

000_0009[1]

000_0010[1]

Here is what the part is supposed to look like for reference.

printed_bracket

~update~

This turned out to be a classic problem with the end-stops causing interference. Disconnected the end-stop on the X-axis and the problem went away. Moving right along !

Bracket mounted on the mill ..

Photo0086[1]