radmesh 0.98.1 → 0.98.2
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.
- checksums.yaml +8 -8
- data/README.md +2 -1
- data/lib/radmesh.rb +2 -726
- data/lib/radmesh/cadmesh.rb +194 -0
- data/lib/radmesh/libc.rb +11 -0
- data/lib/radmesh/os.rb +31 -0
- data/lib/radmesh/stl.rb +730 -0
- data/lib/radmesh/version.rb +5 -0
- data/radmesh.gemspec +5 -3
- data/{block.stl → spec/fixtures/block.stl} +0 -0
- data/spec/radmesh_spec.rb +5 -3
- metadata +9 -4
- data/lib/cadmesh.rb +0 -192
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzY3MGMzMWVhMmJkMWE4YTlhNGZjOWNiYWYzMGJkYzZkMzdjMmI5Ng==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Mjc5OTk5ZmQ4OGMzNGJiZmQzMDg1YWE3YzVmNjc1ZDQ2MTcyYWIxNQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZWFlZTBjZGFhNzczZmI2MWQ4ODYxYjRjNzNhYWIzZjY3MzQ5MGRkODdiZDMy
|
10
|
+
Nzk1ZmRkZmJmZThhYWU2YmI3OTc3ZTk2NzU0MDg5ZmRjNDVhNGIwNjY2MDNi
|
11
|
+
YTNjZDc2NGE3NWMwY2RlMjI3ZTdjMTM2ZDcwMGExYzMyYWNmMWE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YTM5OGQwNWUwNzhhNWVkYWI0NTc4NjgwYzllZmVhMWVhZGNhNzVkNzA3ZmM5
|
14
|
+
OWE3NGUxODE4MTc5NmM4ZmY3NTYxZjkzMjA0YjVkNDE0ZGMwMzg5ODU4MzM4
|
15
|
+
ZDk0NGRkOGU5NjgxZGFmZTM4NjA0YmQ4ZGRjMmVjYTAzYTMyNjc=
|
data/README.md
CHANGED
@@ -49,4 +49,5 @@ Usage
|
|
49
49
|
# and save it
|
50
50
|
stl.write_binary 'block2.stl'
|
51
51
|
|
52
|
-
You can generate the full documentation with [yard](http://yardoc.org/)
|
52
|
+
You can generate the full documentation with [yard](http://yardoc.org/)
|
53
|
+
or see it online on [RubyDoc.info](http://www.rubydoc.info/gems/radmesh/RADMesh/STL).
|
data/lib/radmesh.rb
CHANGED
@@ -1,730 +1,6 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
4
|
-
# @!macro [new] returnself
|
5
|
-
# @return [STL] returns itself
|
6
|
-
|
7
|
-
# @!macro [new] nobang
|
8
|
-
# @note There is also the same method without ! working as expected.
|
9
|
-
# It is not in this reference guide, because it is automatically
|
10
|
-
# generated.
|
1
|
+
require 'radmesh/version'
|
2
|
+
require 'radmesh/stl'
|
11
3
|
|
12
4
|
# High level wrapper around ADMesh
|
13
5
|
module RADMesh
|
14
|
-
# Class representing an STL file.
|
15
|
-
# It has factes and stats.
|
16
|
-
class STL
|
17
|
-
protected
|
18
|
-
|
19
|
-
attr_accessor :stl_ptr
|
20
|
-
attr_accessor :stl_value
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
attr_accessor :exact
|
25
|
-
attr_accessor :shared
|
26
|
-
|
27
|
-
public
|
28
|
-
|
29
|
-
# @param path [String] path to the STL file to load (optional)
|
30
|
-
# @raise [IOError] when ADMesh cannot load the file
|
31
|
-
# @raise [NoMemoryError] when ADMesh cannot allocate empty STL struct
|
32
|
-
def initialize(path = nil)
|
33
|
-
@stl_ptr = FFI::MemoryPointer.new CADMesh::STLFile, 1
|
34
|
-
@stl_value = CADMesh::STLFile.new @stl_ptr
|
35
|
-
init if path.nil?
|
36
|
-
open path unless path.nil?
|
37
|
-
ObjectSpace.define_finalizer self, self.class.finalize(@stl_ptr)
|
38
|
-
@exact = false
|
39
|
-
@shared = false
|
40
|
-
end
|
41
|
-
|
42
|
-
def init
|
43
|
-
CADMesh.stl_initialize(@stl_ptr)
|
44
|
-
error_control_proc(NoMemoryError, 'Could not initialize').call
|
45
|
-
end
|
46
|
-
|
47
|
-
def open(path)
|
48
|
-
CADMesh.stl_open(@stl_ptr, path)
|
49
|
-
error_control_proc(IOError, "Could not open #{path}").call
|
50
|
-
end
|
51
|
-
|
52
|
-
private :init, :open
|
53
|
-
|
54
|
-
# Checks if there is an error flag on internal ADMesh's STL structure
|
55
|
-
#
|
56
|
-
# @return [Boolean] whether there is and error flag
|
57
|
-
def error?
|
58
|
-
CADMesh.stl_get_error(@stl_ptr) == 1
|
59
|
-
end
|
60
|
-
|
61
|
-
# Clear the error flag on internal ADMesh's STL structure
|
62
|
-
#
|
63
|
-
# Only use this, if you know what you are doing.
|
64
|
-
#
|
65
|
-
# @macro returnself
|
66
|
-
def clear_error!
|
67
|
-
CADMesh.stl_clear_error(@stl_ptr)
|
68
|
-
self
|
69
|
-
end
|
70
|
-
|
71
|
-
def error_control_proc(exception, message)
|
72
|
-
proc do
|
73
|
-
if error?
|
74
|
-
clear_error!
|
75
|
-
fail exception, message
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
protected :error_control_proc
|
81
|
-
|
82
|
-
# @!visibility private
|
83
|
-
def self.finalize(ptr)
|
84
|
-
proc { CADMesh.stl_close(ptr) }
|
85
|
-
end
|
86
|
-
|
87
|
-
# Get the statistics about the STL file
|
88
|
-
#
|
89
|
-
# @return [Hash] statistics
|
90
|
-
def stats
|
91
|
-
@stl_value[:stats].to_hash
|
92
|
-
end
|
93
|
-
|
94
|
-
# Calculate volume and save it to the stats
|
95
|
-
#
|
96
|
-
# @macro nobang
|
97
|
-
# @macro returnself
|
98
|
-
def calculate_volume!
|
99
|
-
CADMesh.stl_calculate_volume(@stl_ptr)
|
100
|
-
self
|
101
|
-
end
|
102
|
-
|
103
|
-
# Save the contents of the instance to an ASCII STL file
|
104
|
-
#
|
105
|
-
# @param path [String] path for the output file
|
106
|
-
# @param label [String] label used internally in the output file
|
107
|
-
# @macro returnself
|
108
|
-
# @raise [IOError] when ADMesh cannot save the file
|
109
|
-
def write_ascii(path, label = 'admesh')
|
110
|
-
CADMesh.stl_write_ascii(@stl_ptr, path, label)
|
111
|
-
error_control_proc(IOError, "Could not write to #{path}").call
|
112
|
-
self
|
113
|
-
end
|
114
|
-
|
115
|
-
# Save the contents of the instance to a binary STL file
|
116
|
-
#
|
117
|
-
# @param path [String] path for the output file
|
118
|
-
# @param label [String] label used internally in the output file
|
119
|
-
# @macro returnself
|
120
|
-
# @raise [IOError] when ADMesh cannot save the file
|
121
|
-
def write_binary(path, label = 'admesh')
|
122
|
-
CADMesh.stl_write_binary(@stl_ptr, path, label)
|
123
|
-
error_control_proc(IOError, "Could not write to #{path}").call
|
124
|
-
self
|
125
|
-
end
|
126
|
-
|
127
|
-
# Save the contents of the instance to an OBJ file
|
128
|
-
#
|
129
|
-
# @param path [String] path for the output file
|
130
|
-
# @macro returnself
|
131
|
-
# @raise [IOError] when ADMesh cannot save the file
|
132
|
-
def write_obj(path)
|
133
|
-
generate_shared_vertices! unless @shared
|
134
|
-
CADMesh.stl_write_obj(@stl_ptr, path)
|
135
|
-
error_control_proc(IOError, "Could not write to #{path}").call
|
136
|
-
self
|
137
|
-
end
|
138
|
-
|
139
|
-
# Save the contents of the instance to an OFF file
|
140
|
-
#
|
141
|
-
# @param path [String] path for the output file
|
142
|
-
# @macro returnself
|
143
|
-
# @raise [IOError] when ADMesh cannot save the file
|
144
|
-
def write_off(path)
|
145
|
-
generate_shared_vertices! unless @shared
|
146
|
-
CADMesh.stl_write_off(@stl_ptr, path)
|
147
|
-
error_control_proc(IOError, "Could not write to #{path}").call
|
148
|
-
self
|
149
|
-
end
|
150
|
-
|
151
|
-
# Save the contents of the instance to a DXF file
|
152
|
-
#
|
153
|
-
# @param path [String] path for the output file
|
154
|
-
# @param label [String] label used internally in the output file
|
155
|
-
# @macro returnself
|
156
|
-
# @raise [IOError] when ADMesh cannot save the file
|
157
|
-
def write_dxf(path, label = 'admesh')
|
158
|
-
CADMesh.stl_write_dxf(@stl_ptr, path, label)
|
159
|
-
error_control_proc(IOError, "Could not write to #{path}").call
|
160
|
-
self
|
161
|
-
end
|
162
|
-
|
163
|
-
# Save the contents of the instance to a VRML file
|
164
|
-
#
|
165
|
-
# @param path [String] path for the output file
|
166
|
-
# @macro returnself
|
167
|
-
# @raise [IOError] when ADMesh cannot save the file
|
168
|
-
def write_vrml(path)
|
169
|
-
generate_shared_vertices! unless @shared
|
170
|
-
CADMesh.stl_write_vrml(@stl_ptr, path)
|
171
|
-
error_control_proc(IOError, "Could not write to #{path}").call
|
172
|
-
self
|
173
|
-
end
|
174
|
-
|
175
|
-
# Check each facet of the mesh for its 3 neighbors.
|
176
|
-
#
|
177
|
-
# Since each facet is a triangle, there should be exactly 3
|
178
|
-
# neighboring facets for every facet in the mesh. Since the
|
179
|
-
# mesh defines a solid, there should be no unconnected edges
|
180
|
-
# in the mesh. When this option is specified, the 3 neighbors
|
181
|
-
# of every facet are searched for and, if found, the neighbors
|
182
|
-
# are added to an internal list that keeps track of the neighbors
|
183
|
-
# of each facet. A facet is only considered a neighbor if two of
|
184
|
-
# its vertices EXACTLY match two of the vertices of another facet.
|
185
|
-
# That means that there must be 0 difference between the x, y, and
|
186
|
-
# z coordinates of the two vertices of the first facet and the two
|
187
|
-
# vertices of the second facet.
|
188
|
-
#
|
189
|
-
# Degenerate facets (facets with two or more vertices equal to each
|
190
|
-
# other) are removed during the exact check. No other changes are
|
191
|
-
# made to the mesh.
|
192
|
-
#
|
193
|
-
# @macro returnself
|
194
|
-
# @macro nobang
|
195
|
-
def check_facets_exact!
|
196
|
-
CADMesh.stl_check_facets_exact(@stl_ptr)
|
197
|
-
@exact = true
|
198
|
-
self
|
199
|
-
end
|
200
|
-
|
201
|
-
# Checks each unconnected facet of the mesh for facets that are
|
202
|
-
# almost connected but not quite.
|
203
|
-
#
|
204
|
-
# Due to round-off errors and other
|
205
|
-
# factors, it is common for a mesh to have facets with neighbors that
|
206
|
-
# are very close but don't match exactly. Often, this difference is
|
207
|
-
# only in the 8th decimal place of the vertices, but these facets will
|
208
|
-
# not show up as neighbors during the exact check. This option finds
|
209
|
-
# these nearby neighbors and it changes their vertices so that they
|
210
|
-
# match exactly. {#check_facets_exact!} should always be called before
|
211
|
-
# the nearby check,
|
212
|
-
# so only facets that remain unconnected after the exact check are
|
213
|
-
# candidates for the nearby check.
|
214
|
-
#
|
215
|
-
# @param tolerance [Float] the distance that is searched for the neighboring
|
216
|
-
# facet
|
217
|
-
# @macro returnself
|
218
|
-
# @macro nobang
|
219
|
-
def check_facets_nearby!(tolerance)
|
220
|
-
CADMesh.stl_check_facets_nearby(@stl_ptr, tolerance)
|
221
|
-
self
|
222
|
-
end
|
223
|
-
|
224
|
-
# Removes facets that have 0 neighbors.
|
225
|
-
#
|
226
|
-
# You should probably call {#check_facets_nearby!} before
|
227
|
-
# to get better results.
|
228
|
-
#
|
229
|
-
# @macro returnself
|
230
|
-
# @macro nobang
|
231
|
-
def remove_unconnected_facets!
|
232
|
-
CADMesh.stl_remove_unconnected_facets(@stl_ptr)
|
233
|
-
self
|
234
|
-
end
|
235
|
-
|
236
|
-
# @todo Check what does this actually do :)
|
237
|
-
#
|
238
|
-
# @macro returnself
|
239
|
-
# @macro nobang
|
240
|
-
def verify_neighbors!
|
241
|
-
CADMesh.stl_verify_neighbors(@stl_ptr)
|
242
|
-
self
|
243
|
-
end
|
244
|
-
|
245
|
-
# Fill holes in the mesh by adding facets.
|
246
|
-
#
|
247
|
-
# This should be called after
|
248
|
-
# {#check_facets_exact!} and {#check_facets_nearby!}.
|
249
|
-
# If there are still unconnected facets, then facets will be added to the
|
250
|
-
# mesh, connecting the unconnected facets, until all of the holes have
|
251
|
-
# been filled. This is guaranteed to completely fix all unconnected facets.
|
252
|
-
# However, the resulting mesh may or may not be what the user expects.
|
253
|
-
#
|
254
|
-
# @macro returnself
|
255
|
-
# @macro nobang
|
256
|
-
def fill_holes!
|
257
|
-
CADMesh.stl_fill_holes(@stl_ptr)
|
258
|
-
self
|
259
|
-
end
|
260
|
-
|
261
|
-
# Check and fix if necessary the directions of the facets.
|
262
|
-
#
|
263
|
-
# This only deals with whether the vertices of all the facets are oriented
|
264
|
-
# clockwise or counterclockwise, it doesn't check or modify the value of
|
265
|
-
# the normal vector. Every facet should have its vertices defined in a
|
266
|
-
# counterclockwise order when looked at from the outside of the part.
|
267
|
-
# This option will orient all of the vertices so that they are all facing
|
268
|
-
# in the same direction. However, it it possible that this option will make
|
269
|
-
# all of the facets facet inwards instead of outwards. The algorithm tries
|
270
|
-
# to get a clue of which direction is inside and outside by checking the
|
271
|
-
# value of the normal vector so the chance is very good that the resulting
|
272
|
-
# mesh will be correct. However, it doesn't explicitly check to find which
|
273
|
-
# direction is inside and which is outside.
|
274
|
-
#
|
275
|
-
# @macro returnself
|
276
|
-
# @macro nobang
|
277
|
-
def fix_normal_directions!
|
278
|
-
CADMesh.stl_fix_normal_directions(@stl_ptr)
|
279
|
-
self
|
280
|
-
end
|
281
|
-
|
282
|
-
# Checks and fixes if necessary the normal vectors of every facet.
|
283
|
-
#
|
284
|
-
# The normal vector will point outward for a counterclockwise facet.
|
285
|
-
# The length of the normal vector will be 1.
|
286
|
-
#
|
287
|
-
# @macro returnself
|
288
|
-
# @macro nobang
|
289
|
-
def fix_normal_values!
|
290
|
-
CADMesh.stl_fix_normal_values(@stl_ptr)
|
291
|
-
self
|
292
|
-
end
|
293
|
-
|
294
|
-
# Reverses the directions of all of the facets and normals.
|
295
|
-
#
|
296
|
-
# If {#fix_normal_directions!} ended up making all of the facets facing
|
297
|
-
# inwards instead of outwards, then this method can be used to reverse
|
298
|
-
# all of the facets
|
299
|
-
#
|
300
|
-
# @macro returnself
|
301
|
-
# @macro nobang
|
302
|
-
def reverse_all_facets!
|
303
|
-
CADMesh.stl_reverse_all_facets(@stl_ptr)
|
304
|
-
self
|
305
|
-
end
|
306
|
-
|
307
|
-
# Generates shared vertices.
|
308
|
-
#
|
309
|
-
# Those are needed for some of the output formats.
|
310
|
-
# No need to call this manually.
|
311
|
-
#
|
312
|
-
# @macro returnself
|
313
|
-
# @macro nobang
|
314
|
-
def generate_shared_vertices!
|
315
|
-
check_facets_exact! unless @exact
|
316
|
-
CADMesh.stl_generate_shared_vertices(@stl_ptr)
|
317
|
-
@shared = true
|
318
|
-
self
|
319
|
-
end
|
320
|
-
|
321
|
-
# @!visibility private
|
322
|
-
def self.to_vec(arg, default = 0)
|
323
|
-
hash = { x: default, y: default, z: default }.merge(arg)
|
324
|
-
[hash[:x], hash[:y], hash[:z]]
|
325
|
-
rescue
|
326
|
-
begin
|
327
|
-
[arg.x, arg.y, arg.z]
|
328
|
-
rescue
|
329
|
-
[arg[0], arg[1], arg[2]]
|
330
|
-
end
|
331
|
-
end
|
332
|
-
|
333
|
-
# @!visibility private
|
334
|
-
def self.vector_probe(args, default = 0)
|
335
|
-
if args.size == 3
|
336
|
-
vec = args
|
337
|
-
elsif args.size == 1
|
338
|
-
vec = to_vec(args[0], default)
|
339
|
-
else
|
340
|
-
fail ArgumentError,
|
341
|
-
"wrong number of arguments (#{args.size} for 1 or 3)"
|
342
|
-
end
|
343
|
-
vec
|
344
|
-
end
|
345
|
-
|
346
|
-
# Translate the mesh to the position x,y,z.
|
347
|
-
#
|
348
|
-
# This moves the minimum x, y, and z values
|
349
|
-
# of the mesh to the specified position.
|
350
|
-
#
|
351
|
-
# @param args [Array<Float>] 3 items array with coordinates
|
352
|
-
# @param args [Float, Float, Float] 3 floats with coordinates
|
353
|
-
# @param args [Object] object responding to .x, .y and .z
|
354
|
-
# @param args [Hash] hash with :x, :y and :z (some can be omitted
|
355
|
-
# to use 0 as default)
|
356
|
-
# @macro returnself
|
357
|
-
# @macro nobang
|
358
|
-
# @raise [ArgumentError] when the arguments cannot be parsed
|
359
|
-
def translate!(*args)
|
360
|
-
vec = self.class.vector_probe args
|
361
|
-
CADMesh.stl_translate(@stl_ptr, vec[0], vec[1], vec[2])
|
362
|
-
self
|
363
|
-
end
|
364
|
-
|
365
|
-
# Translate the mesh by a vector x,y,z.
|
366
|
-
#
|
367
|
-
# This moves the mesh relatively to it's current position.
|
368
|
-
#
|
369
|
-
# @param args [Array<Float>] 3 items array with coordinates
|
370
|
-
# @param args [Float, Float, Float] 3 floats with coordinates
|
371
|
-
# @param args [Object] object responding to .x, .y and .z
|
372
|
-
# @param args [Hash] hash with :x, :y and :z (some can be omitted
|
373
|
-
# to use 0 as default)
|
374
|
-
# @macro returnself
|
375
|
-
# @macro nobang
|
376
|
-
# @raise [ArgumentError] when the arguments cannot be parsed
|
377
|
-
def translate_relative!(*args)
|
378
|
-
vec = self.class.vector_probe args
|
379
|
-
CADMesh.stl_translate_relative(@stl_ptr, vec[0], vec[1], vec[2])
|
380
|
-
self
|
381
|
-
end
|
382
|
-
|
383
|
-
# Scale the mesh by the given factor.
|
384
|
-
#
|
385
|
-
# This multiplies all of the coordinates by the specified number.
|
386
|
-
# This method could be used to change the "units" (there are no units
|
387
|
-
# explicitly specified in an STL file) of the mesh. For example,
|
388
|
-
# to change a part from inches to millimeters, just use factor 25.4.
|
389
|
-
#
|
390
|
-
# @param factor [Float] scale factor
|
391
|
-
# @macro returnself
|
392
|
-
# @macro nobang
|
393
|
-
def scale!(factor)
|
394
|
-
CADMesh.stl_scale(@stl_ptr, factor)
|
395
|
-
self
|
396
|
-
end
|
397
|
-
|
398
|
-
# Scale the mesh by the given versor.
|
399
|
-
#
|
400
|
-
# This scales the mesh in different dimensions.
|
401
|
-
#
|
402
|
-
# @param args [Array<Float>] 3 items array with scale factors
|
403
|
-
# @param args [Float, Float, Float] 3 floats with scale factors
|
404
|
-
# @param args [Object] object responding to .x, .y and .z
|
405
|
-
# @param args [Hash] hash with :x, :y and :z (some can be omitted
|
406
|
-
# to use 1 as default)
|
407
|
-
# @macro returnself
|
408
|
-
# @macro nobang
|
409
|
-
# @raise [ArgumentError] when the arguments cannot be parsed
|
410
|
-
def scale_versor!(*args)
|
411
|
-
vec = self.class.vector_probe args, 1
|
412
|
-
FFI::MemoryPointer.new(:float, 3) do |p|
|
413
|
-
p.write_array_of_float(vec)
|
414
|
-
CADMesh.stl_scale_versor(@stl_ptr, p)
|
415
|
-
end
|
416
|
-
self
|
417
|
-
end
|
418
|
-
|
419
|
-
# Rotate the entire mesh about the X axis by the given number of degrees.
|
420
|
-
#
|
421
|
-
# @!macro [new] rotate
|
422
|
-
# The rotation is counter-clockwise about the axis as
|
423
|
-
# seen by looking along the positive axis towards the origin.
|
424
|
-
#
|
425
|
-
# @param angle [Float] angle in degrees
|
426
|
-
# @macro returnself
|
427
|
-
# @macro nobang
|
428
|
-
def rotate_x!(angle)
|
429
|
-
CADMesh.stl_rotate_x(@stl_ptr, angle)
|
430
|
-
self
|
431
|
-
end
|
432
|
-
|
433
|
-
# Rotate the entire mesh about the Y axis by the given number of degrees.
|
434
|
-
#
|
435
|
-
# @macro rotate
|
436
|
-
#
|
437
|
-
# @param angle [Float] angle in degrees
|
438
|
-
# @macro returnself
|
439
|
-
# @macro nobang
|
440
|
-
def rotate_y!(angle)
|
441
|
-
CADMesh.stl_rotate_y(@stl_ptr, angle)
|
442
|
-
self
|
443
|
-
end
|
444
|
-
|
445
|
-
# Rotate the entire mesh about the Z axis by the given number of degrees.
|
446
|
-
#
|
447
|
-
# @macro rotate
|
448
|
-
#
|
449
|
-
# @param angle [Float] angle in degrees
|
450
|
-
# @macro returnself
|
451
|
-
# @macro nobang
|
452
|
-
def rotate_z!(angle)
|
453
|
-
CADMesh.stl_rotate_z(@stl_ptr, angle)
|
454
|
-
self
|
455
|
-
end
|
456
|
-
|
457
|
-
# Rotate the entire mesh about the given axis
|
458
|
-
# by the given number of degrees.
|
459
|
-
#
|
460
|
-
# @macro rotate
|
461
|
-
#
|
462
|
-
# @param axis [Symbol] :x, :y or :z
|
463
|
-
# @param angle [Float] angle in degrees
|
464
|
-
# @macro returnself
|
465
|
-
# @macro nobang
|
466
|
-
# @raise [ArgumentError] when the axis is invalid
|
467
|
-
def rotate!(axis, angle)
|
468
|
-
send("rotate_#{axis}!", angle)
|
469
|
-
rescue
|
470
|
-
raise ArgumentError, "invalid axis #{axis}"
|
471
|
-
end
|
472
|
-
|
473
|
-
# Mirror the mesh about the XY plane.
|
474
|
-
#
|
475
|
-
# @!macro [new] mirror
|
476
|
-
# Mirroring involves reversing the sign of all of the coordinates in a
|
477
|
-
# particular axis. For example, to mirror a mesh about the XY plane,
|
478
|
-
# the signs of all of the Z coordinates in the mesh are reversed.
|
479
|
-
#
|
480
|
-
# @macro returnself
|
481
|
-
# @macro nobang
|
482
|
-
def mirror_xy!
|
483
|
-
CADMesh.stl_mirror_xy(@stl_ptr)
|
484
|
-
self
|
485
|
-
end
|
486
|
-
|
487
|
-
# Mirror the mesh about the YZ plane.
|
488
|
-
#
|
489
|
-
# @macro mirror
|
490
|
-
#
|
491
|
-
# @macro returnself
|
492
|
-
# @macro nobang
|
493
|
-
def mirror_yz!
|
494
|
-
CADMesh.stl_mirror_yz(@stl_ptr)
|
495
|
-
self
|
496
|
-
end
|
497
|
-
|
498
|
-
# Mirror the mesh about the XZ plane.
|
499
|
-
#
|
500
|
-
# @macro mirror
|
501
|
-
#
|
502
|
-
# @macro returnself
|
503
|
-
# @macro nobang
|
504
|
-
def mirror_xz!
|
505
|
-
CADMesh.stl_mirror_xz(@stl_ptr)
|
506
|
-
self
|
507
|
-
end
|
508
|
-
|
509
|
-
# Mirror the mesh about the specified plane.
|
510
|
-
#
|
511
|
-
# @macro mirror
|
512
|
-
#
|
513
|
-
# @param args [Array<Symbol>] array with 2 axis symbols
|
514
|
-
# @param args [Symbol, Symbol] 2 axis symbols (such as :z and :x)
|
515
|
-
# @macro returnself
|
516
|
-
# @macro nobang
|
517
|
-
# @raise [ArgumentError] when the plane is invalid or
|
518
|
-
# the arguments could not be parsed
|
519
|
-
def mirror!(*args)
|
520
|
-
args = args[0] if args.size == 1
|
521
|
-
fail ArgumentError,
|
522
|
-
"wrong number of arguments (#{args.size} for 2)" if args.size != 2
|
523
|
-
args.sort!
|
524
|
-
begin
|
525
|
-
send("mirror_#{args[0]}#{args[1]}!")
|
526
|
-
rescue
|
527
|
-
raise ArgumentError, "invalid axis pair #{args[0]}#{args[1]}"
|
528
|
-
end
|
529
|
-
end
|
530
|
-
|
531
|
-
# Merge the specified file with self.
|
532
|
-
#
|
533
|
-
# No translation is done, so if, for example, a file was merged with itself,
|
534
|
-
# the resulting file would end up with two meshes exactly the same,
|
535
|
-
# occupying exactly the same space. So generally, translations need to be
|
536
|
-
# done to the files to be merged so that when the two meshes are merged
|
537
|
-
# into one, the two resulting parts are properly spaced. If you know the
|
538
|
-
# nature of the parts to be merged, it is possible to "nest" one part
|
539
|
-
# inside the other. Note, however, that no warnings will be given if one
|
540
|
-
# part intersects with the other.
|
541
|
-
#
|
542
|
-
# It is possible to place one part against another, with no space in
|
543
|
-
# between, but you will still end up with two separately defined parts.
|
544
|
-
# If such a mesh was made on a rapid-prototyping machine, the result
|
545
|
-
# would depend on the nature of the machine. Machines that use a
|
546
|
-
# photopolymer would produce a single solid part because the two parts
|
547
|
-
# would be "bonded" during the build process. Machines that use a cutting
|
548
|
-
# process would yield two or more parts.
|
549
|
-
#
|
550
|
-
# @param path [String] path to the file to merge
|
551
|
-
# @macro returnself
|
552
|
-
# @macro nobang
|
553
|
-
# @raise [IOError] when the file cannot be read/parsed
|
554
|
-
# (makes the object unsafe!)
|
555
|
-
# @note Due to some limitations in the C ADMesh library, when the exception
|
556
|
-
# occurs it is no longer safe to touch the object. If you are not
|
557
|
-
# sure the file is readable and parsable, check before, or use the
|
558
|
-
# method without ! and throw the object away when necessary.
|
559
|
-
def open_merge!(path)
|
560
|
-
CADMesh.stl_open_merge(@stl_ptr, path)
|
561
|
-
error_control_proc(IOError, "Could not open #{path}").call
|
562
|
-
self
|
563
|
-
end
|
564
|
-
|
565
|
-
# @!visibility private
|
566
|
-
def self.default_repair_opts
|
567
|
-
{ fixall: true, exact: false, tolerance: 0, increment: 0,
|
568
|
-
nearby: false, iterations: 2, remove_unconnected: false,
|
569
|
-
fill_holes: false, normal_directions: false,
|
570
|
-
normal_values: false, reverse_all: false, verbose: true }
|
571
|
-
end
|
572
|
-
|
573
|
-
# @!visibility private
|
574
|
-
def self.exact?(o)
|
575
|
-
o[:exact] || o[:fixall] || o[:nearby] || o[:remove_unconnected] ||
|
576
|
-
o[:fill_holes] || o[:normal_directions]
|
577
|
-
end
|
578
|
-
|
579
|
-
# @!visibility private
|
580
|
-
def self.bools_to_ints(a)
|
581
|
-
a.each_with_index do |value, idx|
|
582
|
-
a[idx] = 1 if value.class == TrueClass
|
583
|
-
a[idx] = 0 if value.class == FalseClass
|
584
|
-
end
|
585
|
-
a
|
586
|
-
end
|
587
|
-
|
588
|
-
# @!visibility private
|
589
|
-
def self.opts_to_int_array(o)
|
590
|
-
bools_to_ints([o[:fixall], o[:exact], o[:tolerance] != 0, o[:tolerance],
|
591
|
-
o[:increment] != 0, o[:increment], o[:nearby],
|
592
|
-
o[:iterations], o[:remove_unconnected], o[:fill_holes],
|
593
|
-
o[:normal_directions], o[:normal_values], o[:reverse_all],
|
594
|
-
o[:verbose]])
|
595
|
-
end
|
596
|
-
|
597
|
-
# Complex repair of the mesh.
|
598
|
-
#
|
599
|
-
# Does various repairing procedures on the mesh depending on the options.
|
600
|
-
#
|
601
|
-
# @param opts [Hash] hash with options:
|
602
|
-
# * *fixall* (true) - run all the fixes and ignore other flags
|
603
|
-
# * *exact* (false) - run {#check_facets_exact!}
|
604
|
-
# * *tolerance* (0) - set the tolerance level for {#check_facets_nearby!}
|
605
|
-
# * *increment* (0) - increment level of tolerance for each step
|
606
|
-
# * *nearby* (false) - run {#check_facets_nearby!}
|
607
|
-
# * *iterations* (2) - {#check_facets_nearby!} steps count
|
608
|
-
# * *remove_unconnected* (false) - run {#remove_unconnected_facets!}
|
609
|
-
# * *fill_holes* (false) - run {#fill_holes!}
|
610
|
-
# * *normal_directions* (false) - run {#fix_normal_directions!}
|
611
|
-
# * *normal_values* (false) - run {#fix_normal_values!}
|
612
|
-
# * *reverse_all* (false) - run {#reverse_all_facets!}
|
613
|
-
# * *verbose* (false) - be verbose to stout
|
614
|
-
# @macro returnself
|
615
|
-
# @macro nobang
|
616
|
-
# @raise [RuntimeError] when something went wrong internaly
|
617
|
-
def repair!(opts = {})
|
618
|
-
opts = self.class.default_repair_opts.merge(opts)
|
619
|
-
CADMesh.stl_repair(@stl_ptr, *self.class.opts_to_int_array(opts))
|
620
|
-
error_control_proc(RuntimeError,
|
621
|
-
'something went wrong during repair').call
|
622
|
-
@exact = true if self.class.exact? opts
|
623
|
-
self
|
624
|
-
end
|
625
|
-
|
626
|
-
# Get the number of facets
|
627
|
-
#
|
628
|
-
# @return [Fixnum] number of facets
|
629
|
-
def size
|
630
|
-
@stl_value[:stats][:number_of_facets]
|
631
|
-
end
|
632
|
-
|
633
|
-
# Get a facet of given index
|
634
|
-
#
|
635
|
-
# @return [Hash] hash with the facet data
|
636
|
-
def [](idx)
|
637
|
-
fail IndexError,
|
638
|
-
"index #{idx} outside of STL bounds: 0..#{size - 1}" if idx >= size
|
639
|
-
ptr = @stl_value[:facet_start].to_ptr + (idx * CADMesh::STLFacet.size)
|
640
|
-
value = CADMesh::STLFacet.new ptr
|
641
|
-
value.to_hash
|
642
|
-
end
|
643
|
-
|
644
|
-
# get an enumerator for each facet
|
645
|
-
#
|
646
|
-
# @return [Enumerator]
|
647
|
-
def each_facet
|
648
|
-
return to_enum(:each_facet) unless block_given?
|
649
|
-
idx = 0
|
650
|
-
while idx < size
|
651
|
-
yield self[idx]
|
652
|
-
idx += 1
|
653
|
-
end
|
654
|
-
end
|
655
|
-
|
656
|
-
# Get an array of facets
|
657
|
-
#
|
658
|
-
# @return [Array<Hash>]
|
659
|
-
def to_a
|
660
|
-
each_facet.to_a
|
661
|
-
end
|
662
|
-
|
663
|
-
# Get a String representation of STL
|
664
|
-
#
|
665
|
-
# @return [String]
|
666
|
-
def to_s
|
667
|
-
"#<RADMesh::STL header=\"#{stats[:header]}\">"
|
668
|
-
end
|
669
|
-
|
670
|
-
# @!visibility private
|
671
|
-
def self.copy_bulk(src, dest, len)
|
672
|
-
LibC.memcpy(dest, src, len)
|
673
|
-
end
|
674
|
-
|
675
|
-
def clone_facets!(c)
|
676
|
-
self.class.copy_bulk(@stl_value[:facet_start].to_ptr,
|
677
|
-
c.stl_value[:facet_start].to_ptr,
|
678
|
-
size * CADMesh::STLFacet.size)
|
679
|
-
c
|
680
|
-
end
|
681
|
-
|
682
|
-
def clone_neighbors!(c)
|
683
|
-
self.class.copy_bulk(@stl_value[:neighbors_start],
|
684
|
-
c.stl_value[:neighbors_start],
|
685
|
-
size * CADMesh::STLNeighbors.size)
|
686
|
-
c
|
687
|
-
end
|
688
|
-
|
689
|
-
def clone_props!(c)
|
690
|
-
[:fp, :M, :error].each do |key|
|
691
|
-
c.stl_value[key] = @stl_value[key]
|
692
|
-
end
|
693
|
-
c
|
694
|
-
end
|
695
|
-
|
696
|
-
def clone_stats!(c)
|
697
|
-
self.class.copy_bulk(@stl_value[:stats].to_ptr,
|
698
|
-
c.stl_value[:stats].to_ptr,
|
699
|
-
CADMesh::STLStats.size)
|
700
|
-
c
|
701
|
-
end
|
702
|
-
|
703
|
-
private :clone_facets!, :clone_neighbors!, :clone_props!, :clone_stats!
|
704
|
-
|
705
|
-
# Crete a deep copy of the object
|
706
|
-
#
|
707
|
-
# @return [STL] deep copy of the object
|
708
|
-
def clone
|
709
|
-
c = clone_props! self.class.new
|
710
|
-
clone_stats! c
|
711
|
-
CADMesh.stl_reallocate(c.stl_ptr)
|
712
|
-
clone_facets! c
|
713
|
-
clone_neighbors! c
|
714
|
-
c.error_control_proc(NoMemoryError, 'could not clone').call
|
715
|
-
c
|
716
|
-
end
|
717
|
-
|
718
|
-
# take (almost) all ! methods and create their clone on-demand copies
|
719
|
-
instance_methods.each do |method|
|
720
|
-
next unless method.to_s[-1] == '!'
|
721
|
-
next if method == :!
|
722
|
-
next if method == :clear_error!
|
723
|
-
newmethod = proc do |*args|
|
724
|
-
c = clone
|
725
|
-
c.send(method, *args)
|
726
|
-
end
|
727
|
-
define_method(method.to_s.chomp('!').to_sym, newmethod)
|
728
|
-
end
|
729
|
-
end
|
730
6
|
end
|