class RADMesh::STL

Class representing an STL file. It has factes and stats.

Attributes

exact[RW]
shared[RW]
stl_ptr[RW]
stl_value[RW]

Public Class Methods

bools_to_ints(a) click to toggle source

@!visibility private

# File lib/radmesh/stl.rb, line 580
def self.bools_to_ints(a)
  a.each_with_index do |value, idx|
    a[idx] = 1 if value.class == TrueClass
    a[idx] = 0 if value.class == FalseClass
  end
  a
end
copy_bulk(src, dest, len) click to toggle source

@!visibility private

# File lib/radmesh/stl.rb, line 671
def self.copy_bulk(src, dest, len)
  LibC.memcpy(dest, src, len)
end
default_repair_opts() click to toggle source

@!visibility private

# File lib/radmesh/stl.rb, line 566
def self.default_repair_opts
  { fixall: true, exact: false, tolerance: 0, increment: 0,
    nearby: false, iterations: 2, remove_unconnected: false,
    fill_holes: false, normal_directions: false,
    normal_values: false, reverse_all: false, verbose: true }
end
exact?(o) click to toggle source

@!visibility private

# File lib/radmesh/stl.rb, line 574
def self.exact?(o)
  o[:exact] || o[:fixall] || o[:nearby] || o[:remove_unconnected] ||
    o[:fill_holes] || o[:normal_directions]
end
finalize(ptr) click to toggle source

@!visibility private

# File lib/radmesh/stl.rb, line 83
def self.finalize(ptr)
  proc { CADMesh.stl_close(ptr) }
end
new(path = nil) click to toggle source

@param path [String] path to the STL file to load (optional) @raise [IOError] when ADMesh cannot load the file @raise [NoMemoryError] when ADMesh cannot allocate empty STL struct

# File lib/radmesh/stl.rb, line 32
def initialize(path = nil)
  @stl_ptr = FFI::MemoryPointer.new CADMesh::STLFile, 1
  @stl_value = CADMesh::STLFile.new @stl_ptr
  init if path.nil?
  open path unless path.nil?
  ObjectSpace.define_finalizer self, self.class.finalize(@stl_ptr)
  @exact = false
  @shared = false
end
opts_to_int_array(o) click to toggle source

@!visibility private

# File lib/radmesh/stl.rb, line 589
def self.opts_to_int_array(o)
  bools_to_ints([o[:fixall], o[:exact], o[:tolerance] != 0, o[:tolerance],
                 o[:increment] != 0, o[:increment], o[:nearby],
                 o[:iterations], o[:remove_unconnected], o[:fill_holes],
                 o[:normal_directions], o[:normal_values], o[:reverse_all],
                 o[:verbose]])
end
to_vec(arg, default = 0) click to toggle source

@!visibility private

# File lib/radmesh/stl.rb, line 322
def self.to_vec(arg, default = 0)
  hash = { x: default, y: default, z: default }.merge(arg)
  [hash[:x], hash[:y], hash[:z]]
rescue
  begin
    [arg.x, arg.y, arg.z]
  rescue
    [arg[0], arg[1], arg[2]]
  end
end
vector_probe(args, default = 0) click to toggle source

@!visibility private

# File lib/radmesh/stl.rb, line 334
def self.vector_probe(args, default = 0)
  if args.size == 3
    vec = args
  elsif args.size == 1
    vec = to_vec(args[0], default)
  else
    raise ArgumentError,
          "wrong number of arguments (#{args.size} for 1 or 3)"
  end
  vec
end

Public Instance Methods

[](idx) click to toggle source

Get a facet of given index

@return [Hash] hash with the facet data

# File lib/radmesh/stl.rb, line 636
def [](idx)
  raise IndexError,
        "index #{idx} outside of STL bounds: 0..#{size - 1}" if idx >= size
  ptr = @stl_value[:facet_start].to_ptr + (idx * CADMesh::STLFacet.size)
  value = CADMesh::STLFacet.new ptr
  value.to_hash
