Date:2011-07-23 00:31:14 (8 years 4 months ago)
Author:Werner Almesberger
Commit:2db4c1a3a80a5d22de58ed81b9932afe7ea15626
Message:cad/test1/: experiment with Free scripted CAD systems (OpenSCAD and Cadmium)

Files: cad/test1/Makefile (1 diff)
cad/test1/README (1 diff)
cad/test1/button.py (1 diff)
cad/test1/button.scad (1 diff)

Change Details

cad/test1/Makefile
1OUT=scad.stl cadmium.stl
2
3.PHONY: all clean
4
5all: $(OUT)
6
7scad.stl: button.scad
8        time openscad -s $@ $<
9
10cadmium.stl: button.py
11        time ./$<
12        mv botton.stl $@
13
14clean:
15        rm -f $(OUT)
cad/test1/README
1Comparison of Free scripted 3D CAD systems
2==========================================
3
4Werner Almesberger <werner@almesberger.net>
5
6This is a brief evaluation of the scripted 3D CAD systems OpenSCAD
7and Cadmium, comparing the workflow, resource consumption, and the
8quality of the results.
9
10
11Introduction
12============
13
14This file and the sources of the models can be found in
15http://projects.qi-hardware.com/index.php/p/wernermisc/source/tree/master/cad/test1/
16
17
18Objectives
19----------
20
21This test aims to determine the general suitability of currently
22available Free scripted 3D CAD system for the construction of
23real-life objects.
24
25Aspects considered were the ease or difficulty of model development,
26the clarity of the modeling language, resource consumption during
27rendering, and the quality of the resulting mesh.
28
29A second objective was to evaluate the suitability of CSG as the only
30means for constructing models suitable for large-scale industrial
31production.
32
33
34Object description
35------------------
36
37The object to model is a simple button/key cap shape. The shape
38consists of a top part shaped as a 10 x 15 mm rectangle with rounded
39corners and at height of 1.5 mm. The top part rests on a base that's
400.5 mm thin and has a border of 1 mm on each side.
41
42The corners of the rectangle are rounded with a radius of 2 mm. All
43other external edges are rounded (chamfered) with a radius of 0.2 mm.
44The edge where top and base meet is filleted with a radius of 0.4 mm.
45
46Note that a real button would typically have an internal cavity,
47possibly some depression or other structure on its top, and on the
48bottom side a pusher in the middle and possibly other support
49elements.
50
51Also, if the design was to be used for injection molding, sidewalls
52would be slightly tilted.
53
54The rounding of the bottom plate is not strictly necessary and was
55added for visual appearance.
56
57
58Candidate 1: OpenSCAD
59---------------------
60
61OpenSCAD [1] uses its own language, somewhat similar to POV-Ray's, to
62describe 3D objects. It has an IDE with a quick preview capability
63using OpenCSG [2].
64
65High-quality rendering, e.g., to STL, is done with CGAL [3] and can
66also be run non-interactively.
67
68OpenSCAD and OpenCSG are licensed under the GNU GPL v2. Parts of CGAL
69are licensed under the GNU LGPL v2.1 while others are licensed under
70the incompatible QPL. See [4] for details.
71
72The version tested was the openscad 2011.06-1+lucid1 Ubuntu package.
73
74
75OpenSCAD front-ends
76-------------------
77
78There also a number of Python-based scripted front-ends for OpenSCAD,
79namely OpenSCADpy [5], PyOpenSCAD [6], and pySCAD [7].
80
81Furthermore, there is Mecha [8, 9] for Haskell.
82
83Cadmium (see below) appears to be on par or better in terms of syntax
84clarity and tidiness than the OpenSCAD Python bindings. Therefore,
85only pure OpenSCAD was considered for this comparison.
86
87
88Candidate 2: Cadmium
89--------------------
90
91Cadmium [10] is similar in concept to OpenSCAD, but uses Python
92instead of a homegrown language. Open CASCADE [11] (via pythonOCC
93[12]) provides the 3D operations here.
94
95The respective licenses are GNU AGPL v3 for Cadmium, GNU LGPL v3 for
96pythonOCC, and a homegrown "LGPL-like" license [13] for Open CASCADE.
97
98The Cadmium version tested was Sun Jul 10 16:04:07 2011 +0530 commit
99d4ff63b150ee060a8179a74e369b5df3d0a4a3fc, with pythonOCC 0.5.
100
101
102Results and observations
103========================
104
105Model development was efficient with both systems, with most of the
106difficulties coming from the task of making the model, not from
107inadequacies of the tools.
108
109Both systems also also produced correct-looking meshes.
110
111Notable differences exist in the time the rendering takes, where
112rough previews with OpenSCAD are instantaneous and proper rendering
113takes minutes, while Cadmium has no preview and the rendering takes
114hours.
115
116On the other hand, some small anomalies could be found in the mesh
117generated by OpenSCAD while the Cadmium's mesh looks perfect.
118
119
120Model development
121-----------------
122
123Both systems offer the same basic CSG primitives and operations,
124which made the model development per se straightforward and the
125porting from one system to the other effortless.
126
127The very quick preview of OpenSCAD is immensely helpful during
128development. The usefulness of the preview is diminished by
129differences only being shown as unions of the solids involved, with
130color indicating their role. It was thus often necessary to isolate
131and simplify elements before the resulting shape could be guessed, or
132to render with slower CGAL.
133
134Given the slow rendering process, debugging non-trivial designs with
135Cadmium is currently quite time-consuming.
136
137Development of the basic model (without chamfers and fillets) was
138first done with Cadmium. I then switched to OpenSCAD to develop the
139more advanced features, and finally ported them back to the Cadmium
140model.
141
142Designing the model elements for filleting and chamfering was
143somewhat awkward with only CSG and - without understanding the
144entire construction process - it may not be easy to see what the
145resulting code does.
146
147
148Modeling language
149-----------------
150
151The limited programming language of OpenSCAD proved to be more than
152adequate for this simple design. To ease comparison and to reduce the
153porting effort, the Cadmium model has the same code structure as the
154OpenSCAD model.
155
156It should be noted that some redundancy could be avoided in Cadmium
157if all the "rbox_*" functions were placed in a common class whose
158objects could then remember the box's geometry for reuse with the
159fillet and chamfer functions/methods.
160
161One nuisance with OpenSCAD is that mistyped variable names merely
162generate a warning but let rendering proceed - often with confusing
163results.
164
165One difficulty encountered when making the Cadmium model was that
166there appears to be no null value for the "union" operation, which
167means functions that generate all their objects in a loop have to
168special-case the first element, making them look a bit awkward (e.g.,
169rbox_chamfer_top_corners). It should be easy to remedy this
170shortcoming.
171
172The Python language also introduces complications to Cadmium that
173OpenSCAD can avoid, such as the Python parser's limited ability to
174detect continuation lines, requiring continuations to be marked with
175a backslash, and the need to pay attention to the mixing of
176floating-point and integer numbers when using divisions.
177
178Cadmium's ability to use short operators instead of blocks generally
179yielded only marginally more compact code, since many operations
180ended up being longer than one line anyway. In fact, the code
181structure often looks a bit tidier in OpenSCAD.
182
183The placement of transformations before creation of the object in
184OpenSCAD e.g.,
185translate(...) rotate(...) cylinder(...);
186is slightly less intuitive than the reverse order Cadmium uses, e.g.,
187Cylinder(...).rotate(...).translate(...)
188
189Furthermore, if each step is placed on a separate line, Cadmium's
190syntax puts the object in a more prominent position than the list of
191translations.
192
193
194Bugs
195----
196
197OpenSCAD got stuck allocating excessive amounts of memory when trying
198to preview with OpenCSG from the IDE.
199
200Cadmium fails at line 113 of button.py if the "noise" parameter
201introduced to work around this bug is absent or set to zero.
202
203The mesh generated by Open SCAD appears to have some small anomalies,
204see section "Resulting mesh".
205
206
207Execution
208---------
209
210On a lightly loaded Intel Q6600, the "high quality" rendering time
211was as follows:
212
213        real user sys
214OpenSCAD 1m25.491s 1m24.990s 0m00.410s
215Cadmium 81m44.408s 81m41.110s 0m01.540s
216
217This is consistent with the time the rendering of earlier stages of
218the design took: OpenSCAD with CGAL was always much faster than
219Cadmium with Open CASCADE.
220
221I didn't attempt to systematically search for costly operations, but
222observed that the crossed cubes/boxes forming the core of the rounded
223box took considerably longer than a run with one of them removed.
224
225
226Resulting mesh
227--------------
228
229The rendering results are available at
230http://downloads.qi-hardware.com/people/werner/cad/test1/
231
232The STL files are scad.stl.bz2 and cadmium.stl.bz2 for OpenSCAD and
233Cadmium, respectively. scad.png and cadmium.png show screenshots of
234the meshes rendered with MeshLab 1.2.2, with double side lighting and
235"flat" rendering.
236
237The two meshes are of similar size, as reported by MeshLab:
238
239        Vertices Faces
240OpenSCAD 3351 7798
241Cadmium 3183 8362
242
243Note that the OpenSCAD model uses a slightly larger number of circle
244segments (explicitly set with $fn) than the Cadmium model (which just
245uses whatever is the default behaviour).
246
247At earlier stages of the design, the Cadmium mesh was found to be
248significantly larger then the OpenSCAD mesh.
249
250Both meshes look clean and at a first glance show now major
251distortions (*).
252
253(*) Note that the model already takes care of avoiding situations
254    where the subtraction of volumes could leave behind solids with
255    the thickness of a rounding error.
256
257When viewed with MeshLab 1.2.2, with smooth rendering and
258"Fancy Lighting", some faces appear to be inverted. These faces are
259shown in red in
260http://downloads.qi-hardware.com/people/werner/cad/test1/scad-reversed.png
261
262A peek at the inside of the OpenSCAD-generated mesh reveals internal
263structures left over from the construction process, as shown on
264http://downloads.qi-hardware.com/people/werner/cad/test1/scad-inside.png
265
266No anomalies could be found in the mesh generated by Cadmium.
267
268
269Conclusion
270==========
271
272In the conclusions, I first consider the relative performance of the
273two CAD system and then reflect on the whether the CSG-only workflow
274as such proved to be satisfactory.
275
276
277OpenSCAD vs. Cadmium
278--------------------
279
280Both systems succeeded in handling the task. OpenSCAD impressed with
281fast response allowing highly interactive development, while Cadmium
282---------------------------------------------------------------------
283soon gets very slow. It is not clear whether this slowness is a
284general shortcoming of Cadmium or whether it is a consequence of poor
285choices made when making the model.
286
287The mesh generated by OpenSCAD shows some anomalies, but it's not
288clear whether they would affect further processing steps, e.g.,
289conversion to toolpaths.
290
291In terms of resource consumption and stability, even this relatively
292simple model exhausted both systems, with OpenSCAD exhibiting
293stability issues and Cadmium requiring excessive processing time.
294
295Both modeling languages can be used in very similar ways and were
296pleasant to use. Python-based Cadmium may be more suitable for tasks
297requiring structured building blocks.
298
299
300The CSG-only workflow
301---------------------
302
303With both systems, translating the mental models of the various
304components into correct instructions was difficult where more
305abstract operations were involved, requiring some amount of trial and
306error.
307
308Also, the resulting code does not easily reveal its purpose and
309textual comments are an unsatisfactory means of illustrating
310geometrical properties. (As an example, consider the above section
311"Object description".)
312
313A workflow that includes distinct steps with a visual representation
314of intermediate results, e.g., instead of CSG, using extrusion with
315shapes and paths generated by some 2D CAD system, may be less
316demanding.
317
318Also, while generating the basic shape was very easy, most of the
319work went into the addition of fillets and chamfers. Neither of the
320two systems provides operations to automate such tasks.
321
322
323References
324==========
325
326[1] http://www.openscad.org/
327[2] http://www.opencsg.org/
328[3] http://www.cgal.org/
329[4] http://www.cgal.org/license.html
330[5] https://github.com/hmeyer/openscadpy
331[6] https://github.com/etjones/MCAD/tree/master/PyOpenScad
332[7] https://github.com/kevinmehall/pyscad
333[8] http://hackage.haskell.org/package/mecha/
334[9] https://github.com/tomahawkins/mecha/blob/master/Language/Mecha/Examples/CSG.hs
335[10] http://jayesh3.github.com/cadmium/
336[11] http://www.opencascade.org/
337[12] http://www.pythonocc.org/
338[13] http://www.opencascade.org/getocc/license/
339
340---------------------------------------------------------------------
cad/test1/button.py
1#!/usr/bin/python
2
3# all dimensions are mm
4
5from cadmium import *
6
7
8epsilon = 0.01
9noise = epsilon/10
10
11but_top_x = 10.0
12but_top_y = but_top_x+5.0
13but_top_z = 1.5
14
15but_corner_r = 2.0
16
17but_base_border = 1.0
18but_base_x = but_top_x+2*but_base_border
19but_base_y = but_top_y+2*but_base_border
20but_base_z = 0.5
21
22but_fillet_r = 0.4
23but_chamfer_r = 0.2
24
25
26# ----- Helper elements for fillets -------------------------------------------
27
28
29def fillet_line(x, r):
30    s = Box(x, r, r)
31    s -= Cylinder(r, h = x+2*epsilon). \
32        translate(0, 0, -epsilon). \
33        rotate(Y_axis, 90). \
34        translate(0, r, r)
35    return s.translate(-x/2, 0, 0)
36
37def fillet_circle(r, fillet_r):
38    return Cylinder(r+fillet_r, h = fillet_r)- \
39        Torus(r+fillet_r, fillet_r, center = True). \
40        translate(0, 0, fillet_r)
41
42
43# ----- Helper elements for chamfers ------------------------------------------
44
45
46def chamfer_line (x, r):
47    s = Box(x, r+epsilon, r+epsilon)
48    s -= Cylinder(r, h = x+2*epsilon). \
49        translate(0, 0, -epsilon). \
50        rotate(Y_axis, 90)
51    return s.translate(-x/2, -r, -r)
52
53def chamfer_circle(r, fillet_r):
54    return Box(2*(r+epsilon), 2*(r+epsilon), fillet_r+epsilon). \
55        translate(-r-epsilon, -r-epsilon, -fillet_r)- \
56        Cylinder(r-fillet_r, h = fillet_r). \
57        translate(0, 0, -fillet_r)- \
58        Torus(r-fillet_r, fillet_r, center = True). \
59        translate(0, 0, -fillet_r)
60
61
62# ----- Box with rounded corners ----------------------------------------------
63
64
65def rbox_core(x, y, z, r):
66    return Box(x-2*r, y, z, center = True).translate(0, 0, z/2)+ \
67        Box(x, y-2*r, z, center = True).translate(0, 0, z/2)
68
69def rbox(x, y, z, r):
70    s = rbox_core(x, y, z, r)
71    for dx in [-1, 1]:
72        for dy in [-1, 1]:
73            s += Cylinder(r, h = z). \
74                translate(dx*(x/2-r), dy*(y/2-r), 0)
75    return s
76
77def rbox_fillet_bottom(x, y, z, r, fillet_r):
78    s = None
79    for a in [0, 180]:
80        t = fillet_line(x-2*r, fillet_r). \
81            translate(0, y/2, 0). \
82            rotate(Z_axis, a)
83        if s is None:
84            s = t
85        else:
86            s += t
87        s += fillet_line(y-2*r, fillet_r). \
88            translate(0, x/2, 0). \
89            rotate(Z_axis, a+90)
90    for dx in [-1, 1]:
91        for dy in [-1, 1]:
92            s += fillet_circle(r, fillet_r). \
93                translate(dx*(x/2-r), dy*(y/2-r), 0)
94    return s
95
96def rbox_chamfer_top_corners(x, y, z, r, chamfer_r):
97    s = None
98    for dx in [-1, 1]:
99        for dy in [-1, 1]:
100            t = chamfer_circle(r, chamfer_r). \
101                translate(dx*(x/2-r), dy*(y/2-r), z)
102            if s is None:
103                s = t
104            else:
105                s += t
106    return s-rbox_core(x-epsilon, y-epsilon, z, r)
107
108def rbox_chamfer_top(x, y, z, r, chamfer_r):
109    s = rbox_chamfer_top_corners(x, y, z, r, chamfer_r)
110    for a in [0, 180]:
111        s += chamfer_line(x-2*r, chamfer_r). \
112            translate(0, y/2, z+noise). \
113            rotate(Z_axis, a)
114        s += chamfer_line(y-2*r, chamfer_r). \
115            translate(0, x/2, z+noise). \
116            rotate(Z_axis, a+90)
117    return s
118
119def rbox_chamfer_bottom(x, y, z, r, chamfer_r):
120    return rbox_chamfer_top(x, y, z, r, chamfer_r). \
121        translate(0, 0, -z). \
122        rotate(X_axis, 180)
123
124# ----- Button ----------------------------------------------------------------
125
126
127def button_top():
128    return rbox(but_top_x, but_top_y, but_top_z, but_corner_r)- \
129        rbox_chamfer_top(but_top_x, but_top_y, but_top_z, \
130        but_corner_r, but_chamfer_r)+ \
131        rbox_fillet_bottom(but_top_x, but_top_y, but_top_z, but_corner_r,
132        but_fillet_r)
133
134def button_base():
135    s = rbox(but_base_x, but_base_y, but_base_z, but_corner_r)- \
136        rbox_chamfer_top(but_base_x, but_base_y, but_base_z, \
137        but_corner_r, but_chamfer_r)- \
138        rbox_chamfer_bottom(but_base_x, but_base_y, but_base_z, \
139        but_corner_r, but_chamfer_r)
140    return s.translate(0, 0, -but_base_z)
141
142def button():
143    return button_top()+button_base()
144
145b = button()
146b.toSTL("button.stl")
cad/test1/button.scad
1epsilon = 0.01;
2
3but_top_x = 10;
4but_top_y = but_top_x+5;
5but_top_z = 1.5;
6
7but_corner_r = 2;
8
9but_base_border = 1;
10but_base_x = but_top_x+2*but_base_border;
11but_base_y = but_top_y+2*but_base_border;
12but_base_z = 0.5;
13
14but_push_r = 5;
15but_push_z = 0.5;
16
17but_fillet_r = 0.4;
18but_chamfer_r = 0.2;
19
20$fn = 40;
21
22
23/* ----- Basic solids ------------------------------------------------------ */
24
25
26module torus(r0, r1)
27{
28    rotate_extrude()
29        translate([r0, 0, 0])
30        circle(r = r1);
31}
32
33
34/* ----- Helper elements for fillets --------------------------------------- */
35
36
37module fillet_line(x, r)
38{
39    translate([-x/2, 0, 0])
40        difference() {
41        cube([x, r, r]);
42        translate([0, r, r])
43            rotate([0, 90, 0])
44            translate([0, 0, -epsilon])
45            cylinder(h = x+2*epsilon, r = r);
46    }
47}
48
49
50module fillet_circle(r, fillet_r)
51{
52    difference() {
53        cylinder(h = fillet_r, r = r+fillet_r);
54        translate([0, 0, fillet_r])
55            torus(r+fillet_r, fillet_r);
56    }
57}
58
59
60/* ----- Helper elements for chamfers -------------------------------------- */
61
62
63module chamfer_line(x, r)
64{
65    translate([-x/2, -r, -r])
66        difference() {
67        cube([x, r+epsilon, r+epsilon]);
68        rotate([0, 90, 0])
69            translate([0, 0, -epsilon])
70            cylinder(h = x+2*epsilon, r = r);
71    }
72}
73
74
75module chamfer_circle(r, fillet_r)
76{
77    difference() {
78        translate([-r-epsilon, -r-epsilon, -fillet_r])
79            cube([2*(r+epsilon), 2*(r+epsilon), fillet_r+epsilon]);
80        translate([0, 0, -fillet_r])
81            cylinder(h = fillet_r, r = r-fillet_r);
82        translate([0, 0, -fillet_r])
83            torus(r-fillet_r, fillet_r);
84    }
85}
86
87
88/* ----- Box with rounded corners ------------------------------------------ */
89
90
91module rbox_core(x, y, z, r)
92{
93    union() {
94        translate([0, 0, z/2])
95            cube([x-2*r, y, z], center = true);
96        translate([0, 0, z/2])
97            cube([x, y-2*r, z], center = true);
98    }
99}
100
101
102module rbox(x, y, z, r)
103{
104    union() {
105        rbox_core(x, y, z, r);
106        for (dx = [-1, 1]) {
107            for (dy = [-1, 1]) {
108                translate([dx*(x/2-r), dy*(y/2-r), 0])
109                        cylinder(h = z, r = r);
110            }
111        }
112    }
113}
114
115
116module rbox_fillet_bottom(x, y, z, r, fillet_r)
117{
118    union() {
119        for (a = [0, 180]) {
120            rotate([0, 0, a])
121                translate([0, y/2, 0])
122                fillet_line(x-2*r, fillet_r);
123            rotate([0, 0, a+90])
124                translate([0, x/2, 0])
125                fillet_line(y-2*r, fillet_r);
126        }
127        for (dx = [-1, 1]) {
128            for (dy = [-1, 1]) {
129                translate([dx*(x/2-r), dy*(y/2-r), 0])
130                    fillet_circle(r, fillet_r);
131            }
132        }
133    }
134}
135
136
137module rbox_chamfer_top_corners(x, y, z, r, chamfer_r)
138{
139    difference() {
140        union() {
141            for (dx = [-1, 1]) {
142                for (dy = [-1, 1]) {
143                    translate([dx*(x/2-r), dy*(y/2-r), z])
144                        chamfer_circle(r, chamfer_r);
145                }
146            }
147        }
148        rbox_core(x-epsilon, y-epsilon, z, r);
149    }
150}
151
152
153module rbox_chamfer_top(x, y, z, r, chamfer_r)
154{
155    union() {
156        for (a = [0, 180]) {
157            rotate([0, 0, a])
158                translate([0, y/2, z])
159                chamfer_line(x-2*r, chamfer_r);
160            rotate([0, 0, a+90])
161                translate([0, x/2, z])
162                chamfer_line(y-2*r, chamfer_r);
163        }
164        rbox_chamfer_top_corners(x, y, z, r, chamfer_r);
165    }
166}
167
168
169module rbox_chamfer_bottom(x, y, z, r, chamfer_r)
170{
171    rotate([180, 0, 0])
172        translate([0, 0, -z])
173        rbox_chamfer_top(x, y, z, r, chamfer_r);
174}
175
176
177/* ----- Button ------------------------------------------------------------ */
178
179
180module button_top()
181{
182    union() {
183        difference() {
184            rbox(but_top_x, but_top_y, but_top_z, but_corner_r);
185            rbox_chamfer_top(but_top_x, but_top_y, but_top_z, but_corner_r, but_chamfer_r);
186        }
187        rbox_fillet_bottom(but_top_x, but_top_y, but_top_z,
188             but_corner_r, but_fillet_r);
189    }
190}
191
192
193module button_base()
194{
195    translate([0, 0, -but_base_z])
196        difference() {
197        rbox(but_base_x, but_base_y, but_base_z, but_corner_r);
198        rbox_chamfer_top(but_base_x, but_base_y, but_base_z,
199            but_corner_r, but_chamfer_r);
200        rbox_chamfer_bottom(but_base_x, but_base_y, but_base_z,
201            but_corner_r, but_chamfer_r);
202    }
203}
204
205
206module button()
207{
208    union() {
209        button_top();
210        button_base();
211// button_pusher();
212    }
213}
214
215
216button();

Archive Download the corresponding diff file

Branches:
master



interactive