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.
- data/.gitignore +25 -0
- data/Gemfile +4 -0
- data/README.md +22 -0
- data/Rakefile +6 -0
- data/ext/_rcad/_rcad.cpp +795 -0
- data/ext/_rcad/extconf.rb +22 -0
- data/lib/rcad.rb +384 -0
- data/lib/rcad/gears.rb +144 -0
- data/lib/rcad/nuts.rb +87 -0
- data/lib/rcad/version.rb +3 -0
- data/pedestal.rb +13 -0
- data/rcad.gemspec +27 -0
- metadata +122 -0
@@ -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')
|
data/lib/rcad.rb
ADDED
@@ -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
|
data/lib/rcad/gears.rb
ADDED
@@ -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
|