diff --git a/cad/test1/Makefile b/cad/test1/Makefile new file mode 100644 index 0000000..86a9f40 --- /dev/null +++ b/cad/test1/Makefile @@ -0,0 +1,15 @@ +OUT=scad.stl cadmium.stl + +.PHONY: all clean + +all: $(OUT) + +scad.stl: button.scad + time openscad -s $@ $< + +cadmium.stl: button.py + time ./$< + mv botton.stl $@ + +clean: + rm -f $(OUT) diff --git a/cad/test1/README b/cad/test1/README new file mode 100644 index 0000000..1044722 --- /dev/null +++ b/cad/test1/README @@ -0,0 +1,340 @@ +Comparison of Free scripted 3D CAD systems +========================================== + +Werner Almesberger + +This is a brief evaluation of the scripted 3D CAD systems OpenSCAD +and Cadmium, comparing the workflow, resource consumption, and the +quality of the results. + + +Introduction +============ + +This file and the sources of the models can be found in +http://projects.qi-hardware.com/index.php/p/wernermisc/source/tree/master/cad/test1/ + + +Objectives +---------- + +This test aims to determine the general suitability of currently +available Free scripted 3D CAD system for the construction of +real-life objects. + +Aspects considered were the ease or difficulty of model development, +the clarity of the modeling language, resource consumption during +rendering, and the quality of the resulting mesh. + +A second objective was to evaluate the suitability of CSG as the only +means for constructing models suitable for large-scale industrial +production. + + +Object description +------------------ + +The object to model is a simple button/key cap shape. The shape +consists of a top part shaped as a 10 x 15 mm rectangle with rounded +corners and at height of 1.5 mm. The top part rests on a base that's +0.5 mm thin and has a border of 1 mm on each side. + +The corners of the rectangle are rounded with a radius of 2 mm. All +other external edges are rounded (chamfered) with a radius of 0.2 mm. +The edge where top and base meet is filleted with a radius of 0.4 mm. + +Note that a real button would typically have an internal cavity, +possibly some depression or other structure on its top, and on the +bottom side a pusher in the middle and possibly other support +elements. + +Also, if the design was to be used for injection molding, sidewalls +would be slightly tilted. + +The rounding of the bottom plate is not strictly necessary and was +added for visual appearance. + + +Candidate 1: OpenSCAD +--------------------- + +OpenSCAD [1] uses its own language, somewhat similar to POV-Ray's, to +describe 3D objects. It has an IDE with a quick preview capability +using OpenCSG [2]. + +High-quality rendering, e.g., to STL, is done with CGAL [3] and can +also be run non-interactively. + +OpenSCAD and OpenCSG are licensed under the GNU GPL v2. Parts of CGAL +are licensed under the GNU LGPL v2.1 while others are licensed under +the incompatible QPL. See [4] for details. + +The version tested was the openscad 2011.06-1+lucid1 Ubuntu package. + + +OpenSCAD front-ends +------------------- + +There also a number of Python-based scripted front-ends for OpenSCAD, +namely OpenSCADpy [5], PyOpenSCAD [6], and pySCAD [7]. + +Furthermore, there is Mecha [8, 9] for Haskell. + +Cadmium (see below) appears to be on par or better in terms of syntax +clarity and tidiness than the OpenSCAD Python bindings. Therefore, +only pure OpenSCAD was considered for this comparison. + + +Candidate 2: Cadmium +-------------------- + +Cadmium [10] is similar in concept to OpenSCAD, but uses Python +instead of a homegrown language. Open CASCADE [11] (via pythonOCC +[12]) provides the 3D operations here. + +The respective licenses are GNU AGPL v3 for Cadmium, GNU LGPL v3 for +pythonOCC, and a homegrown "LGPL-like" license [13] for Open CASCADE. + +The Cadmium version tested was Sun Jul 10 16:04:07 2011 +0530 commit +d4ff63b150ee060a8179a74e369b5df3d0a4a3fc, with pythonOCC 0.5. + + +Results and observations +======================== + +Model development was efficient with both systems, with most of the +difficulties coming from the task of making the model, not from +inadequacies of the tools. + +Both systems also also produced correct-looking meshes. + +Notable differences exist in the time the rendering takes, where +rough previews with OpenSCAD are instantaneous and proper rendering +takes minutes, while Cadmium has no preview and the rendering takes +hours. + +On the other hand, some small anomalies could be found in the mesh +generated by OpenSCAD while the Cadmium's mesh looks perfect. + + +Model development +----------------- + +Both systems offer the same basic CSG primitives and operations, +which made the model development per se straightforward and the +porting from one system to the other effortless. + +The very quick preview of OpenSCAD is immensely helpful during +development. The usefulness of the preview is diminished by +differences only being shown as unions of the solids involved, with +color indicating their role. It was thus often necessary to isolate +and simplify elements before the resulting shape could be guessed, or +to render with slower CGAL. + +Given the slow rendering process, debugging non-trivial designs with +Cadmium is currently quite time-consuming. + +Development of the basic model (without chamfers and fillets) was +first done with Cadmium. I then switched to OpenSCAD to develop the +more advanced features, and finally ported them back to the Cadmium +model. + +Designing the model elements for filleting and chamfering was +somewhat awkward with only CSG and - without understanding the +entire construction process - it may not be easy to see what the +resulting code does. + + +Modeling language +----------------- + +The limited programming language of OpenSCAD proved to be more than +adequate for this simple design. To ease comparison and to reduce the +porting effort, the Cadmium model has the same code structure as the +OpenSCAD model. + +It should be noted that some redundancy could be avoided in Cadmium +if all the "rbox_*" functions were placed in a common class whose +objects could then remember the box's geometry for reuse with the +fillet and chamfer functions/methods. + +One nuisance with OpenSCAD is that mistyped variable names merely +generate a warning but let rendering proceed - often with confusing +results. + +One difficulty encountered when making the Cadmium model was that +there appears to be no null value for the "union" operation, which +means functions that generate all their objects in a loop have to +special-case the first element, making them look a bit awkward (e.g., +rbox_chamfer_top_corners). It should be easy to remedy this +shortcoming. + +The Python language also introduces complications to Cadmium that +OpenSCAD can avoid, such as the Python parser's limited ability to +detect continuation lines, requiring continuations to be marked with +a backslash, and the need to pay attention to the mixing of +floating-point and integer numbers when using divisions. + +Cadmium's ability to use short operators instead of blocks generally +yielded only marginally more compact code, since many operations +ended up being longer than one line anyway. In fact, the code +structure often looks a bit tidier in OpenSCAD. + +The placement of transformations before creation of the object in +OpenSCAD e.g., +translate(...) rotate(...) cylinder(...); +is slightly less intuitive than the reverse order Cadmium uses, e.g., +Cylinder(...).rotate(...).translate(...) + +Furthermore, if each step is placed on a separate line, Cadmium's +syntax puts the object in a more prominent position than the list of +translations. + + +Bugs +---- + +OpenSCAD got stuck allocating excessive amounts of memory when trying +to preview with OpenCSG from the IDE. + +Cadmium fails at line 113 of button.py if the "noise" parameter +introduced to work around this bug is absent or set to zero. + +The mesh generated by Open SCAD appears to have some small anomalies, +see section "Resulting mesh". + + +Execution +--------- + +On a lightly loaded Intel Q6600, the "high quality" rendering time +was as follows: + + real user sys +OpenSCAD 1m25.491s 1m24.990s 0m00.410s +Cadmium 81m44.408s 81m41.110s 0m01.540s + +This is consistent with the time the rendering of earlier stages of +the design took: OpenSCAD with CGAL was always much faster than +Cadmium with Open CASCADE. + +I didn't attempt to systematically search for costly operations, but +observed that the crossed cubes/boxes forming the core of the rounded +box took considerably longer than a run with one of them removed. + + +Resulting mesh +-------------- + +The rendering results are available at +http://downloads.qi-hardware.com/people/werner/cad/test1/ + +The STL files are scad.stl.bz2 and cadmium.stl.bz2 for OpenSCAD and +Cadmium, respectively. scad.png and cadmium.png show screenshots of +the meshes rendered with MeshLab 1.2.2, with double side lighting and +"flat" rendering. + +The two meshes are of similar size, as reported by MeshLab: + + Vertices Faces +OpenSCAD 3351 7798 +Cadmium 3183 8362 + +Note that the OpenSCAD model uses a slightly larger number of circle +segments (explicitly set with $fn) than the Cadmium model (which just +uses whatever is the default behaviour). + +At earlier stages of the design, the Cadmium mesh was found to be +significantly larger then the OpenSCAD mesh. + +Both meshes look clean and at a first glance show now major +distortions (*). + +(*) Note that the model already takes care of avoiding situations + where the subtraction of volumes could leave behind solids with + the thickness of a rounding error. + +When viewed with MeshLab 1.2.2, with smooth rendering and +"Fancy Lighting", some faces appear to be inverted. These faces are +shown in red in +http://downloads.qi-hardware.com/people/werner/cad/test1/scad-reversed.png + +A peek at the inside of the OpenSCAD-generated mesh reveals internal +structures left over from the construction process, as shown on +http://downloads.qi-hardware.com/people/werner/cad/test1/scad-inside.png + +No anomalies could be found in the mesh generated by Cadmium. + + +Conclusion +========== + +In the conclusions, I first consider the relative performance of the +two CAD system and then reflect on the whether the CSG-only workflow +as such proved to be satisfactory. + + +OpenSCAD vs. Cadmium +-------------------- + +Both systems succeeded in handling the task. OpenSCAD impressed with +fast response allowing highly interactive development, while Cadmium +--------------------------------------------------------------------- +soon gets very slow. It is not clear whether this slowness is a +general shortcoming of Cadmium or whether it is a consequence of poor +choices made when making the model. + +The mesh generated by OpenSCAD shows some anomalies, but it's not +clear whether they would affect further processing steps, e.g., +conversion to toolpaths. + +In terms of resource consumption and stability, even this relatively +simple model exhausted both systems, with OpenSCAD exhibiting +stability issues and Cadmium requiring excessive processing time. + +Both modeling languages can be used in very similar ways and were +pleasant to use. Python-based Cadmium may be more suitable for tasks +requiring structured building blocks. + + +The CSG-only workflow +--------------------- + +With both systems, translating the mental models of the various +components into correct instructions was difficult where more +abstract operations were involved, requiring some amount of trial and +error. + +Also, the resulting code does not easily reveal its purpose and +textual comments are an unsatisfactory means of illustrating +geometrical properties. (As an example, consider the above section +"Object description".) + +A workflow that includes distinct steps with a visual representation +of intermediate results, e.g., instead of CSG, using extrusion with +shapes and paths generated by some 2D CAD system, may be less +demanding. + +Also, while generating the basic shape was very easy, most of the +work went into the addition of fillets and chamfers. Neither of the +two systems provides operations to automate such tasks. + + +References +========== + +[1] http://www.openscad.org/ +[2] http://www.opencsg.org/ +[3] http://www.cgal.org/ +[4] http://www.cgal.org/license.html +[5] https://github.com/hmeyer/openscadpy +[6] https://github.com/etjones/MCAD/tree/master/PyOpenScad +[7] https://github.com/kevinmehall/pyscad +[8] http://hackage.haskell.org/package/mecha/ +[9] https://github.com/tomahawkins/mecha/blob/master/Language/Mecha/Examples/CSG.hs +[10] http://jayesh3.github.com/cadmium/ +[11] http://www.opencascade.org/ +[12] http://www.pythonocc.org/ +[13] http://www.opencascade.org/getocc/license/ + +--------------------------------------------------------------------- diff --git a/cad/test1/button.py b/cad/test1/button.py new file mode 100755 index 0000000..7e58864 --- /dev/null +++ b/cad/test1/button.py @@ -0,0 +1,146 @@ +#!/usr/bin/python + +# all dimensions are mm + +from cadmium import * + + +epsilon = 0.01 +noise = epsilon/10 + +but_top_x = 10.0 +but_top_y = but_top_x+5.0 +but_top_z = 1.5 + +but_corner_r = 2.0 + +but_base_border = 1.0 +but_base_x = but_top_x+2*but_base_border +but_base_y = but_top_y+2*but_base_border +but_base_z = 0.5 + +but_fillet_r = 0.4 +but_chamfer_r = 0.2 + + +# ----- Helper elements for fillets ------------------------------------------- + + +def fillet_line(x, r): + s = Box(x, r, r) + s -= Cylinder(r, h = x+2*epsilon). \ + translate(0, 0, -epsilon). \ + rotate(Y_axis, 90). \ + translate(0, r, r) + return s.translate(-x/2, 0, 0) + +def fillet_circle(r, fillet_r): + return Cylinder(r+fillet_r, h = fillet_r)- \ + Torus(r+fillet_r, fillet_r, center = True). \ + translate(0, 0, fillet_r) + + +# ----- Helper elements for chamfers ------------------------------------------ + + +def chamfer_line (x, r): + s = Box(x, r+epsilon, r+epsilon) + s -= Cylinder(r, h = x+2*epsilon). \ + translate(0, 0, -epsilon). \ + rotate(Y_axis, 90) + return s.translate(-x/2, -r, -r) + +def chamfer_circle(r, fillet_r): + return Box(2*(r+epsilon), 2*(r+epsilon), fillet_r+epsilon). \ + translate(-r-epsilon, -r-epsilon, -fillet_r)- \ + Cylinder(r-fillet_r, h = fillet_r). \ + translate(0, 0, -fillet_r)- \ + Torus(r-fillet_r, fillet_r, center = True). \ + translate(0, 0, -fillet_r) + + +# ----- Box with rounded corners ---------------------------------------------- + + +def rbox_core(x, y, z, r): + return Box(x-2*r, y, z, center = True).translate(0, 0, z/2)+ \ + Box(x, y-2*r, z, center = True).translate(0, 0, z/2) + +def rbox(x, y, z, r): + s = rbox_core(x, y, z, r) + for dx in [-1, 1]: + for dy in [-1, 1]: + s += Cylinder(r, h = z). \ + translate(dx*(x/2-r), dy*(y/2-r), 0) + return s + +def rbox_fillet_bottom(x, y, z, r, fillet_r): + s = None + for a in [0, 180]: + t = fillet_line(x-2*r, fillet_r). \ + translate(0, y/2, 0). \ + rotate(Z_axis, a) + if s is None: + s = t + else: + s += t + s += fillet_line(y-2*r, fillet_r). \ + translate(0, x/2, 0). \ + rotate(Z_axis, a+90) + for dx in [-1, 1]: + for dy in [-1, 1]: + s += fillet_circle(r, fillet_r). \ + translate(dx*(x/2-r), dy*(y/2-r), 0) + return s + +def rbox_chamfer_top_corners(x, y, z, r, chamfer_r): + s = None + for dx in [-1, 1]: + for dy in [-1, 1]: + t = chamfer_circle(r, chamfer_r). \ + translate(dx*(x/2-r), dy*(y/2-r), z) + if s is None: + s = t + else: + s += t + return s-rbox_core(x-epsilon, y-epsilon, z, r) + +def rbox_chamfer_top(x, y, z, r, chamfer_r): + s = rbox_chamfer_top_corners(x, y, z, r, chamfer_r) + for a in [0, 180]: + s += chamfer_line(x-2*r, chamfer_r). \ + translate(0, y/2, z+noise). \ + rotate(Z_axis, a) + s += chamfer_line(y-2*r, chamfer_r). \ + translate(0, x/2, z+noise). \ + rotate(Z_axis, a+90) + return s + +def rbox_chamfer_bottom(x, y, z, r, chamfer_r): + return rbox_chamfer_top(x, y, z, r, chamfer_r). \ + translate(0, 0, -z). \ + rotate(X_axis, 180) + +# ----- Button ---------------------------------------------------------------- + + +def button_top(): + return rbox(but_top_x, but_top_y, but_top_z, but_corner_r)- \ + rbox_chamfer_top(but_top_x, but_top_y, but_top_z, \ + but_corner_r, but_chamfer_r)+ \ + rbox_fillet_bottom(but_top_x, but_top_y, but_top_z, but_corner_r, + but_fillet_r) + +def button_base(): + s = rbox(but_base_x, but_base_y, but_base_z, but_corner_r)- \ + rbox_chamfer_top(but_base_x, but_base_y, but_base_z, \ + but_corner_r, but_chamfer_r)- \ + rbox_chamfer_bottom(but_base_x, but_base_y, but_base_z, \ + but_corner_r, but_chamfer_r) + return s.translate(0, 0, -but_base_z) + +def button(): + return button_top()+button_base() + +b = button() +b.toSTL("button.stl") diff --git a/cad/test1/button.scad b/cad/test1/button.scad new file mode 100644 index 0000000..f5fc7bb --- /dev/null +++ b/cad/test1/button.scad @@ -0,0 +1,216 @@ +epsilon = 0.01; + +but_top_x = 10; +but_top_y = but_top_x+5; +but_top_z = 1.5; + +but_corner_r = 2; + +but_base_border = 1; +but_base_x = but_top_x+2*but_base_border; +but_base_y = but_top_y+2*but_base_border; +but_base_z = 0.5; + +but_push_r = 5; +but_push_z = 0.5; + +but_fillet_r = 0.4; +but_chamfer_r = 0.2; + +$fn = 40; + + +/* ----- Basic solids ------------------------------------------------------ */ + + +module torus(r0, r1) +{ + rotate_extrude() + translate([r0, 0, 0]) + circle(r = r1); +} + + +/* ----- Helper elements for fillets --------------------------------------- */ + + +module fillet_line(x, r) +{ + translate([-x/2, 0, 0]) + difference() { + cube([x, r, r]); + translate([0, r, r]) + rotate([0, 90, 0]) + translate([0, 0, -epsilon]) + cylinder(h = x+2*epsilon, r = r); + } +} + + +module fillet_circle(r, fillet_r) +{ + difference() { + cylinder(h = fillet_r, r = r+fillet_r); + translate([0, 0, fillet_r]) + torus(r+fillet_r, fillet_r); + } +} + + +/* ----- Helper elements for chamfers -------------------------------------- */ + + +module chamfer_line(x, r) +{ + translate([-x/2, -r, -r]) + difference() { + cube([x, r+epsilon, r+epsilon]); + rotate([0, 90, 0]) + translate([0, 0, -epsilon]) + cylinder(h = x+2*epsilon, r = r); + } +} + + +module chamfer_circle(r, fillet_r) +{ + difference() { + translate([-r-epsilon, -r-epsilon, -fillet_r]) + cube([2*(r+epsilon), 2*(r+epsilon), fillet_r+epsilon]); + translate([0, 0, -fillet_r]) + cylinder(h = fillet_r, r = r-fillet_r); + translate([0, 0, -fillet_r]) + torus(r-fillet_r, fillet_r); + } +} + + +/* ----- Box with rounded corners ------------------------------------------ */ + + +module rbox_core(x, y, z, r) +{ + union() { + translate([0, 0, z/2]) + cube([x-2*r, y, z], center = true); + translate([0, 0, z/2]) + cube([x, y-2*r, z], center = true); + } +} + + +module rbox(x, y, z, r) +{ + union() { + rbox_core(x, y, z, r); + for (dx = [-1, 1]) { + for (dy = [-1, 1]) { + translate([dx*(x/2-r), dy*(y/2-r), 0]) + cylinder(h = z, r = r); + } + } + } +} + + +module rbox_fillet_bottom(x, y, z, r, fillet_r) +{ + union() { + for (a = [0, 180]) { + rotate([0, 0, a]) + translate([0, y/2, 0]) + fillet_line(x-2*r, fillet_r); + rotate([0, 0, a+90]) + translate([0, x/2, 0]) + fillet_line(y-2*r, fillet_r); + } + for (dx = [-1, 1]) { + for (dy = [-1, 1]) { + translate([dx*(x/2-r), dy*(y/2-r), 0]) + fillet_circle(r, fillet_r); + } + } + } +} + + +module rbox_chamfer_top_corners(x, y, z, r, chamfer_r) +{ + difference() { + union() { + for (dx = [-1, 1]) { + for (dy = [-1, 1]) { + translate([dx*(x/2-r), dy*(y/2-r), z]) + chamfer_circle(r, chamfer_r); + } + } + } + rbox_core(x-epsilon, y-epsilon, z, r); + } +} + + +module rbox_chamfer_top(x, y, z, r, chamfer_r) +{ + union() { + for (a = [0, 180]) { + rotate([0, 0, a]) + translate([0, y/2, z]) + chamfer_line(x-2*r, chamfer_r); + rotate([0, 0, a+90]) + translate([0, x/2, z]) + chamfer_line(y-2*r, chamfer_r); + } + rbox_chamfer_top_corners(x, y, z, r, chamfer_r); + } +} + + +module rbox_chamfer_bottom(x, y, z, r, chamfer_r) +{ + rotate([180, 0, 0]) + translate([0, 0, -z]) + rbox_chamfer_top(x, y, z, r, chamfer_r); +} + + +/* ----- Button ------------------------------------------------------------ */ + + +module button_top() +{ + union() { + difference() { + rbox(but_top_x, but_top_y, but_top_z, but_corner_r); + rbox_chamfer_top(but_top_x, but_top_y, but_top_z, but_corner_r, but_chamfer_r); + } + rbox_fillet_bottom(but_top_x, but_top_y, but_top_z, + but_corner_r, but_fillet_r); + } +} + + +module button_base() +{ + translate([0, 0, -but_base_z]) + difference() { + rbox(but_base_x, but_base_y, but_base_z, but_corner_r); + rbox_chamfer_top(but_base_x, but_base_y, but_base_z, + but_corner_r, but_chamfer_r); + rbox_chamfer_bottom(but_base_x, but_base_y, but_base_z, + but_corner_r, but_chamfer_r); + } +} + + +module button() +{ + union() { + button_top(); + button_base(); +// button_pusher(); + } +} + + +button();