rcad 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ require 'mkmf-rice'
2
+
3
+ OCE_INCLUDE_DIR = '/usr/local/include/oce'
4
+ OCE_LIB_DIR = '/usr/local/lib'
5
+
6
+ dir_config('TKG3d', OCE_INCLUDE_DIR, OCE_LIB_DIR)
7
+ dir_config('TKBRep', OCE_INCLUDE_DIR, OCE_LIB_DIR)
8
+ dir_config('TKPrim', OCE_INCLUDE_DIR, OCE_LIB_DIR)
9
+ dir_config('TKOffset', OCE_INCLUDE_DIR, OCE_LIB_DIR)
10
+ dir_config('TKBO', OCE_INCLUDE_DIR, OCE_LIB_DIR)
11
+ dir_config('TKSTL', OCE_INCLUDE_DIR, OCE_LIB_DIR)
12
+ dir_config('qhull', '/usr/include/qhull', '/usr/lib')
13
+
14
+ have_library('TKG3d') or raise
15
+ have_library('TKBRep') or raise
16
+ have_library('TKPrim') or raise
17
+ have_library('TKOffset') or raise
18
+ have_library('TKBO') or raise
19
+ have_library('TKSTL') or raise
20
+ have_library('qhull') or raise
21
+
22
+ create_makefile('rcad/_rcad')
@@ -0,0 +1,384 @@
1
+ require 'rcad/version'
2
+ require 'rcad/_rcad'
3
+
4
+
5
+ class Numeric
6
+ def mm
7
+ self
8
+ end
9
+
10
+ def cm
11
+ self * 10.0
12
+ end
13
+
14
+ def um
15
+ self / 1000.0
16
+ end
17
+
18
+ def in
19
+ self * 25.4
20
+ end
21
+
22
+ def deg_to_rad
23
+ self * Math::PI / 180
24
+ end
25
+
26
+ def rad_to_deg
27
+ self * 180 / Math::PI
28
+ end
29
+ end
30
+
31
+
32
+ def to_polar(r, a)
33
+ return [r * Math::cos(a), r * Math::sin(a)]
34
+ end
35
+
36
+
37
+ TOLERANCE = 50.um
38
+
39
+
40
+ class Shape
41
+ # if @shape isn't defined in a Shape's initialize() method, then render()
42
+ # should be overridden to create and return it on-the-fly.
43
+ def render
44
+ @shape
45
+ end
46
+
47
+ def +(right)
48
+ Union.new(self, right)
49
+ end
50
+
51
+ def -(right)
52
+ Difference.new(self, right)
53
+ end
54
+
55
+ def *(right)
56
+ Intersection.new(self, right)
57
+ end
58
+
59
+ def ~@
60
+ if $shape_mode == :hull
61
+ $shape << self
62
+ else
63
+ $shape = ($shape == nil) ? self : $shape.send($shape_mode, self)
64
+ end
65
+
66
+ self
67
+ end
68
+
69
+ def move_x(delta)
70
+ move(delta, 0, 0)
71
+ end
72
+
73
+ def move_y(delta)
74
+ move(0, delta, 0)
75
+ end
76
+
77
+ def move_z(delta)
78
+ move(0, 0, delta)
79
+ end
80
+
81
+ def rot_x(angle)
82
+ rotate(angle, [1, 0, 0])
83
+ end
84
+
85
+ def rot_y(angle)
86
+ rotate(angle, [0, 1, 0])
87
+ end
88
+
89
+ def rot_z(angle)
90
+ rotate(angle, [0, 0, 1])
91
+ end
92
+
93
+ def scale_x(factor)
94
+ scale(factor, 1, 1)
95
+ end
96
+
97
+ def scale_y(factor)
98
+ scale(1, factor, 1)
99
+ end
100
+
101
+ def scale_z(factor)
102
+ scale(1, 1, factor)
103
+ end
104
+
105
+ def extrude(height, twist=0)
106
+ LinearExtrusion.new(self, height, twist)
107
+ end
108
+
109
+ def revolve(angle=360.deg_to_rad)
110
+ Revolution.new(self, angle)
111
+ end
112
+
113
+ def bbox
114
+ # TODO
115
+ end
116
+
117
+ def min_x
118
+ bbox[0].x
119
+ end
120
+
121
+ def min_y
122
+ bbox[0].y
123
+ end
124
+
125
+ def min_z
126
+ bbox[0].z
127
+ end
128
+
129
+ def max_x
130
+ bbox[1].x
131
+ end
132
+
133
+ def max_y
134
+ bbox[1].y
135
+ end
136
+
137
+ def max_z
138
+ bbox[1].z
139
+ end
140
+
141
+ def x_size
142
+ max_x - min_x
143
+ end
144
+
145
+ def y_size
146
+ max_y - min_y
147
+ end
148
+
149
+ def z_size
150
+ max_z - min_z
151
+ end
152
+ end
153
+
154
+ $shape_stack = []
155
+ $shape = nil
156
+ $shape_mode = :+
157
+
158
+ def _shape_mode_block(mode, &block)
159
+ $shape_stack.push([$shape, $shape_mode])
160
+ $shape = nil
161
+ $shape_mode = mode
162
+
163
+ block.call
164
+ res = $shape
165
+
166
+ $shape, $shape_mode = $shape_stack.pop
167
+ res
168
+ end
169
+
170
+ def add(&block)
171
+ _shape_mode_block(:+, &block)
172
+ end
173
+
174
+ def sub(&block)
175
+ _shape_mode_block(:-, &block)
176
+ end
177
+
178
+ def mul(&block)
179
+ _shape_mode_block(:*, &block)
180
+ end
181
+
182
+ def hull(&block)
183
+ $shape_stack.push([$shape, $shape_mode])
184
+ $shape = []
185
+ $shape_mode = :hull
186
+
187
+ block.call
188
+ res = _hull($shape)
189
+
190
+ $shape, $shape_mode = $shape_stack.pop
191
+ res
192
+ end
193
+
194
+ def write_stl(*args)
195
+ $shape != nil or raise
196
+ $shape.write_stl(*args)
197
+ end
198
+
199
+ def clear_shape
200
+ $shape = nil
201
+ end
202
+
203
+ at_exit do
204
+ if $shape && ($! == nil)
205
+ output_file = File.basename($0, ".*") + ".stl"
206
+ printf("Rendering '%s'\n", output_file)
207
+ write_stl(output_file)
208
+ end
209
+ end
210
+
211
+
212
+ class Polygon
213
+ attr_reader :points, :paths
214
+
215
+ def initialize(points, paths=nil)
216
+ @points = points
217
+ @paths = paths || [(0...points.size).to_a]
218
+ end
219
+ end
220
+
221
+
222
+ class RegularPolygon < Polygon
223
+ attr_reader :sides, :radius
224
+
225
+ def initialize(sides, radius)
226
+ @sides = sides
227
+ @radius = radius
228
+
229
+ angles = (1..sides).map { |i| i * 2 * Math::PI / sides }
230
+ points = angles.map { |a| to_polar(radius, a) }
231
+ super(points)
232
+ end
233
+ end
234
+
235
+
236
+ class Square < Polygon
237
+ attr_reader :size
238
+
239
+ def initialize(size)
240
+ @size = size
241
+
242
+ super([[0,0], [size,0], [size,size], [0,size]])
243
+ end
244
+ end
245
+
246
+
247
+ class Circle < Shape
248
+ attr_accessor :dia
249
+
250
+ def initialize(dia)
251
+ @dia = dia
252
+ end
253
+ end
254
+
255
+
256
+ class Text < Shape
257
+ # TODO
258
+ end
259
+
260
+
261
+ class Box < Shape
262
+ attr_accessor :xsize, :ysize, :zsize
263
+
264
+ def initialize(xsize, ysize, zsize)
265
+ @xsize = xsize
266
+ @ysize = ysize
267
+ @zsize = zsize
268
+ end
269
+ end
270
+
271
+ class Cube < Box
272
+ def initialize(size)
273
+ super(size, size, size)
274
+ end
275
+ end
276
+
277
+
278
+ class Cylinder < Shape
279
+ attr_accessor :height, :dia
280
+
281
+ def initialize(height, dia)
282
+ @height = height
283
+ @dia = dia
284
+ end
285
+
286
+ def radius
287
+ dia / 2.0
288
+ end
289
+ end
290
+
291
+
292
+ class Sphere < Shape
293
+ attr_accessor :dia
294
+
295
+ def initialize(dia)
296
+ @dia = dia
297
+ end
298
+
299
+ def radius
300
+ dia / 2.0
301
+ end
302
+ end
303
+
304
+
305
+ class Polyhedron < Shape
306
+ attr_accessor :points, :faces
307
+
308
+ def initialize(points, faces)
309
+ @points = points
310
+ @faces = faces
311
+ end
312
+ end
313
+
314
+
315
+ class Torus < Shape
316
+ attr_accessor :inner_dia, :outer_dia, :angle
317
+
318
+ def initialize(inner_dia, outer_dia, angle=nil)
319
+ @inner_dia = inner_dia
320
+ @outer_dia = outer_dia
321
+ @angle = angle
322
+ end
323
+
324
+ def inner_radius
325
+ inner_dia / 2.0
326
+ end
327
+
328
+ def outer_radius
329
+ outer_dia / 2.0
330
+ end
331
+ end
332
+
333
+
334
+ class LinearExtrusion < Shape
335
+ attr_reader :profile, :height
336
+
337
+ def initialize(profile, height, twist=0)
338
+ @profile = profile
339
+ @height = height
340
+ @twist = twist
341
+ end
342
+ end
343
+
344
+
345
+ class Revolution < Shape
346
+ attr_reader :profile, :angle
347
+
348
+ def initialize(profile, angle=nil)
349
+ @profile = profile
350
+ @angle = angle
351
+ end
352
+ end
353
+
354
+
355
+ class RegularPrism < LinearExtrusion
356
+ attr_reader :sides, :radius
357
+
358
+ def initialize(sides, radius, height)
359
+ @sides = sides
360
+ @radius = radius
361
+
362
+ poly = RegularPolygon.new(sides, radius)
363
+ super(poly, height)
364
+ end
365
+ end
366
+
367
+
368
+ def make_maker(name, klass)
369
+ Object.send(:define_method, name, &klass.method(:new))
370
+ end
371
+
372
+ make_maker :polygon, Polygon
373
+ make_maker :reg_poly, RegularPolygon
374
+ make_maker :square, Square
375
+ make_maker :circle, Circle
376
+ make_maker :text, Text
377
+
378
+ make_maker :box, Box
379
+ make_maker :cube, Cube
380
+ make_maker :cylinder, Cylinder
381
+ make_maker :sphere, Sphere
382
+ make_maker :polyhedron, Polyhedron
383
+ make_maker :torus, Torus
384
+ make_maker :reg_prism, RegularPrism
@@ -0,0 +1,144 @@
1
+ # calculations based on:
2
+ # * http://makezine.com/2010/06/28/make-your-own-gears/
3
+ # based in turn on http://www.bostongear.com/pdf/gear_theory.pdf
4
+ # (can be found in Wayback Machine)
5
+ # * http://en.wikipedia.org/wiki/Gear
6
+ # * http://www.metrication.com/engineering/gears.html
7
+
8
+
9
+ require 'rcad'
10
+
11
+
12
+ class GearProfile < Shape
13
+ attr_reader :pitch_dia, :module_, :p_angle
14
+
15
+ # pitch_dia - effective diameter of gear
16
+ # (not the same as outer diameter)
17
+ # module_ - ratio of pitch diameter to number of teeth (basically the
18
+ # arc length of the tooth spacing)
19
+ # p_angle - pressure angle.
20
+ # it seems 20 deg angle is better for torque, but
21
+ # 14.5 deg angle is better for backlash.
22
+ def initialize(pitch_dia, module_=4, p_angle=20)
23
+ # converting everything to floats ensures that floating point
24
+ # division will be performed later
25
+ @pitch_dia = pitch_dia.to_f
26
+ @module_ = module_.to_f
27
+ @p_angle = p_angle.to_f
28
+
29
+ if @pitch_dia % @module_ != 0
30
+ raise ArgumentError, "non-integer number of teeth!"
31
+ end
32
+ end
33
+
34
+ def num_teeth
35
+ (pitch_dia / module_).to_int
36
+ end
37
+
38
+ def diametrical_pitch
39
+ 1.0 / module_
40
+ end
41
+
42
+ def circular_pitch
43
+ Math::PI / diametrical_pitch
44
+ end
45
+
46
+ def addendum
47
+ 1.0 / diametrical_pitch
48
+ end
49
+
50
+ def outer_dia
51
+ pitch_dia + 2.0 * addendum
52
+ end
53
+
54
+ def whole_depth
55
+ module_ < 1.25 ? (2.4 * module_) : (2.25 * module_)
56
+ end
57
+
58
+ def dedendum
59
+ whole_depth - addendum
60
+ end
61
+
62
+ def root_dia
63
+ pitch_dia - 2 * dedendum
64
+ end
65
+
66
+ # tooth thickness at pitch dia
67
+ def tooth_thickness
68
+ Math::PI / 2.0 / diametrical_pitch
69
+ end
70
+
71
+ def render
72
+ # tooth thickness at tooth tip (TODO: is this correct?)
73
+ tooth_tip_thickness = tooth_thickness - addendum * Math::sin(p_angle)
74
+
75
+ # half of thickness at root/center/tip in degrees
76
+ half_t_root_angle = Math::atan(tooth_thickness / 2 / (root_dia / 2))
77
+ half_t_angle = Math::atan(tooth_thickness / 2 / (pitch_dia / 2))
78
+ half_t_tip_angle = Math::atan(tooth_tip_thickness / 2 / (outer_dia / 2))
79
+
80
+ root_r = root_dia / 2
81
+ pitch_r = pitch_dia / 2
82
+ outer_r = outer_dia / 2
83
+
84
+ points = []
85
+ (1..num_teeth).each do |i|
86
+ angle = (2 * Math::PI / num_teeth) * i
87
+ points << to_polar(root_r, angle - half_t_root_angle)
88
+ points << to_polar(pitch_r, angle - half_t_angle)
89
+ points << to_polar(outer_r, angle - half_t_tip_angle)
90
+ points << to_polar(outer_r, angle + half_t_tip_angle)
91
+ points << to_polar(pitch_r, angle + half_t_angle)
92
+ points << to_polar(root_r, angle + half_t_root_angle)
93
+ end
94
+
95
+ polygon(points)
96
+ end
97
+ end
98
+
99
+
100
+ class SpurGear < Shape
101
+ attr_reader :height, :profile
102
+
103
+ def initialize(height, *args)
104
+ @height = height
105
+ @profile = GearProfile.new(*args)
106
+ @shape = profile.extrude(height)
107
+ end
108
+ end
109
+
110
+
111
+ class HelicalGear < Shape
112
+ attr_reader :height, :helix_angle, :profile
113
+
114
+ def initialize(pitch_dia, height, helix_angle=Math::PI / 3.0)
115
+ @height = height
116
+ @helix_angle = helix_angle
117
+ @profile = GearProfile.new(pitch_dia)
118
+
119
+ @shape = profile.extrude(height, twist)
120
+ end
121
+
122
+ def twist
123
+ twist_length = Math::tan(helix_angle) * height
124
+ pitch_circumference = Math::PI * profile.pitch_dia
125
+
126
+ twist_length * (2 * Math::PI / pitch_circumference)
127
+ end
128
+ end
129
+
130
+
131
+ class HerringboneGear < Shape
132
+ attr_reader :helical_gear
133
+
134
+ def initialize(pitch_dia, height, helix_angle=Math::PI / 3.0)
135
+ @helical_gear = HelicalGear.new(pitch_dia, height / 2.0, helix_angle)
136
+
137
+ @shape = add do
138
+ ~helical_gear
139
+ ~helical_gear
140
+ .scale_z(-1)
141
+ .move_z(height)
142
+ end
143
+ end
144
+ end