end
calculate_volume!() click to toggle source

Calculate volume and save it to the stats

@macro nobang @macro returnself

# File lib/radmesh/stl.rb, line 98
def calculate_volume!
  CADMesh.stl_calculate_volume(@stl_ptr)
  self
end
check_facets_exact!() click to toggle source

Check each facet of the mesh for its 3 neighbors.

Since each facet is a triangle, there should be exactly 3 neighboring facets for every facet in the mesh. Since the mesh defines a solid, there should be no unconnected edges in the mesh. When this option is specified, the 3 neighbors of every facet are searched for and, if found, the neighbors are added to an internal list that keeps track of the neighbors of each facet. A facet is only considered a neighbor if two of its vertices EXACTLY match two of the vertices of another facet. That means that there must be 0 difference between the x, y, and z coordinates of the two vertices of the first facet and the two vertices of the second facet.

Degenerate facets (facets with two or more vertices equal to each other) are removed during the exact check. No other changes are made to the mesh.

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 195
def check_facets_exact!
  CADMesh.stl_check_facets_exact(@stl_ptr)
  @exact = true
  self
end
check_facets_nearby!(tolerance) click to toggle source

Checks each unconnected facet of the mesh for facets that are almost connected but not quite.

Due to round-off errors and other factors, it is common for a mesh to have facets with neighbors that are very close but don’t match exactly. Often, this difference is only in the 8th decimal place of the vertices, but these facets will not show up as neighbors during the exact check. This option finds these nearby neighbors and it changes their vertices so that they match exactly. {#check_facets_exact!} should always be called before the nearby check, so only facets that remain unconnected after the exact check are candidates for the nearby check.

@param tolerance [Float] the distance that is searched for the neighboring

facet

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 219
def check_facets_nearby!(tolerance)
  CADMesh.stl_check_facets_nearby(@stl_ptr, tolerance)
  self
end
clear_error!() click to toggle source

Clear the error flag on internal ADMesh’s STL structure

Only use this, if you know what you are doing.

@macro returnself

# File lib/radmesh/stl.rb, line 66
def clear_error!
  CADMesh.stl_clear_error(@stl_ptr)
  self
end
clone() click to toggle source

Crete a deep copy of the object

@return [STL] deep copy of the object

# File lib/radmesh/stl.rb, line 708
def clone
  c = clone_props! self.class.new
  clone_stats! c
  CADMesh.stl_reallocate(c.stl_ptr)
  clone_facets! c
  clone_neighbors! c
  c.error_control_proc(NoMemoryError, 'could not clone').call
  c
end
each_facet() { |self| ... } click to toggle source

get an enumerator for each facet

@return [Enumerator]

# File lib/radmesh/stl.rb, line 647
def each_facet
  return to_enum(:each_facet) unless block_given?
  idx = 0
  while idx < size
    yield self[idx]
    idx += 1
  end
end
error?() click to toggle source

Checks if there is an error flag on internal ADMesh’s STL structure

@return [Boolean] whether there is and error flag

# File lib/radmesh/stl.rb, line 57
def error?
  CADMesh.stl_get_error(@stl_ptr) == 1
end
fill_holes!() click to toggle source

Fill holes in the mesh by adding facets.

This should be called after {#check_facets_exact!} and {#check_facets_nearby!}. If there are still unconnected facets, then facets will be added to the mesh, connecting the unconnected facets, until all of the holes have been filled. This is guaranteed to completely fix all unconnected facets. However, the resulting mesh may or may not be what the user expects.

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 256
def fill_holes!
  CADMesh.stl_fill_holes(@stl_ptr)
  self
end
fix_normal_directions!() click to toggle source

Check and fix if necessary the directions of the facets.

This only deals with whether the vertices of all the facets are oriented clockwise or counterclockwise, it doesn’t check or modify the value of the normal vector. Every facet should have its vertices defined in a counterclockwise order when looked at from the outside of the part. This option will orient all of the vertices so that they are all facing in the same direction. However, it it possible that this option will make all of the facets facet inwards instead of outwards. The algorithm tries to get a clue of which direction is inside and outside by checking the value of the normal vector so the chance is very good that the resulting mesh will be correct. However, it doesn’t explicitly check to find which direction is inside and which is outside.

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 277
def fix_normal_directions!
  CADMesh.stl_fix_normal_directions(@stl_ptr)
  self
end
fix_normal_values!() click to toggle source

Checks and fixes if necessary the normal vectors of every facet.

The normal vector will point outward for a counterclockwise facet. The length of the normal vector will be 1.

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 289
def fix_normal_values!
  CADMesh.stl_fix_normal_values(@stl_ptr)
  self
end
generate_shared_vertices!() click to toggle source

Generates shared vertices.

Those are needed for some of the output formats. No need to call this manually.

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 314
def generate_shared_vertices!
  check_facets_exact! unless @exact
  CADMesh.stl_generate_shared_vertices(@stl_ptr)
  @shared = true
  self
end
mirror!(*args) click to toggle source

Mirror the mesh about the specified plane.

@macro mirror

@param args [Array<Symbol>] array with 2 axis symbols @param args [Symbol, Symbol] 2 axis symbols (such as :z and :x) @macro returnself @macro nobang @raise [ArgumentError] when the plane is invalid or

the arguments could not be parsed
# File lib/radmesh/stl.rb, line 519
def mirror!(*args)
  args = args[0] if args.size == 1
  raise ArgumentError,
        "wrong number of arguments (#{args.size} for 2)" if args.size != 2
  args.sort!
  begin
    send("mirror_#{args[0]}#{args[1]}!")
  rescue
    raise ArgumentError, "invalid axis pair #{args[0]}#{args[1]}"
  end
end
mirror_xy!() click to toggle source

Mirror the mesh about the XY plane.

@!macro [new] mirror

Mirroring involves reversing the sign of all of the coordinates in a
particular axis. For example, to mirror a mesh about the XY plane,
the signs of all of the Z coordinates in the mesh are reversed.

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 482
def mirror_xy!
  CADMesh.stl_mirror_xy(@stl_ptr)
  self
end
mirror_xz!() click to toggle source

Mirror the mesh about the XZ plane.

@macro mirror

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 504
def mirror_xz!
  CADMesh.stl_mirror_xz(@stl_ptr)
  self
end
mirror_yz!() click to toggle source

Mirror the mesh about the YZ plane.

@macro mirror

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 493
def mirror_yz!
  CADMesh.stl_mirror_yz(@stl_ptr)
  self
end
open_merge!(path) click to toggle source

Merge the specified file with self.

No translation is done, so if, for example, a file was merged with itself, the resulting file would end up with two meshes exactly the same, occupying exactly the same space. So generally, translations need to be done to the files to be merged so that when the two meshes are merged into one, the two resulting parts are properly spaced. If you know the nature of the parts to be merged, it is possible to “nest” one part inside the other. Note, however, that no warnings will be given if one part intersects with the other.

It is possible to place one part against another, with no space in between, but you will still end up with two separately defined parts. If such a mesh was made on a rapid-prototyping machine, the result would depend on the nature of the machine. Machines that use a photopolymer would produce a single solid part because the two parts would be “bonded” during the build process. Machines that use a cutting process would yield two or more parts.

@param path [String] path to the file to merge @macro returnself @macro nobang @raise [IOError] when the file cannot be read/parsed

(makes the object unsafe!)

@note Due to some limitations in the C ADMesh library, when the exception

occurs it is no longer safe to touch the object. If you are not
sure the file is readable and parsable, check before, or use the
method without ! and throw the object away when necessary.
# File lib/radmesh/stl.rb, line 559
def open_merge!(path)
  CADMesh.stl_open_merge(@stl_ptr, path)
  error_control_proc(IOError, "Could not open #{path}").call
  self
end
remove_unconnected_facets!() click to toggle source

Removes facets that have 0 neighbors.

You should probably call {#check_facets_nearby!} before to get better results.

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 231
def remove_unconnected_facets!
  CADMesh.stl_remove_unconnected_facets(@stl_ptr)
  self
end
repair!(opts = {}) click to toggle source

Complex repair of the mesh.

Does various repairing procedures on the mesh depending on the options.

@param opts [Hash] hash with options:

* *fixall* (true) - run all the fixes and ignore other flags
* *exact* (false) - run {#check_facets_exact!}
* *tolerance* (0) - set the tolerance level for {#check_facets_nearby!}
* *increment* (0) - increment level of tolerance for each step
* *nearby* (false) - run {#check_facets_nearby!}
* *iterations* (2) - {#check_facets_nearby!} steps count
* *remove_unconnected* (false) - run {#remove_unconnected_facets!}
* *fill_holes* (false) - run {#fill_holes!}
* *normal_directions* (false) - run {#fix_normal_directions!}
* *normal_values* (false) - run {#fix_normal_values!}
* *reverse_all* (false) - run {#reverse_all_facets!}
* *verbose* (false) - be verbose to stout

@macro returnself @macro nobang @raise [RuntimeError] when something went wrong internaly

# File lib/radmesh/stl.rb, line 617
def repair!(opts = {})
  opts = self.class.default_repair_opts.merge(opts)
  CADMesh.stl_repair(@stl_ptr, *self.class.opts_to_int_array(opts))
  error_control_proc(RuntimeError,
                     'something went wrong during repair').call
  @exact = true if self.class.exact? opts
  self
end
reverse_all_facets!() click to toggle source

Reverses the directions of all of the facets and normals.

If {#fix_normal_directions!} ended up making all of the facets facing inwards instead of outwards, then this method can be used to reverse all of the facets

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 302
def reverse_all_facets!
  CADMesh.stl_reverse_all_facets(@stl_ptr)
  self
end
rotate!(axis, angle) click to toggle source

Rotate the entire mesh about the given axis by the given number of degrees.

@macro rotate

@param axis [Symbol] :x, :y or :z @param angle [Float] angle in degrees @macro returnself @macro nobang @raise [ArgumentError] when the axis is invalid

# File lib/radmesh/stl.rb, line 467
def rotate!(axis, angle)
  send("rotate_#{axis}!", angle)
rescue
  raise ArgumentError, "invalid axis #{axis}"
end
rotate_x!(angle) click to toggle source

Rotate the entire mesh about the X axis by the given number of degrees.

@!macro [new] rotate

The rotation is counter-clockwise about the axis as
seen by looking along the positive axis towards the origin.

@param angle [Float] angle in degrees @macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 428
def rotate_x!(angle)
  CADMesh.stl_rotate_x(@stl_ptr, angle)
  self
end
rotate_y!(angle) click to toggle source

Rotate the entire mesh about the Y axis by the given number of degrees.

@macro rotate

@param angle [Float] angle in degrees @macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 440
def rotate_y!(angle)
  CADMesh.stl_rotate_y(@stl_ptr, angle)
  self
end
rotate_z!(angle) click to toggle source

Rotate the entire mesh about the Z axis by the given number of degrees.

@macro rotate

@param angle [Float] angle in degrees @macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 452
def rotate_z!(angle)
  CADMesh.stl_rotate_z(@stl_ptr, angle)
  self
end
scale!(factor) click to toggle source

Scale the mesh by the given factor.

This multiplies all of the coordinates by the specified number. This method could be used to change the “units” (there are no units explicitly specified in an STL file) of the mesh. For example, to change a part from inches to millimeters, just use factor 25.4.

@param factor [Float] scale factor @macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 393
def scale!(factor)
  CADMesh.stl_scale(@stl_ptr, factor)
  self
end
scale_versor!(*args) click to toggle source

Scale the mesh by the given versor.

This scales the mesh in different dimensions.

@param args [Array<Float>] 3 items array with scale factors @param args [Float, Float, Float] 3 floats with scale factors @param args [Object] object responding to .x, .y and .z @param args [Hash] hash with :x, :y and :z (some can be omitted

to use 1 as default)

@macro returnself @macro nobang @raise [ArgumentError] when the arguments cannot be parsed

# File lib/radmesh/stl.rb, line 410
def scale_versor!(*args)
  vec = self.class.vector_probe args, 1
  FFI::MemoryPointer.new(:float, 3) do |p|
    p.write_array_of_float(vec)
    CADMesh.stl_scale_versor(@stl_ptr, p)
  end
  self
end
size() click to toggle source

Get the number of facets

@return [Fixnum] number of facets

# File lib/radmesh/stl.rb, line 629
def size
  @stl_value[:stats][:number_of_facets]
end
stats() click to toggle source

Get the statistics about the STL file

@return [Hash] statistics

# File lib/radmesh/stl.rb, line 90
def stats
  @stl_value[:stats].to_hash
end
to_a() click to toggle source

Get an array of facets

@return [Array<Hash>]

# File lib/radmesh/stl.rb, line 659
def to_a
  each_facet.to_a
end
to_s() click to toggle source

Get a String representation of STL

@return [String]

# File lib/radmesh/stl.rb, line 666
def to_s
  "#<RADMesh::STL header=\"#{stats[:header]}\">"
end
translate!(*args) click to toggle source

Translate the mesh to the position x,y,z.

This moves the minimum x, y, and z values of the mesh to the specified position.

@param args [Array<Float>] 3 items array with coordinates @param args [Float, Float, Float] 3 floats with coordinates @param args [Object] object responding to .x, .y and .z @param args [Hash] hash with :x, :y and :z (some can be omitted

to use 0 as default)

@macro returnself @macro nobang @raise [ArgumentError] when the arguments cannot be parsed

# File lib/radmesh/stl.rb, line 359
def translate!(*args)
  vec = self.class.vector_probe args
  CADMesh.stl_translate(@stl_ptr, vec[0], vec[1], vec[2])
  self
end
translate_relative!(*args) click to toggle source

Translate the mesh by a vector x,y,z.

This moves the mesh relatively to it’s current position.

@param args [Array<Float>] 3 items array with coordinates @param args [Float, Float, Float] 3 floats with coordinates @param args [Object] object responding to .x, .y and .z @param args [Hash] hash with :x, :y and :z (some can be omitted

to use 0 as default)

@macro returnself @macro nobang @raise [ArgumentError] when the arguments cannot be parsed

# File lib/radmesh/stl.rb, line 377
def translate_relative!(*args)
  vec = self.class.vector_probe args
  CADMesh.stl_translate_relative(@stl_ptr, vec[0], vec[1], vec[2])
  self
end
verify_neighbors!() click to toggle source

@todo Check what does this actually do :)

@macro returnself @macro nobang

# File lib/radmesh/stl.rb, line 240
def verify_neighbors!
  CADMesh.stl_verify_neighbors(@stl_ptr)
  self
end
write_ascii(path, label = 'admesh') click to toggle source

Save the contents of the instance to an ASCII STL file

@param path [String] path for the output file @param label [String] label used internally in the output file @macro returnself @raise [IOError] when ADMesh cannot save the file

# File lib/radmesh/stl.rb, line 109
def write_ascii(path, label = 'admesh')
  CADMesh.stl_write_ascii(@stl_ptr, path, label)
  error_control_proc(IOError, "Could not write to #{path}").call
  self
end
write_binary(path, label = 'admesh') click to toggle source

Save the contents of the instance to a binary STL file

@param path [String] path for the output file @param label [String] label used internally in the output file @macro returnself @raise [IOError] when ADMesh cannot save the file

# File lib/radmesh/stl.rb, line 121
def write_binary(path, label = 'admesh')
  CADMesh.stl_write_binary(@stl_ptr, path, label)
  error_control_proc(IOError, "Could not write to #{path}").call
  self
end
write_dxf(path, label = 'admesh') click to toggle source

Save the contents of the instance to a DXF file

@param path [String] path for the output file @param label [String] label used internally in the output file @macro returnself @raise [IOError] when ADMesh cannot save the file

# File lib/radmesh/stl.rb, line 157
def write_dxf(path, label = 'admesh')
  CADMesh.stl_write_dxf(@stl_ptr, path, label)
  error_control_proc(IOError, "Could not write to #{path}").call
  self
end
write_obj(path) click to toggle source

Save the contents of the instance to an OBJ file

@param path [String] path for the output file @macro returnself @raise [IOError] when ADMesh cannot save the file

# File lib/radmesh/stl.rb, line 132
def write_obj(path)
  generate_shared_vertices! unless @shared
  CADMesh.stl_write_obj(@stl_ptr, path)
  error_control_proc(IOError, "Could not write to #{path}").call
  self
end
write_off(path) click to toggle source

Save the contents of the instance to an OFF file

@param path [String] path for the output file @macro returnself @raise [IOError] when ADMesh cannot save the file

# File lib/radmesh/stl.rb, line 144
def write_off(path)
  generate_shared_vertices! unless @shared
  CADMesh.stl_write_off(@stl_ptr, path)
  error_control_proc(IOError, "Could not write to #{path}").call
  self
end
write_vrml(path) click to toggle source

Save the contents of the instance to a VRML file

@param path [String] path for the output file @macro returnself @raise [IOError] when ADMesh cannot save the file

# File lib/radmesh/stl.rb, line 168
def write_vrml(path)
  generate_shared_vertices! unless @shared
  CADMesh.stl_write_vrml(@stl_ptr, path)
  error_control_proc(IOError, "Could not write to #{path}").call
  self
end

Protected Instance Methods

error_control_proc(exception, message) click to toggle source
# File lib/radmesh/stl.rb, line 71
def error_control_proc(exception, message)
  proc do
    if error?
      clear_error!
      raise exception, message
    end
  end
end

Private Instance Methods

clone_facets!(c) click to toggle source
# File lib/radmesh/stl.rb, line 675
def clone_facets!(c)
  self.class.copy_bulk(@stl_value[:facet_start].to_ptr,
                       c.stl_value[:facet_start].to_ptr,
                       size * CADMesh::STLFacet.size)
  c
end
clone_neighbors!(c) click to toggle source
# File lib/radmesh/stl.rb, line 682
def clone_neighbors!(c)
  self.class.copy_bulk(@stl_value[:neighbors_start],
                       c.stl_value[:neighbors_start],
                       size * CADMesh::STLNeighbors.size)
  c
end
clone_props!(c) click to toggle source
# File lib/radmesh/stl.rb, line 689
def clone_props!(c)
  [:fp, :M, :error].each do |key|
    c.stl_value[key] = @stl_value[key]
  end
  c
end
clone_stats!(c) click to toggle source
# File lib/radmesh/stl.rb, line 696
def clone_stats!(c)
  self.class.copy_bulk(@stl_value[:stats].to_ptr,
                       c.stl_value[:stats].to_ptr,
                       CADMesh::STLStats.size)
  c
end
init() click to toggle source
# File lib/radmesh/stl.rb, line 42
def init
  CADMesh.stl_initialize(@stl_ptr)
  error_control_proc(NoMemoryError, 'Could not initialize').call
end
open(path) click to toggle source
# File lib/radmesh/stl.rb, line 47
def open(path)
  CADMesh.stl_open(@stl_ptr, path)
  error_control_proc(IOError, "Could not open #{path}").call
end