YASP, Yet Another SCAD Program
YASP is a dead simple OpenSCAD DSL in Ruby
Why
There are other DSLs, but they try to do too much, and their code is kind of confusing. Those aren't the greatest justifications for making a new one (hence the name) but YASP focuses on being as simple, predictable, and explicit as possible. In addition, YASP has the ability to save partial trees to variables, if that's your kind of thing.
Design
YASP has three classes:
- Yasp, in charge of the various output methods (string, stdout, file)
- YaspTree, the core of yasp, is an AST builder and evaluator
- Command represents an SCAD directive (primitive or operation) and its variables and children
As of writing these classes comprise all of the 136 lines of code used to run YASP
Caveats
- children must always be scoped within a block. YASP can't understand
translate([1,2,3]) cube(4), it needs to be in the formtranslate([1,2,3]) { cube(4) }(or of coursetranslate([1,2,3]) do cube(4) end) - Yasp does absolutely no validation for you, and will dutifully print your
trnaslateblock or add children to acubeprimitive - OpenSCAD module and function definitions, as well as special variables and for loops, are currently not supported. Modules and functions are meant to be superseded by Ruby modules and functions; special variables could definitely be implemented for later, but for now they can be used in primitive arguments by passing a string key:
sphere(r: 12, '$fn': 24). For loops are again meant to be superseded by Ruby list comprehension. - if you want to save a block of code for later, you need to wrap it in an
assignblock and later reference it's value by using theaddfunction (see the example for an example). in order to support not having to use theaddfunction around every command, method_missing immediately adds a command to the AST. usingassigninitializes an entirely new, separate YaspTree from that block, and then returns the resulting AST. That's great, but referencing the variable (or function) will never hit method_missing, so you need to manually add it to the AST by callingadd
Example
require 'yasp'
Yasp.file('scad.scad') do
# you can load Commands into variables!
sphere = assign do
sphere(3)
end
# and functions!
def square_at(position)
translate(position) do
cube(6, center = true)
end
end
# and use them wherever you'd like!
5.times do |y|
translate([y*10, 0,0]) do
rotate([-y * 15, 0, 0]) do
cylinder(r: 3, h: 10, '$fn': 10 * y)
# anything that returns Commands must directly use the
# add() function, since method_missing can't catch them.
add(sphere);
square_at([0,0,y])
end
end
end
end
produces:
translate([0, 0, 0]){
rotate([0, 0, 0]){
cylinder(r=3, h=10, $fn=0);
sphere(3);
translate([0, 0, 0]){
cube(6, true);
};
};
};
translate([10, 0, 0]){
rotate([-15, 0, 0]){
cylinder(r=3, h=10, $fn=10);
sphere(3);
translate([0, 0, 1]){
cube(6, true);
};
};
};
translate([20, 0, 0]){
rotate([-30, 0, 0]){
cylinder(r=3, h=10, $fn=20);
sphere(3);
translate([0, 0, 2]){
cube(6, true);
};
};
};
translate([30, 0, 0]){
rotate([-45, 0, 0]){
cylinder(r=3, h=10, $fn=30);
sphere(3);
translate([0, 0, 3]){
cube(6, true);
};
};
};
translate([40, 0, 0]){
rotate([-60, 0, 0]){
cylinder(r=3, h=10, $fn=40);
sphere(3);
translate([0, 0, 4]){
cube(6, true);
};
};
};
That should be it, hit me up with any questions!