![DSC_0430[1] - コピー](https://i0.wp.com/kuriuzu.io/wp-content/uploads/2017/06/dsc_04301-e382b3e38394e383bc.jpg?resize=1100%2C631&ssl=1)

基板加工の流れ
- KiCADで基板を設計する
- KiCADからガーバーデータを出力する
- ガーバーデータをGynostemmaでiModela制御コマンドに変換する
- VirtualMODELAで結果を確認する
- iModela Controllerで変換したデータを使って切削
基板加工のための刃物
一番いいのは、オリジナルマインドで販売している土佐昌典VC。ただし、iModelaで土佐昌典VCを使うためには、シャンク径が違うためiModelaのコレットの改造が必要。
土佐昌典VCは本当にすばらしい。細くてシャープな線を切削してくれる。さすが基板専用カッターは違うと感動を覚えた。これは追加購入決定ですわ。
ここまでシャープだと表面実装もイケそう。すばらしい。
無改造iModelaで良い結果を得られたのは、0.3mmまたは0.4mmのボールエンドミル。土佐昌典VCと比較すると、折れやすい、切削速度が遅い、調整が難しい、細かいパターンが表現できない、バリが出やすい、寿命が短い、という問題がある。こういった点に気を付ければ使えるレベルではある。
iModelaのコレットの改造
iModelaのコレットの直径は標準だと2.35mmである。しかし、土佐昌典VCの直径は3.175mmである。つまり、直径が異なるので使うことができない。そこで、コレットを旋盤で加工し、穴を3.2mmに広げることで土佐昌典VCを使えるように改造した。
![DSC_0427[1] - コピー](https://i0.wp.com/kuriuzu.io/wp-content/uploads/2017/06/dsc_04271-e382b3e38394e383bc1.jpg?resize=255%2C300&ssl=1)
![DSC_0421[1] - コピー](https://i0.wp.com/kuriuzu.io/wp-content/uploads/2017/06/dsc_04211-e382b3e38394e383bc.jpg?resize=300%2C268&ssl=1)
わざわざ加工なんぞしなくても直径3.175mmのコレットを販売している会社はあったのだが、今はもう販売していないようだ。
Gynostemma RML-1 MOD
rerofumi氏が作成した「Gynostemma」という、ガーバーデータをGコードに変換するフリーソフトがある。今回は、GynostemmaをModelaシリーズの制御コマンド「RML-1」に対応させる改造を行った。
iModelaはわざわざRML-1に変換しなくてもGコードを直接読み込めるらしいのでこの改造はVirtual MODELAが使えるくらいしかメリットがない、かもしれない。
perf.iniとclass\gcode_formatter.rbを下記内容で上書きしてください。
perf.iniは土佐昌典VC用の設定になっています。
perf.ini
[sourcecode]
resolution 24
pass 1
cuttersize 0.25
patternfeed 0.5
patterndepth 0.06
outlinefeed 0.3
outlinelayer 0.4
boardthin 1.65
drilldepth 2.3
drilldwell 0
drillinputfix 4
threadnum 8
[/sourcecode]
class\gcode_formatter.rb
[sourcecode]
require ‘draw_primitive.rb’
# ————————————————————
# — utils
# ————————————————————
module PathLineUtil
def match_line(lineary, x, y)
result = -1
pp = PPoint.new
pp.x = x
pp.y = y
pp.z = 0
for i in 0…lineary.size
item = lineary.at(i)
p1 = PPoint.new
p2 = PPoint.new
p1.x = item[‘x1’]
p1.y = item[‘y1’]
p1.z = 0
p2.x = item[‘x2’]
p2.y = item[‘y2’]
p2.z = 0
if (p1 == pp) || (p2 == pp) then
result = i
break
end
end
return result
end
def nearest_line(lineary, x, y)
result = -1
near = 100000000.0
for i in 0…lineary.size
item = lineary.at(i)
vec1 = PPoint.new(item[‘x1’]-x, item[‘y1’]-y)
if vec1.length < near then
result = i
near = vec1.length
end
vec2 = PPoint.new(item[‘x2’]-x, item[‘y2’]-y)
if vec2.length < near then
result = i
near = vec2.length
end
end
return result
end
def align_line(lineitem, x, y)
match = false
if ((lineitem[‘x1’] == x) && (lineitem[‘y1’] == y)) ||
((lineitem[‘x2’] == x) && (lineitem[‘y2’] == y)) then
match = true
if (lineitem[‘x2’] == x) && (lineitem[‘y2’] == y) then
tx = lineitem[‘x1’]
ty = lineitem[‘y1’]
lineitem[‘x1’] = lineitem[‘x2’]
lineitem[‘y1’] = lineitem[‘y2’]
lineitem[‘x2’] = tx
lineitem[‘y2’] = ty
end
else
vec1 = PPoint.new(lineitem[‘x1’]-x, lineitem[‘y1’]-y)
vec2 = PPoint.new(lineitem[‘x2’]-x, lineitem[‘y2’]-y)
if vec2.length < vec1.length then
tx = lineitem[‘x1’]
ty = lineitem[‘y1’]
lineitem[‘x1’] = lineitem[‘x2’]
lineitem[‘y1’] = lineitem[‘y2’]
lineitem[‘x2’] = tx
lineitem[‘y2’] = ty
end
end
return match
end
end
# ————————————————————
# — GCode formatter for export
# ————————————————————
class GCodeFormatter
include PathLineUtil
def initialize
@linedata = []
@seqdata = []
@offset_x = 0.0
@offset_y = 0.0
@float_height = 2.0
@layer_height = -0.4
@mil_depth = -0.2
@mil_feed = 0.2
@spin_speed = 3600
@position_x = 0.0
@position_y = 0.0
@position_z = 0.0
@near_collect = 0.001
@is_build = false
@is_home = true
end
attr_accessor :offset_x
attr_accessor :offset_y
attr_accessor :float_height
attr_accessor :mil_depth
attr_accessor :mil_feed
attr_accessor :spin_speed
attr_accessor :layer_height
attr_accessor :near_collect
def clear
@linedata.clear
@seqdata.clear
@offset_x = 0.0
@offset_y = 0.0
@float_height = 10.0
@position_x = 0.0
@position_y = 0.0
@is_build = false
@h_mirror = 1.0
end
def rebuild
@is_build = false
end
def add_line(x1, y1, x2, y2)
pos = {}
pos[‘x1’] = x1
pos[‘y1’] = y1
pos[‘x2’] = x2
pos[‘y2’] = y2
@linedata << pos
@is_build = false
end
def calc_offset
@offset_x = 10000000.0
@offset_y = 10000000.0
@linedata.each do |item|
if item[‘x1’] < @offset_x then
@offset_x = item[‘x1’]
end
if item[‘x2’] < @offset_x then
@offset_x = item[‘x2’]
end
if item[‘y1’] < @offset_y then
@offset_y = item[‘y1’]
end
if item[‘y2’] < @offset_y then
@offset_y = item[‘y2’]
end
end
end
def add_seq_move(lineitem)
if ((lineitem[‘x1’] – @position_x).abs < @near_collect) &&
((lineitem[‘y1’] – @position_y).abs < @near_collect) &&
(@is_home == false) then
pos = {}
pos[‘g’] = ‘G01’
pos[‘x’] = lineitem[‘x2’]
pos[‘y’] = lineitem[‘y2’]
pos[‘z’] = @position_z
@seqdata << pos
else
pos = {}
pos[‘g’] = ‘G01’
pos[‘x’] = @position_x
pos[‘y’] = @position_y
pos[‘z’] = @float_height
@seqdata << pos
pos = {}
pos[‘g’] = ‘G00’
pos[‘x’] = lineitem[‘x1’]
pos[‘y’] = lineitem[‘y1’]
pos[‘z’] = @float_height
@seqdata << pos
pos = {}
pos[‘g’] = ‘G01’
pos[‘x’] = lineitem[‘x1’]
pos[‘y’] = lineitem[‘y1’]
pos[‘z’] = @position_z
@seqdata << pos
pos = {}
pos[‘g’] = ‘G01’
pos[‘x’] = lineitem[‘x2’]
pos[‘y’] = lineitem[‘y2’]
pos[‘z’] = @position_z
@seqdata << pos
end
@position_x = lineitem[‘x2’]
@position_y = lineitem[‘y2’]
@is_home = false
end
def prepare_seq
pos = {}
pos[‘g’] = ‘G00’
pos[‘x’] = 0.0
pos[‘y’] = 0.0
pos[‘z’] = @float_height
@seqdata << pos
@is_home = true
end
def finish_seq
pos = {}
pos[‘g’] = ‘G01’
pos[‘x’] = @position_x
pos[‘y’] = @position_y
pos[‘z’] = @float_height
@seqdata << pos
pos = {}
pos[‘g’] = ‘G00’
pos[‘x’] = 0.0
pos[‘y’] = 0.0
pos[‘z’] = @float_height
@seqdata << pos
@position_x = 0.0
@position_y = 0.0
@is_home = true
end
def build_path
@seqdata.clear
@position_z = 0.0
prepare_seq
while @position_z > @mil_depth do
# — depth
if (@mil_depth – @position_z) < @layer_height then
@position_z += @layer_height
else
@position_z = @mil_depth
end
# — path
workline = []
@linedata.each do |item|
citem = {}
citem[‘x1’] = item[‘x1’] – @offset_x
citem[‘y1’] = item[‘y1’] – @offset_y
citem[‘x2’] = item[‘x2’] – @offset_x
citem[‘y2’] = item[‘y2’] – @offset_y
workline << citem
end
# make path
while workline.size > 0 do
index = match_line(workline, @position_x, @position_y)
if index == -1 then
index = nearest_line(workline, @position_x, @position_y)
end
lineitem = workline.at(index)
workline.delete_at(index)
align_line(lineitem, @position_x, @position_y)
add_seq_move(lineitem)
end
finish_seq
end
@is_build = true
end
def save(filename)
open(filename, "w") do |fp|
fp << get_gcode_header
#
fp << get_path
#
fp << get_gcode_footer
end
end
def get_path
if @is_build == false then
build_path
end
build_seq = ""
@seqdata.each do |item|
if item[‘g’] == "G00" then
build_seq << sprintf("V4.0;\n")
else
build_seq << sprintf("V%.1f;",@mil_feed);
end
build_seq << sprintf("Z%d,%d,%d;\n",
item[‘x’]*100, item[‘y’]*100, item[‘z’]*100)
end
return build_seq
end
def get_gcode_header
gcode = ""
gcode << sprintf(";;^IN;\n")
gcode << sprintf("!MC1;\n")
gcode << sprintf("V4.0;");
gcode << sprintf("^PR;");
gcode << sprintf("Z0,0,100;\n")
gcode << sprintf("^PA;");
return gcode
end
def get_gcode_footer
gcode = ""
gcode << sprintf("!VZ4.0;!ZM0;\n")
gcode << sprintf("!MC0;^IN;\n")
gcode << sprintf("\n")
return gcode
end
end
# ————————————————————
# — Drill
# ————————————————————
class GCodeDrill < GCodeFormatter
def initialize
super
@layer_height = -5.0
@mil_depth = -2.0
@mil_feed = 0.2
@tool_set = []
@tool_size = {}
@position = []
@scale = 100
@dwell = 0
@mil_size = 0.5
end
attr_reader :position
attr_accessor :scale
attr_accessor :dwell
attr_accessor :mil_size
attr_accessor :tool_set
attr_accessor :tool_size
def load(filename, h_mirror=1.0)
drill_size = 0
is_inch = false
@seqdata.clear
@position.clear
open(filename, "r") do |fp|
prepare_seq
fp.each do |line|
if line=~ /^INCH,/ then
is_inch = true
end
if line=~ /^METRIC,/ then
is_inch = false
end
if line=~ /^T([-0-9]+)/ then
drill_size = $1.to_i
if @tool_set.include?(drill_size) == false then
@tool_set << drill_size
@tool_size["#{drill_size}"] = 0.5
end
end
# — XY
if line=~ /^X([-0-9]+)Y([-0-9]+)/ then
a = $1
b = $2
@position_x = (a.to_f / @scale) * h_mirror
@position_y = (b.to_f / @scale)
if is_inch == true then
@position_x *= 25.4
@position_y *= 25.4
end
@position_x -= @offset_x
@position_y -= @offset_y
add_drill(drill_size)
# — X
elsif line=~ /^X([-0-9]+)/ then
a = $1
@position_x = (a.to_f / @scale) * h_mirror
if is_inch == true then
@position_x *= 25.4
end
@position_x -= @offset_x
add_drill(drill_size)
# — Y
elsif line=~ /^Y([-0-9]+)/ then
b = $1
@position_y = (b.to_f / @scale)
if is_inch == true then
@position_y *= 25.4
end
@position_y -= @offset_y
add_drill(drill_size)
end
end
finish_seq
@is_build = true
end
end
def add_drill(drill_size)
pos = {}
pos[‘g’] = ‘G00’
pos[‘x’] = @position_x
pos[‘y’] = @position_y
pos[‘z’] = @float_height
pos[‘tool’] = drill_size
@seqdata << pos
pos = {}
pos[‘g’] = ‘G01’
pos[‘x’] = @position_x
pos[‘y’] = @position_y
pos[‘z’] = @mil_depth
pos[‘tool’] = drill_size
@seqdata << pos
if @dwell > 0 then
pos = {}
pos[‘g’] = ‘G04’
pos[‘x’] = @position_x
pos[‘y’] = @position_y
pos[‘z’] = @mil_depth
pos[‘msec’] = @dwell
pos[‘tool’] = drill_size
@seqdata << pos
end
pos = {}
pos[‘g’] = ‘G01’
pos[‘x’] = @position_x
pos[‘y’] = @position_y
pos[‘z’] = @float_height
pos[‘tool’] = 0
@seqdata << pos
pos = {}
pos[‘g’] = ‘G00’
pos[‘x’] = @position_x
pos[‘y’] = @position_y
pos[‘z’] = @float_height
pos[‘tool’] = drill_size
@seqdata << pos
pos = {}
pos[‘x’] = @position_x
pos[‘y’] = @position_y
pos[‘tool’] = drill_size
@position << pos
end
def save_drill(filename)
@tool_set.each do |tsize|
workseq = routing_tool(@seqdata, tsize)
#
dotsp = filename.split(‘.’)
last = dotsp.size
if last > 1 then
dotsp[last-2] = sprintf("%s_t%02d", dotsp[last-2], tsize)
end
outputname = dotsp.join(‘.’)
output(outputname, workseq)
end
end
def output(filename, seqdata)
open(filename, "w") do |fp|
printf(fp, ";;^IN;\n")
printf(fp, "!MC1;\n")
printf(fp, "V4.0;^PR;\n")
printf(fp, "V4.0;Z0,0,100;\n")
printf(fp, "^PA;\n")
#
seqdata.each do |item|
if item[‘g’] == ‘G04’ then
elsif item[‘g’] == ‘G00’ then
printf(fp, "V%.1f;\nZ%d,%d,%d;\n",
4.0,item[‘x’]*100, item[‘y’]*100, item[‘z’]*100)
else
printf(fp, "V%.1f;\nZ%d,%d,%d;\n",
@mil_feed,item[‘x’]*100, item[‘y’]*100, item[‘z’]*100)
end
end
#
printf(fp, "!VZ4.0;!ZM0;\n")
printf(fp, "!MC0;^IN;\n")
printf(fp, "\n")
end
end
def routing_tool(allseq, tool)
route = []
workseq = []
allseq.each do |point|
if point[‘tool’] == tool then
workseq << point
end
end
current = PPoint.new(0.0, 0.0)
while workseq.size > 0 do
index = count = 0
near = PPoint.new(workseq[0][‘x’], workseq[0][‘y’]).length
workseq.each do |nextseq|
dist = PPoint.new(nextseq[‘x’]-current.x, nextseq[‘y’]-current.y).length
if dist < near then
index = count
near = dist
end
count += 1
end
route << workseq.at(index)
current.x = workseq[index][‘x’]
current.y = workseq[index][‘y’]
workseq.delete_at(index)
end
return route
end
def routing(workseq)
route = []
current = PPoint.new(0.0, 0.0)
while workseq.size > 0 do
index = count = 0
near = PPoint.new(workseq[0][‘x’], workseq[0][‘y’]).length
workseq.each do |nextseq|
dist = PPoint.new(nextseq[‘x’]-current.x, nextseq[‘y’]-current.y).length
if dist < near then
index = count
near = dist
end
count += 1
end
route << workseq.at(index)
current.x = workseq[index][‘x’]
current.y = workseq[index][‘y’]
workseq.delete_at(index)
end
return route
end
end
# ————————————————————
# — One Opalation Drill
# ————————————————————
class GCodeDrillOneOpelation < GCodeDrill
def build_drill_path(round_resolution)
@seqdata.clear
@linedata.clear
workpos = routing(@position)
workpos.each do |item|
size = (@tool_size["#{item[‘tool’].to_s}"] – @mil_size) / 2.0
if size > 0 then
add_drillpath(item[‘x’], item[‘y’], size, round_resolution)
end
end
#
@is_build = false
build_path
end
def add_drillpath(x, y, size, res)
p1 = PPoint.new(size, 0, 0)
p2 = PPoint.new(size, 0, 0)
rot = 360.0 / res.to_f
p2.rotate_z(rot)
for i in 0…res
add_line(x+p1.x, y+p1.y, x+p2.x, y+p2.y)
p1.rotate_z(rot)
p2.rotate_z(rot)
end
end
end
[/sourcecode]