class Laser::Cutter::Notching::PathGenerator

One of the key “tricks” that this algorithm applies, is that it converts everything into pure set of lines in the end. It then tries to find all intersections of the lines so that we can remove duplicates. So any segment of any line that is covered by 2 lines or more is removed, cleared completely for an empty space. This turns out to be very useful indeed, because we can paint with wide brush strokes to get the carcass, and then fine tune it by adding or removing line segments. Some of the lines below are added to actually remove the lines that might have otherwise been there.

This comes especially handy when drawing corner boxes, which are deliberately made not to match the notch width, but to match thickness of the material. The corner notces for these sides will therefore have length equal to the thickness + regular notch length.

Attributes

edge[RW]

Public Class Methods

new(edge) click to toggle source

This class generates lines that zigzag between two lines: the outside line, and the inside line of a single edge. Edge class encapsulates both of them with additional properties.

# File lib/laser-cutter/notching/path_generator.rb, line 58
def initialize(edge)
  @edge = edge
end

Public Instance Methods

adjust_for_kerf(vertices, direction) click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 90
def adjust_for_kerf(vertices, direction)
  if kerf?
    point = vertices.pop
    point = corners ? point.plus(2 * direction * shift_vector(1)) : point
    vertices << point
  end
end
corner_box_sides() click to toggle source

These two boxes occupy the corners of the 3D box. They do not match in width to our notches because they are usually merged with them. Their size is equal to the thickness of the material (adjusted for kerf) It's just an aesthetic choice I guess.

# File lib/laser-cutter/notching/path_generator.rb, line 102
def corner_box_sides
  boxes = []
  extra_lines = []

  boxes << Geometry::Rect[edge.inside.p1.clone, edge.outside.p1.clone]
  boxes << Geometry::Rect[edge.inside.p2.clone, edge.outside.p2.clone]

  extra_lines << add_corners if adjust_corners && kerf?
  sides = boxes.flatten.map(&:relocate!).map(&:sides)
  sides << extra_lines if !extra_lines.empty?
  sides.flatten
end
d_index_across() click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 132
def d_index_across
  (d_index_along + 1) % 2
end
d_index_along() click to toggle source

0 = X, 1 = Y

# File lib/laser-cutter/notching/path_generator.rb, line 128
def d_index_along
  (edge.inside.p1.x == edge.inside.p2.x) ? 1 : 0
end
direction_across() click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 140
def direction_across
  (edge.inside.p1.coords.[](d_index_across) < edge.outside.p1.coords.[](d_index_across)) ? 1 : -1
end
direction_along() click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 136
def direction_along
  (edge.inside.p1.coords.[](d_index_along) < edge.inside.p2.coords.[](d_index_along)) ? 1 : -1
end
generate() click to toggle source

Calculates a notched path that flows between the outer edge of the box (outside_line) and inner (inside_line). Relative location of these lines also defines the direction and orientation of the box, and hence the notches.

We always want to create a symmetric path that has a notch in the middle (for center_out = true) or dip in the middle (center_out = false)

# File lib/laser-cutter/notching/path_generator.rb, line 68
def generate
  shifts = define_shifts
  vertices = []
  lines = []

  if corners
    lines << corner_box_sides
  end

  point = starting_point

  vertices << point
  adjust_for_kerf(vertices,-1) if adjust_corners && !first_notch_out?
  shifts.each do |shift|
    point = shift.next_point_after point
    vertices << point
  end
  adjust_for_kerf(vertices, 1) if adjust_corners && !first_notch_out?
  lines << create_lines(vertices)
  lines.flatten
end
shift_vector(index, dim_shift = 0) click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 115
def shift_vector(index, dim_shift = 0)
  shift = []
  shift[(d_index_across + dim_shift) % 2] = 0
  shift[(d_index_along + dim_shift) % 2] = kerf / 2.0 * edge.send("v#{index}".to_sym).[]((d_index_along + dim_shift) % 2)
  Vector.[](*shift)
end
starting_point() click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 123
def starting_point
  edge.inside.p1.clone # start
end

Private Instance Methods

add_corners() click to toggle source

Helper method to calculate dimensions of our corners.

# File lib/laser-cutter/notching/path_generator.rb, line 146
def add_corners
  k, direction, dim_index, edge_along, edge_across = if first_notch_out?
    [2, -1, 1, :inside, :outside]
  else
    [-2, 1, 0, :outside, :inside]
  end
  v1 = direction * k * shift_vector(1, dim_index)
  v2 = direction * k * shift_vector(2, dim_index)

  r1 = define_corner_rect(:p1, v1, edge_along, edge_across)
  r2 = define_corner_rect(:p2, v2, edge_along, edge_across)

  lines = [r1, r2].map(&:sides).flatten

  # Our clever algorithm removes automatically duplicate lines. These lines
  # below are added to actually clear out this space and remove the existing
  # lines that are already there.
  lines << Geometry::Line[edge.inside.p1.plus(v1), edge.inside.p1.clone]
  lines << Geometry::Line[edge.inside.p2.plus(v2), edge.inside.p2.clone]
  lines
end
create_iterator_across() click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 217
def create_iterator_across
  InfiniteIterator.new([Shift.new(thickness, direction_across, d_index_across),
                        Shift.new(thickness, -direction_across, d_index_across)])
end
create_iterator_along() click to toggle source

As we draw notches, shifts define the 'delta' – movement from one point to the next. This method defines three types of movements we'll be doing: one alongside the edge, and two across (towards the box and outward from the box)

# File lib/laser-cutter/notching/path_generator.rb, line 213
def create_iterator_along
  InfiniteIterator.new([Shift.new(notch_width, direction_along, d_index_along)])
end
create_lines(vertices) click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 222
def create_lines(vertices)
  lines = []
  vertices.each_with_index do |v, i|
    if v != vertices.last
      lines << Geometry::Line.new(v, vertices[i+1])
    end
  end
  lines.flatten
end
define_corner_rect(point, delta, edge_along, edge_across) click to toggle source
# File lib/laser-cutter/notching/path_generator.rb, line 168
def define_corner_rect(point, delta, edge_along, edge_across)
  p1 = edge.inside.send(point).plus(delta)
  coords = []
  coords[d_index_along] = edge.send(edge_along).send(point)[d_index_along]
  coords[d_index_across] = edge.send(edge_across).send(point)[d_index_across]
  p2 = Geometry::Point[*coords]
  Geometry::Rect[p1, p2]
end
define_shifts() click to toggle source

This method has the bulk of the logic: we create the list of path deltas to be applied when we walk the edge next. @param [Object] shift

# File lib/laser-cutter/notching/path_generator.rb, line 181
def define_shifts
  along_iter = create_iterator_along
  across_iter = create_iterator_across

  shifts = []
  inner = true # false when we are drawing outer notch, true when inner

  if first_notch_out?
    shifts << across_iter.next
    inner = !inner
  end

  (1..edge.notch_count).to_a.each do |notch_number|
    shifts << along_iter.next do |shift, index|
      if inner && (notch_number > 1 && notch_number < edge.notch_count)
        shift.delta -= kerf
      elsif !inner
        shift.delta += kerf
      end
      inner = !inner
      shift
    end
    shifts << across_iter.next unless notch_number == edge.notch_count
  end

  shifts << across_iter.next if first_notch_out?
  shifts
end