original TravisProg added
Change-Id: I2a3be656b5f7237491958d79a5ab3e4c0cb1d9d9
Signed-off-by: Max Fisch <maxf6.283@gmail.com>
diff --git a/manufacturing/TravisProg1/lib_files/data/codecs/data_view.rb b/manufacturing/TravisProg1/lib_files/data/codecs/data_view.rb
new file mode 100755
index 0000000..8b7d846
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/data/codecs/data_view.rb
@@ -0,0 +1,135 @@
+class DataView
+ attr_accessor :i, :endi
+ def initialize(val)
+ @val = val
+ @i = 0
+ @endi = @val.length
+ end
+ def unpack_single(i, l, code)
+ raise "overran buffer" if (i + l > @val.bytesize)
+ v = @val.byteslice(i, l).unpack(code)
+ @i = i + l
+ return v[0]
+ end
+ def unpack_multiple(i, l, n, code)
+ raise "overran buffer" if (i + l * n > @val.bytesize)
+ v = @val.byteslice(i, l * n).unpack("#{code}#{n}")
+ @i = i + l * n
+ return v
+ end
+ def f64(i = @i)
+ unpack_single(i, 8, "D")
+ end
+ def nf64(n, i = @i)
+ unpack_multiple(i, 8, n, "D")
+ end
+ def i8(i = @i)
+ unpack_single(i, 1, "c")
+ end
+ def i16(i = @i)
+ unpack_single(i, 2, "s")
+ end
+ def i32(i = @i)
+ unpack_single(i, 4, "i")
+ end
+ def u8(i = @i)
+ unpack_single(i, 1, "C")
+ end
+ def u16(i = @i)
+ unpack_single(i, 2, "S")
+ end
+ def u32(i = @i)
+ unpack_single(i, 4, "I")
+ end
+ def varint(i = @i)
+ @i = i
+ v = 0
+ p = 0
+ while true
+ k = @val.getbyte(@i)
+ v += (k & 0x7f) << p
+ p += 7
+ @i += 1
+ if (k & 0x80 == 0)
+ return v
+ end
+ end
+ end
+ def get_utf8_string(len, i = @i)
+ val = @val.byteslice(i, len)
+ @i = i + len
+ return val.force_encoding("UTF-8")
+ end
+ def null_utf16(i = @i)
+ i_n = i
+ i_n += 2 while @val.byteslice(i_n, 2) != "\0\0"
+ val = @val.byteslice(i, i_n - i).force_encoding("UTF-16LE")
+ @i = i_n + 2
+ val
+ end
+ def null_ascii8(i = @i)
+ i_n = i
+ i_n += 1 while @val.byteslice(i_n, 1) != "\0"
+ val = @val.byteslice(i, i_n - i)
+ @i = i_n + 1
+ val
+ end
+end
+BaseNullString = "\0".force_encoding("ASCII-8BIT")
+class OutputView
+ attr_accessor :i, :val
+ def initialize(len)
+ @val = BaseNullString * len
+ @i = 0
+ end
+ def pack_single(v, i, l, code)
+ raise "overran buffer" if (i + l > @val.bytesize)
+ add_bytes([v].pack(code).bytes, i)
+ return self
+ end
+ def f64(v, i = @i)
+ pack_single(v, i, 8, "D")
+ end
+ def i8(v, i = @i)
+ pack_single(v, i, 1, "c")
+ end
+ def i16(v, i = @i)
+ pack_single(v, i, 2, "s")
+ end
+ def i32(v, i = @i)
+ pack_single(v, i, 4, "i")
+ end
+ def u8(v, i = @i)
+ pack_single(v, i, 1, "C")
+ end
+ def u16(v, i = @i)
+ pack_single(v, i, 2, "S")
+ end
+ def u32(v, i = @i)
+ pack_single(v, i, 4, "I")
+ end
+ def add_bytes(v, i = @i)
+ v.each do |j|
+ @val.setbyte(i, j)
+ i += 1
+ end
+ @i = i
+ end
+ def varint(v, i = @i)
+ @i = i
+ v &= 0xFFFFFFFF
+ while (v >> 7) != 0
+ @val.setbyte(@i, (v & 0x7f) | 0x80)
+ @i += 1
+ v >>= 7
+ end
+ @val.setbyte(@i, (v & 0x7f))
+ @i += 1
+ end
+ def self.varint_size(v)
+ i = 1
+ v &= 0xFFFFFFFF
+ i += 1 while (v >>= 7) != 0
+ return i
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/data/codecs/serialize_buffer.rb b/manufacturing/TravisProg1/lib_files/data/codecs/serialize_buffer.rb
new file mode 100755
index 0000000..5aea1c2
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/data/codecs/serialize_buffer.rb
@@ -0,0 +1,186 @@
+require "data/codecs/data_view.rb"
+class SerializeBuffer
+ class Writer
+ def initialize()
+ @build_stack = [[]]
+ end
+ def write_varint(varint)
+ @build_stack[-1].push(SerializeBuffer::WriteVarint.new(varint))
+ end
+ def code_string(str)
+ @build_stack[-1].push(SerializeBuffer::WriteString.new(str))
+ end
+ def write_f64(val)
+ @build_stack[-1].push(SerializeBuffer::WriteF64.new(val))
+ end
+ def push()
+ @build_stack.push([])
+ end
+ def pop()
+ t = @build_stack.pop()
+ @build_stack[-1].push(SerializeBuffer::WriteList.new(t))
+ end
+ def data() @build_stack[0] end
+ def depth() @build_stack.length end
+ end
+ class WriteVarint
+ attr_accessor :varint
+ def initialize(varint)
+ @varint = varint
+ end
+ def inspect() @varint.inspect end
+ def pretty_print(i)
+ puts "#{" " * i}#{@varint.inspect}"
+ end
+ def get_concat_len()
+ return OutputView.varint_size(@varint)
+ end
+ def concat_output(output)
+ #puts "varint: #{output.i}"
+ output.varint(@varint)
+ end
+ end
+ class WriteF64
+ attr_accessor :val
+ def initialize(val)
+ @val = val
+ end
+ def inspect() @val.inspect end
+ def pretty_print(i)
+ puts "#{" " * i}#{@val.inspect}"
+ end
+ def get_concat_len()
+ return 8
+ end
+ def concat_output(output)
+ #puts "varint: #{output.i}"
+ output.f64(@val)
+ end
+ end
+ class WriteString
+ attr_accessor :str
+ def initialize(str)
+ @str = str
+ end
+ def inspect() @str.inspect end
+ def pretty_print(i)
+ puts "#{" " * i}#{@str.inspect}"
+ end
+ def get_concat_len()
+ len = @str.bytes.length
+ return len + OutputView.varint_size(len)
+ end
+ def concat_output(output)
+ #puts "varstring: #{output.i} #{@str.inspect}"
+ output.varint(@str.bytes.length)
+ #puts "varstrdata: #{output.i}"
+ output.add_bytes(@str.bytes)
+ end
+ end
+ class WriteList
+ attr_accessor :items
+ def initialize(items)
+ @items = items
+ end
+ def inspect()
+ "#{@items.inspect}"
+ end
+ def pretty_print(i)
+ @items.each do |item|
+ if (item.class == self.class)
+ #puts "#{" " * i}["
+ @items.each do |v|
+ v.pretty_print(i + 1)
+ end
+ #puts "#{" " * i}]"
+ return
+ end
+ end
+ #puts "#{" " * i}#{@items.inspect}"
+ end
+ def get_concat_len()
+ @len = SerializeBuffer.get_concat_length(@items)
+ return @len + OutputView.varint_size(@len)
+ end
+ def concat_output(output)
+ #puts "write_list: #{output.i} #{@len}"
+ output.varint(@len)
+ # puts "start_list_list: #{output.i}"
+ SerializeBuffer.concat_output(output, @items)
+ end
+ def get_ipos(ids, idoff)
+ @len || get_concat_len()
+ return OutputView.varint_size(@len) +
+ SerializeBuffer.get_ipos(ids, @items, idoff)
+ end
+ end
+ def self.concat_output(output, arr)
+ arr.each do |obj|
+ obj.concat_output(output)
+ end
+ end
+ def self.get_concat_length(arr)
+ len = 0
+ arr.each do |obj|
+ len += obj.get_concat_len()
+ end
+ return len
+ end
+ def self.get_ipos(ids, arr, idoff = 0)
+ ipos = 0
+ arr.each.with_index do |obj, i|
+ if (i == ids[idoff])
+ idoff += 1
+ if (idoff < ids.length)
+ return ipos + obj.get_ipos(ids, idoff)
+ else
+ return ipos
+ end
+ else
+ ipos += obj.get_concat_len()
+ end
+ end
+ return ipos
+ end
+ def self.pretty_print(arr)
+ arr.each do |v|
+ v.pretty_print(0)
+ end
+ end
+ # example:
+ # buf = SerializeBuffer.code { |writer| ... }
+ # File.write(fname, buf)
+ def self.code(&blk)
+ ser = SerializeBuffer::Writer.new()
+ blk.call(ser)
+ data = ser.data()
+ len = SerializeBuffer.get_concat_length(data)
+ buffer = OutputView.new(len)
+ SerializeBuffer.concat_output(buffer, data)
+ buffer.val
+ end
+end
+class StreamReader
+ def initialize(data)
+ @data = DataView.new(data)
+ @endis = []
+ end
+ def ipos()
+ @data.i
+ end
+ def push()
+ len = @data.varint()
+ @endis.push(@data.endi)
+ newendi = @data.i + len
+ raise "problem!" if (newendi > @data.endi)
+ @data.endi = @data.i + len
+ end
+ def parse_f64() @data.f64() end
+ def parse_varint() @data.varint() end
+ def parse_string() @data.get_utf8_string(@data.varint()) end
+ def not_done() @data.i < @data.endi end
+ def pop()
+ raise "problem!" if @data.endi != @data.i
+ @data.endi = @endis.pop()
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/bool_ops/dxf_offset.rb b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/dxf_offset.rb
new file mode 100755
index 0000000..7e3d2fa
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/dxf_offset.rb
@@ -0,0 +1,29 @@
+require "geometry/dxf_to_gcode/2dpoint.rb"
+require_relative "offset_profiles.rb"
+
+require "geometry/dxf_to_gcode/dxf.rb"
+require_relative "dxf_to_offset_profiles.rb"
+
+require "geometry/dxf_to_gcode/timed_view.rb"
+require_relative "overlay.rb"
+require_relative "pairwise_inter.rb"
+
+def offset_dxf_integer(dxf, scale, resolution, offset)
+ lns = dxf[0].to_offsetable(scale)
+
+ all_lns = []
+ lns.each do |ln|
+ rlns = ln.offset(((offset) * scale).to_i)
+ segments = []
+ rlns.each do |rln|
+ rln.append_to(segments, resolution)
+ end
+ shape = Shape.new(segments)
+ all_lns << shape
+ end
+
+# swl_p, nticks = SweepLineUnion.make_outside_profile(all_lns)
+ swl_p = AllPairsInter.inter(all_lns)
+
+ return swl_p#, all_lns
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/bool_ops/dxf_to_offset_profiles.rb b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/dxf_to_offset_profiles.rb
new file mode 100755
index 0000000..54b045b
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/dxf_to_offset_profiles.rb
@@ -0,0 +1,47 @@
+
+class Line2d
+ def to_offsetable(st, scale)
+ st = CompGeo::Point2di.new((st.x * scale).to_i, (st.y * scale).to_i)
+ ed = CompGeo::Point2di.new((@ed.x * scale).to_i, (@ed.y * scale).to_i)
+ return OffsetableLine.new(st, ed)
+ end
+ def to_offsetable_rev(ed, scale)
+ st = CompGeo::Point2di.new((@st.x * scale).to_i, (@st.y * scale).to_i)
+ ed = CompGeo::Point2di.new((ed.x * scale).to_i, (ed.y * scale).to_i)
+ return OffsetableLine.new(st, ed)
+ end
+end
+
+class QuadrentArc
+ def to_offsetable(st, scale)
+ st_ed_offsettable(st, ed, scale)
+ end
+ def to_offsetable_rev(ed, scale)
+ st_ed_offsettable(st, ed, scale)
+ end
+ def st_ed_offsettable(st, ed, scale)
+ st = CompGeo::Point2di.new((st.x * scale).to_i, (st.y * scale).to_i)
+ ed = CompGeo::Point2di.new((ed.x * scale).to_i, (ed.y * scale).to_i)
+ c = CompGeo::Point2di.new((@c.x * scale).to_i, (@c.y * scale).to_i)
+ OffsettableArc.new(st, ed, c, (r * scale).to_i)
+ end
+end
+
+class SegmentRef
+ class ReversedSegRef
+ def to_offsetable(st, scale)
+ @seg.to_offsetable_rev(st, scale)
+ end
+ end
+ def to_offsetable(st, scale)
+ @seg.to_offsetable(st, scale)
+ end
+end
+class TwinableProfile
+ def to_offsetable(scale)
+ @items.length.times.collect do |i|
+ item = @items[i]
+ item.to_offsetable(item.st, scale)
+ end
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/bool_ops/offset_profiles.rb b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/offset_profiles.rb
new file mode 100755
index 0000000..d0ca72d
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/offset_profiles.rb
@@ -0,0 +1,217 @@
+class XMonotonicSubdivisionArc
+ attr_accessor :st, :ed
+ def initialize(st, ed, d)
+ @st = st
+ @ed = ed
+ @d = d
+
+ @d_denom = 1
+
+ #d = d.abs
+ #a = (ed - st).rot90()
+ #mag = a.mag_sqr()
+
+ #df = d.to_f
+ #@r_real = ((df) ** 2 + mag / 4) / (2 * df)
+
+# @guard
+
+ end
+ def draw_subseq(cr, scale, st, ed, d, d_denom, d_limit, l)
+ sign = d <=> 0
+ d = d.abs
+ a = (ed - st).rot90()
+ mag = a.mag_sqr()
+
+ magv = mag * d_denom * d_denom
+ df = d.to_f / d_denom
+ restimate = ((df) ** 2 + mag / 4) / (2 * df)
+ if (d < d_denom * d_limit)# * 4)
+ (@k &1 == 0 ? Color::Red : Color::Green).set(cr)
+ @k += 1
+ cr.move_to(st.x * scale, -st.y * scale)
+ cr.line_to(ed.x * scale, -ed.y * scale)
+ cr.stroke()
+ else
+ vec = (a * (sign * d * 2)) / integer_sqrt(magv)
+ mp = (st + ed + vec) / 2 + a.round_up_fudge
+
+ puts "restimate: #{l} -> #{restimate}"
+
+# l1 = (st - mp).mag_sqr()
+# l2 = (ed - mp).mag_sqr()
+
+ #psqr = ((restimate - df / 2)) ** 2 + mag / (4**2)
+ psqr = ((((df) ** 2 + mag / 4) / (2 * df) - df / 2)) ** 2 + mag / (4**2)
+ dnxt = restimate - Math.sqrt(psqr) #integer_sqrt(psqr)
+ d_denom = d_denom * 16 #* 16
+
+ #puts "d: #{df / dnxt}"
+ #puts "d: #{df} -> #{dnxt}"
+ dnxt = sign * (dnxt * d_denom).floor.to_i
+
+ draw_subseq(cr, scale, st, mp, dnxt, d_denom, d_limit, l + 1)
+ draw_subseq(cr, scale, mp, ed, dnxt, d_denom, d_limit, l + 1)
+
+
+ #arbn = ((4 * d * d_denom))
+ #arbd = (mag * d_denom * d_denom - d * d * 4)
+
+ #arbd2 = (restimate * d_denom - d) * 4 * 2 * d * d_denom
+ #puts "restimate: #{restimate} arb: #{arbn} / #{arbd} arbd: #{arbd / arbd2}"
+=begin
+ s = 2 * Math.sqrt(mag) / ((restimate - df) * 4)
+
+
+ m = (a * s).to_i_vec + a.round_up_fudge * 2
+ #puts m.inspect
+ k = (m * sign + st + ed) / 2
+
+ Color::Pink.set(cr)
+ cr.move_to(st.x * scale, -st.y * scale)
+ cr.line_to(k.x * scale, -k.y * scale)
+ cr.line_to(ed.x * scale, -ed.y * scale)
+ cr.stroke()
+=end
+ end
+ end
+ def draw_to(cr, scale)
+# derr = (@st - @ed)
+# p = derr.mag_sqr()
+
+# num =
+
+# c = ((derr.rot90() * (2 * num)) / denom + (@st + @ed)) / 2
+
+ @k = 0
+ st = @st
+# cr.move_to(@st.x * scale, -@st.y * scale)
+ d_denom_limit = [1, (1 / scale)].max
+ #d_denom_limit = 1
+ draw_subseq(cr, scale, @st, @ed, @d * 1, @d_denom, d_denom_limit, 0)
+ cr.stroke()
+=begin
+ c = to_offsetable().c
+ Color::Pink.set(cr)
+ cr.line_to(@ed.x * scale, -@ed.y * scale)
+ cr.line_to(c .x * scale, -c.y * scale)
+ cr.line_to(@st.x * scale, -@st.y * scale)
+ cr.stroke()
+=end
+ #cr.arc(@c.x * scale, -@c.y * scale, @r * scale, @sa, @ea)
+ end
+ def append_subseq_to(segs, st, ed, d, d_denom, d_limit, l)
+ sign = d <=> 0
+ d = d.abs
+ a = (ed - st).rot90()
+ mag = a.mag_sqr()
+
+ magv = mag * d_denom * d_denom
+ df = d.to_f / d_denom
+ restimate = ((df) ** 2 + mag / 4) / (2 * df)
+ if (d < d_denom * d_limit)# * 4)
+ segs << Shape::Segment.new(st, ed)
+ else
+ vec = (a * (sign * d * 2)) / integer_sqrt(magv)
+ mp = (st + ed + vec) / 2 + a.round_up_fudge
+
+ psqr = ((((df) ** 2 + mag / 4) / (2 * df) - df / 2)) ** 2 + mag / (4**2)
+ dnxt = restimate - Math.sqrt(psqr) #integer_sqrt(psqr)
+ d_denom = d_denom * 16 #* 16
+
+ dnxt = sign * (dnxt * d_denom).floor.to_i
+
+ append_subseq_to(segs, st, mp, dnxt, d_denom, d_limit, l + 1)
+ append_subseq_to(segs, mp, ed, dnxt, d_denom, d_limit, l + 1)
+ end
+ end
+ def to_offsetable()
+ sign = -(@d <=> 0)
+ d = @d.abs
+ a = (@st - @ed).rot90()
+ mag = a.mag_sqr()
+
+ magv = mag * @d_denom * @d_denom
+ df = d.to_f / @d_denom
+ restimate = ((df) ** 2 + mag / 4) / (2 * df)
+ vec = (a * (sign * (d - (restimate * @d_denom).to_i) * 2)) / integer_sqrt(magv)
+
+ c = (@st + @ed + vec) / 2
+ OffsettableArc.new(@st, @ed, c, restimate.to_i)
+ end
+ def append_to(segs, scale)
+ append_subseq_to(segs, @st, @ed, @d * 1, @d_denom, scale, 0)
+ end
+ def flip()
+ @st, @ed = @ed, @st
+ @d = -@d
+ end
+end
+
+class OffsettableArc
+ attr_accessor :c
+ def initialize(st, ed, c, r)
+ @st, @ed, @c, @r = st, ed, c, r
+ end
+ def offset_pt(pt, dist)
+ v = (pt - @c)
+ magsqr = v.mag_sqr()
+ l = integer_sqrt(magsqr * 2**20)
+ v = (v * dist * 2**10) / l
+ return pt + v, pt - v
+ end
+ def offset(dist)
+ sign = (@ed - @c).cross(@st - @c) <=> 0
+ #lns = []
+ sto, sti = offset_pt(@st, dist)
+ edo, edi = offset_pt(@ed, dist)
+ #lns << OffsettableArc.new(sto, edo, @c, @r + dist)
+ #lns << OffsettableArc.new(sti, edi, @c, @r - dist)
+ #lns << OffsettableArc.new(sti, sto, @st, dist)
+ #lns << OffsettableArc.new(edo, edi, @ed, dist)
+ rlns = []
+ #lns.each { |ln| ln.to_xmonotone(rlns, sign) }
+ OffsettableArc.new(edo, sto, @c, @r + dist).to_xmonotone(rlns, -sign)
+ OffsettableArc.new(sto, sti, @st, dist).to_xmonotone(rlns, -sign)
+ OffsettableArc.new(sti, edi, @c, @r - dist).to_xmonotone(rlns, sign)
+ OffsettableArc.new(edi, edo, @ed, dist).to_xmonotone(rlns, -sign)
+ if (sign < 0)
+ rlns.each do |rln|
+ rln.flip()
+ end
+ rlns.reverse!
+ end
+ return rlns
+ end
+ def to_xmonotone(lns, sign)
+ magsqr = ((@st + @ed) - @c * 2).mag_sqr()
+ lns << XMonotonicSubdivisionArc.new(@st, @ed, sign * (@r - integer_sqrt(magsqr) / 2))
+ end
+end
+class TransferLine
+ def initialize(st, ed) @st, @ed = st, ed end
+ def append_to(segs, scale)
+ segs << Shape::Segment.new(@st, @ed)
+ end
+end
+class OffsetableLine
+ def initialize(st, ed)
+ @st, @ed = st, ed
+ end
+ def offset_pt(pt, dist)
+ v = (@ed - @st).rot90()
+ magsqr = v.mag_sqr()
+ l = integer_sqrt(magsqr * 2**20)
+ v = (v * dist * 2**10) / l
+ return pt + v, pt - v
+ end
+ def offset(dist)
+ sto, sti = offset_pt(@st, dist)
+ edo, edi = offset_pt(@ed, dist)
+ rlns = []
+ rlns << TransferLine.new(edo, sto)
+ OffsettableArc.new(sto, sti, @st, dist).to_xmonotone(rlns, -1)
+ rlns << TransferLine.new(sti, edi)
+ OffsettableArc.new(edi, edo, @ed, dist).to_xmonotone(rlns, -1)
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/bool_ops/overlay.rb b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/overlay.rb
new file mode 100755
index 0000000..f844803
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/overlay.rb
@@ -0,0 +1,498 @@
+require_relative "predicate.rb"
+
+class Shape
+ class Segment
+ init_fields :st, :ed
+ end
+ attr_accessor :segs
+ def initialize(segs)
+ @segs = segs
+ end
+ def draw_blank_to(cr, scale)
+# cr.move_to(@segs[0].st.x * scale, -@segs[0].st.y * scale)
+ @segs.each do |seg|
+ cr.move_to(seg.st.x * scale, -seg.st.y * scale)
+ cr.line_to(seg.ed.x * scale, -seg.ed.y * scale)
+ end
+ cr.stroke()
+ end
+ def draw_to(cr, scale)
+# cr.move_to(@segs[0].st.x * scale, -@segs[0].st.y * scale)
+ i = 0
+ @segs.each do |seg|
+ k = (1.0 * i) / (@segs.length - 1)
+ cr.set_source_rgb(1.0, k, 1.0 - k)
+ cr.move_to(seg.st.x * scale, -seg.st.y * scale)
+ cr.line_to(seg.ed.x * scale, -seg.ed.y * scale)
+ cr.stroke()
+ mp = (seg.st + seg.ed) * (2**-1 * scale)
+ v = (seg.ed - seg.st)
+ l = v.mag_sqr().to_i
+ if (l > 1000)
+ puts "#{v.mag_sqr()} -> #{v.mag_sqr().to_i}" if v.mag_sqr().to_i == 0
+ v = v * (integer_sqrt(v.mag_sqr().to_i * 2**10)**-1 * 2**5)
+ #puts "#{mp.x.inspect} #{mp.y.inspect}"
+ #puts "#{v.x.inspect} #{v.y.inspect}"
+ s1 = mp - v * 10 - v.rot90() * 5
+ s2 = mp - v * 10 + v.rot90() * 5
+ cr.move_to(mp.x, -mp.y)
+ cr.line_to(s1.x, -s1.y)
+ cr.line_to(s2.x, -s2.y)
+ cr.line_to(mp.x, -mp.y)
+ cr.stroke()
+ end
+ i += 1
+ end
+ end
+ def add_pts(pts)
+ @segs.each do |seg|
+ pts << seg.st
+ end
+ end
+ def add_evts(evts)
+ status_lines = @segs.collect do |seg|
+ side = x_lex_order(seg.st, seg.ed)
+ (side < 0) ? StatusLine.new(seg.st, seg.ed, 1) : StatusLine.new(seg.ed, seg.st, -1)
+ end
+
+ @segs.length.times do |i|
+ pt = @segs[i].st
+ to_add = []
+ to_del = []
+ ((status_lines[i].side > 0) ? to_add : to_del) << status_lines[i]
+ ((status_lines[i - 1].side < 0) ? to_add : to_del) << status_lines[i - 1]
+=begin
+ to_add.each do |spt|
+ puts "to_add: #{(spt.st - pt).inspect}"
+ end
+ to_del.each do |spt|
+ puts "to_del: #{(spt.ed - pt).inspect}"
+ end
+=end
+ evts.add_event(AddDelEvent.new(pt, to_add, to_del))
+ end
+ end
+end
+
+class EventQueue
+ attr_accessor :event_limit
+ def initialize()
+ @events = []
+ end
+ def flip()
+ @events.sort!() { |a, b| b <=> a }
+ @lnevents = @events
+ @events = []
+ end
+ def add_event(event)
+ if @event_limit
+ return if @event_limit && (event <=> @event_limit) <= 0
+ end
+ @events << event
+ end
+ def pop_min_helper()
+ @lnevents.sort!() { |a, b| b <=> a }
+ m1 = @lnevents[-1]
+ m2 = @events[-1]
+ return @lnevents.pop() if !m2
+ return @events.pop() if !m1
+ d = m1 <=> m2
+ #raise "events match!" if (d == 0)
+ return (d < 0 ? @lnevents : @events).pop()
+ end
+ def pop_min()
+ min = pop_min_helper()
+ #puts "min: #{min}"
+ while ((min2 = next_event()) && (min2 <=> min) == 0)
+ min.merge_in(min2)
+ pop_min()
+ end
+ return min
+ end
+ def next_event()
+ @lnevents.sort!() { |a, b| b <=> a }
+ m1 = @lnevents[-1]
+ m2 = @events[-1]
+ return m1 if !m2
+ return m2 if !m1
+ d = m1 <=> m2
+ raise "events match!" if (d == 0)
+ return d < 0 ? m1 : m2
+ end
+ def empty()
+ @events.empty?() && @lnevents.empty?()
+ end
+end
+
+class StatusLine
+ attr_accessor :st, :ed, :side, :last_event
+ def initialize(st, ed, side) # side is int from -1 to 1
+ @st, @ed = st, ed
+ @side = side
+ @last_event = nil
+ end
+ def get_n_d_at(x)
+ n = st.y * (ed.x - x) + ed.y * (x - st.x)
+ return n, (ed.x - st.x)
+ end
+ def compare_at_x(x, o)
+ n,d = get_n_d_at(x)
+ no, d_o = o.get_n_d_at(x)
+ n * d_o <=> d * no
+ end
+ def compare_to_xy(x, pt)
+ n,d = get_n_d_at(x)
+ n <=> d * pt.y
+ end
+ def wrap_up(ed_pt)
+ @last_event.connect_to(ed_pt, @side)
+ end
+end
+
+class Status
+ def initialize()
+ @active = []
+ end
+ def add_lines(evts, to_adds)
+ st = to_adds[0].st
+ x = st.x
+ i = 0
+ i += 1 while (i < @active.length && @active[i].compare_to_xy(x, st) < 0)
+ imin = i
+ to_adds.each do |to_add|
+ @active.insert(i, to_add)
+ end
+ imax = to_adds.length + imin - 1
+ check_pair(evts, imin - 1) if (imin > 0)
+ check_pair(evts, imax) if (imax < @active.length - 1)
+ end
+ def check_pair(evts, ival)
+ if (@active[ival].ed != @active[ival + 1].ed && @active[ival].st != @active[ival + 1].st)
+ if (inter = intersect(@active[ival], @active[ival + 1]))
+ evts.add_event(InterEvent.make(inter, @active[ival], @active[ival + 1]))
+ end
+ end
+ end
+ def del_line(evts, to_del)
+ i = 0
+ i += 1 while (i < @active.length && @active[i] != to_del)
+# raise "error" if i == @active.length
+ @active.delete_at(i)
+ return if (i <= 0 || i >= @active.length)
+ check_pair(evts, i - 1)
+ end
+ def inter_lines(evts, line1, line2)
+ i = 0
+ i += 1 while (i < @active.length && @active[i] != line1)
+ return false if i == @active.length
+ return false if @active[i] != line1 || @active[i + 1] != line2
+ @active[i], @active[i + 1] = @active[i + 1], @active[i]
+ check_pair(evts, i - 1) if (i > 0)
+ check_pair(evts, i + 1) if (i + 2 < @active.length)
+ return true
+ end
+ def get_regions(x, yst, yed)
+ #@active.sort! { |a, b|
+ # a.compare_at_x(x, b)
+ #}
+ y_cur = yst
+ regions = []
+ cnt = 0
+ @active.each do |active|
+ n,d = active.get_n_d_at(x)
+ if (d != 0)
+ y_val = n * d**-1
+ else
+ y_val = active.st.y
+ end
+# puts "yval: #{y_val}"
+ regions << [y_cur, y_val, cnt]
+ cnt += active.side
+ y_cur = y_val
+ end
+ regions << [y_cur, yed, cnt]
+ end
+end
+
+
+
+class PointInfo
+ class TwinnedLine
+ attr_accessor :twin, :st, :ed
+ def initialize(st, ed, side) @st, @ed, @side = st, ed, side end
+ def self.make(st, ed, side)
+ ln = self.new(st, ed, side)
+ ltwin = self.new(ed, st, -side)
+ ln.twin = ltwin
+ ltwin.twin = ln
+ return ln
+ end
+ def to_seg()
+ return Shape::Segment.new(@st.pt, @ed.pt)
+ end
+ def rot()
+ @st.rot(self)
+ end
+ def next()
+ @twin.rot()
+ end
+ end
+ def pt() @pt end
+ def initialize(pt)
+ raise "problem" if pt.class == Integer
+ @pt = pt
+ @cons = []
+ @ordered = nil
+ end
+ def do_dedup()
+ @cons.uniq!()
+ end
+ def compare_rot_order(a, b)
+ p1 = x_lex_order(a.ed.pt, @pt) <=> x_lex_order(b.ed.pt, @pt)
+ return p1 if p1 != 0
+ return (a.ed.pt - @pt).cross(b.ed.pt - @pt) <=> 0
+ end
+ def rot_order()
+ @ordered ||= (
+ @cons.sort! { |a, b|
+ compare_rot_order(a, b)
+ };
+ )
+ end
+ def rot(item)
+ rot_order()
+ @cons[(@cons.index(item) - 1) % @cons.length]
+ end
+ def connect_to(ept, side)
+ ln = TwinnedLine.make(self, ept, side)
+ insert_ln(ln)
+ ept.insert_ln(ln.twin)
+ end
+ def insert_ln(ln)
+ @cons << ln
+ end
+ # assuming pt is the lex min, get an outside line.
+ def lex_min_line()
+ rot_order()
+ @cons[0]
+ end
+end
+
+class AddDelEvent
+ def initialize(pt, to_add, to_del)
+ @pt = pt
+ @to_add = to_add
+ @to_del = to_del
+ @to_add.sort! { |a, b|
+ (a.ed - @pt).cross(b.ed - @pt) <=> 0
+ }
+ @point_info = PointInfo.new(@pt)
+ end
+ def <=>(o) x_lex_order(pt, o.pt) end
+ def pt() @pt end
+ def inspect()
+ n = "<#{pt.x}, #{pt.y}"
+ n += ", a:#{@to_add.length}" if @to_add.length > 0
+ n += ", d:#{@to_del.length}" if @to_del.length > 0
+ return n + ">"
+ end
+ def do_event(status, evts)
+ @to_del.each do |to_del|
+ status.del_line(evts, to_del)
+ end
+ status.add_lines(evts, @to_add) if @to_add.length > 0
+ @to_del.each do |to_del|
+ to_del.wrap_up(@point_info)
+ end
+ @to_add.each do |to_add|
+ to_add.last_event = @point_info
+ end
+ end
+ def lex_min_line() @point_info.lex_min_line() end
+ def to_del() @to_del.dup() end
+ def to_add() @to_add.dup() end
+ def merge_in(other)
+ @to_del += other.to_del()
+ @to_add += other.to_add()
+ @to_add.uniq()
+ @to_del.uniq()
+ @to_add.sort! { |a, b|
+ (a.ed - @pt).cross(b.ed - @pt) <=> 0
+ }
+ end
+end
+
+class InterEvent
+ def self.make(pt, item1, item2)
+ its = [item1, item2]
+ AddDelEvent.new(pt, its, its.dup())
+ end
+ def initialize(pt, item1, item2)
+ @pt = pt
+ @item1, @item2 = item1, item2
+ @point_info = PointInfo.new(@pt)
+ end
+ def inspect()
+ n = "<#{pt.x}, #{pt.y}"
+ return n + ">"
+ end
+ def <=>(o) x_lex_order(pt, o.pt) end
+ def pt() @pt end
+ def do_event(status, evts)
+ if status.inter_lines(evts, @item1, @item2)
+ @item1.wrap_up(@point_info)
+ @item1.last_event = @point_info
+ @item2.wrap_up(@point_info)
+ @item2.last_event = @point_info
+ end
+ end
+end
+
+class AddEvent
+ def initialize(to_add)
+ @to_add = to_add
+ end
+ def <=>(o) x_lex_order(pt, o.pt) end
+ def pt() @to_add.st() end
+ def inspect() "a<#{pt.x}, #{pt.y}>" end
+ def do_event(status)
+ status.add_line(@to_add)
+ end
+end
+
+class DelEvent
+ def initialize(to_del)
+ @to_del = to_del
+ end
+ def <=>(o) x_lex_order(pt, o.pt) end
+ def pt() @to_del.ed() end
+ def inspect() "d<#{pt.x}, #{pt.y}>" end
+ def do_event(status)
+ status.del_line(@to_del)
+ end
+end
+
+class LabeledLineList
+ def initialize()
+ @labs = []
+ end
+ attr_accessor :labs
+ def draw_to(cr, scale)
+ @labs.each do |ln|
+ st, ed = ln.st.pt, ln.ed.pt
+ cr.move_to(scale * st.x, -scale * st.y)
+ cr.line_to(scale * ed.x, -scale * ed.y)
+ cr.stroke()
+ end
+ end
+ def add_line(ln)
+ @labs << ln
+ end
+ def to_shape()
+ Shape.new(@labs.collect { |lab| lab.to_seg() })
+ end
+end
+
+class ActiveSweepLine
+ def initialize(evts)
+ @evts = evts
+ @status = Status.new()
+ @past_evts = []
+ end
+ def empty()
+ @evts.empty()
+ end
+ def tick()
+ min = @evts.pop_min()
+ @past_evts << min
+ @evts.event_limit = min
+ puts "processing event: #{min.inspect}"
+ @first_evt ||= min
+ @last_x = min.pt.x
+ min.do_event(@status, @evts)
+ end
+ def last_x()
+ return @last_x if @evts.empty()
+ return @evts.next_event().pt.x
+ end
+ def get_regions(x, st, ed)
+ @status.get_regions(x, st, ed)
+ end
+ def extract_polys()
+ labl = LabeledLineList.new()
+ lno = ln = @first_evt.lex_min_line().twin
+ i = 0
+ while (labl.add_line(ln) ; ln = ln.next(); ln != lno)
+ i += 1
+ break if i == 20
+ end
+
+# @first_evt.extract_polys()
+ return [labl.to_shape(), @past_evts]
+ end
+end
+
+class SweepLineUnion
+ def self.reset_sweep(shapes)
+ evts = EventQueue.new()
+ shapes.each do |shape|
+ shape.add_evts(evts)
+ end
+ evts.flip()
+ return ActiveSweepLine.new(evts)
+ end
+ def self.make_outside_profile(shapes)
+ sweep_line = reset_sweep(shapes)
+ nticks = 0
+ while !sweep_line.empty()
+ sweep_line.tick()
+ nticks += 1
+ end
+ return sweep_line.extract_polys(), nticks
+ end
+ def initialize(shapes)
+ @shapes = shapes
+ @poly, @nticks = self.class.make_outside_profile(shapes)
+ @sweep_line = self.class.reset_sweep(@shapes)
+ @ticki = 0
+ end
+ def n_views()
+ @nticks
+ end
+ CntColors = [Color::White, Color::Red, Color::Blue, Color.new(0.5, 0.5, 0.0), Color::Pink, Color.new(0.0,1.0,0.0)]
+ def draw_to(cr, scale, view_n)
+ if (@ticki > view_n)
+ @sweep_line = self.class.reset_sweep(@shapes)
+ @ticki = 0
+ end
+ while (@ticki < view_n)
+ @ticki += 1
+ @sweep_line.tick()
+ end
+
+ @shapes.each { |shape| shape.draw_to(cr, scale) }
+ cr.set_source_rgb(0.0, 0.5, 1.0)
+ last_x = @sweep_line.last_x
+ yed = -cr.get_clip_rectangle[1].y
+ yst = yed - cr.get_clip_rectangle[1].height
+# puts cr.get_clip_rectangle[1].y
+# puts cr.get_clip_rectangle[1].height
+
+ yst = ((yst + 10) / scale).to_i
+ yed = ((yed - 10) / scale).to_i
+
+ segs = @sweep_line.get_regions(last_x, yst, yed)
+ #puts segs.inspect
+ segs.each do |y1, y2, cnt|
+ CntColors[cnt].set(cr)
+# cr.set_source_rgb(rand(), rand(), rand())
+ cr.move_to(last_x * scale, -y1 * scale)
+ cr.line_to(last_x * scale, -y2 * scale)
+ cr.stroke()
+ end
+
+ #Color::.set(cr)
+ cr.set_source_rgb(0.0, 1.0, 0.5)
+ @poly.draw_to(cr, scale)
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/bool_ops/pairwise_inter.rb b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/pairwise_inter.rb
new file mode 100755
index 0000000..3c447bd
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/pairwise_inter.rb
@@ -0,0 +1,101 @@
+require_relative "predicate.rb"
+
+def lex_order_pt(a, b)
+ return a, b if x_lex_order(a, b) < 0
+ return b, a
+end
+
+def intersect_parallel_filter(a, b)
+ ast, aed = lex_order_pt(a.st, a.ed)
+ bst, bed = lex_order_pt(b.st, b.ed)
+ return nil if x_lex_order(aed, bst) < 0
+ return nil if x_lex_order(bed, ast) < 0
+ return intersect(a, b)
+end
+
+def find_intersect_overlap_dup(ln1, ln2, inters, i, j)
+ ast, aed = lex_order_pt(ln1.st, ln1.ed)
+ bst, bed = lex_order_pt(ln2.st, ln2.ed)
+ return if x_lex_order(aed, bst) < 0
+ return if x_lex_order(bed, ast) < 0
+ return if !test_inter_or_parrallel(ln1, ln2)
+ return if !test_inter_or_parrallel(ln2, ln1)
+
+ a1, b1, c1 = get_sys_eqn(ln1)
+ a2, b2, c2 = get_sys_eqn(ln2)
+ d = a1 * b2 - b1 * a2
+ if d == 0
+ if (x_lex_order(ast, bst) > 0) #ast, aed
+ inters << [bed, i, j]
+ if (x_lex_order(ast, bed) != 0)
+ inters << [ast, i, j]
+ end
+ else # aed, bst
+ inters << [bst, i, j]
+ if (x_lex_order(aed, bst) != 0)
+ inters << [aed, i, j]
+ end
+ end
+ return
+ end
+ xn = c1 * b2 - c2 * b1
+ yn = c2 * a1 - c1 * a2
+
+ inters << [Point2di.new(Rational(xn, d), Rational(yn, d)), i, j]
+end
+
+class AllPairsInter
+ def self.inter(shapes)
+ all_lns = []
+ shapes.each do |shape|
+ all_lns += shape.segs
+ end
+ inters = []
+ 0.upto(all_lns.length - 1) do |i|
+ (i + 1).upto(all_lns.length - 1) do |j|
+ find_intersect_overlap_dup(all_lns[i], all_lns[j], inters, i, j)
+ end
+ end
+ inters.sort! { |a, b| x_lex_order(a[0], b[0]) }
+ prevs = []
+ prev_pt = nil
+ inters_lst = []
+ inters.each do |a, b, c|
+ if (!prev_pt)
+ prevs = []
+ prev_pt = a
+ elsif (x_lex_order(prev_pt, a) != 0)
+ inters_lst << [prev_pt, prevs.uniq()]
+ prevs = []
+ prev_pt = a
+ end
+ prevs << b << c
+ end
+ inters_lst << [prev_pt, prevs.uniq()] if prev_pt
+
+ line_last_inter = []
+ pt_infos = inters_lst.collect do |pt, is|
+ pt_i = PointInfo.new(pt)
+ #puts is.inspect
+ is.each do |i|
+ if line_last_inter[i]
+ line_last_inter[i].connect_to(pt_i, -1)
+ end
+ line_last_inter[i] = pt_i
+ end
+ pt_i
+ end
+ pt_infos.each do |pt_info|
+ pt_info.do_dedup()
+ end
+
+ labl = LabeledLineList.new()
+ lno = ln = pt_infos[0].lex_min_line().twin
+ i = 0
+ while (labl.add_line(ln) ; ln = ln.next(); ln != lno)
+ i += 1
+ end
+
+ return labl.to_shape()
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/bool_ops/predicate.rb b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/predicate.rb
new file mode 100755
index 0000000..93c20e1
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/bool_ops/predicate.rb
@@ -0,0 +1,51 @@
+def x_lex_order(pta, ptb)
+ return pta.x <=> ptb.x if pta.x != ptb.x
+ return pta.y <=> ptb.y
+end
+
+# test that line1 intersects line2
+def test_inter(ln1, ln2)
+ st = ln1.st
+ tst = ln1.ed - st
+ as = tst.cross(ln2.st - st) <=> 0
+ bs = tst.cross(ln2.ed - st) <=> 0
+
+ raise "colinear" if as == 0 && bs == 0
+ return true if as == 0 || bs == 0
+ return true if as != bs
+ return false
+end
+
+# test that line1 intersects line2
+def test_inter_or_parrallel(ln1, ln2)
+ st = ln1.st
+ tst = ln1.ed - st
+ as = tst.cross(ln2.st - st) <=> 0
+ bs = tst.cross(ln2.ed - st) <=> 0
+
+ return true if as == 0 && bs == 0
+ return true if as == 0 || bs == 0
+ return true if as != bs
+ return false
+end
+
+def get_sys_eqn(ln)
+ a = ln.ed.y - ln.st.y
+ b = ln.st.x - ln.ed.x
+ c = ln.st.x * a + ln.st.y * b
+ return a, b, c
+end
+
+def intersect(ln1, ln2)
+ # puts "intersecting: #{ln1} #{ln2}"
+ return nil if !test_inter(ln1, ln2)
+ return nil if !test_inter(ln2, ln1)
+
+ a1, b1, c1 = get_sys_eqn(ln1)
+ a2, b2, c2 = get_sys_eqn(ln2)
+ d = a1 * b2 - b1 * a2
+ raise "problem!" if d == 0
+ xn = c1 * b2 - c2 * b1
+ yn = c2 * a1 - c1 * a2
+ return Point2di.new(Rational(xn, d), Rational(yn, d))
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/2dpoint.rb b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/2dpoint.rb
new file mode 100755
index 0000000..8996429
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/2dpoint.rb
@@ -0,0 +1,53 @@
+#system("bazel build -c opt //geometry/voronoi:test.so")
+
+require "tokens/tokenizer_lib_base.rb"
+#require "geometry/voronoi/test.so"
+class CompGeo
+end
+
+class CompGeo::Point2di
+ init_fields :x, :y
+ def self.make(a, b)
+ CompGeo::Point2di.new(a, b)
+ end
+ def +(o) CompGeo::Point2di.new(@x + o.x, @y + o.y) end
+ def -(o) CompGeo::Point2di.new(@x - o.x, @y - o.y) end
+ def /(o) CompGeo::Point2di.new(@x / o, @y / o) end
+ def *(o) CompGeo::Point2di.new(@x * o, @y * o) end
+ def mag_sqr() return @x * @x + @y * @y end
+ def xf() @x.to_f end
+ def yf() @y.to_f end
+ def to_i_vec() CompGeo::Point2di.new(@x.to_i, @y.to_i) end
+ def round_up_fudge() CompGeo::Point2di.new(@x <=> 0, @y <=> 0) end
+ def dot(o) @x * o.x + @y * o.y end
+# alias :old_cross :cross
+ def cross(o)
+ v = @x * o.y - @y * o.x
+ #v2 = old_cross(o)
+ #raise "problem! #{v} #{v2}" if (v != v2)
+ return v
+ end
+ def ==(o) self.class == o.class && @x == o.x && @y == o.y end
+
+ def dist(o)
+ return ArcPointTime.new((self - o).mag_sqr(), 0)
+ end
+ def rot90()
+ return CompGeo::Point2di.new(-@y, @x)
+ end
+end
+Point2di = CompGeo::Point2di
+
+def integer_sqrt(n)
+ n = n * 4
+ i = n >> (n.bit_length / 2)
+ oi = n
+ k = 0
+ while (oi / 2 != i / 2 && k < 100)
+ oi = i
+ i = (n / i + i) / 2
+ k += 1
+ end
+ raise "integer sqrt did not converge #{n}" if k == 100
+ return i / 2
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/dxf.rb b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/dxf.rb
new file mode 100755
index 0000000..96cbbe7
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/dxf.rb
@@ -0,0 +1,16 @@
+require_relative "dxf_parse.rb"
+require_relative "entities.rb"
+require_relative "profile_utils.rb"
+require_relative "entity_draw.rb"
+require_relative "make_loops.rb"
+require_relative "2dpoint.rb"
+
+
+def full_load_dxf(fname)
+ items = get_entities(fname)
+ items2d = items.collect { |item| item.to_2d() }
+
+ items2d_orient = Oriented.import_2d(items2d)
+ items2d_orient = SweepLine.stitch_curves(items2d_orient)
+ return items2d_orient
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/dxf_parse.rb b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/dxf_parse.rb
new file mode 100755
index 0000000..42b9460
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/dxf_parse.rb
@@ -0,0 +1,248 @@
+class DXFParser
+ class Section
+ attr_accessor :sec_name
+ def initialize(sec_name)
+ @sec_name = sec_name
+ @items = []
+ end
+
+ def print_sec()
+ puts "section #{@sec_name}:"
+ @items.each do |item|
+ puts " #{item.inspect}"
+ end
+ end
+
+ def add_item(item)
+ @items << item
+ end
+
+ def self.make_section(sec_name)
+ return if sec_name != "ENTITIES"
+ return Section.new(sec_name)
+ end
+ end
+ class GroupPair
+ attr_accessor :group_code, :value
+ def initialize(file)
+ @group_code = file.gets().match(/\d+/)[0].to_i
+ @value = file.gets().chomp()
+ if (@group_code == 0)
+ @value.upcase!
+ end
+# rescue
+# raise Exception.new("dxf file does not follow expected format.")
+ end
+ def inspect()
+ "#{@group_code}: #{@value.inspect}"
+ end
+ end
+ def initialize(fname)
+ @file = fname.class == File ? fname : File.open(fname, "r")
+ @seen_eof = false
+ end
+ def get_next()
+ return GroupPair.new(@file)
+ end
+ def expect_next(code)
+ obj = get_next()
+ raise "expected group_code: #{code} not #{obj.inspect}" if obj.group_code != code
+ return obj
+ end
+ def get_section(section_parser = Section)
+ while true
+ return if @seen_eof
+ obj = expect_next(0)
+ if obj.value == "EOF"
+ @seen_eof = true
+ return
+ end
+ raise "expected SECTION\n" if (obj.value != "SECTION")
+ secname = expect_next(2).value
+ secval = section_parser.make_section(secname)
+ while true
+ obj = get_next()
+ if (obj.group_code == 0 && obj.value == "ENDSEC")
+ if secval
+ secval.finished_parsing() if secval.respond_to?(:finished_parsing)
+ return secval
+ end
+ break
+ end
+ secval.add_item(obj) if secval
+ end
+ end
+ end
+ class SectionRep
+ def self.set_section_type(section_type) @section_type = section_type end
+ def self.section_type() @section_type end
+ def self.make_section(section)
+ return self.new()
+ end
+ end
+ class Entities < SectionRep
+ set_section_type "ENTITIES"
+ def initialize()
+ end
+ def add_item(item)
+ if (item.group_code == 0)
+ end
+ end
+ end
+ class EntityParse
+ class Instance
+ def initialize(entity_parse)
+ @entity_parse = entity_parse
+ @items = []
+ end
+ attr_accessor :items
+ def add_item(item)
+ if (item.group_code == 0)
+ @items.push(@last_item) if @last_item
+ @last_item = nil
+ @item_parse = @entity_parse.item_parser(item.value)
+ @last_item = @item_parse.start_item() if @item_parse
+ else
+ @item_parse.handle(item, @last_item) if(@item_parse)
+ end
+ end
+ def print_sec()
+ puts "section ENTITIES:"
+ @items.each do |item|
+ puts item.inspect
+ end
+ end
+ end
+ class ItemParser
+ def initialize(item_type) @item_type = item_type
+ @handlers = {}
+ end
+ def add(group_code, &parse_block)
+ @handlers[group_code] = parse_block
+ end
+ def handle(item, obj)
+ handler = @handlers[item.group_code]
+ if (handler)
+ handler.call(obj, item.value)
+ elsif obj.respond_to?(:extras)
+ obj.extras(item)
+ end
+ end
+ def start_item()
+ return @item_type.new()
+ end
+ end
+ def section_type() return "ENTITIES" end
+ def initialize()
+ @item_parsers = {}
+ end
+ def add_item_type(key, parser)
+ @item_parsers[key] = ItemParser.new(parser)
+ end
+ def item_parser(key)
+ return @item_parsers[key]
+ end
+ def make_section(section)
+ return Instance.new(self)
+ end
+ end
+ class DelagateParser
+ def initialize(parser_list)
+ @per_section = {}
+ parser_list.each do |parser|
+ @per_section[parser.section_type()] = parser
+ end
+ end
+ def make_section(section)
+ return if !(parse = @per_section[section])
+ return parse.make_section(section)
+ end
+ end
+ JustEntities = DelagateParser.new([Entities])
+end
+
+class Point3d
+ attr_accessor :x, :y, :z
+ def initialize(x, y, z) @x, @y, @z = x, y, z end
+ def inspect() "<#{@x}, #{@y}, #{@z}>" end
+ def project() Point2d.new(@x, @y) end
+end
+class Line
+ attr_accessor :st, :ed
+ def initialize()
+ @st = Point3d.new(0.0, 0.0, 0.0)
+ @ed = Point3d.new(0.0, 0.0, 0.0)
+ end
+ def draw_to(cr, scale)
+ cr.move_to(@st.x * scale, - @st.y * scale)
+ cr.line_to(@ed.x * scale, - @ed.y * scale)
+ cr.stroke()
+ end
+ def corners() return [@st.project, @ed.project] end
+ def to_2d() return Line2d.new(@st.project, @ed.project) end
+end
+class Arc
+ attr_accessor :c, :r, :st, :ed
+ def initialize()
+ @c = Point3d.new(0.0, 0.0, 0.0)
+ end
+ def draw_to(cr, scale)
+ cr.arc(@c.x * scale, - @c.y * scale, @r * scale, -@ed * Math::PI / 180.0, -@st * Math::PI / 180.0)
+ cr.stroke()
+ end
+ def corners()
+ c = @c.project()
+ st_theta = @st * Math::PI / 180.0
+ ed_theta = @ed * Math::PI / 180.0
+ [
+ c + Point2d.new(@r * Math.cos(st_theta), @r * Math.sin(st_theta)),
+ c + Point2d.new(@r * Math.cos(ed_theta), @r * Math.sin(ed_theta)),
+ ]
+ end
+ def to_2d() return Arc2d.new(@c.project, @r, @st / 180.0 * Math::PI, @ed / 180.0 * Math::PI) end
+end
+class Circle
+ attr_accessor :c, :r
+ def initialize()
+ @c = Point3d.new(0.0, 0.0, 0.0)
+ end
+ def draw_to(cr, scale)
+ cr.arc(@c.x * scale, - @c.y * scale, @r * scale, 0, Math::PI * 2)
+ cr.stroke()
+ end
+ def corners() return [] end
+ def to_2d() return CircleProfile2d.new(@c.project, @r) end
+end
+def get_entities(filename)
+ entity_parse = DXFParser::EntityParse.new()
+ line_parse = entity_parse.add_item_type("LINE", Line)
+ line_parse.add(10) { |l, v| l.st.x = v.to_f}
+ line_parse.add(20) { |l, v| l.st.y = v.to_f}
+ line_parse.add(30) { |l, v| l.st.z = v.to_f}
+ line_parse.add(11) { |l, v| l.ed.x = v.to_f}
+ line_parse.add(21) { |l, v| l.ed.y = v.to_f}
+ line_parse.add(31) { |l, v| l.ed.z = v.to_f}
+
+ line_parse = entity_parse.add_item_type("ARC", Arc)
+ line_parse.add(10) { |l, v| l.c.x = v.to_f}
+ line_parse.add(20) { |l, v| l.c.y = v.to_f}
+ line_parse.add(30) { |l, v| l.c.z = v.to_f}
+ line_parse.add(40) { |l, v| l.r = v.to_f}
+ line_parse.add(50) { |l, v| l.st = v.to_f}
+ line_parse.add(51) { |l, v| l.ed = v.to_f}
+
+ line_parse = entity_parse.add_item_type("CIRCLE", Circle)
+ line_parse.add(10) { |l, v| l.c.x = v.to_f}
+ line_parse.add(20) { |l, v| l.c.y = v.to_f}
+ line_parse.add(30) { |l, v| l.c.z = v.to_f}
+ line_parse.add(40) { |l, v| l.r = v.to_f}
+
+ parser = DXFParser::DelagateParser.new([entity_parse])
+ dxf_file = DXFParser.new(filename)
+ #dxf_file = DXFParser.new("flat_dxf.dxf")
+
+ sec = dxf_file.get_section(parser)
+
+ raise "error! two entity sections" if dxf_file.get_section(parser) != nil
+ return sec.items
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/entities.rb b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/entities.rb
new file mode 100755
index 0000000..e130d0f
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/entities.rb
@@ -0,0 +1,292 @@
+require 'set'
+require_relative "../point2d.rb"
+
+class Line2d
+ init_fields :st, :ed
+end
+
+class Arc2d
+ init_fields :c, :r, :sta, :eda
+ def pt_for(a) @c + Point2d.new(@r * Math.cos(a), @r * Math.sin(a)) end
+ def st() pt_for(@sta) end
+ def ed() pt_for(@eda) end
+end
+
+class CircleProfile2d
+ init_fields :c, :r
+ def prepare_sweepline()
+ end
+end
+
+
+class LoopProfile2d
+ def initialize(items)
+ @items = items
+ end
+end
+
+class PtRef
+ init_fields :pt, :obj, :key
+ attr_accessor :refpair, :pair
+ def inspect() "#{@pt.inspect}" end
+ def comp_v(o) @pt.comp_v(o.pt) end
+ def comp_h(o) @pt.comp_h(o.pt) end
+ def self.add_pair(item, k, pts)
+ pts << a = PtRef.new(item.st, k, pts)
+ pts << b = PtRef.new(item.ed, item, k)
+ a.refpair = b
+ b.refpair = a
+ end
+ def self.add_twin_pair(item, k, pts)
+ pts << a = PtRef.new(item.st, item, k)
+ pts << b = PtRef.new(item.twin.st, item.twin, k)
+ a.refpair = b
+ b.refpair = a
+ end
+end
+
+def point_ref_cluster_x(pts, broken, tolerance)
+ return if (pts.length == 0)
+ pts.sort!() { |a, b| a.comp_h(b) }
+
+ broken_pts = []
+ old_pt_pt_x = pts[0].pt.x
+
+ pts.each do |pt|
+ if (pt.pt.x - old_pt_pt_x > tolerance)
+ broken << broken_pts
+ broken_pts = []
+ end
+ broken_pts << pt
+ old_pt_pt_x = pt.pt.x
+ end
+ broken << broken_pts
+end
+
+def point_ref_cluster_y(pts, broken, tolerance)
+ return if (pts.length == 0)
+ pts.sort!() { |a, b| a.comp_v(b) }
+
+ broken_pts = []
+ old_pt_pt_y = pts[0].pt.y
+
+ pts.each do |pt|
+ if (pt.pt.y - old_pt_pt_y > tolerance)
+ broken << broken_pts
+ broken_pts = []
+ end
+ broken_pts << pt
+ old_pt_pt_y = pt.pt.y
+ end
+ broken << broken_pts
+end
+
+
+def clock_wise_cluster(a, b)
+ #puts " --- #{a.inspect} --- #{b.inspect} --- "
+ if a.obj.st.comp_v(a.obj.ed) != a.obj.st.comp_v(b.obj.ed)
+ flip = a.obj.ed.comp_v(b.obj.ed)
+ else
+# puts orient2d(a.obj.st, a.obj.ed, b.obj.ed)
+ end
+ return flip > 0 ? b : a
+end
+
+def point_ref_cluster(pts, tolerance)
+ pts = [pts]
+ broken = []
+ pts.each do |segment|
+ point_ref_cluster_x(segment, broken, tolerance)
+ end
+ pts = broken
+ broken = []
+ pts.each do |segment|
+ point_ref_cluster_y(segment, broken, tolerance)
+ end
+ return broken
+end
+
+def point_ref_cluster_and_validate(pts, tolerance)
+ clusters = point_ref_cluster(pts, tolerance)
+
+ clusters.each do |cluster|
+ if (cluster.length > 2)
+ raise "profile features too fine for tolerance = #{tolerance}"
+ end
+ if (cluster.length < 2)
+ #raise "sketch has open contours..."
+ cluster[0].pair = cluster[0]
+ else
+ cluster[0].pair = cluster[1]
+ cluster[1].pair = cluster[0]
+ end
+ #cluster[0].pair = cluster[1]
+ #cluster[1].pair = cluster[0]
+ end
+ clusters.sort! { |a, b|
+ a[0].comp_h(b[0])
+ }
+
+ return clusters
+end
+
+def join_entities_into_loop_graph(item_list, tolerance)
+ profiles = []
+ pts = []
+
+ k = 0
+ item_list.each do |item|
+ if (item.respond_to?(:st))
+ PtRef.add_pair(item, k, pts)
+ k += 1
+ else
+ profiles << item
+ end
+ end
+
+ clusters = point_ref_cluster_and_validate(pts, tolerance)
+
+ visited = []
+
+ pts.each do |ptref|
+ if (!visited[ptref.key])
+ sub_profile = []
+ while !visited[ptref.key]
+ visited[ptref.key] = true
+ sub_profile << ptref
+ ptref = ptref.pair.refpair
+ end
+ profiles << LoopProfile2d.new(sub_profile)
+ end
+ end
+
+ return profiles
+end
+
+require 'algorithms'
+require "containers/priority_queue.rb"
+
+class ForkEvent
+ attr_accessor :profile_id
+ init_fields :pt, :obj1, :obj2
+ def draw_to(cr, scale)
+ cr.set_source_rgb(0.0, 1.0, 0.0)
+ cr.arc(@pt.x * scale, - @pt.y * scale, 0.05 * scale, 0, 6.25)
+ cr.fill()
+ end
+end
+
+class MergeEvent
+ attr_accessor :profile_id
+ init_fields :pt, :obj1, :obj2
+ def draw_to(cr, scale)
+ cr.set_source_rgb(1.0, 0.0, 0.0)
+ cr.arc(@pt.x * scale, - @pt.y * scale, 0.05 * scale, 0, 6.25)
+ cr.fill()
+ end
+end
+
+class ChainEvent
+ attr_accessor :profile_id
+ init_fields :pt, :obj_in, :obj_out
+ def draw_to(cr, scale)
+ cr.set_source_rgb(1.0, 0.0, 0.0)
+ cr.arc(@pt.x * scale, - @pt.y * scale, 0.05 * scale, 0, 6.25)
+ cr.fill()
+ end
+end
+
+class EventObj
+ def fork_event(other) ForkEvent.new(st(), self, other) end
+ def merge_event(other) MergeEvent.new(ed(), self, other) end
+ def st_chain_event(other) ChainEvent.new(st(), self, other) end
+ def ed_chain_event(other) ChainEvent.new(ed(), other, self) end
+end
+
+class UpperArc < EventObj
+ init_fields :sta, :eda, :c, :r
+ def pt_for(a) @c + Point2d.new(@r * Math.cos(a), @r * Math.sin(a)) end
+ def st() pt_for(@sta) end
+ def ed() pt_for(@eda) end
+end
+
+class LowerArc < EventObj
+ init_fields :sta, :eda, :c, :r
+ def pt_for(a) @c + Point2d.new(@r * Math.cos(a), @r * Math.sin(a)) end
+ def st() pt_for(@sta) end
+ def ed() pt_for(@eda) end
+end
+
+class LinePt < EventObj
+ init_fields :st, :ed
+end
+
+module Math; PI2 = Math::PI * 2; end
+
+class Arc2d
+ def get_add_events_to(sweep, i)
+ if (@sta < Math::PI && @eda > Math::PI)
+ larc = UpperArc.new(Math::PI, @sta, @c, @r)
+ uarc = LowerArc.new(-Math::PI, @eda - Math::PI2, @c, @r)
+ puts "arc2d: #{@sta / Math::PI} #{@eda / Math::PI}"
+
+ sweep.add_event(larc.fork_event(uarc), i)
+ end
+ if (@eda < @sta && @sta != 0 && @eda != 0)
+ larc = UpperArc.new(@sta, 0, @c, @r)
+ uarc = LowerArc.new(@eda - Math::PI2, 0, @c, @r)
+ puts "arc2d: #{@sta / Math::PI} #{@eda / Math::PI}"
+
+ sweep.add_event(larc.merge_event(uarc), i)
+ end
+ end
+end
+
+class Line2d
+ def get_add_events_to(sweep, i)
+ if (@st.comp_h(@ed) < 0)
+ sweep.add_event(ChainEvent.new(@st, self, nil), i)
+ else
+ sweep.add_event(ChainEvent.new(@ed, self, nil), i)
+ end
+ end
+end
+
+class LoopProfile2d
+ def add_events_to(sweep, i)
+ @items.each do |item|
+ item.obj.get_add_events_to(sweep, i)
+ end
+ end
+end
+
+class CircleProfile2d
+ def add_events_to(sweep, i)
+ obju = UpperArc.new(Math::PI, 0, @c, @r)
+ objl = LowerArc.new(-Math::PI, 0, @c, @r)
+ sweep.add_event(obju.fork_event(objl), i)
+ sweep.add_event(obju.merge_event(objl), i)
+ end
+end
+
+class SweepLineConnectivity
+ class ArcSweepLinePoint
+
+ end
+ attr_accessor :events
+ def initialize()
+ @profile_ids = []
+ @events = []
+ end
+ def add_event(evt, i)
+ evt.profile_id = i
+ @events << evt
+ end
+ def get_lex_min_profile()
+ @events.sort! { |a, b| a.pt.comp_h(b.pt) }
+ @profile_ids[@events[0].profile_id]
+ end
+ def set_profile_id(i, profile)
+ @profile_ids[i] = profile
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/entity_draw.rb b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/entity_draw.rb
new file mode 100755
index 0000000..4187615
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/entity_draw.rb
@@ -0,0 +1,102 @@
+require_relative "entities.rb"
+class TwinableProfile
+ def draw_to(cr, scale)
+ @items.each do |item|
+ cr.set_source_rgb(rand(), rand(), rand())
+ item.seg.draw_to(cr, scale)
+ end
+ end
+ def draw_path_to(cr, scale)
+ draw_to(cr, scale)
+ return;
+ # @items[0].draw_start_path(cr, scale)
+ @items.each do |item|
+ item.draw_path_to(cr, scale)
+ end
+ cr.stroke()
+ end
+ def draw_direction_to(cr, scale)
+ @items.each do |item|
+ item.draw_direction_to(cr, scale)
+ end
+ end
+end
+
+class SegmentRef
+ class ReversedSegRef
+ def draw_to(cr, scale)
+ @seg.draw_to(cr, scale)
+ draw_direction_to(cr, scale)
+ end
+ def draw_start_path(cr, scale)
+ @seg.draw_rev_start_path(cr, scale)
+ end
+
+ def draw_path_to(cr, scale)
+ @seg.draw_rev_path_to(cr, scale)
+ end
+ def draw_direction_to(cr, scale)
+ mp, tang = @seg.midpoint_and_tangent()
+ # cr.set_source_rgb(0, 1, 1)
+ tang = tang.scale(-0.05)
+ arrt = 0.5
+ cr.move_to((mp.x + tang.x) * scale, -(mp.y + tang.y) * scale)
+ cr.line_to((mp.x - arrt * tang.y) * scale, -(mp.y + arrt * tang.x) * scale)
+ cr.line_to((mp.x + arrt * tang.y) * scale, -(mp.y - arrt * tang.x) * scale)
+ cr.line_to((mp.x + tang.x) * scale, -(mp.y + tang.y) * scale)
+ cr.fill()
+ end
+ end
+ def draw_path_to(cr, scale)
+ @seg.draw_path_to(cr, scale)
+ end
+ def draw_start_path(cr, scale)
+ @seg.draw_start_path(cr, scale)
+ end
+ def draw_to(cr, scale)
+ @seg.draw_to(cr, scale)
+ draw_direction_to(cr, scale)
+ end
+ def draw_direction_to(cr, scale)
+ mp, tang = @seg.midpoint_and_tangent()
+# cr.set_source_rgb(0, 1, 1)
+ tang = tang.scale(0.05)
+ arrt = 0.5
+ cr.move_to((mp.x + tang.x) * scale, -(mp.y + tang.y) * scale)
+ cr.line_to((mp.x - arrt * tang.y) * scale, -(mp.y + arrt * tang.x) * scale)
+ cr.line_to((mp.x + arrt * tang.y) * scale, -(mp.y - arrt * tang.x) * scale)
+ cr.line_to((mp.x + tang.x) * scale, -(mp.y + tang.y) * scale)
+ cr.fill()
+ end
+end
+
+class QuadrentArc
+ def draw_to(cr, scale)
+# puts "arclen: #{@eda - @sta} "
+ cr.arc(@c.x * scale, - @c.y * scale, @r * scale, -@eda, -@sta)
+ cr.stroke()
+ end
+ def draw_path_to(cr, scale)
+ cr.arc_negative(@c.x * scale, - @c.y * scale, @r * scale, -@sta, -@eda)
+ #cr.stroke()
+ end
+ def draw_rev_path_to(cr, scale)
+ cr.arc(@c.x * scale, - @c.y * scale, @r * scale, -@eda, -@sta)
+ end
+end
+
+class Line2d
+ def draw_to(cr, scale)
+ cr.move_to(@st.x * scale, - @st.y * scale)
+ cr.line_to(@ed.x * scale, - @ed.y * scale)
+ cr.stroke()
+ end
+ def draw_path_to(cr, scale)
+ #cr.move_to(@st.x * scale, - @st.y * scale)
+ cr.line_to(@ed.x * scale, - @ed.y * scale)
+ end
+ def draw_rev_path_to(cr, scale)
+ #cr.move_to(@ed.x * scale, - @ed.y * scale)
+ cr.line_to(@st.x * scale, - @st.y * scale)
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/make_loops.rb b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/make_loops.rb
new file mode 100755
index 0000000..defccc8
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/make_loops.rb
@@ -0,0 +1,168 @@
+module SweepLine
+ def self.order_st_ed(items)
+ items.collect { |item|
+ if (item.st().comp_h(item.ed()) > 0)
+ item.twin()
+ else
+ item
+ end
+ }
+ end
+ def self.stitch_curves(items, tolerance = 0.00001)
+ items_out = []
+ pts = []
+ items.each.with_index { |item, k| PtRef.add_twin_pair(item, k, pts) }
+
+ clusters = point_ref_cluster_and_validate(pts, tolerance)
+
+ visited = []
+ clusters.each do |a, b|
+ ptref = a
+ if (!visited[ptref.key])
+ ptref = clock_wise_cluster(a, b)
+ sub_profile_a = []
+ sub_profile_b = []
+ while !visited[ptref.key]
+ visited[ptref.key] = true
+ sub_profile_a << ptref.obj
+ sub_profile_b << ptref.obj.twin
+ ptref = ptref.pair.refpair
+ end
+ sub_profile_a.reverse!
+ items_out << TwinableProfile.make_pair(sub_profile_a, sub_profile_b)
+ #profiles << LoopProfile2d.new(sub_profile)
+ end
+ end
+ return items_out
+ end
+end
+
+class TwinableProfile
+ def self.make_pair(a, b)
+ a, b = self.new(a), self.new(b)
+ a.twin = b
+ b.twin = a
+ return a
+ end
+ attr_accessor :twin, :items
+ def initialize(items)
+ @items = items
+ end
+end
+
+module ProfileOffset
+ def self.add_if_is_tangent(a, b)
+ tana = a.tangent_at(a.eda)
+ tanb = b.tangent_at(b.sta)
+ return nil if (tana.area(tanb) > -1e-6 && tana.dot(tanb) > 0.0)
+ puts "#{tana.inspect} #{tanb.inspect} = #{tana.area(tanb)}"
+
+ sta = Math.atan2(-tana.x, tana.y)
+ eda = Math.atan2(-tanb.x, tanb.y)
+ join_arcs = []
+ sta += Q4 if (sta < 0)
+ eda += Q4 if (eda < 0)
+ Arc2d.new(a.ed, 0, sta, eda).to_oriented(join_arcs)
+ return join_arcs.collect { |a| SegmentRef.make_pair(a) }
+ end
+ def self.offset(profile, dist)
+ sub_profile_a = []
+ sub_profile_b = []
+ items = profile.items
+ items.each.with_index do |item, i|
+ if (join_arcs = add_if_is_tangent(items[i - 1], items[i]))
+ join_arcs.each do |join_arc|
+ join_arc = join_arc.offset(dist)
+ #dist -= 0.025
+ sub_profile_a << join_arc
+ sub_profile_b << join_arc.twin
+ end
+ end
+ item_off = item.offset(dist)
+ dist -= 0.025
+ sub_profile_a << item_off
+ sub_profile_b << item_off.twin
+ end
+ sub_profile_b.reverse!
+ return TwinableProfile.make_pair(sub_profile_a, sub_profile_b)
+ end
+ def self.prepare_for_offset(profile)
+ sub_profile_a = []
+ sub_profile_b = []
+ items = profile.items
+ items.each.with_index do |item, i|
+ if (join_arcs = add_if_is_tangent(items[i - 1], items[i]))
+ join_arcs.each do |join_arc|
+ sub_profile_a << join_arc
+ sub_profile_b << join_arc.twin
+ end
+ end
+ sub_profile_a << item
+ sub_profile_b << item.twin
+ end
+ sub_profile_b.reverse!
+ return TwinableProfile.make_pair(sub_profile_a, sub_profile_b)
+ end
+ def self.just_offset(profile, dist)
+ sub_profile_a = []
+ sub_profile_b = []
+ items = profile.items
+ items.each.with_index do |item, i|
+ item_off = item.offset(dist)
+ sub_profile_a << item_off
+ sub_profile_b << item_off.twin
+ end
+ sub_profile_b.reverse!
+ return TwinableProfile.make_pair(sub_profile_a, sub_profile_b)
+ end
+ def self.neighbor_curves(profile)
+ #profile = prepare_for_offset(profile)
+ #profile = just_offset(profile, 0.05)
+ a, dbg = VoronoiDiagramConstructor.neighbor_curves(profile)
+ return dbg
+ end
+end
+
+class SegmentRef
+ class ReversedSegRef
+ def offset(dist)
+ return @seg.offset(-dist).twin
+ end
+ end
+ def offset(dist)
+ return @seg.offset(dist)
+ end
+end
+
+class QuadrentArc
+ def offset(dist)
+ q = QuadrentArc.new()
+ q.c, q.r, q.sta, q.eda = @c, @r + dist, @sta, @eda
+
+ if q.r < 0
+ if (q.sta < Q2)
+ q.sta += Q2
+ q.eda += Q2
+ else
+ q.sta -= Q2
+ q.eda -= Q2
+ end
+ q.r = -q.r
+ puts "I was flipped"
+ end
+ return SegmentRef.make_pair(q)
+ end
+end
+
+class Line2d
+ def offset(dist)
+ ptoff = (@st - @ed).rot90.normalize().scale(dist)
+ return SegmentRef.make_pair(Line2d.new(@st + ptoff, @ed + ptoff))
+ end
+end
+
+class OffsetGenerator
+ def initialize()
+
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/profile_utils.rb b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/profile_utils.rb
new file mode 100755
index 0000000..ac66386
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/profile_utils.rb
@@ -0,0 +1,136 @@
+require_relative "entities.rb"
+
+class SegmentRef
+ class ReversedSegRef
+ attr_accessor :twin, :seg
+ def initialize(seg)
+ @seg = seg
+ end
+ def tangent_at(a) -@seg.tangent_at(a) end
+ def st() @seg.ed end
+ def ed() @seg.st end
+ def sta() @seg.eda end
+ def eda() @seg.sta end
+ def is_twin() true end
+ end
+ attr_accessor :twin, :seg
+ def initialize(seg)
+ @seg = seg
+ end
+ def tangent_at(a) @seg.tangent_at(a) end
+ def sta() @seg.sta end
+ def eda() @seg.eda end
+ def st() @seg.st end
+ def ed() @seg.ed end
+ def is_twin() false end
+ def self.make_pair(seg)
+ ref = SegmentRef.new(seg)
+ tref = ReversedSegRef.new(seg)
+ ref.twin = tref
+ tref.twin = ref
+ return ref
+ end
+end
+
+class Oriented
+ def self.import_2d(items2d)
+ all_items = []
+ items2d.each do |item|
+ item.to_oriented(all_items)
+ end
+ return all_items.collect { |item| SegmentRef.make_pair(item) }
+ end
+end
+
+class QuadrentArc
+ attr_accessor :c, :r, :sta, :eda
+ def self.make(c, r, sta, eda)
+ q = QuadrentArc.new()
+ q.c, q.r, q.sta, q.eda = c, r, sta, eda
+ return q
+ end
+ def pt_for(a) @c + Point2d.new(@r * Math.cos(a), @r * Math.sin(a)) end
+ def tangent_at(a)
+ return Point2d.new(-Math.sin(a), Math.cos(a))
+ end
+ def midpoint_and_tangent()
+ a = (@sta + @eda) / 2
+ return [pt_for(a), tangent_at(a)]
+ end
+ def st() pt_for(@sta) end
+ def ed() pt_for(@eda) end
+end
+
+class Line2d
+ def midpoint_and_tangent()
+ return (@st + @ed).scale(0.5), (@ed - @st).normalize()
+ end
+ def sta() return 0.0 end
+ def eda() return 1.0 end
+ def tangent_at(a)
+ return (@ed - @st).normalize()
+ end
+end
+
+Q0 = 0
+Q1 = Math::PI / 2
+Q2 = 2 * Q1
+Q3 = 3 * Q1
+Q4 = 4 * Q1
+
+class Arc2d
+ def emit_arcs(st, ed, output)
+# puts "emitting arcs: #{st} -> #{ed}"
+ return if st == ed
+
+=begin
+ 4.times do |i|
+ iSt = i * Q1
+ iEd = (i + 1) * Q1
+ if (st < iEd && ed >= iSt)
+ ast = st < iSt ? iSt : st
+ aed = ed < iEd ? ed : iEd
+ if (aed > ast)
+ output.push QuadrentArc.make(@c, @r, ast, aed)
+ end
+ end
+ end
+=end
+
+ end
+ def to_oriented(output)
+ ed, st = @eda, @sta
+ ed += Math::PI * 2 if (ed < st)
+ dt = ((ed - st) / Q1).ceil().to_i
+ dx = (ed - st) / dt
+ ast = st
+ dt.times do
+ aed = ast + dx
+ output.push QuadrentArc.make(@c, @r, ast, aed)
+ ast = aed
+ end
+ return
+ if (@sta > @eda)
+# puts "wrapped arcs: #{@sta} -> #{@eda}"
+ emit_arcs(Q0, @eda, output)
+ emit_arcs(@sta, Q4, output)
+ else
+ emit_arcs(@sta, @eda, output)
+ end
+ end
+end
+
+class CircleProfile2d
+ def to_oriented(output)
+ output.push QuadrentArc.make(@c, @r, Q0, Q1)
+ output.push QuadrentArc.make(@c, @r, Q1, Q2)
+ output.push QuadrentArc.make(@c, @r, Q2, Q3)
+ output.push QuadrentArc.make(@c, @r, Q3, Q4)
+ end
+end
+
+class Line2d
+ def to_oriented(output)
+ output.push self
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/timed_view.rb b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/timed_view.rb
new file mode 100755
index 0000000..7b8f442
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/dxf_to_gcode/timed_view.rb
@@ -0,0 +1,83 @@
+require "gtk3"
+
+require "cairo"
+require "gui/gui_lib/ev_window.rb"
+require "gui/gtk_utils/pango_inspect.rb"
+require "gui/gtk_utils/clipping.rb"
+require "tokens/tokenizer_lib_base.rb"
+
+class TimeSeriesViewer
+ class Point2d
+ init_fields :x, :y
+ def -(o) Point2d.new(@x - o.x, @y - o.y) end
+ def +(o) Point2d.new(@x + o.x, @y + o.y) end
+ end
+ def set_scale(scale) @scale = scale end
+ def initialize()
+ @scale = 1.0
+ @xoff = 200
+ @yoff = 200
+ end
+ def draw_fn(&blk)
+ @draw_fn = blk
+ end
+ def resize(bbox)
+ @bbox = bbox
+ end
+ def scroll_event(ev)
+# puts "#{ev.x} #{ev.y}"
+ #ev.x = @xoff + xcur * @scale
+ #ev.y = @yoff - ycur * @scale
+ xcur = (ev.x - @xoff) / @scale
+ ycur = (@yoff - ev.y) / @scale
+ if (ev.direction == Gdk::ScrollDirection::DOWN)
+ @scale *= 1.1
+ elsif (ev.direction == Gdk::ScrollDirection::UP)
+ @scale /= 1.1
+ end
+ @xoff = ev.x - xcur * @scale
+ @yoff = ev.y + ycur * @scale
+ end
+ def click_fn(&blk) @click_fn = blk end
+ def drag_fn(&blk) @drag_fn = blk end
+ def key_press_fn(&blk) @key_press_fn = blk end
+ def key_release_fn(&blk) @key_release_fn = blk end
+ def to_model(ev)
+ xcur = (ev.x - @xoff) / @scale
+ ycur = (@yoff - ev.y) / @scale
+ return Point2d.new(xcur, ycur)
+ end
+ def button_press_event(ev)
+ if (ev.button == 1)
+ @click_point = Point2d.new(ev.x, ev.y)
+ @click_fn.call(ev, to_model(ev)) if @click_fn
+ end
+ end
+ def button_release_event(ev)
+ @click_point = nil if (ev.button == 1)
+ end
+ def motion_notify_event(ev)
+ if @click_point
+ if !(@drag_fn && @drag_fn.call(to_model(ev)))
+ cp = Point2d.new(ev.x, ev.y)
+ delta = cp - @click_point
+ @click_point = cp
+ @xoff += delta.x
+ @yoff += delta.y
+ end
+ end
+ end
+ def key_press(ev)
+ @key_press_fn.call(ev) if @key_press_fn
+ end
+ def key_release(ev)
+ @key_release_fn.call(ev) if @key_release_fn
+ end
+ def draw(cr)
+ cr.set_source_rgb(0.0,0.1,0.1)
+ cr.paint()
+ cr.translate(@xoff, @yoff)
+ cr.set_source_rgb(0.0,1.0,0.1)
+ @draw_fn.call(cr, @scale) if (@draw_fn)
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/gcode/gcode_file_format.rb b/manufacturing/TravisProg1/lib_files/geometry/gcode/gcode_file_format.rb
new file mode 100755
index 0000000..59e36bf
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/gcode/gcode_file_format.rb
@@ -0,0 +1,157 @@
+require "tokens/tokenizer_lib_base.rb"
+
+require_relative "simulate.rb"
+
+class GcodeInterpret
+ attr_accessor :stpt
+ attr_accessor :spindle_speed, :st_h
+ def initialize()
+ @cmds = []
+ end
+ def add_cmd(cmd)
+ @cmds << cmd
+ end
+end
+class UnparsedGcode
+ def initialize(view)
+ @view = view
+ end
+ def inspect() "<UnparsedGcode#{self.object_id}>" end
+end
+
+def expect_view_line(view, exp_line)
+ view.st += 1 while view[0].class == CommentLine
+ if !view[0].match_gcode(exp_line)
+ raise "unexpected: #{exp_line.inspect} vs #{view[0].inspect}"
+ end
+ view.st += 1
+end
+
+def expect_footer_view_line(view, exp_line)
+ view.ed -= 1 while view[-1].class == CommentLine
+ if !view[-1].match_gcode(exp_line)
+ raise "unexpected: #{exp_line.inspect} vs #{view[-1].inspect}"
+ end
+ view.ed -= 1
+end
+
+def expect_not_comment_lines(view, lines)
+ lines.split(/\n/).each do |ln|
+ expline = parse_gcode_line(ln)
+ expect_view_line(view, expline)
+ end
+end
+
+def expect_footer_not_comment_lines(view, lines)
+ lines.split(/\n/).reverse.each do |ln|
+ expline = parse_gcode_line(ln)
+ expect_footer_view_line(view, expline)
+ end
+end
+
+def get_spindle_speed(view)
+ view.st += 1 while view[0].class == CommentLine
+ words = view[0].words
+ raise "expected spindle speed, got: #{view[0].inspect}" if (words.length != 2 ||
+ words[0].code != "S" || words[1].code != "M" || words[1].number != "3")
+ view.st += 1
+ return words[0].number
+end
+
+def match_header(view)
+ expect_not_comment_lines(view, <<END1)
+%
+G90 G94 G17 G91.1
+G64 P0.001 Q0.001
+G20
+G53 G0 Z0.
+M9
+T1 M6
+END1
+# S19638 M3
+ speed = get_spindle_speed(view)
+expect_not_comment_lines(view, <<END1)
+G4 P10.
+G54
+M7
+END1
+ln = view[0]
+if (ln.words.length != 3 || ln.words[0].code != "G" ||
+ ln.words[0].number != "0" || ln.words[1].code != "X" ||
+ ln.words[2].code != "Y")
+ raise "expecting starting point. got #{ln.inspect} "
+end
+stx = ln.words[1].number
+sty = ln.words[2].number
+st_pt = Gcode::XYPoint.new(stx.to_f, sty.to_f)
+
+view.st += 1
+ expect_not_comment_lines(view, <<END1)
+G43 Z0.6 H1
+END1
+ln = view[0]
+if (ln.words.length != 2 || ln.words[0].code != "G" ||
+ ln.words[0].number != "0" || ln.words[1].code != "Z")
+ raise "expecting starting height. got #{ln.inspect} "
+end
+st_h = ln.words[1].number
+
+view.st += 1
+#G0 Z#z_height
+return st_pt, speed, st_h
+end
+
+def match_footer(view)
+# expect_footer_not_comment_lines(view, <<END1)
+#M9
+#END1
+
+expect_footer_not_comment_lines(view, <<END1)
+M30
+%
+END1
+ln = view[-1]
+if (ln.words.length == 2)
+ expline = parse_gcode_line("G53 Z0.")
+ expect_footer_view_line(view, expline)
+else
+ expline = parse_gcode_line("G53 G0 Z0.")
+ expect_footer_view_line(view, expline)
+end
+
+end
+
+def extract_lines(view, interpret, state)
+ view.lines do |ln|
+ begin
+ if ln.class != CommentLine
+ line_cmd, state = state.emit_state_update(ln)
+ if (line_cmd)
+ interpret.add_cmd(line_cmd)
+ end
+ end
+ rescue Exception => e
+ $stderr.puts("Parse problem #{ln.filename}:#{ln.line_number}: #{ln.emit()}")
+ raise e
+ end
+ end
+end
+
+def interpret_gcode(gcode_file, do_not_validate = false)
+ interpret = GcodeInterpret.new()
+ view = GcodeFileView.new(gcode_file)
+ st_pt, speed, st_h = match_header(view)
+ match_footer(view)
+
+ interpret.stpt = st_pt
+ interpret.st_h = st_h
+ interpret.spindle_speed = speed
+
+ if (do_not_validate)
+ interpret.add_cmd(UnparsedGcode.new(view))
+ else
+ state = GcodeState.init_defaults(st_pt)
+ extract_lines(view, interpret, state)
+ end
+ return interpret
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/gcode/gcode_layout.rb b/manufacturing/TravisProg1/lib_files/geometry/gcode/gcode_layout.rb
new file mode 100755
index 0000000..da9f48f
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/gcode/gcode_layout.rb
@@ -0,0 +1,263 @@
+require_relative "simulate.rb"
+require_relative "../point2d.rb"
+
+module Gcode
+ class Motion
+ def emit_gcode(f, state)
+ state = state.change_arc_plane(f, @arc_plane)
+ state, cmd_tag = state.change_mach_state(f, @mach_state)
+ f.puts emit_gcode_cmd(f, state) + cmd_tag
+ state.motion_type = self.class
+ return state
+ end
+ end
+ class XYZPoint
+ def to_s()
+ "<#{@x.inspect}, #{@y.inspect}, #{@z.inspect}>"
+ end
+ end
+ class Arc < Motion
+ def emit_gcode(f, state)
+ state = state.change_arc_plane(f, @arc_plane)
+ state, cmd_tag = state.change_mach_state(f, @mach_state)
+ f.puts emit_gcode_cmd(f, state) + cmd_tag
+ state.motion_type = self.class
+ return state
+ end
+ end
+ class CCWArc < Arc
+ def emit_gcode_cmd(f, state)
+ cmd = ""
+ cmd += "G3 " if state.motion_type != self.class
+ state.assert_cur_pt(@st)
+ da = state.update_arc_center(@c)
+ cmd += state.update_pos(@ed)
+ cmd += da
+ end
+ def inspect
+ "CCWArc #{@st} -> #{@ed} c:#{@c}"
+ end
+ end
+ class CWArc < Arc
+ def emit_gcode_cmd(f, state)
+ cmd = ""
+ cmd += "G2 " if state.motion_type != self.class
+ state.assert_cur_pt(@st)
+ da = state.update_arc_center(@c)
+ cmd += state.update_pos(@ed)
+ cmd += da
+ end
+ def inspect
+ "CWArc #{@st} -> #{@ed} c:#{@c}"
+ end
+ end
+ class Rapid < Motion
+ def emit_gcode_cmd(f, state)
+ cmd = ""
+ cmd += "G0 " if state.motion_type != self.class
+ state.assert_cur_pt(@st)
+ cmd += state.update_pos(@ed)
+ end
+ def inspect
+ "Rapid #{@st} -> #{@ed}"
+ end
+ end
+ class Linear < Motion
+ def emit_gcode_cmd(f, state)
+ cmd = ""
+ cmd += "G1 " if state.motion_type != self.class
+ state.assert_cur_pt(@st)
+ cmd += state.update_pos(@ed)
+ end
+ def inspect
+ "Linear #{@st} -> #{@ed}"
+ end
+ end
+end
+
+class GcodeState
+ class MachineState
+ def change_mach_state(f, o)
+ cmd_tag = ""
+ if @feed_rate != o.feed_rate && o.feed_rate
+ cmd_tag = "F#{o.feed_rate} "
+ @feed_rate = o.feed_rate
+ end
+ tmist = o.mist || @mist
+ tflood = o.flood || @flood
+ if (tmist == :off && @mist != :off) || (tflood == :off && @flood != :off)
+ f.puts("M9")
+ @mist = :off
+ @flood = :off
+ end
+ if tmist == :on && @mist == :off
+ f.puts("M7")
+ @mist = :on
+ end
+ if tflood == :on && @flood == :off
+ f.puts("M8")
+ @flood = :on
+ end
+ if o.cutter_comp != @cutter_comp
+ if o.cutter_comp == nil
+ f.puts("G40")
+ elsif o.cutter_comp == :right || o.cutter_comp == :left
+ code = "2"
+ code = "1" if o.cutter_comp == :left
+ if o.cutter_diameter == nil
+ throw Exception.new("missing cutter diameter")
+ end
+ f.puts("G4#{code} D#{o.cutter_diameter}")
+ @cutter_diameter = o.cutter_diameter
+ else
+ throw Exception.new("invalid_cutter_comp #{o.cutter_comp}")
+ end
+ @cutter_comp = o.cutter_comp
+ end
+ if @cutter_diameter != o.cutter_diameter
+ if o.cutter_diameter != nil
+ f.puts("D#{o.cutter_diameter}")
+ end
+ @cutter_diameter = o.cutter_diameter
+ end
+ return cmd_tag
+ end
+ end
+ def comp_d(d1, d2)
+ return d2 && (d1 != d2)
+ end
+ def assert_cur_pt(pt)
+ puts pt.inspect
+ if comp_d(pt.x, @cur_pt.x) ||
+ comp_d(pt.y, @cur_pt.y) ||
+ comp_d(pt.z, @cur_pt.z)
+ raise "problem! <#{pt.x}, #{pt.y}, #{pt.z}> " +
+ "vs cur:<#{@cur_pt.x}, #{@cur_pt.y}, #{@cur_pt.z}>"
+ end
+ end
+ def fmt_float(f)
+ "%f"%f
+ end
+ def update_pos(pt)
+ cmd = ""
+ cmd += "X#{fmt_float(pt.x)} " if (pt.x && @cur_pt.x != pt.x)
+ cmd += "Y#{fmt_float(pt.y)} " if (pt.y && @cur_pt.y != pt.y)
+ cmd += "Z#{fmt_float(pt.z)} " if (pt.z && @cur_pt.z != pt.z)
+ @cur_pt.x = pt.x || @cur_pt.x
+ @cur_pt.y = pt.y || @cur_pt.y
+ @cur_pt.z = pt.z || @cur_pt.z
+ return cmd
+ end
+ def update_arc_center(pt)
+ cmd = ""
+ ap = @arc_plane
+ cmd += "I#{fmt_float(pt.x - @cur_pt.x)} " if (pt.x && ap.has_x)# != @arc_center.x && pt.x)
+ cmd += "J#{fmt_float(pt.y - @cur_pt.y)} " if (pt.y && ap.has_y)# != @arc_center.y && pt.y)
+ cmd += "K#{fmt_float(pt.z - @cur_pt.z)} " if (pt.z && ap.has_z)# != @arc_center.z && pt.z)
+ @arc_center.x = pt.x || @arc_center.x
+ @arc_center.y = pt.y || @arc_center.y
+ @arc_center.z = pt.z || @arc_center.z
+ return cmd
+ end
+ def change_arc_plane(f, arc_plane)
+ return self if (arc_plane == @arc_plane)
+ if (Gcode::XYPlane == arc_plane)
+ f.puts("G17")
+ elsif (Gcode::XZPlane == arc_plane)
+ f.puts("G18")
+ elsif (Gcode::YZPlane == arc_plane)
+ f.puts("G19")
+ end
+ @arc_plane = arc_plane
+ return self
+ end
+ def change_mach_state(f, mach_state)
+ cmd_tag = @mach_state.change_mach_state(f, mach_state)
+ return self, cmd_tag
+ end
+end
+
+class UnparsedGcode
+ def emit_gcode(f, state)
+ @view.lines do |ln|
+ if (ln.class != CommentLine)
+ f.puts(ln.emit())
+ end
+ end
+ return state
+ end
+end
+
+class GcodeInterpret
+ def emit_gcode(f, state)
+ f.puts(<<END)
+S#{@spindle_speed} M3
+G0 X#{@stpt.x} Y#{@stpt.y}
+G43 Z0.6 H1
+G0 Z#{@st_h}
+END
+ state.cur_pt.x = @stpt.x
+ state.cur_pt.y = @stpt.y
+ state.cur_pt.z = nil
+ @cmds.each do |cmd|
+ puts cmd.inspect
+ state = cmd.emit_gcode(f, state)
+ end
+ return state
+ end
+end
+
+class GcodeLayout
+ def initialize()
+ @items = []
+ @start
+ end
+ class Pos
+ def initialize(gcode, pos, rot)
+ @gcode = gcode
+ @pos = pos
+ @rot = rot
+ end
+ def spindle_speed() @gcode.spindle_speed end
+ def emit_gcode(f, state)
+ f.puts(<<END)
+G10 L2 P9 X[#5221+#{@pos.x}] Y[#5222+#{@pos.y}] Z[#5223] R#{@rot * 180.0 / Math::PI}
+G59.3
+END
+
+ return @gcode.emit_gcode(f, state)
+ end
+ end
+
+ def add_item(gcode, pos, rot)
+ @items << Pos.new(gcode, pos, rot)
+ end
+
+ def write_gcode(fname)
+ state = GcodeState.init_defaults(Gcode::XYZPoint.new(0,0,0))
+ File.open(fname, "w+") { |f|
+ f.puts(<<END)
+%
+G90 G94 G17 G91.1
+G64 P0.001 Q0.001
+G20
+G53 G0 Z0.
+(BORE8)
+M9
+T1 M6
+S#{@items[0].spindle_speed} M3
+G4 P10.
+G54
+M7
+END
+@items.each do |item|
+ state = item.emit_gcode(f, state)
+end
+ f.puts(<<END)
+M9
+G53 G0. Z0.
+M30
+END
+ }
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/gcode/parse.rb b/manufacturing/TravisProg1/lib_files/geometry/gcode/parse.rb
new file mode 100755
index 0000000..69d50e0
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/gcode/parse.rb
@@ -0,0 +1,109 @@
+class GcodeLine
+ attr_accessor :words, :line_number, :filename
+ def initialize(words)
+ @words = words
+ end
+ def match_gcode(o)
+ return false if self.class != o.class
+ return false if @words.length != o.words.length
+ @words.length.times do |i|
+ return false if @words[i] != o.words[i]
+ end
+ return true
+ end
+ def emit() "#{@words.collect {|word| word.emit()}.join(" ") }" end
+end
+
+class GcodePercentLine
+ attr_accessor :line_number, :filename
+ def match_gcode(o) return self.class == o.class end
+end
+
+class CommentLine
+ attr_accessor :line_number, :filename
+ attr_accessor :cmt
+ def initialize(cmt) @cmt = cmt end
+end
+
+class GcodeWord
+ attr_accessor :code, :number
+ def inspect() "#{@code.inspect}#{@number.inspect}" end
+ def initialize(code, number)
+ @code, @number = code, number
+ end
+ def emit() "#{@code}#{@number}" end
+ def ==(o)
+ @code == o.code && @number == o.number
+ end
+end
+
+class GcodeFile
+ attr_accessor :lines
+ def initialize()
+ @lines = []
+ end
+ def add_line(ln) @lines << ln end
+ def [](i) return @lines[i] end
+ def gcode_file() self end
+ def st() 0 end
+ def ed() @lines.length end
+end
+
+class GcodeFileView
+ def initialize(gcodef)
+ @gcodef = gcodef.gcode_file()
+ @st = gcodef.st()
+ @ed = gcodef.ed()
+ end
+ def st() @st end
+ def st=(v) @st = v end
+ def ed() @ed end
+ def ed=(v) @ed = v end
+ def gcode_file() @gcodef end
+ def length() @ed - @st end
+ def [](i)
+ l = length()
+ return nil if (i < -l || i >= l)
+ return @gcodef[i >= 0 ? i + @st : @ed + i]
+ end
+ def lines(&blk)
+ return Enumerator.new do |y|
+ (@st...@ed).each do |i|
+ y << @gcodef[i]
+ end
+ end if !blk
+ lines.each(&blk)
+ self
+ end
+end
+
+def parse_gcode_line(line)
+ line_no_space = line.gsub(/\s/,"")
+ if line_no_space[0] == "(" && line_no_space[-1] == ")"
+ #puts "comment_line: #{line.inspect}"
+ return CommentLine.new(line.chomp)
+ elsif (line_no_space == "%")
+ #puts "percent_line: \"%\""
+ return GcodePercentLine.new()
+ else
+ wl = line_no_space.scan(/[A-Za-z][0-9.\-]*/)
+ raise "Invalid line: #{line_no_space}"if (wl.join() != line_no_space)
+ #puts wl.inspect + " vs " + line.inspect
+ return GcodeLine.new(wl.collect { |w| GcodeWord.new(w[0], w[1..-1])})
+ end
+end
+
+def load_gcode(fname)
+ i = 0
+ fout = GcodeFile.new()
+ File.readlines(fname).each do |line|
+ line = parse_gcode_line(line)
+ i += 1
+ if (line)
+ line.line_number = i
+ line.filename = fname
+ fout.add_line(line)
+ end
+ end
+ return fout
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/gcode/simulate.rb b/manufacturing/TravisProg1/lib_files/geometry/gcode/simulate.rb
new file mode 100755
index 0000000..2d53d10
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/gcode/simulate.rb
@@ -0,0 +1,255 @@
+
+
+module Gcode
+ class CmdLineView
+ def initialize(line)
+ @word_mapping = {}
+ line.words.each do |word|
+ (@word_mapping[word.code] ||= []) << word.number
+ end
+ #puts @word_mapping.inspect
+ end
+ def del_code(code)
+ l = @word_mapping.delete(code)
+ return l if !l
+ raise "expected less than one word of type: #{code}" if (l.length > 1)
+ return l[0]
+ end
+ def del_code_list(code)
+ l = @word_mapping.delete(code) || []
+ return l
+ end
+ def get(code)
+ return @word_mapping[code]
+ end
+ def assert_empty()
+ raise "word list not empty #{@word_mapping}" if @word_mapping.length != 0
+ end
+ def format_as_gcode()
+ return @word_mapping.collect do |k, v|
+ v.collect {|a| "#{k}#{a}"}.join(" ")
+ end.join(" ")
+ end
+ end
+ class XYPoint
+ init_fields :x, :y
+ def +(o) XYPoint.new(@x + o.x, @y + o.y) end
+ def -(o) XYPoint.new(@x - o.x, @y - o.y) end
+ def /(o) XYPoint.new(@x / o, @y / o) end
+ def *(o) XYPoint.new(@x * o, @y * o) end
+ def mag_sqr() return @x * @x + @y * @y end
+ def dot(o) @x * o.x + @y * o.y end
+ def cross(o) @x * o.y - @y * o.x end
+ def ==(o) self.class == o.class && @x == o.x && @y == o.y end
+ end
+ class XYZPoint
+ init_fields :x, :y, :z
+ def -@() XYZPoint.new(-@x, -@y, -@z) end
+ def ==(o) o.class == XYZPoint && @x == o.x && @y == o.y && @z == o.z && @x && @y && @z end
+ end
+ class ArcPlane
+ attr_accessor :twin
+ init_fields :vect
+ def self.make(pt)
+ a = ArcPlane.new(pt)
+ b = ArcPlane.new(-pt)
+ a.twin = b
+ b.twin = a
+ return a
+ end
+ def has_x() @vect.x == 0 end
+ def has_y() @vect.y == 0 end
+ def has_z() @vect.z == 0 end
+ end
+ XYPlane = ArcPlane.make(XYZPoint.new(0, 0, 1))
+ XZPlane = ArcPlane.make(XYZPoint.new(0, 1, 0))
+ YZPlane = ArcPlane.make(XYZPoint.new(1, 0, 0))
+ class Motion
+ end
+ class Arc < Motion
+ end
+ class CCWArc < Arc
+ init_fields :c, :st, :ed, :arc_plane, :mach_state
+ def self.state_construct(s1, s2)
+ self.new(s2.arc_center, s1.cur_pt, s2.cur_pt, s2.arc_plane, s2.mach_state())
+ end
+ end
+ class CWArc < Arc
+ init_fields :c, :st, :ed, :arc_plane, :mach_state
+ def self.state_construct(s1, s2)
+ self.new(s2.arc_center, s1.cur_pt, s2.cur_pt, s2.arc_plane, s2.mach_state())
+ end
+ end
+ class Rapid < Motion
+ init_fields :st, :ed, :arc_plane, :mach_state
+ def self.state_construct(s1, s2)
+ self.new(s1.cur_pt, s2.cur_pt, s2.arc_plane, s2.mach_state())
+ end
+ end
+ class Linear < Motion
+ init_fields :st, :ed, :arc_plane, :mach_state
+ def self.state_construct(s1, s2)
+ self.new(s1.cur_pt, s2.cur_pt, s2.arc_plane, s2.mach_state())
+ end
+ end
+end
+
+class GcodeState
+ class MachineState
+ init_fields :feed_rate, :mist, :flood, :cutter_diameter, :cutter_comp
+ def self.init_defaults()
+ return self.new(nil, nil, nil, nil, nil)
+ end
+ def update_mach_state(view)
+ feed_rate = view.del_code("F") || @feed_rate
+ cutter_diameter = view.del_code("D") || @cutter_diameter
+ mist, flood = @mist, @flood
+ view.del_code_list("M").each do |code|
+ if (code == "7")
+ mist = :on
+ elsif (code == "8")
+ flood = :on
+ elsif (code == "9")
+ flood = :off
+ mist = :off
+ else
+ raise "unknown code: #{code}"
+ end
+ end
+ MachineState.new(feed_rate, mist, flood, cutter_diameter, @cutter_comp)
+ end
+ def set_cutter_comp(new_comp)
+ MachineState.new(@feed_rate, @mist, @flood, @cutter_diameter, new_comp)
+ end
+ end
+ attr_accessor :cur_pt, :arc_plane, :arc_center, :mach_state, :motion_type
+ def initialize(cur_pt, arc_plane, arc_center, motion_type, mach_state)
+ @cur_pt = cur_pt
+ @arc_plane = arc_plane
+ @arc_center = arc_center
+ @motion_type = motion_type
+ @mach_state = mach_state
+ end
+ def mach_state() @mach_state end
+ def self.init_defaults(st_pt)
+ z = st_pt.respond_to?(:z) ? st_pt.z : nil
+ st_pt = Gcode::XYZPoint.new(st_pt.x, st_pt.y, z)
+ cent = Gcode::XYZPoint.new(nil, nil, nil)
+ mach_state = MachineState.init_defaults()
+ return self.new(st_pt, Gcode::XYPlane, cent, nil, mach_state)
+ end
+end
+
+class G98ModalMotion
+ def initialize(view)
+ @view = view
+ end
+ def emit_gcode(f, state)
+ f.puts "#{@view.format_as_gcode}"
+ return state
+ end
+end
+
+class G80ModalMotion
+ def initialize()
+ end
+ def emit_gcode(f, state)
+ f.puts "G80"
+ state.motion_type = nil
+ return state
+ end
+end
+
+class ExpectClearModalMotion
+ def initialize(pstate)
+ @pstate = pstate
+ end
+ def emit_state_update(gcode_line)
+ view = Gcode::CmdLineView.new(gcode_line)
+ init_g_codes = view.del_code_list("G")
+ if (init_g_codes.empty?())
+ return G98ModalMotion.new(view), self
+ end
+ if (init_g_codes != ["80"])
+ raise "G98 supposed to be followed by G80"
+ end
+ view.assert_empty()
+ return G80ModalMotion.new(), @pstate
+ end
+end
+
+class GcodeState
+ def pt_to_f(v)
+ return nil if !v
+ raise "not a float: #{v}" if (!v =~ /-?[0-9]+\.[0-9]*/)
+ return v.to_f
+ end
+ def get_npoint(gcode_line)
+ xval = pt_to_f(gcode_line.del_code("X"))
+ yval = pt_to_f(gcode_line.del_code("Y"))
+ zval = pt_to_f(gcode_line.del_code("Z"))
+ return Gcode::XYZPoint.new(xval ? xval : @cur_pt.x,
+ yval ? yval : @cur_pt.y,
+ zval ? zval : @cur_pt.z)
+ end
+ def get_center(gcode_line)
+ xval = pt_to_f(gcode_line.del_code("I"))
+ yval = pt_to_f(gcode_line.del_code("J"))
+ zval = pt_to_f(gcode_line.del_code("K"))
+ return Gcode::XYZPoint.new(xval ? xval + @cur_pt.x : @arc_center.x,
+ yval ? yval + @cur_pt.y : @arc_center.y,
+ zval ? zval + @cur_pt.z : @arc_center.z)
+ end
+ def get_arc_plane()
+ return @arc_plane
+ end
+ def construct_update_cmd(old_state)
+ if (old_state.cur_pt == self.cur_pt)
+ return nil
+ else
+ return @motion_type.state_construct(old_state, self)
+ end
+ end
+ def emit_state_update(gcode_line)
+ view = Gcode::CmdLineView.new(gcode_line)
+ init_g_code = (view.get("G") || [])[0]
+ if (init_g_code == "98") || (init_g_code == "83")
+ return G98ModalMotion.new(view), ExpectClearModalMotion.new(self)
+ end
+ npoint = get_npoint(view)
+ center = get_center(view)
+ mach_state = @mach_state.update_mach_state(view)
+ codes = view.del_code_list("G")
+ arc_plane = @arc_plane
+ motion_type = @motion_type
+ codes.each do |code|
+ if (code == "0")
+ motion_type = Gcode::Rapid
+ elsif (code == "1")
+ motion_type = Gcode::Linear
+ elsif (code == "2")
+ motion_type = Gcode::CWArc
+ elsif (code == "3")
+ motion_type = Gcode::CCWArc
+ elsif (code == "40")
+ mach_state = mach_state.set_cutter_comp(nil)
+ elsif (code == "41")
+ mach_state = mach_state.set_cutter_comp(:left)
+ elsif (code == "42")
+ mach_state = mach_state.set_cutter_comp(:right)
+ elsif (code == "17")
+ arc_plane = Gcode::XYPlane
+ elsif (code == "18")
+ arc_plane = Gcode::XZPlane
+ elsif (code == "19")
+ arc_plane = Gcode::YZPlane
+ else
+ raise "unknown code: #{code}"
+ end
+ end
+ view.assert_empty()
+ nstate = self.class.new(npoint, arc_plane, center, motion_type, mach_state)
+ updated_cmd = nstate.construct_update_cmd(self)
+ return updated_cmd, nstate
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/placement/click_tester.rb b/manufacturing/TravisProg1/lib_files/geometry/placement/click_tester.rb
new file mode 100755
index 0000000..0840638
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/placement/click_tester.rb
@@ -0,0 +1,147 @@
+require "geometry/bool_ops/dxf_offset.rb"
+IntegerScale = 2**20
+class ProfileCrossing
+ class Entity
+ attr_accessor :st, :ed
+ def initialize(st, ed) @st = st; @ed = ed; end
+ def check_query(query)
+ q = Point2di.new((query.x * IntegerScale).to_i, (query.y * IntegerScale).to_i)
+ st = Point2di.new((@st.x * IntegerScale).to_i, (@st.y * IntegerScale).to_i)
+ ed = Point2di.new((@ed.x * IntegerScale).to_i, (@ed.y * IntegerScale).to_i)
+ return 0 if st.y == ed.y
+ dir = 1
+ st, ed, dir = ed, st, -1 if (st.y > ed.y)
+ return 0 if (st.y > q.y || ed.y <= q.y)
+
+ ttm = (q - st).cross(ed - st)
+ return ttm > 0 ? dir : 0
+ end
+ end
+ def add_only_drawable(drawable)
+ @only_drawable << drawable
+ end
+ attr_accessor :filename
+ def initialize()
+ @profile_entity = []
+ @only_drawable = []
+ end
+ def query_crossings(query)
+ crossings = 0
+ @profile_entity.each do |entity|
+ crossings += entity.check_query(query)
+ end
+ return crossings
+ end
+ def emit(st, ed)
+ @profile_entity << Entity.new(st, ed)
+ end
+ def draw_to(cr, scale)
+ item = @profile_entity[0]
+ @profile_entity.each do |item|
+ cr.move_to(item.st.x * scale, -item.st.y * scale)
+ cr.line_to(item.ed.x * scale, -item.ed.y * scale)
+ cr.stroke()
+ end
+ cr.set_source_rgb(1.0, 0.5, 0.0)
+ @only_drawable.each do |item|
+ item.items.each { |sitem| sitem.draw_to(cr, scale) }
+ end
+ end
+end
+
+class Line2d
+ def emit_rev_crossing(st, crossing)
+ crossing.emit(st, @st)
+ end
+ def emit_crossing(st, crossing)
+ crossing.emit(st, @ed)
+ end
+end
+
+class QuadrentArc
+ def emit_rev_crossing(st, crossing)
+ crossing.emit(st, self.st())
+ end
+ def emit_crossing(st, crossing)
+ crossing.emit(st, self.ed())
+ end
+end
+
+class SegmentRef
+ class ReversedSegRef
+ def emit_crossing(st, crossing)
+ @seg.emit_rev_crossing(st, crossing)
+ end
+ end
+ def emit_crossing(st, crossing)
+ @seg.emit_crossing(st, crossing)
+ end
+end
+
+class TwinableProfile
+ def to_crossing(crossing)
+ @items.length.times do |i|
+ item = @items[i]
+ item.emit_crossing(@items[i - 1].ed, crossing)
+ end
+ end
+end
+
+
+class Shape
+ # not correct if there is only ever two points. This is silly though.
+ def to_crossing(scale, pm, res = 1)
+ res_sqr = res * res
+ st = @segs[0].st
+ n = 0
+ @segs.each do |seg|
+ v = (seg.ed - st).mag_sqr.to_i
+ if (v > res_sqr)
+ st = seg.ed
+ n += 1
+ end
+ end
+ raise "problem" if n <= 2
+ dnom = scale ** -1
+ @segs.each do |seg|
+ v = (seg.ed - st).mag_sqr.to_i
+ if (v > res_sqr)
+ ed = seg.ed
+ pm.emit(ed * dnom, st * dnom)
+ end
+ st = seg.ed
+ end
+ end
+end
+
+def offset_dxf(dxf, scale, resolution, pm)
+ swl_p = offset_dxf_integer(dxf, IntegerScale, 2**15)
+end
+
+obj_name = (ARGV[0] || "9971-15-P-0015_transmition_plate.dxf")
+class PMLoader
+ def initialize()
+ @cache = {}
+ end
+ def direct_load(fname)
+ items = get_entities(fname)
+ items2d = items.collect { |item| item.to_2d() }
+
+ items2d_orient = Oriented.import_2d(items2d)
+ items2d_orient = SweepLine.stitch_curves(items2d_orient)
+ items = items2d_orient
+
+ scale = IntegerScale
+ swl_p = offset_dxf_integer(items, scale, 2**15)
+ pm = ProfileCrossing.new()
+ pm.filename = fname
+ swl_p.to_crossing(scale, pm)
+ for item in items
+ pm.add_only_drawable(item)
+ end
+ return pm
+ end
+ def load_pm(fname)
+ @cache[fname] ||= direct_load(fname)
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/placement/drag_drop_rot_handler.rb b/manufacturing/TravisProg1/lib_files/geometry/placement/drag_drop_rot_handler.rb
new file mode 100755
index 0000000..719a76b
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/placement/drag_drop_rot_handler.rb
@@ -0,0 +1,72 @@
+class DragDropRotHandler
+ def initialize(layout)
+ @layout = layout
+ @picked = nil
+ @click_point = nil
+ @rot_point = nil
+ @ctrl_held = false
+ @a_held = false
+ end
+ def add_item_fn(&blk) @add_item_fn = blk end
+ def click(ev, cp)
+ if @a_held
+ @add_item_fn.call(cp) if @add_item_fn
+ @picked = nil
+ return
+ end
+ if @ctrl_held
+ @click_point = nil
+ if (!@rot_point)
+ @picked = @layout.pick(cp)
+ @rot_point = @picked ? cp : nil
+ else
+ @rot_drag_point = cp
+ end
+ else
+ @picked = @layout.pick(cp)
+ @click_point = cp
+ end
+ end
+ def drag(cp)
+ if @picked
+ if @click_point
+ delta = (cp - @click_point)
+ @picked.update(delta)
+ @click_point = cp
+ elsif @rot_drag_point
+ a1 = @rot_drag_point - @rot_point
+ a2 = cp - @rot_point
+ dang = Math.atan2(a2.y, a2.x) - Math.atan2(a1.y, a1.x)
+ @picked.rotate(@rot_point, dang)
+ @rot_drag_point = cp
+ end
+ end
+ return @picked ? true : nil
+ end
+ def key_release_fn(kv)
+ if (Gdk::Keyval::KEY_Control_L == kv.keyval || Gdk::Keyval::KEY_Control_R == kv.keyval)
+ @rot_point = nil
+ @rot_drag_point = nil
+ @ctrl_held = false
+ end
+ if (Gdk::Keyval::KEY_a == kv.keyval)
+ @a_held = false
+ end
+ end
+ def key_press_fn(kv)
+ if (Gdk::Keyval::KEY_Control_L == kv.keyval || Gdk::Keyval::KEY_Control_R == kv.keyval)
+ @ctrl_held = true
+ end
+ if (Gdk::Keyval::KEY_a == kv.keyval)
+ @a_held = true
+ end
+ end
+ def draw_scaled(cr, scale)
+ if @rot_point
+ cr.set_source_rgb(1.0, 0.5, 0.0)
+ draw_crosshair(cr, scale, @rot_point)
+ end
+ @layout.draw_to(cr, scale)
+ end
+ attr_accessor :layout
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/placement/sheet.rb b/manufacturing/TravisProg1/lib_files/geometry/placement/sheet.rb
new file mode 100755
index 0000000..1861834
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/placement/sheet.rb
@@ -0,0 +1,151 @@
+require "data/codecs/data_view.rb"
+require "data/codecs/serialize_buffer.rb"
+class SheetLayout
+ class BBox
+ attr_accessor :min, :max
+ def initialize()
+ reset()
+ end
+ def reset()
+ inf = Float::INFINITY
+ @min = Point2d.new(inf, inf)
+ @max = Point2d.new(-inf, -inf)
+ end
+ def add_point(pt)
+ @min.x = pt.x if pt.x < @min.x
+ @min.y = pt.y if pt.y < @min.y
+ @max.x = pt.x if pt.x > @max.x
+ @max.y = pt.y if pt.y > @max.y
+ end
+ end
+ class Pos
+ attr_accessor :c, :rot, :item
+ def initialize(c, rot, item)
+ @c, @rot, @item = c, rot, item
+ # @bbox = BBox.new()
+ end
+ def draw_to(cr, scale)
+ cr.save()
+ cr.translate(c.x * scale, -c.y * scale)
+ cr.rotate(-@rot)
+ @item.draw_to(cr, scale)
+ cr.restore()
+ end
+ def query_crossings(q)
+ q = q - @c
+ c = Math.cos(-@rot)
+ s = Math.sin(-@rot)
+ q = Point2d.new(q.x * c - q.y * s, q.x * s + q.y * c)
+ @item.query_crossings(q)
+ end
+ def update(delta)
+ @c += delta
+ end
+ def rotate(cent, da)
+ creal = cent
+ d1 = @c - cent
+ c = Math.cos(-@rot)
+ s = Math.sin(-@rot)
+ d1 = Point2d.new(d1.x * c - d1.y * s, d1.x * s + d1.y * c)
+
+ @rot += da
+
+ c = Math.cos(@rot)
+ s = Math.sin(@rot)
+ dc = Point2d.new(c * d1.x - d1.y * s, d1.x * s + d1.y * c)
+ @c = creal + dc
+ end
+ end
+
+ def initialize()
+ @items = []
+ @selected = nil
+ end
+
+ attr_accessor :selected
+ def delete(item) @items.delete(item) end
+
+ def items() @items end
+
+ def add_item(item, c, rot)
+ @items << Pos.new(c, rot, item)
+ end
+ def pick(q)
+ @selected = nil
+ @items.each do |item|
+ cp_count = item.query_crossings(q)
+ return (@selected = item) if cp_count == 1
+ end
+ return nil
+ end
+ def draw_to(cr, scale, fixed = nil)
+ @items.each do |item|
+ (@selected == item ? Color::Red : Color::Green).set(cr)
+ Color::Blue.set(cr) if (fixed)
+ item.draw_to(cr, scale)
+ end
+ end
+end
+
+def draw_crosshair(cr, scale, pt)
+ cr.move_to(pt.x * scale + 5, -pt.y * scale + 5)
+ cr.line_to(pt.x * scale - 5, -pt.y * scale - 5)
+ cr.stroke()
+ cr.move_to(pt.x * scale + 5, -pt.y * scale - 5)
+ cr.line_to(pt.x * scale - 5, -pt.y * scale + 5)
+ cr.stroke()
+end
+
+class Point2d
+ def write_to(ser)
+ ser.write_f64(@x)
+ ser.write_f64(@y)
+ end
+ def self.load_from(ser)
+ self.new(ser.parse_f64(), ser.parse_f64())
+ end
+end
+
+class SheetLayout
+ class Pos
+ def write_to(ser)
+ ser.write_f64(@c.x)
+ ser.write_f64(@c.y)
+ ser.write_f64(@rot)
+ ser.code_string(@item.filename)
+ end
+ end
+ def write_to(ser)
+ ser.push()
+ @items.each do |item|
+ item.write_to(ser)
+ end
+ ser.pop()
+ end
+ def self.load_file(fname, pm_loader)
+ layout = self.new()
+ reader = StreamReader.new(File.open(fname, "rb").read())
+ reader.push()
+ while (reader.not_done())
+ pt = Point2d.load_from(reader)
+ ang = reader.parse_f64()
+ ofname = reader.parse_string()
+ pm = pm_loader.load_pm(ofname)
+ layout.add_item(pm, pt, ang)
+ #puts "<#{pt.x}, #{pt.y}> #{ang} #{ofname.inspect}"
+ end
+ reader.pop()
+ return layout
+ end
+end
+
+def write_out_sheet(sheet, fname)
+ ser = SerializeBuffer::Writer.new()
+ sheet.write_to(ser)
+ data = ser.data()
+
+ len = SerializeBuffer.get_concat_length(data)
+ buffer = OutputView.new(len)
+ SerializeBuffer.concat_output(buffer, data)
+ File.open(fname, "w+") { |f| f.write(buffer.val) }
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/point2d.rb b/manufacturing/TravisProg1/lib_files/geometry/point2d.rb
new file mode 100755
index 0000000..146b0aa
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/point2d.rb
@@ -0,0 +1,41 @@
+class Point2d
+ attr_accessor :x, :y
+ def initialize(x, y) @x, @y = x, y end
+ def inspect() "<#{@x}, #{@y}>" end
+ def to_s() "#<Point2d:#{@x} #{@y}>" end
+ def +(other) Point2d.new(@x + other.x, @y + other.y) end
+ def -(other) Point2d.new(@x - other.x, @y - other.y) end
+ def scale(d)
+ return Point2d.new(@x * d, @y * d)
+ end
+ def -@()
+ return Point2d.new(-@x, -@y)
+ end
+ def dot(o)
+ return o.x * @x + o.y * @y
+ end
+ def area(o)
+ return o.x * @y - o.y * @x
+ end
+ def normalize()
+ d = Math.sqrt(@x * @x + @y * @y)
+ return Point2d.new(@x / d, @y / d)
+ end
+ def mag() Math.sqrt(@x * @x + @y * @y) end
+ def mag_sqr() @x * @x + @y * @y end
+ def div(d) Point2d.new(@x / d, @y / d) end
+
+ def comp_h(o)
+ a = @x <=> o.x
+ return a if a != 0
+ return @y <=> o.y
+ end
+ def comp_v(o)
+ a = @y <=> o.y
+ return a if a != 0
+ return @x <=> o.x
+ end
+ def rot90()
+ return Point2d.new(-@y, @x)
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/router/drag_window.rb b/manufacturing/TravisProg1/lib_files/geometry/router/drag_window.rb
new file mode 100755
index 0000000..7c31248
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/router/drag_window.rb
@@ -0,0 +1,275 @@
+require "geometry/placement/sheet.rb"
+require "geometry/placement/drag_drop_rot_handler.rb"
+require "geometry/placement/click_tester.rb"
+
+module Router
+ class HoleSpec
+ def self.direct_load()
+ r = HoleSpec::Radius
+ pts = 16.times.collect do |i|
+ th = i * Math::PI * 2 / 16.0
+ Point2d.new(Math.cos(th) * r, Math.sin(th) * r)
+ end
+ pm = ProfileCrossing.new()
+ pm.filename = self
+ 16.times do |i|
+ pm.emit(pts[i], pts[i - 1])
+ end
+ return pm
+ end
+ end
+ class GcodeSpec
+ def direct_load()
+ part_spec = self
+ fname = part_spec.dxf_name()
+ items = get_entities(fname)
+ items2d = items.collect { |item| item.to_2d() }
+
+ items2d_orient = Oriented.import_2d(items2d)
+ items = [TwinableProfile.new(items2d_orient)]
+ #items2d_orient = SweepLine.stitch_curves(items2d_orient)
+# items = items2d_orient
+
+ scale = IntegerScale
+ swl_p = offset_dxf_integer(items, scale, 2**16, 0.25)
+# scale_points(swl_p, 1, 1)
+ pm = ProfileCrossing.new()
+ pm.filename = part_spec
+ swl_p.to_crossing(scale, pm)
+ for item in items
+ pm.add_only_drawable(item)
+ end
+ return pm
+ end
+ end
+ class SheetCut
+ def add_originable_points(lst)
+ @layout.items.each do |item|
+ lst << item.c if (item.item.filename == HoleSpec)
+ end
+ end
+ end
+ class SheetInfo
+ def add_originable_points(lst)
+ lst << Point2d.new(0, 0)
+ lst << Point2d.new(@w, 0)
+ lst << Point2d.new(0, @h)
+ lst << Point2d.new(@w, @h)
+ end
+ end
+ class MetalSheet
+ def originable_points()
+ lst = []
+ @cuts.each do |cut|
+ cut.add_originable_points(lst)
+ end
+ @sheet_info.add_originable_points(lst)
+ return lst
+ end
+ end
+end
+
+class OriginSelector
+ def initialize(sheet)
+ @points = sheet.originable_points()
+ @selected = nil
+ end
+ attr_accessor :selected
+ def pick(q)
+ @points.each do |point|
+ dist = (point - q)
+ dist = dist.x * dist.x + dist.y * dist.y
+ if dist < 4 * Router::HoleSpec::Radius ** 2
+ @selected = point
+ end
+ end
+ return nil
+ end
+ def draw_scaled(cr, scale)
+ if (@selected)
+ cr.set_source_rgb(1.0, 0.5, 0.5)
+ draw_crosshair(cr, scale, @selected)
+ end
+ end
+end
+
+class PMLoader
+ def initialize()
+ @cache = {}
+ end
+ def load_pm(part_spec)
+ return nil if (part_spec == nil)
+ @cache[part_spec] ||= part_spec.direct_load()
+ end
+ def load_pm_string(fname)
+ puts "loading part: #{fname}"
+ load_pm(@parts_library.realize(fname))
+ end
+ attr_accessor :parts_library
+end
+
+class DragWindow
+ attr_accessor :bbox
+ def resize(bbox)
+ @bbox = bbox
+ end
+ def add_item(layout, cp)
+ if (@adder && c_item = @adder.current_item())
+ layout.add_item(@pm_loader.load_pm(c_item), cp, 0)
+# @pm_loader.load_pm("/home/parker/repo/geometry/gcode/sample/gcode.dxf"), cp, 0)
+ end
+ end
+ def set_active_adder(adder)
+ @adder = adder
+ end
+ def set_sheet(sheet)
+ @sheet = sheet
+ @sheet.active_cut_change() { |cut|
+ update_sheet_layout(cut.layout)
+ }
+ end
+ def update_origin_picker(origin_picker)
+ if (@drag_drop_handler)
+ @drag_drop_handler.layout.selected = nil
+ end
+ @origin_picker = origin_picker
+ ts = self
+ @drag_drop_handler = nil
+ ts.click_fn() do |ev, cp|
+ @origin_picker.pick(cp)
+ end
+ ts.drag_fn() do |cp|
+ end
+ ts.key_press_fn() do |kv|
+ end
+ ts.key_release_fn() do |kv|
+ end
+ ts.draw_fn() do |cr, scale|
+ @origin_picker.draw_scaled(cr, scale)
+ GC.start()
+ end
+ end
+ def update_sheet_layout(layout)
+ ts = self
+ drag_drop_handler = DragDropRotHandler.new(layout)
+ @drag_drop_handler = drag_drop_handler
+ drag_drop_handler.add_item_fn() do |cp|
+ add_item(layout, cp)
+ end
+ ts.click_fn() do |ev, cp|
+ drag_drop_handler.click(ev, cp)
+ end
+ ts.drag_fn() do |cp|
+ drag_drop_handler.drag(cp)
+ end
+ ts.key_press_fn() do |kv|
+ drag_drop_handler.key_press_fn(kv)
+ end
+ ts.key_release_fn() do |kv|
+ drag_drop_handler.key_release_fn(kv)
+ end
+ ts.draw_fn() do |cr, scale|
+ drag_drop_handler.draw_scaled(cr, scale)
+ GC.start()
+ end
+ end
+ def set_scale(scale) @scale = scale end
+ def initialize(pm_loader)
+ @pm_loader = pm_loader
+ @scale = 20.0
+ @xoff = 200
+ @yoff = 200
+ end
+ def take_focus(ev) self end
+ def draw_fn(&blk)
+ @draw_fn = blk
+ end
+ def scroll_event(ev, cur)
+# puts "#{ev.x} #{ev.y}"
+ #ev.x = @xoff + xcur * @scale
+ #ev.y = @yoff - ycur * @scale
+ xcur = (ev.x - @xoff) / @scale
+ ycur = (@yoff - ev.y) / @scale
+ if (ev.direction == Gdk::ScrollDirection::DOWN)
+ @scale *= 1.1
+ elsif (ev.direction == Gdk::ScrollDirection::UP)
+ @scale /= 1.1
+ end
+ @xoff = ev.x - xcur * @scale
+ @yoff = ev.y + ycur * @scale
+ end
+ def key_press(ev)
+ @key_press_fn.call(ev) if @key_press_fn
+ end
+ def key_release(ev)
+ @key_release_fn.call(ev) if @key_release_fn
+ end
+ def click_fn(&blk) @click_fn = blk end
+ def drag_fn(&blk) @drag_fn = blk end
+ def key_press_fn(&blk) @key_press_fn = blk end
+ def key_release_fn(&blk) @key_release_fn = blk end
+ def to_model(ev)
+ xcur = (ev.x - @xoff) / @scale
+ ycur = (@yoff - ev.y) / @scale
+ return Point2d.new(xcur, ycur)
+ end
+ def button_press_event(ev, cp)
+ if (ev.button == 1)
+ @click_point = Point2d.new(cp.x, cp.y)
+ @click_fn.call(ev, to_model(cp)) if @click_fn
+ end
+ end
+ def button_release_event(ev, pt)
+ @click_point = nil if (ev.button == 1)
+ end
+ def motion_notify_event(ev, cp)
+ if @click_point
+ if !(@drag_fn && @drag_fn.call(to_model(cp)))
+ delta = cp - @click_point
+ @click_point = cp
+ @xoff += delta.x
+ @yoff += delta.y
+ end
+ end
+ end
+ def draw(cr)
+ if (@sheet)
+ cr.set_source_rgb(0.0,0.1,0.1)
+ cr.paint()
+ cr.translate(@xoff, @yoff)
+ cr.set_source_rgb(0.0,1.0,0.1)
+ @sheet.draw_others(cr, @scale, @drag_drop_handler ? @drag_drop_handler.layout : nil)
+ @draw_fn.call(cr, @scale) if (@draw_fn)
+ end
+ end
+end
+module Router
+ class MetalSheet
+ def draw_others(cr, scale, layout)
+ @sheet_info.draw_bounds(cr, scale)
+ @cuts.each do |cut|
+ cut.layout.draw_to(cr, scale, cut != @active_cut) if (cut.layout != layout)
+ end
+ end
+ end
+ class SheetInfo
+ def draw_bounds(cr, scale)
+ cr.set_source_rgb(1.0, 0.5, 0.0)
+ bounds = sheet_dxf()
+ if bounds
+ bounds.each do |bound|
+ bound.draw_to(cr, scale) #.each { |sitem| sitem.draw_to(cr, scale) }
+ end
+ return
+ end
+ w, h = @w * scale, @h * scale
+ cr.move_to(0, 0)
+ cr.line_to(w, 0)
+ cr.line_to(w, -h)
+ cr.line_to(0, -h)
+ cr.line_to(0, 0)
+ cr.stroke()
+ end
+ end
+end
+
diff --git a/manufacturing/TravisProg1/lib_files/geometry/router/emit_gcode.rb b/manufacturing/TravisProg1/lib_files/geometry/router/emit_gcode.rb
new file mode 100755
index 0000000..ee55371
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/router/emit_gcode.rb
@@ -0,0 +1,67 @@
+require "geometry/gcode/parse.rb"
+require "geometry/gcode/gcode_file_format.rb"
+require "geometry/gcode/gcode_layout.rb"
+
+class GcodeLoader
+ def initialize() @cache = {} end
+ def load_pm(fname)
+ @cache[fname] ||= interpret_gcode(load_gcode(fname))
+ end
+end
+
+module Router
+ class HoleSpec
+ def self.emit_gcode_group(item, gcode_group)
+ gcode_group.add_hole(item)
+ end
+ end
+ class GcodeSpec
+ def emit_gcode_group(item, gcode_group)
+ @gcode_files.each do |gcode_file|
+ ext = gcode_file.basename.to_path[0...-gcode_file.extname.length].split("_")[-1]
+ gcode_group.add_part(ext, gcode_file, item)
+ end
+ gcode_group
+ end
+ end
+ class GcodeGrouping
+ def initialize()
+ @suffix_groups = {}
+ @holes = []
+ end
+ def emit_group(group, fname, origin)
+ loader = GcodeLoader.new()
+ lay = GcodeLayout.new()
+ group.each do |pos, gcode|
+ lay.add_item(loader.load_pm(gcode), pos.c - origin, pos.rot)
+ end
+ lay.write_gcode(fname)
+ end
+ def emit_holes(fname, sheet_info, origin)
+ loader = GcodeLoader.new()
+ lay = GcodeLayout.new()
+ hole_fname = sheet_info.hole_file()
+ @holes.each do |pos|
+ lay.add_item(loader.load_pm(hole_fname), pos.c - origin, pos.rot)
+ end
+ lay.write_gcode(fname)
+ end
+ def groups() @suffix_groups end
+ def add_part(ext, gcode_file, item)
+ (@suffix_groups[ext] ||= []) << [item, gcode_file]
+ end
+ def add_hole(hole)
+ @holes << hole
+ end
+ def has_holes() @holes.length > 0 end
+ end
+ class SheetCut
+ def make_gcode_groupings()
+ gcode_group = GcodeGrouping.new()
+ @layout.items.each do |item|
+ item.item.filename.emit_gcode_group(item, gcode_group)
+ end
+ return gcode_group
+ end
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/router/file_formats.rb b/manufacturing/TravisProg1/lib_files/geometry/router/file_formats.rb
new file mode 100755
index 0000000..2a2c3d4
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/router/file_formats.rb
@@ -0,0 +1,52 @@
+module Router
+ class GcodeSpec
+ def serialize(ser)
+ ser.write_varint(0)
+ ser.code_string(full_path())
+ end
+ end
+ class HoleSpec
+ def self.serialize(ser)
+ ser.write_varint(1)
+ end
+ end
+ class SheetCut
+ def serialize(ser)
+ ser.push()
+ @layout.items.each { |a|
+ ser.write_f64(a.c.x)
+ ser.write_f64(a.c.y)
+ ser.write_f64(a.rot)
+ a.item.filename.serialize(ser)
+ }
+ ser.pop()
+ end
+ def self.load(skel, pm_loader)
+ reader = StreamReader.new(File.open(skel.fname, "rb").read())
+ reader.push()
+ while (reader.not_done())
+ pt = Point2d.load_from(reader)
+ ang = reader.parse_f64()
+ t = reader.parse_varint()
+ pm = nil
+ error_spec = nil
+ if (t == 0) # GcodeSpec
+ pm_fname = reader.parse_string()
+ pm = pm_loader.load_pm_string(Pathname.new(pm_fname))
+ error_spec = "Could not load #{pm_fname}" if !pm
+ elsif (t == 1) # HoleSpec
+ pm = pm_loader.load_pm(HoleSpec)
+ error_spec = "Could not load #{HoleSpec}" if !pm
+ else
+ raise "error loading: #{skel.fname}"
+ end
+ # Comment the following line out to enable skipping and deleting of unknown parts.
+ raise error_spec if error_spec
+ if (pm)
+ skel.layout.add_item(pm, pt, ang)
+ end
+ end
+ reader.pop()
+ end
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/router/metal_sheet.rb b/manufacturing/TravisProg1/lib_files/geometry/router/metal_sheet.rb
new file mode 100755
index 0000000..c3cd21e
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/router/metal_sheet.rb
@@ -0,0 +1,154 @@
+require "json"
+module Router
+ class SheetCut
+ attr_accessor :layout, :fname
+ def initialize(fname)
+ @fname = fname
+ @layout = SheetLayout.new()
+ end
+ def save_changes()
+ ser = SerializeBuffer::Writer.new()
+ serialize(ser)
+ data = ser.data
+ len = SerializeBuffer.get_concat_length(data)
+ buffer = OutputView.new(len)
+ SerializeBuffer.concat_output(buffer, data)
+ File.open(@fname, "wb+") { |f| f.write(buffer.val) }
+ end
+ def inspect()
+ lns = @layout.items.collect { |a|
+ a.item.filename.name()
+ }
+ return "<SheetCut: #{@fname}, #{lns.inspect}>"
+ end
+ def layout() @layout end
+ end
+ class MetalSheet
+ def initialize(sheet_info, sheet_folder)
+ @sheet_folder = sheet_folder
+ @cuts = []
+ @sheet_info = sheet_info
+ @active_cut = nil
+ end
+ def sheet_info() @sheet_info end
+ def load_cut(fname, pm_loader)
+ puts "loading cut: #{fname}"
+ cut = SheetCut.new(fname)
+ SheetCut.load(cut, pm_loader)
+ @cuts << cut
+ end
+ def cuts() @cuts end
+ def inspect() "<MetalSheet: #{@sheet_folder}, #{@sheet_info}, #{@cuts.inspect}>" end
+ def add_cut(name)
+ @cuts << cut = SheetCut.new(@sheet_folder + "#{name}.cut")
+ cut
+ end
+ def save_changes()
+ @cuts.each do |cut|
+ cut.save_changes()
+ end
+ end
+ def active_cut_change(&blk)
+ @active_cut_change = blk
+ @active_cut_change.call(@active_cut) if @active_cut
+ end
+ def sheet_name() @sheet_folder.basename.to_path end
+ def active_cut() @active_cut end
+ def set_active(cut)
+ @active_cut_change.call(cut) if @active_cut_change
+ @active_cut = cut
+ end
+ def empty?() @cuts.empty?() end
+ end
+ class MetalSheetLoader
+ def initialize(sheet, cuts)
+ @sheet_name = sheet
+ @sheet = SheetInfo.parse(sheet)
+ @cuts = cuts
+ end
+ def sheet_name()
+ @sheet_name.dirname.basename.to_path
+ end
+ def self.make(folder)
+ c = folder.children.select(&:file?)
+ sheets = []
+ cuts = []
+ c.each do |child|
+ sheets << child if (child.extname == ".sheet")
+ cuts << child if (child.extname == ".cut")
+ end
+ return nil if (sheets.length != 1)
+ return MetalSheetLoader.new(sheets[0], cuts)
+ end
+ def open(pm_loader)
+ ms = MetalSheet.new(@sheet, @sheet_name.dirname)
+ @cuts.each do |cut|
+ ms.load_cut(cut, pm_loader)
+ end
+ if (active_fname = @sheet.active())
+ ms.cuts.each do |cut|
+ if (cut.fname.to_path == active_fname)
+ ms.set_active(cut)
+ end
+ end
+ end
+ return ms
+ end
+ end
+ class SheetDeltaLayout
+ end
+
+ class SheetList < Array
+ def initialize(folder)
+ folder.children.collect do |child|
+ c = MetalSheetLoader.make(child)
+ self << c if c
+ end
+ @open_sheets = {}
+ end
+ def open_sheet(i, parts_lib)
+ sheet_load = self[i]
+ name = sheet_load.sheet_name
+ return @open_sheets[name] ||= sheet_load.open(parts_lib)
+ end
+ end
+ class SheetInfo
+ def initialize(fname, json, w, h)
+ @fname = fname
+ @json = json
+ @w, @h = w, h
+ end
+ def self.parse(fname)
+ json = JSON.parse(File.read(fname))
+ w = json["w"]
+ h = json["h"]
+ self.new(fname, json, w.to_f, h.to_f)
+ end
+ def set_active(cut)
+ @json["active"] = cut.fname.to_path
+ File.open(@fname, "w+") { |f| f.write(JSON.dump(@json)) }
+ end
+ def active()
+ @json["active"]
+ end
+ def sheet_dxf()
+ fname = @json["sheet_dxf"]
+ return nil if !fname
+ @sheet_dxf ||= (
+ items = get_entities(fname)
+ items2d = items.collect { |item| item.to_2d() }
+
+ items2d_orient = Oriented.import_2d(items2d)
+# items2d_orient = SweepLine.stitch_curves(items2d_orient)
+ items2d_orient
+ )
+ end
+ def hole_file()
+ hf = @json["hole_file"]
+ raise "sheet: #{@fname} missing hole filename" if !hf
+ return hf
+ end
+ end
+end
+
+require_relative "file_formats.rb"
diff --git a/manufacturing/TravisProg1/lib_files/geometry/router/nester.rb b/manufacturing/TravisProg1/lib_files/geometry/router/nester.rb
new file mode 100755
index 0000000..be6ba88
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/router/nester.rb
@@ -0,0 +1,46 @@
+require_relative "parts_library.rb"
+require_relative "metal_sheet.rb"
+require_relative "view.rb"
+
+module Router
+ def self.do_nesting()
+ parts_lib = PartsLibrary.new(Pathname.new("./parts_library"))
+ sheets = SheetList.new(Pathname.new("./sheets"))
+ puts sheets.inspect
+ view = RouterLayoutView.new(parts_lib, sheets)
+ view = ClickTypeSplitView.new(view)
+ EvWindow.run(view, 1000, 640)
+ end
+end
+
+# Folder organized list of parts files (folders binding DXF to GCODE).
+#
+# Stories:
+#
+# Add Part to Delta Cut File:
+# - select part
+# - click to place
+#
+# From Any View:
+# - Save Stuff. [Breadcrums: Go to view selection step; Bac]
+#
+# Views:
+#
+# List of Sheets:
+# - Click sheet name -> sheet-edit
+#
+# Edit Sheet:
+# - Add New Delta Sheet Cut. Add new part. Delete selected part. Generate Gcode.
+# - Edit notes.
+# - Select particular part.
+# - Switch to different Delta Sheet Cut. (list of delta-sheet cuts).
+#
+# TODO(parker):
+# - List + delete items
+# - Serialize active cut choice.
+# - Scrolling on lists that might grow too big.
+# - Visual save indicator.
+# - Error mechanism.
+# DONE
+# - Select + emit non-zero origin.
+# - Select active cut. List cuts.
diff --git a/manufacturing/TravisProg1/lib_files/geometry/router/parts_library.rb b/manufacturing/TravisProg1/lib_files/geometry/router/parts_library.rb
new file mode 100755
index 0000000..ea0dd7a
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/router/parts_library.rb
@@ -0,0 +1,111 @@
+require "fileutils"
+require "pathname"
+require "set"
+
+module Router
+ class GcodeSpec
+ class SpecError
+ def initialize(msg)
+ @msg = msg
+ end
+ def msg() @msg end
+ def is_error() true end
+ end
+ def is_error() false end
+ def initialize(folder, dxf, gcode_files)
+ @folder = folder
+ @dxf = dxf
+ @gcode_files = gcode_files
+ puts self.inspect()
+ end
+ def dxf_name() @folder + @dxf end
+ def inspect()
+ "<Part: #{@folder.to_path.inspect}, #{@dxf.inspect}, #{@gcode_files.inspect}>"
+ end
+ def name()
+ @folder.dirname.to_path.inspect
+ end
+ def full_path() @folder.to_path end
+ def self.make(folder, children)
+ # puts "#{folder.inspect} -> #{children.inspect}"
+ dxfs = []
+ ngcs = []
+ children.each do |child|
+ dxfs << child if (child.extname == ".dxf")
+ dxfs << child if (child.extname == ".DXF")
+ ngcs << child if (child.extname == ".ngc")
+ end
+ return nil if (dxfs.length == 0 && ngcs.length == 0)
+ return SpecError.new("#{folder.inspect} #{dxfs.length} dxfs!") if (dxfs.length != 1)
+ return SpecError.new("#{folder.inspect} no ngcs!") if (ngcs.length == 0)
+ return GcodeSpec.new(folder, dxfs[0].basename.to_path, ngcs)
+ end
+ end
+ class HoleSpec
+ Radius = 0.25
+ end
+ class PartsLibrary
+ def fname() @folder.basename.to_path end
+ def initialize(folder)
+ @folder = folder
+ @sub_libs_by_name = {}
+ rescan()
+ end
+ def rescan()
+ #puts "entering #{fname()}"
+ c = @folder.children
+ bad = c.select() { |a| !(a.directory? || a.file?) }
+ raise "bad filenames of #{fname()} -> #{bad}" if (!bad.empty?())
+ @spec = GcodeSpec.make(@folder, c.select(&:file?))
+ @sub_libs = []
+ c.select(&:directory?).each do |d|
+ subl = PartsLibrary.new(d)
+ if subl.non_empty()
+ if subl2 = @sub_libs_by_name[subl.fname]
+ subl2.rescan()
+ subl = subl2
+ else
+ puts "storing: #{subl.fname.inspect}"
+ @sub_libs_by_name[subl.fname] = subl
+ end
+ @sub_libs << subl
+ end
+ end
+ end
+ def sub_libs() @sub_libs end
+ def spec() @spec end
+ def non_empty()
+ @spec || @sub_libs.length != 0
+ end
+ def realize(fname)
+ # puts "fname: #{fname.inspect} #{@folder.inspect}"
+ deltp = fname.relative_path_from(@folder)
+ key = get_key_from(deltp)
+ # puts "fname: #{deltp} -> key: #{key}"
+ if (key)
+ #puts "key: #{key.inspect}"
+ target = @sub_libs_by_name[key]
+ if (target)
+ return @sub_libs_by_name[key].realize(fname)
+ else
+ puts "cannot find #{key} in #{@sub_libs_by_name.keys.inspect}"
+ return nil
+ end
+ else
+ if (@spec.is_error)
+ puts "realizing error parts library spec: #{fname} err: #{@spec.msg}"
+ end
+ return (!@spec || @spec.is_error) ? nil : @spec
+ end
+ #raise "problem!"
+ end
+ def get_key_from(deltp)
+ goal = Pathname.new(".")
+ return nil if (deltp == goal)
+ while deltp.dirname != goal
+ deltp = deltp.dirname
+ end
+ return deltp.to_path
+ end
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/geometry/router/view.rb b/manufacturing/TravisProg1/lib_files/geometry/router/view.rb
new file mode 100755
index 0000000..59737b5
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/geometry/router/view.rb
@@ -0,0 +1,851 @@
+require "gui/gtk_utils/colors.rb"
+
+require "gtk3"
+require "pathname"
+
+require "cairo"
+require "gui/gui_lib/ev_window.rb"
+require "gui/gtk_utils/pango_inspect.rb"
+require "gui/gtk_utils/clipping.rb"
+require "tokens/tokenizer_lib_base.rb"
+require "gui/gui_lib/line_edit.rb"
+require_relative "drag_window.rb"
+require_relative "emit_gcode.rb"
+
+def get_cursor_extents()
+ lay = get_desc_pango()
+ lay.set_text(" ")
+ extents = lay.pixel_extents[1]
+ return [extents.width, extents.height]
+end
+
+CursorExtents = get_cursor_extents()
+class StringEditView
+ attr_accessor :bbox
+ def resize(bbox) @bbox = bbox end
+ def xoff() @xoff || 2 end
+ def set_xoff(xoff) @xoff = xoff; self end
+ def initialize(txt)
+ @lay = get_desc_pango()
+ @txt = txt
+ @cursor = 0
+ update_txt()
+ end
+ def update_txt() @lay.set_text(@txt) end
+ def draw(cr)
+ y = 0
+ x = xoff
+ Color::Yellow.set(cr)
+ cr.update_pango_layout(@lay)
+
+ if (cursor)
+ attrs = Pango::AttrList.new()
+ attrs.insert(Color::Black.pattr_fg(cursor, cursor + 1))
+ p = @lay.index_to_pos(cursor)
+ Color::Yellow.set(cr)
+ ox, oy = x + p.x / Pango::SCALE, y + p.y / Pango::SCALE
+ if (@txt.length == cursor)
+ cr.rectangle(ox, oy, CursorExtents[0], CursorExtents[1])
+ else
+ p2 = @lay.index_to_pos(cursor + 1)
+ cr.rectangle(ox, oy, (p2.x - p.x) / Pango::SCALE, CursorExtents[1])
+ end
+ cr.fill()
+ @lay.attributes = attrs
+ end
+
+ cr.move_to(x, y)
+ cr.show_pango_layout(@lay)
+ @lay.attributes = nil if (cursor)
+ return y + height
+ end
+ def cursor() @cursor end
+ def key_press(ev)
+ cursor = [@cursor]
+ TextEditEvent.key_press(self, @txt, ev, cursor)
+ @cursor = cursor[0]
+ end
+ def button_press_event(ev, pt)
+ @cursor = get_cursor_x(pt.x)
+ end
+ def get_cursor_x(x)
+ x -= xoff
+ inside, index, trailing = @lay.xy_to_index(x * Pango::SCALE, 0)
+ index += 1 if index + 1 <= @txt.length && x > 0 && !inside
+ return index
+ end
+ def take_focus(ev) self end
+ def height() h = @lay.pixel_extents[1].height end
+end
+
+def rounded_rect(cr, x, y, w, h, r)
+ cr.move_to(x + r, y)
+ cr.line_to(x, y + r) # should be arc
+ cr.line_to(x, y + h - r)
+ cr.line_to(x + r, y + h) # should be arc
+ cr.line_to(x + w - r, y + h)
+ cr.line_to(x + w, y + h - r) # should be arc
+ cr.line_to(x + w, y + r)
+ cr.line_to(x + w - r, y) # should be arc
+ cr.line_to(x + r, y)
+end
+
+class ActionButton
+ def initialize(txt, obj, *action_args)
+ @lay = get_desc_pango()
+ @lay.set_text(txt)
+ @obj = obj
+ @action = action_args
+ end
+ Highlight = Color.new(0, 0, 0.75)
+ Normal = Color.new(0.0, 0, 1.0)
+ def draw(cr)
+ w = 0
+ (@hi ? Highlight : Normal).set(cr)
+ rounded_rect(cr, 2, 2, width() - 4, height() - 4, 3)
+ cr.fill()
+ Color::Yellow.set(cr)
+ cr.move_to(10, 5)
+ cr.show_pango_layout(@lay)
+ end
+ def button_press_event(ev, pt)
+ @obj.send(*@action)
+ end
+ def motion_notify_event(ev, pt) @hi = true end
+ def exit_notify_event() @hi = false end
+ def height() @lay.pixel_extents[1].height + 10 end
+ def width() @lay.pixel_extents[1].width + 20 end
+ attr_accessor :bbox
+ def resize(bbox) @bbox = bbox end
+end
+
+class VerticalPacker
+ class Padding
+ def just_padding() true end
+ def xoff() nil end
+ def draw(cr) end
+ def height() @height end
+ def initialize(h) @height = h end
+ end
+ def initialize()
+ @non_pad_children = []
+ @children = []
+ end
+ def reset()
+ @non_pad_children = []
+ @children = []
+ end
+ def height()
+ h = 0
+ @children.each { |child| h += child.height }
+ return h
+ end
+ def width()
+ w = 0
+ @children.each { |child| w = [w, child.width].max() }
+ return w
+ end
+ attr_accessor :bbox
+ def resize(bbox)
+ @bbox = bbox
+ w = @bbox.w
+ y = 0
+ @children.each do |item|
+ h = item.height
+ if !(item.respond_to?(:just_padding) && item.just_padding())
+ item.resize(SClipBBox.new(0, y, w, h))
+ end
+ y += h
+ end
+ end
+ def children() @non_pad_children end
+ def add(item)
+ raise 'adding null item' if (!item)
+ @children.push(item)
+ if !(item.respond_to?(:just_padding) && item.just_padding())
+ @non_pad_children.push(item)
+ end
+ end
+ def draw(cr)
+ cr.save()
+ y = 0
+ @children.each do |child|
+ child.draw(cr)
+ cr.translate(0, child.height)
+ end
+ cr.restore()
+ end
+end
+class EmptyView
+ def self.bbox() SClipBBox::InvalidBBox end
+ def self.resize(bbox) end
+ def self.draw(cr)
+ end
+end
+class SplitScreen
+ attr_accessor :bbox
+ def initialize(lhs, rhs, split)
+ @children = [lhs, rhs]
+ @split = split
+ end
+ def lhs() @children[0] end
+ def rhs() @children[1] end
+ def lhs=(child)
+ @children[0] = child
+ resize(@bbox) if @bbox
+ end
+ def rhs=(child)
+ @children[0] = child
+ resize(@bbox) if @bbox
+ end
+ def resize(bbox)
+ @bbox = bbox
+ a = bbox.dup()
+ a.y = a.x = 0
+ b = a.dup()
+ b.x = @split
+ a.w = @split
+ b.w -= @split
+ @children[0].resize(a)
+ @children[1].resize(b)
+ end
+ def children()
+ @children
+ end
+ def draw(cr)
+ @children.each do |child|
+ child.bbox.enter(cr) {
+ child.draw(cr)
+ }
+ end
+ end
+end
+
+class TextWindow
+ attr_accessor :bbox
+ def resize(bbox) @bbox = bbox end
+ def initialize()
+ @lay = get_desc_pango()
+ @lay.set_text("hello world")
+ end
+ def draw(cr)
+ cr.set_source_rgb(0.0,0.3,0.1)
+ cr.paint()
+ cr.set_source_rgb(1.0,1.0,0.0)
+ cr.update_pango_layout(@lay)
+ cr.move_to(2, 2)
+ cr.show_pango_layout(@lay)
+ end
+end
+
+class MyEventWrapper
+ attr_accessor :ev, :x, :y
+ def initialize(ev)
+ @ev = ev
+ @x = ev.x
+ @y = ev.y
+ end
+end
+
+class ClickTypeSplitView
+ def initialize(tree)
+ @tree = tree
+ end
+ def resize(bbox)
+ @bbox = bbox
+ @tree.resize(bbox.dup())
+ end
+ def event_recurse(ev, evnt_m, tree)
+ x, y = ev.x, ev.y
+ if tree.respond_to?(evnt_m)
+ tree.send(evnt_m, ev.ev, Point2d.new(ev.x, ev.y))
+ return tree, 0, 0
+ end
+ return nil, 0, 0 if !tree.respond_to?(:children)
+ tree.children.each do |c|
+ if (c.bbox.contain(x, y))
+ ev.x -= c.bbox.x
+ ev.y -= c.bbox.y
+ # puts "event_recurse: #{c.bbox.x}, #{c.bbox.y}"
+ v, dx, dy = event_recurse(ev, evnt_m, c)
+ return v, dx + c.bbox.x, dy + c.bbox.y if v
+ ev.x,ev.y = x,y
+ end
+ end
+ return nil, 0, 0
+ end
+ def scroll_event(ev)
+ event_recurse(MyEventWrapper.new(ev), :scroll_event, @tree)
+ end
+ def button_press_event(ev)
+ return if ev.time == @last_click_time
+ @last_clicker, @lcx, @lcy = event_recurse(MyEventWrapper.new(ev), :button_press_event, @tree)
+ if @last_clicker && @last_clicker.respond_to?(:take_focus)
+ @focus = @last_clicker.take_focus(ev)
+ end
+ @last_click_time = ev.time
+ end
+ def button_release_event(ev)
+ pt = Point2d.new(ev.x, ev.y)
+ if @last_clicker && @last_clicker.respond_to?(:button_release_event)
+ pt = Point2d.new(ev.x, ev.y)
+ pt.x -= @last_clicker.bbox.x
+ pt.y -= @last_clicker.bbox.y
+ @last_clicker.button_release_event(ev, pt)
+ end
+ @last_clicker = nil
+ end
+ def motion_notify_event(ev)
+ if @last_clicker && @last_clicker.respond_to?(:motion_notify_event)
+ pt = Point2d.new(ev.x, ev.y)
+ pt.x -= @lcx
+ pt.y -= @lcy
+ @last_clicker.motion_notify_event(ev, pt)
+ else
+ obj, dx, dy = event_recurse(MyEventWrapper.new(ev), :motion_notify_event, @tree)
+ if (@exit_notify && obj != @exit_notify)
+ @exit_notify.exit_notify_event()
+ @exit_notify = nil
+ end
+ @exit_notify = obj if (obj.respond_to?(:exit_notify_event))
+ end
+ end
+ def key_press(ev)
+ if @focus && @focus.respond_to?(:key_press)
+ @focus.key_press(ev)
+ end
+ end
+ def key_release(ev)
+ if @focus && @focus.respond_to?(:key_release)
+ @focus.key_release(ev)
+ end
+ end
+ def draw(cr)
+ @tree.draw(cr)
+ end
+end
+
+class VerticalPackerPlan < Array
+ def make(obj)
+ pck = VerticalPacker.new()
+ self.each do |item|
+ if (item.is_a?(Symbol))
+ pck.add(obj.send(item))
+ else
+ pck.add(item)
+ end
+ end
+ return pck
+ end
+end
+
+module DelegateToLayout
+ def height() @layout.height end
+ def width() @layout.width end
+ def resize(bbox)
+ @layout.resize(bbox)
+ end
+ def bbox() @layout ? @layout.bbox : nil end
+ def children() @layout.children end
+ def draw(cr) @layout.draw(cr) end
+end
+
+module Router
+ class ConstStringLineView
+ def initialize(str)
+ @lay = get_desc_pango()
+ @lay.set_text(str)
+ end
+ def draw_to(cr, x, y)
+ #cr.set_source_rgb(1.0,1.0,0.0)
+ cr.move_to(x, y)
+ cr.update_pango_layout(@lay)
+ cr.show_pango_layout(@lay)
+ return y + height()
+ end
+ def height() @lay.pixel_extents[1].height end
+ end
+ class ConstStringView
+ def initialize(str, col = nil)
+ @col = col || Color::Yellow
+ @lay = get_desc_pango()
+ @lay.set_text(str)
+ end
+ def reset_text(txt) @lay.set_text(txt) end
+ def just_padding() return true end
+ attr_accessor :xoff
+ def set_xoff(xoff) @xoff = xoff; self end
+ def draw(cr)
+ y = 0
+ @col.set(cr)
+ cr.move_to(@xoff || 0, y)
+ cr.update_pango_layout(@lay)
+ cr.show_pango_layout(@lay)
+ return y + height()
+ end
+ def height() @lay.pixel_extents[1].height end
+ end
+ class ListView
+ HoverC = Color::White
+ DullC = Color::Yellow
+ attr_accessor :bbox
+ def resize(bbox) @bbox = bbox end
+ def initialize(list, to_viewable, on_item_click)
+ @on_item_click = on_item_click
+ @views = list.collect(&to_viewable)
+ end
+ def draw_to(cr, x, y)
+ @views.each.with_index do |item, i|
+ (i == @hover_i ? HoverC : DullC).set(cr)
+ y = item.draw_to(cr, x, y)
+ end
+ return y
+ end
+ def height() @height ||= @views.collect(&:height).inject(0, &:+) end
+ def button_press_event(ev, cur)
+ y = cur.y
+ @views.each.with_index do |view, i|
+ if (view.height() > y && y > 0)
+ @on_item_click.call(i)
+ end
+ y -= view.height()
+ end
+ end
+ def motion_notify_event(ev, cur)
+ y = cur.y
+ @views.each.with_index do |view, i|
+ if (view.height() > y && y > 0)
+ @hover_i = i
+ end
+ y -= view.height()
+ end
+ end
+ end
+ class SheetListView
+ Sheets = ConstStringLineView.new("Select Sheet:")
+ attr_accessor :bbox
+ def resize(bbox)
+ @bbox = bbox
+ y = @scroll
+ y += 2 + Sheets.height + 5
+ @lv.bbox = SClipBBox.new(0, y, @bbox.w, h = @lv.height())
+ y += h
+ end
+ def children() [@lv] end
+ def initialize(sheets, master)
+ @sheets = sheets
+ @lv = ListView.new(@sheets, lambda {|a| ConstStringLineView.new("sheet #{a.sheet_name}") },lambda { |i|
+ master.set_sheet_view(master.open_sheet(i))
+ })
+ @scroll = 0
+ end
+ def scroll_event(ev, pt)
+ return if !ev.state.shift_mask?
+ if (ev.direction == Gdk::ScrollDirection::DOWN)
+ @scroll -= (Sheets.height * 3).to_i
+ elsif (ev.direction == Gdk::ScrollDirection::UP)
+ @scroll += (Sheets.height * 3).to_i
+ end
+ resize(@bbox)
+ end
+ def draw(cr)
+ x, y = 2, 2 + @scroll
+ Color::Yellow.set(cr)
+ y = Sheets.draw_to(cr, x, y)
+ @lv.draw_to(cr, x + 5, y + 5)
+ end
+ end
+ class RouterLayoutView
+ include DelegateToLayout
+ def initialize(parts_lib, sheets)
+ @parts_lib = parts_lib
+ @sheets = sheets
+ @pm_loader = PMLoader.new()
+ @pm_loader.parts_library = @parts_lib
+ @layout = SplitScreen.new(EmptyView, @drag_window = DragWindow.new(@pm_loader), 400)
+ set_sheet_list_view()
+
+# set_sheet_view(sheet = open_sheet(0))
+# if sheet.cuts.length == 0
+# @layout.lhs.cut_name = "cut1"
+# @layout.lhs.add_cut()
+# else
+# sheet.set_active(sheet.cuts[0])
+# end
+ end
+ def open_sheet(i)
+ @sheets.open_sheet(i, @pm_loader)
+ end
+ def set_sheet_list_view()
+ @layout.lhs = SheetListView.new(@sheets, self)
+ end
+ def set_sheet_view(sheet)
+ SheetView.make(sheet, @parts_lib, self).config_main_view(self)
+ end
+ def drag_window() @drag_window end
+ def set_active_view(view)
+ @layout.lhs = view
+ end
+ end
+ class SheetView
+ include DelegateToLayout
+ def initialize(sheet, parts_lib, master_view)
+ @master_view = master_view
+ @sheet, @parts_lib = sheet, parts_lib
+ @layout = LayoutPlan.make(self)
+ @add_entity_view = AddEntityView.new(self, @parts_lib)
+ @add_hole_view = AddHoleView.new(self)
+ end
+ def config_main_view(main_view)
+ if @sheet.empty?()
+ main_view.set_active_view(NewSheetCutView.new(self))
+ else
+ main_view.set_active_view(self)
+ end
+ main_view.drag_window.set_sheet(@sheet)
+ end
+ def self.make(sheet, parts_lib, master_view)
+ sheet_view = self.new(sheet, parts_lib, master_view)
+ end
+ def add_cut(cut_name)
+ set_active_cut(@sheet.add_cut(cut_name))
+ @active_cut_selector.set_current_cut()
+ @active_cut_selector.update_cut_list()
+ end
+ def set_active_cut(cut)
+ @sheet.set_active(cut)
+ @sheet.sheet_info.set_active(cut)
+ @master_view.set_active_view(self)
+ end
+ def enter_add_cut_view()
+ @master_view.set_active_view(NewSheetCutView.new(self))
+ end
+ def add_sheet_cmds()
+ return (V2HLayout
+ .add(ActionButton.new("Add Hole", self, :add_hole))
+ .add(ActionButton.new("Add Item", self, :add_entity))
+ .add(ActionButton.new("Del Select", self, :del_select))
+ )
+ end
+ def add_rescan_cmds()
+ return (V2HLayout
+ .add(ActionButton.new("Rescan parts_library", self, :rescan))
+ )
+ end
+ def rescan()
+ @parts_lib.rescan()
+ @add_entity_view = AddEntityView.new(self, @parts_lib)
+ end
+ def del_select()
+ active_cut = @sheet.active_cut
+ return puts "no active cut." if (!active_cut)
+ layout = active_cut.layout
+ select = layout.selected
+ return puts "nothing selected." if (!select)
+ layout.delete(select)
+ layout.selected = nil
+ end
+ def select_active_cut()
+ @active_cut_selector ||= ActiveCutSelector.new(self, @sheet)
+ end
+ def add_cut_cmds()
+ return (V2HLayout
+ .add(ActionButton.new("Emit Gcode", self, :emit_gcode))
+ .add(ActionButton.new("Save", self, :save_all_changes))
+ .add(ActionButton.new("Add Cut", self, :enter_add_cut_view))
+ )
+ end
+ def emit_gcode()
+ if (@sheet.active_cut == nil)
+ puts "first select active cut before emitting gcode."
+ return
+ end
+ @master_view.set_active_view(EmitGcodeView.new(@sheet, self, @master_view))
+ end
+ def save_all_changes()
+ @sheet.save_changes()
+ end
+ def add_hole()
+ @master_view.set_active_view(@add_hole_view)
+ @master_view.drag_window.set_active_adder(@add_hole_view)
+ end
+ def add_entity()
+ @master_view.set_active_view(@add_entity_view)
+ @master_view.drag_window.set_active_adder(@add_entity_view)
+ end
+ def done_adding_items()
+ @master_view.set_active_view(self)
+ @master_view.drag_window.set_active_adder(nil)
+ end
+ def show_sheet_name()
+ ConstStringView.new("Editing Sheet: #{@sheet.sheet_name}").set_xoff(2)
+ end
+
+ LayoutPlan = VerticalPackerPlan.new([
+ VerticalPacker::Padding.new(2),
+ :show_sheet_name,
+ :add_sheet_cmds,
+ :add_rescan_cmds,
+# VerticalPacker::Padding.new(5),
+ VerticalPacker::Padding.new(5),
+ :add_cut_cmds,
+ :select_active_cut,
+ ])
+ end
+ class NewSheetCutView
+ include DelegateToLayout
+ attr_accessor :cut_name
+ def initialize(sheet_view)
+ @sheet_view = sheet_view
+ @cut_name = ""
+ @layout = LayoutPlan.make(self)
+ end
+ def add_cut()
+ if @cut_name.length == 0
+ puts "error! cut filename is empty"
+ return
+ end
+ @sheet_view.add_cut(@cut_name)
+ end
+ def sheet_name_edit()
+ return StringEditView.new(@cut_name)
+ end
+ def add_cut_button()
+ return V2HLayout.add(ActionButton.new("Add Sheet Cut", self, :add_cut))
+ end
+ LayoutPlan = VerticalPackerPlan.new([
+ VerticalPacker::Padding.new(2),
+ ConstStringView.new("Adding new sheet cut. Enter filename:").set_xoff(2),
+ :sheet_name_edit,
+ :add_cut_button,
+ ])
+ end
+ class AddEntityView
+ include DelegateToLayout
+ def initialize(sheet_view, parts_lib)
+ @sheet_view, @parts_lib = sheet_view, parts_lib
+ @part_selector = PartSelectorView.new(@parts_lib, self)
+ @layout = LayoutPlan.make(self)
+ end
+ def part_selector()
+ @part_selector
+ end
+ def add_items() @sheet_view.done_adding_items() end
+ def to_sheet_view()
+ return V2HLayout.add(ActionButton.new("Done Adding Items", self, :add_items))
+ end
+ def current_item
+ @part_selector.current_item()
+ end
+ LayoutPlan = VerticalPackerPlan.new([
+ :to_sheet_view,
+ VerticalPacker::Padding.new(2),
+ ConstStringView.new("Adding new part by clicking\n while holding a:").set_xoff(2),
+ :part_selector,
+ #:sheet_name_edit,
+ #:add_cut_button,
+ ])
+ end
+ class AddHoleView
+ include DelegateToLayout
+ def initialize(sheet_view)
+ @sheet_view = sheet_view
+ @layout = LayoutPlan.make(self)
+ end
+ def current_item()
+ HoleSpec
+ end
+ def done_adding_holes() @sheet_view.done_adding_items() end
+ def to_sheet_view()
+ return V2HLayout.add(ActionButton.new("Done Adding Holes", self, :done_adding_holes))
+ end
+ LayoutPlan = VerticalPackerPlan.new([
+ :to_sheet_view,
+ VerticalPacker::Padding.new(2),
+ ConstStringView.new("Adding new hole by clicking\n while holding a.").set_xoff(2),
+ #:sheet_name_edit,
+ #:add_cut_button,
+ ])
+ end
+ class EmitGcodeView
+ include DelegateToLayout
+ def initialize(sheet, sheet_view, master_view)
+ @sheet = sheet
+ @sheet_view = sheet_view
+ @master_view = master_view
+ cut_fname = @sheet.active_cut.fname.basename
+ cut_name = cut_fname.to_path[0...-(cut_fname.extname.length)]
+ @fname = "output/#{Time.new().strftime("%Y%m%d/#{cut_name}.ngc")}"
+ Pathname.new(@fname).dirname.mkpath
+ @gcode_groups = @sheet.active_cut.make_gcode_groupings()
+ #@origin_status = ConstStringView.new("origin currently unset.").set_xoff(2),
+ @layout = LayoutPlan.make(self)
+ @origin_picker = OriginSelector.new(@sheet)
+ @master_view.drag_window.update_origin_picker(@origin_picker)
+ end
+ def done_adding_holes()
+ @sheet_view.done_adding_items()
+ @master_view.drag_window().update_sheet_layout(@sheet.active_cut.layout)
+ end
+ def to_sheet_view()
+ return V2HLayout.add(ActionButton.new("Done Emitting Gcode", self, :done_adding_holes))
+ end
+ def select_origin()
+ return V2HLayout.add(ActionButton.new("Set Origin", self, :do_select_origin))
+ end
+ def do_select_origin()
+ if (!@origin_picker.selected)
+ puts "could not set origin"
+ return
+ end
+ # TODO(parker): this might just need to be deleted.
+ end
+ def emit_group(group)
+ origin = @origin_picker.selected
+ return puts "origin not set!" if (!origin)
+ @gcode_groups.emit_group(group, @fname, origin)
+ end
+ def emit_holes()
+ origin = @origin_picker.selected
+ return puts "origin not set!" if (!origin)
+ @gcode_groups.emit_holes(@fname, @sheet.sheet_info, origin)
+ end
+ def list_gcode_groups()
+ @list_gcode_groups ||= (
+ vp = VerticalPacker.new();
+ vp.add(V2HLayout.add(ActionButton.new("Emit holes", self, :emit_holes))) if (@gcode_groups.has_holes());
+ @gcode_groups.groups.each do |name, group|
+ vp.add(V2HLayout.add(ActionButton.new("Emit #{name.inspect}", self, :emit_group, group)));
+ end
+ vp;
+ )
+ return @list_gcode_groups
+ end
+ def gcode_name_edit()
+ return StringEditView.new(@fname)
+ end
+ LayoutPlan = VerticalPackerPlan.new([
+ :to_sheet_view,
+ VerticalPacker::Padding.new(2),
+ ConstStringView.new("Set Gcode filename:").set_xoff(2),
+ :gcode_name_edit,
+ #:select_origin,
+ ConstStringView.new("Select Gcode grouping to emit.").set_xoff(2),
+ :list_gcode_groups
+ #:sheet_name_edit,
+ #:add_cut_button,
+ ])
+ end
+ class ActiveCutSelector
+ include DelegateToLayout
+ def initialize(sheet_view, sheet)
+ @sheet_view = sheet_view
+ @sheet = sheet
+ @current_cut = ConstStringView.new(" ").set_xoff(2)
+ @cut_list = VerticalPacker.new()
+ set_current_cut()
+ update_cut_list()
+ @layout = LayoutPlan.make(self)
+ end
+ def set_current_cut()
+ if (cut = @sheet.active_cut)
+ @current_cut.reset_text("current cut: #{cut.fname.basename.to_path}")
+ else
+ @current_cut.reset_text("no active cut. Select One.")
+ end
+ end
+ def select_cut(cut)
+ @sheet_view.set_active_cut(cut)
+ set_current_cut()
+ end
+ def update_cut_list()
+ @cut_list.reset()
+ @sheet.cuts.each do |cut|
+ @cut_list.add(V2HLayout.add(ActionButton.new(
+ "cut: #{cut.fname.basename.to_path}", self, :select_cut, cut)))
+ end
+ @sheet_view.resize(@sheet_view.bbox) if @sheet_view.bbox
+ end
+ attr_accessor :current_cut, :cut_list
+ LayoutPlan = VerticalPackerPlan.new([
+ :current_cut,
+ #:cut_list,
+ ])
+ end
+ class PartSelectorView
+ include DelegateToLayout
+ def initialize(parts_lib, to_update)
+ @to_update = to_update
+ @parts_lib = parts_lib
+ @parts_lib_stack = [@parts_lib]
+ make_layout()
+ end
+ def current_item()
+ spec = @parts_lib.spec
+ return (!spec || spec.is_error) ? nil : spec
+ end
+ def up_dir()
+ @parts_lib_stack.pop()
+ @parts_lib = @parts_lib_stack[-1]
+ update_layout()
+ end
+ def down_dir(sub_lib)
+ @parts_lib = sub_lib
+ @parts_lib_stack.push(sub_lib)
+ update_layout()
+ end
+ def update_layout()
+ make_layout()
+ @to_update.resize(@to_update.bbox) if @to_update.bbox
+ end
+ def make_layout()
+ packer = VerticalPacker.new()
+ if (@parts_lib_stack.length > 1)
+ packer.add(V2HLayout.add(ActionButton.new("..", self, :up_dir)))
+ end
+ @parts_lib.sub_libs.each do |sub_lib|
+ packer.add(V2HLayout.add(ActionButton.new(sub_lib.fname(), self, :down_dir, sub_lib)))
+ end
+ if spec = @parts_lib.spec()
+ if (spec.is_error)
+ packer.add(ConstStringView.new("error: #{spec.msg}").set_xoff(2))
+ else
+ packer.add(ConstStringView.new("cur_part: #{spec.name}").set_xoff(2))
+ end
+ elsif (@parts_lib_stack.length > 1)
+ packer.add(ConstStringView.new("current folder: #{@parts_lib.fname()}").set_xoff(2))
+ end
+ @layout = packer
+ end
+ #def resize(bbox) @bbox = bbox end
+ end
+end
+class V2HLayout
+ def self.add(item) self.new().add(item) end
+ def initialize()
+ @items = []
+ @height = 0
+ end
+ def add(item)
+ @items << item
+ @height = [@height, item.height].max()
+ self
+ end
+ def children() @items end
+ def height() @height end
+ def resize(bbox)
+ @bbox = bbox
+ x = 0
+ @items.each do |item|
+ w = item.width
+ item.resize(SClipBBox.new(x, 0, w, @height))
+ x += w
+ end
+ end
+ attr_accessor :bbox
+ def draw(cr)
+ @items.each do |item|
+ item.bbox.enter(cr) { item.draw(cr) }
+ end
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/gui/gtk_utils/clipping.rb b/manufacturing/TravisProg1/lib_files/gui/gtk_utils/clipping.rb
new file mode 100755
index 0000000..9378adb
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/gui/gtk_utils/clipping.rb
@@ -0,0 +1,26 @@
+class SClipBBox
+ attr_accessor :x, :y, :w, :h
+ def initialize(x, y, w, h) @x = x; @y = y; @w = w; @h = h; end
+ def ex() @x + @w end
+ def ey() @y + @h end
+ def ex=(v) @x = v - @w end
+ def ey=(v) @y = v - @h end
+ def sy=(y) @h += (@y - y); @y = y end
+ def sx=(x) @w += (@x - x); @x = x end
+ def hy=(y) @h = (y - @y) end
+ def wx=(x) @w = (x - @x) end
+ def contain(x, y)
+ (@x <= x && @y <= y && x < @x + @w && y < @y + @h)
+ end
+ def enter(cr, &blk)
+ cr.save()
+ cr.rectangle(@x, @y, @w, @h)
+ cr.translate(@x, @y)
+ cr.clip()
+ clipr = cr.get_clip_rectangle()[1]
+ blk.call() if (clipr.width != 0 && clipr.height != 0)
+ cr.restore()
+ return self
+ end
+ InvalidBBox = SClipBBox.new(0,0,0,0)
+end
diff --git a/manufacturing/TravisProg1/lib_files/gui/gtk_utils/colors.rb b/manufacturing/TravisProg1/lib_files/gui/gtk_utils/colors.rb
new file mode 100755
index 0000000..7f01146
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/gui/gtk_utils/colors.rb
@@ -0,0 +1,38 @@
+class Color
+ def initialize(r, g, b)
+ @r, @g, @b = r, g, b
+ end
+ def set(cr)
+ cr.set_source_rgb(@r, @g, @b)
+ end
+ def hex_code()
+ r = (@r * 255).to_i.to_s(16).rjust(2, "0")
+ g = (@g * 255).to_i.to_s(16).rjust(2, "0")
+ b = (@b * 255).to_i.to_s(16).rjust(2, "0")
+ "##{r}#{g}#{b}"
+ end
+ def code_255()
+ return 16 + 36*(@r * 5).to_i + 6*(@g * 5).to_i + (@b * 5).to_i
+ end
+ def pattr_fg(st, ed)
+ attr = Pango::AttrForeground.new(65535 * @r, 65535 * @g, 65535 * @b)
+ attr.start_index = st
+ attr.end_index = ed
+ return attr
+ end
+ def pattr_bg(st, ed)
+ attr = Pango::AttrBackground.new(65535 * @r, 65535 * @g, 65535 * @b)
+ attr.start_index = st
+ attr.end_index = ed
+ return attr
+ end
+ Yellow = self.new(1.0, 1.0, 0.0)
+ Red = self.new(1.0, 0.0, 0.0)
+ Green = self.new(0.0, 1.0, 0.0)
+ Blue = self.new(0.0, 0.0, 1.0)
+ Pink = self.new(1.0, 0.5, 0.5)
+ Gray = self.new(0.5, 0.5, 0.5)
+ Teal = self.new(0.5, 0.5, 1.0)
+ Black = self.new(0.0, 0.0, 0.0)
+ White = self.new(1.0, 1.0, 1.0)
+end
diff --git a/manufacturing/TravisProg1/lib_files/gui/gtk_utils/pango_inspect.rb b/manufacturing/TravisProg1/lib_files/gui/gtk_utils/pango_inspect.rb
new file mode 100755
index 0000000..ed7310a
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/gui/gtk_utils/pango_inspect.rb
@@ -0,0 +1,58 @@
+require "pango"
+
+require_relative "pango_tools.rb"
+
+class Pango::Layout
+ def inspect()
+ ls = self.lines()
+ txt = "pango_layout {\n"
+ ls.each do |line|
+ txt += " #{line.display_line(font_description)}\n"
+ end
+ return txt + "}"
+ end
+end
+class Pango::LayoutLine
+ def inspect() "#<pango_line: #{display_line(layout.font_description)}>" end
+ def display_line(desc)
+ "[ #{runs.collect { |rn| rn.display_run(desc)}.join(", ")}]"
+ end
+end
+FontTable = {}
+def get_font_mapping(i)
+ if (FontTable.length == 0)
+ l = Gdk.default_root_window.create_cairo_context.create_pango_layout
+ l.set_font_description(Desc)
+ " ".upto("~") do |c|
+ l.set_text(c)
+ g = l.lines[0].runs[0].glyphs.glyphs[0][0].glyph
+ puts g.inspect
+ puts g - c.ord
+ puts "#{g} => #{c.ord}"
+ FontTable[g] = c
+ end
+ txt = " ".upto("~").to_a.join()
+ l.set_text(txt)
+ #puts c.inspect
+ end
+ return FontTable[i]
+end
+class Pango::GlyphItem
+ def display_run(desc)
+ throw Exception.new("Glyph item is buggy.")
+ self.glyphs.glyphs.collect do |glyph, i|
+ t = glyph.glyph
+ #puts t.inspect
+ #puts (t + 29).chr.inspect
+ #get_font_mapping(t.chr)
+ (t - 1).chr
+
+# (glyph.glyph + 29).chr
+ end.join()
+ end
+end
+class Pango::Rectangle
+ def inspect()
+ "#<Pango::Rectangle: #{x}, #{y}, #{width} x #{height}>"
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/gui/gtk_utils/pango_tools.rb b/manufacturing/TravisProg1/lib_files/gui/gtk_utils/pango_tools.rb
new file mode 100755
index 0000000..650a91e
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/gui/gtk_utils/pango_tools.rb
@@ -0,0 +1,28 @@
+ENV["FONTCONFIG_FILE"] = "/tmp/fontcfg"
+require "gtk3"
+$layout_creator = Pango::Context.new()
+$layout_creator.font_map = Pango::CairoFontMap.default
+Desc = Pango::FontDescription.new(
+ RUBY_PLATFORM =~ /linux/ ? "Fixed Medium Semi-Condensed 10"
+#: "Lucida Console Normal 9")
+: "Lucida Sans Unicode Normal 12")
+
+def get_default_pango()
+ return Pango::Layout.new($layout_creator)
+end
+def update_pango_context(cr)
+ cr.update_pango_context($layout_creator)
+end
+
+def get_desc_pango()
+ p = get_default_pango()
+ p.set_font_description(Desc)
+ return p
+end
+
+def get_text_desc_pango(txt)
+ p = get_default_pango()
+ p.set_font_description(Desc)
+ p.set_text(txt)
+ return p
+end
diff --git a/manufacturing/TravisProg1/lib_files/gui/gui_lib/ev_window.rb b/manufacturing/TravisProg1/lib_files/gui/gui_lib/ev_window.rb
new file mode 100755
index 0000000..a2dd95d
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/gui/gui_lib/ev_window.rb
@@ -0,0 +1,112 @@
+require "gtk3"
+
+require "gui/gtk_utils/pango_tools.rb"
+require "gui/gtk_utils/colors.rb"
+require "gui/gtk_utils/clipping.rb"
+
+class EventWindow
+ def initialize(wi = 640, hi = 480)
+ w = Gtk::Window.new("Window name")
+ w.set_size_request(wi, hi)
+ w.move(800, 0)
+ w.add(@d = Gtk::DrawingArea.new())
+ w.signal_connect("delete_event") { Gtk.main_quit }
+ @needs_draw = false
+ # Keep the last 2 because of ref-counting issues.
+ # I suspect that the update_pango_context which stores a
+ # reference to the cairo_context in the pango context no longer
+ # maintains that reference.
+ # This is a problem currently for ruby version:
+ # ruby 2.3.3p222 (2016-11-21 revision 56859) [x64-mingw32]
+ # and gems:
+ # gtk3-3.1.0-x64-mingw32
+ # cairo-gobject-3.1.0-x64-mingw32
+ # cairo-1.15.4-x64-mingw32
+ # pango-3.1.0-x64-mingw32
+ # Sat 01/14/2017
+ # Parker, Michael, & Travis
+ @leak_cr = []
+ @d.signal_connect("draw") { |w, cr|
+ # @leak_cr.push(cr)
+ # @leak_cr.unshift if (@leak_cr.length() > 2)
+ update_pango_context(cr)
+ Color.new(0.1,0.1,0.1).set(cr)
+ cr.paint()
+ draw(cr)
+# obj.draw(cr)
+ @needs_draw = true
+ }
+ GC.start()
+ @d.set_events(Gdk::EventMask::ALL_EVENTS_MASK &
+ ~Gdk::EventMask::SMOOTH_SCROLL_MASK)
+
+ @d.set_can_focus(true)
+ @d.set_can_default(true)
+ @d.set_focus(true)
+
+ [["button_press_event", :button_press_event],
+ ["button_release_event", :button_release_event],
+ ["motion_notify_event", :motion_notify_event],
+ ["key_press_event", :key_press],
+ ["key_release_event", :key_release],
+ ["scroll_event", :scroll_event],
+ ["configure_event", :configure_event],
+ ].each do |evname, evtype|
+ if respond_to?(evtype)
+ @d.signal_connect(evname) { |w, e|
+ send(evtype, e)
+ }
+ elsif has_event_delegate(evtype)
+ @d.signal_connect(evname) { |w, e|
+ ev_delegate_send(evtype, e)
+ }
+ end
+ end
+ w.show_all()
+ end
+ def has_event_delegate(evtype) return false end
+ def redraw()
+ if (@needs_draw)
+ @d.queue_draw()
+ @needs_draw = false
+ end
+ end
+ attr_accessor :widget
+end
+
+class EvWindow < EventWindow
+ def initialize(obj, *args)
+ @obj = obj
+ super(*args)
+ end
+ def has_event_delegate(evtype)
+ @obj.respond_to?(evtype)# || @obj.has_event_delegate(evtype)
+ end
+ def ev_delegate_send(evtype, ev)
+ @obj.send(evtype, ev)
+ redraw()
+ end
+ def configure_event(ev)
+ changed = false
+ if (@bbox == nil)
+ changed = true
+ @bbox = SClipBBox.new(0,0,ev.width, ev.height)
+ end
+ if @bbox.w != ev.width
+ @bbox.w = ev.width
+ changed = true
+ end
+ if @bbox.h != ev.height
+ @bbox.h = ev.height
+ changed = true
+ end
+ @obj.resize(@bbox) if changed && @obj.respond_to?(:resize)
+ end
+ def draw(cr)
+ @obj.draw(cr)
+ end
+ def self.run(w, *args)
+ self.new(w, *args)
+ Gtk.main()
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/gui/gui_lib/line_edit.rb b/manufacturing/TravisProg1/lib_files/gui/gui_lib/line_edit.rb
new file mode 100755
index 0000000..3e9b654
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/gui/gui_lib/line_edit.rb
@@ -0,0 +1,285 @@
+require "gtk3"
+
+require "gui/gtk_utils/colors.rb"
+require "gui/gtk_utils/pango_inspect.rb"
+require "gui/gtk_utils/clipping.rb"
+require_relative "text_edit_event.rb"
+
+class ExpLine
+ def initialize(txt)
+ @lay = get_desc_pango()
+ @txt = txt
+ update_txt()
+ end
+ def empty() return @txt.length == 0 end
+ def draw(cr, cursor = nil, x = 0, y = 0)
+ Color::Yellow.set(cr)
+ cr.update_pango_layout(@lay)
+
+ if (cursor)
+ attrs = Pango::AttrList.new()
+ attrs.insert(Color::Black.pattr_fg(cursor, cursor + 1))
+ @lay.attributes = attrs
+ p = @lay.index_to_pos(cursor)
+ Color::Yellow.set(cr)
+ ox, oy = x + p.x / Pango::SCALE, y + p.y / Pango::SCALE
+ cr.rectangle(ox, oy, 6, 13)
+ cr.fill()
+ end
+
+ cr.move_to(x, y)
+ cr.show_pango_layout(@lay)
+ @lay.attributes = nil if (cursor)
+ end
+
+ def height() return @lay.pixel_extents[1].height end
+
+ def update_txt() @lay.set_text(@txt) end
+
+ def key_press(ev, cursor)
+ TextEditEvent.key_press(self, @txt, ev, cursor)
+ end
+ def get_cursor_xpos(cursor)
+ p = @lay.index_to_pos(cursor)
+ return p.x / Pango::SCALE + (@xoff || 0)
+ end
+ def get_cursor_x(x)
+ x -= (@xoff || 0)
+ inside, index, trailing = @lay.xy_to_index(x * Pango::SCALE, 0)
+ index += 1 if index + 1 <= @txt.length && x > 0 && !inside
+ return index
+ end
+ def get_bbox(x, y)
+ ext = @lay.pixel_extents[1]
+ return SClipBBox.new(x + @xoff, y, ext.width + 6, ext.height)
+ end
+ attr_accessor :cursor, :xoff
+end
+
+class GenericFormattedLine
+ def draw(cr, cursor = nil, x = 0, y = 0)
+ Color::Yellow.set(cr)
+ cr.update_pango_layout(@lay)
+
+ if (cursor)
+ cursor = cursor[0] if cursor.class != Integer
+ attrs = Pango::AttrList.new()
+ attrs.insert(Color::Black.pattr_fg(cursor, cursor + 1))
+ @lay.attributes = attrs
+ p = @lay.index_to_pos(cursor)
+ Color::Yellow.set(cr)
+ ox, oy = x + p.x / Pango::SCALE, y + p.y / Pango::SCALE
+ cr.rectangle(ox, oy, 6, 13)
+ cr.fill()
+ end
+
+ cr.move_to(x, y)
+ cr.show_pango_layout(@lay)
+ @lay.attributes = nil if (cursor)
+ end
+ def height() return @lay.pixel_extents[1].height end
+ def key_press(ev, cursor)
+ cur = cursor[0]
+ g = 0
+ items = get_items()
+ items.each do |prefix, p, i|
+ g += prefix.length
+ cur = cursor[0] - g
+ if cur <= p.length
+ curref = [cur]
+ if !TextEditEvent.key_press(self, p, ev, curref)
+ k = ev.keyval
+ if (k == Gdk::Keyval::KEY_Right && i < (items.length - 1))
+ cursor[0] = g + p.length + items[i + 1][0].length
+ elsif (k == Gdk::Keyval::KEY_Left && i > 0)
+ cursor[0] = g - prefix.length
+ else
+ return false
+ end
+ return true
+ end
+ cursor[0] = g + curref[0]
+ return true
+ end
+ g += p.length
+ end
+ return false
+ end
+ def get_cursor_xpos(cursor)
+ p = @lay.index_to_pos(cursor)
+ return p.x / Pango::SCALE + (@xoff || 0)
+ end
+ def cap_length(index, len)
+ index = 0 if index < 0
+ index = len if index >= len
+ return index
+ end
+ def get_cursor_x(x)
+ x -= (@xoff || 0)
+ inside, index, trailing = @lay.xy_to_index(x * Pango::SCALE, 0)
+ items = get_items()
+ index += 1 if !inside
+ g = 0
+ items.each do |prefix, p, i|
+ next_prefix = items.length - 1 > i ? items[i + 1][0] : nil
+
+ g += prefix.length
+ if (!next_prefix || index - g < p.length + 1 + next_prefix.length / 2)
+ return g + cap_length(index - g, p.length)
+ end
+ g += p.length
+ end
+ end
+ def get_bbox(x, y)
+ ext = @lay.pixel_extents[1]
+ return SClipBBox.new(x + @xoff, y, ext.width + 6, ext.height)
+ end
+ attr_accessor :cursor, :xoff
+end
+
+class ForStartLine < GenericFormattedLine
+ A = "for ("
+ B = "; "
+ C = "; "
+ D = "):"
+ def initialize(a, b, c)
+ @a = a
+ @b = b
+ @c = c
+ @lay = get_desc_pango()
+ update_txt()
+ end
+ def update_txt()
+ @lay.set_text(A + @a + B + @b + C + @c + D)
+ end
+ def get_items()
+ return [[A, @a, 0], [B, @b, 1], [C, @c, 2]]
+ end
+end
+
+class IfStartLine < GenericFormattedLine
+ A = "if ("
+ D = "):"
+ def initialize(a)
+ @a = a
+ @lay = get_desc_pango()
+ update_txt()
+ end
+ def update_txt()
+ @lay.set_text(A + @a + D)
+ end
+ def get_items()
+ return [[A, @a, 0]]
+ end
+end
+
+class ElseIfStartLine < GenericFormattedLine
+ A = "else if ("
+ D = "):"
+ def initialize(a)
+ @a = a
+ @lay = get_desc_pango()
+ update_txt()
+ end
+ def update_txt()
+ @lay.set_text(A + @a + D)
+ end
+ def get_items()
+ return [[A, @a, 0]]
+ end
+end
+class ElseStartLine < GenericFormattedLine
+ A = "else:"
+ def initialize()
+ @lay = get_desc_pango()
+ update_txt()
+ end
+ def update_txt()
+ @lay.set_text(A)
+ end
+ def key_press(ev, cursor) return false end
+ def get_cursor_x(x) return A.length end
+end
+
+class FunctionStartLine < GenericFormattedLine
+ A = "function "
+ B = "("
+ C = ") -> "
+ D = ":"
+ def initialize(fn)
+ @name = fn.name
+ @args = fn.args
+ @ret_t = fn.ret_t
+ @lay = get_desc_pango()
+ update_txt()
+ end
+ def update_txt()
+ @lay.set_text(A + @name + B + @args + C + @ret_t + D)
+ end
+ def get_items()
+ return [[A, @name, 0], [B, @args, 1], [C, @ret_t, 2]]
+ end
+end
+
+class ClassStartLine < GenericFormattedLine
+ A = "class "
+ B = ":"
+ def initialize(cls)
+ @name = cls.name
+ @lay = get_desc_pango()
+ update_txt()
+ end
+ def update_txt()
+ @lay.set_text(A + @name + B)
+ end
+ def get_items()
+ return [[A, @name, 0]]
+ end
+end
+
+def compile_suite_to(suite, lines, xoff = 0)
+ suite.each do |item|
+ item.compile_to(lines, xoff)
+ end
+end
+
+
+
+class ExpStmt
+ def compile_to(lines, xoff)
+ lines << exp = ExpLine.new(@exp)
+ exp.xoff = xoff
+ end
+end
+
+class IfStmt
+ def compile_to(lines, xoff)
+ i = 0
+ while (i < @suites.length || i < @conds.length)
+ suite = @suites[i] ||= []
+ cond = @conds[i]
+ if i == 0
+ lines << if_stmt = IfStartLine.new(cond)
+ if_stmt.xoff = xoff
+ else
+ if (cond)
+ lines << if_stmt = ElseIfStartLine.new(cond)
+ if_stmt.xoff = xoff
+ else
+ lines << if_stmt = ElseStartLine.new()
+ if_stmt.xoff = xoff
+ end
+ end
+ compile_suite_to(suite, lines, xoff + 10)
+ i += 1
+ end
+ end
+end
+
+class ForStmt
+ def compile_to(lines, xoff)
+ lines << f_stmt = ForStartLine.new(@a, @b, @c)
+ f_stmt.xoff = xoff
+ compile_suite_to(@suite, lines, xoff + 10)
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/gui/gui_lib/text_edit_event.rb b/manufacturing/TravisProg1/lib_files/gui/gui_lib/text_edit_event.rb
new file mode 100755
index 0000000..261572c
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/gui/gui_lib/text_edit_event.rb
@@ -0,0 +1,29 @@
+class TextEditEvent
+ def self.key_press(obj, txt, ev, cursor)
+ k = ev.keyval
+ if (k >= 32 && k <= 126)
+ txt.insert(cursor[0], k.chr)
+ obj.update_txt()
+ cursor[0] += 1
+ elsif k == Gdk::Keyval::KEY_Right && cursor[0] < txt.length
+ cursor[0] += 1
+ elsif k == Gdk::Keyval::KEY_BackSpace && cursor[0] > 0
+ cursor[0] -= 1
+ txt.slice!(cursor[0], 1)
+ obj.update_txt()
+ elsif k == Gdk::Keyval::KEY_Delete && cursor[0] < txt.length
+ txt.slice!(cursor[0], 1)
+ obj.update_txt()
+ elsif k == Gdk::Keyval::KEY_Left && cursor[0] > 0
+ cursor[0] -= 1
+ elsif k == Gdk::Keyval::KEY_Home
+ cursor[0] = 0
+ elsif k == Gdk::Keyval::KEY_End
+ cursor[0] = txt.length
+ else
+ return false
+ end
+ return true
+ end
+end
+
diff --git a/manufacturing/TravisProg1/lib_files/tokens/tokenizer_lib_base.rb b/manufacturing/TravisProg1/lib_files/tokens/tokenizer_lib_base.rb
new file mode 100755
index 0000000..02b7543
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/tokens/tokenizer_lib_base.rb
@@ -0,0 +1,148 @@
+class StringBuffer
+ def is_buffered(); true; end
+ def initialize(string)
+ @string = string
+ @line = 1
+ @col_nums = []
+ @col = 0
+ @i = 0
+ end
+ def lineno()
+ return @line
+ end
+ def filename
+ return @filename || "<string>"
+ end
+ def pos()
+ "#{filename()}:#{lineno()},#{@col}"
+ end
+ def pop_char()
+ val = @string[@i,1]
+ @i += 1
+ @col_nums[@line] = @col += 1
+ if(val == "\n")
+ @line += 1
+ @col = 0
+ end
+ return val
+ end
+ def unpop_char(char)
+ @i -= 1
+ if(char == "\n")
+ @line -= 1
+ @col = @col_nums[@line]
+ end
+ @col -= 1
+ end
+end
+class BufferedReader
+ def is_buffered(); true; end
+ def initialize(file)
+ @file = file
+ @line = 1
+ @chars = []
+ @col_nums = []
+ @col = 0
+ @i = 0
+ @charbuf_i = 1e9
+ @ti = 0
+ end
+ def path()
+ @file.path
+ end
+ def filename
+ return File.basename(@file.path)
+ end
+ def pos()
+ "#{filename()}:#{lineno()},#{@col}"
+ end
+ def clear_comment()
+ @file.gets
+ @line += 1
+ end
+ def lineno()
+ return @line
+ return @file.lineno + 1
+ end
+ def read_next_char()
+ if @charbuf_i >= 2048
+ @charbuf = @file.read(2048)
+ @ti += 2048
+ @charbuf_i = 0
+ end
+ return nil if @charbuf == nil
+ val = @charbuf[@charbuf_i]
+ @charbuf_i += 1
+ return val
+ end
+ def pop_char()
+ @i += 1
+ val = @chars.pop() || read_next_char()
+ @col_nums[@line] = @col += 1
+ if(val == "\n")
+ @line += 1
+ @col = 0
+ end
+
+ return val
+ end
+ def unpop_char(char)
+ @i -= 1
+ if(char == "\n")
+ @line -= 1
+ @col = @col_nums[@line]
+ end
+ @col -= 1
+ @chars.push(char)
+ end
+ def get_data()
+ ie = @i - @ti + 2048
+ is = @old_i - @ti + 2048
+ if (is > 0)
+ return @charbuf[is...ie]
+ end
+ #puts "get_data: #{is} #{ie}"
+ @file.seek(@old_i, IO::SEEK_SET)
+ #@chars = []
+ val = @file.read(@i - @old_i)
+ @file.seek(@ti, IO::SEEK_SET)
+ return val
+ end
+ def mark_data()
+ @old_i = @i - 1
+ end
+end
+require "yaml"
+class TokensConvert
+ def self.unEscapeString(str)
+ return YAML.load(%Q(---\n#{str}\n))
+ end
+ def self.toFloat(str)
+ return str.to_f
+ end
+ def self.toInteger(str)
+ return str.to_i
+ end
+end
+class Module
+ def init_fields(*fields)
+ fn = "def initialize(#{fields.join(",")})\n"
+ fn += "super()\n"
+ fields.each do |fname|
+ fn += "@#{fname} = #{fname}\n"
+ end
+ fn += "end\n"
+ self.class_eval(fn)
+ attr_accessor(*fields)
+ end
+ def init_fields_ext(*fields)
+ fn = "def initialize(#{fields.join(",")})\n"
+ fields.each do |fname|
+ fn += "@#{fname} = #{fname}\n"
+ end
+ fn += "initialize_extended()"
+ fn += "end\n"
+ self.class_eval(fn)
+ attr_accessor(*fields)
+ end
+end
diff --git a/manufacturing/TravisProg1/lib_files/tools/repo_utils/unrepoize.rb b/manufacturing/TravisProg1/lib_files/tools/repo_utils/unrepoize.rb
new file mode 100755
index 0000000..4c39eb4
--- /dev/null
+++ b/manufacturing/TravisProg1/lib_files/tools/repo_utils/unrepoize.rb
@@ -0,0 +1,35 @@
+def unrepoize(extern_path, target)
+ if ARGV[0] == "-unrepoize"
+ $LOAD_PATH[0] = "~/repo"
+ require 'fileutils'
+ ARGV.slice!(0..0)
+ extern_path = File.expand_path(extern_path)
+ repo_path = File.expand_path("~/repo")
+ target = extern_path + target
+ exp = [File.expand_path("~/repo/tools/repo_utils/unrepoize.rb")]
+
+# puts target
+ $unrepoize_handler = Proc.new() {
+ (exp + $").each do |f|
+ #f = String.new(f)
+ #puts f.inspect, f.class
+# FileUtils.mkpath(target)
+ if (f.start_with?(repo_path) && !f.start_with?(extern_path))
+ sf = f
+ tf = target + f[repo_path.length..-1]
+ FileUtils.mkpath(File.dirname(tf))
+ FileUtils.cp(sf, tf)
+ #puts "unrepoize #{tf}"
+ else
+ #puts "skipping: #{f.inspect}"
+ end
+ end
+ }
+ end
+end
+
+def unrepoize_finish()
+ if $unrepoize_handler
+ $unrepoize_handler.call()
+ end
+end
diff --git a/manufacturing/TravisProg1/travis_prog.rb b/manufacturing/TravisProg1/travis_prog.rb
new file mode 100755
index 0000000..d0ad046
--- /dev/null
+++ b/manufacturing/TravisProg1/travis_prog.rb
@@ -0,0 +1,3 @@
+$LOAD_PATH.unshift(File.dirname(__FILE__) + "/lib_files")
+require "geometry/router/nester.rb"
+Router::do_nesting()