레이블이 ROBOT인 게시물을 표시합니다. 모든 게시물 표시
레이블이 ROBOT인 게시물을 표시합니다. 모든 게시물 표시

2015년 6월 3일 수요일

FGPG_V11 ::: Introduce of Fine Involute Gear Tooth Profile Generator


FGPG_V11


새로운 인볼류트 기어 설계용 프로그램을 작성해 보았다.
기존의 GPG_V08을 대체한다는 의미로 FGPG 라고 이름을 붙였다.
Fine Gear Profile Generator 의 줄임말이다.
아직 미완성본이지만, 일단 필요한 기능들은 대부분 어설프게나마 다 구현되었으므로 기록 차원에서 올려본다.

특징은 대략 다음과 같다.

1. Julia Language로 작성되었다.
2. 아직 독립 실행파일(Standalone Executable)은 빌드하지 않았다.
3. Julia를 이용하여 Python MatPlotLib의 플랏팅 능력을 활용한다.
4. Gmsh 및 Elmer를 이용하여 자동적으로 FEM 해석을 실시해 주는 Batch 파일을 만들어준다.  윈도우용 뿐만 아니라, 리눅스용 Shell Script도 만들어지므로 운영체제에 상관없이 사용 가능하다.
5. 입력하는 파라미터들은 별도의 파일로 작성해 두면, 그것을 읽어서 계산해 낸다.
6. GUI는 없다.
7. AutoCAD 또는 DraftSight에서 기어 형상을 자동으로 그려줄 수 있도록 Script 파일이 자동으로 생성된다.  여기서 dxf 파일로 저장한 후, 3D CAD 에서 모델링 할 수 있다.


우선 프로그램의 소스코드는 다음과 같다.
파일 이름은 FGPG_V11.jl 이다.  다른 이름을 붙여도 상관없다.



#########################################################################
# FGPG (Fine_Gear_Profile_Generator) V11
# 20150602
# by Dongho Kim  from Korea
# dymaxion.kim@gmail.com
# http://dymaxionkim.blogspot.kr/
#########################################################################
## Ref.
# http://tro.kr/29
# http://en.wikipedia.org/wiki/Coordinate_rotations_and_reflections
# http://dip28p.web.fc2.com/
#########################################################################
## History
# 20141114 V0.44 : SageMath Version
# 20150519 V0.1 : Trying to Conversion for Julia, Trying Interact Macro
# 20150519 V0.2 : Studying about Lack
# 20150527 V01 : Plotting Whole Gear at first
# 20150527 V02 : Trying Interact Macro
# 20150527 V03 : Debug V01
# 20150527 V04 : Adding Annotations, Saving Profile Data File, ...
# 20150527 V05 : Adding More Error Checks, Making geo File
# 20150528 V06 : Debug Making geo File
# 20150528 V07 : Align vertically Gear, Add Shaft
# 20150528 V08 : Modifying Sequences, Debug Making geo File, Make new Figure, Make svg Pictures, Save scr File, Modify Annotations
# 20150530 V09 : Modifying Shaft, Internal-External Condition, geo File
# 20150531 V10 : Adding version Variable
# 20150602 V11 : Making case.sif, case.bat, case.sh files
#########################################################################
## Todo
# Write Theory Doc
# Write User Manual
# Write Some Actual Examples
# Tip Relief to Modifying Tooth Profile
# Making Spec Table
# Making case.sif
# Profile Repairing in Undercut Condition
# Technical Error Check & Anouncing
# Change Variable Names for easy code reading
# Re-code in Functional Criteria (?)
# Wndows Batch File
# Bash Script
# Change Result File Name based in Time
# Build to Standalone Executable

# On JUNO envirenment
cd(dirname(@__FILE__))

using PyPlot
PyPlot.svg(true)

############################
# Parameters
version = "v11"
m = 1  # Module
z = 20  # Teeth Number
alpha_0_deg = 20  # Pressure Angle [Deg]
x = 0.2  # Offset Factor
b = 0  # Backlash Factor
a = 1.0  # Addendum of Tooth
d = 1.2  # Dedendum of Tooth
c = 0.3  # Radius Factor of Edge Round of Hob
e = 0.2  # Radius Factor of Edge Round of Tooth
# Center of Gear
x_0 = 0
y_0 = 0
# Segmentation Points Numbers for each curves
seg_circle = 360
seg_involute = 50
seg_edge_r = 4
seg_root_r = 20
seg_center = 2
seg_outter = 4
seg_root = 4


############################
# Input Parameters from input4fgpg.csv file
input4fgpg = readcsv("input4fgpg.csv")
input4fgpg[:,3] = float64(input4fgpg[:,3])
m = input4fgpg[1,3]
z = int64(input4fgpg[2,3])
alpha_0_deg = input4fgpg[3,3]
alpha_0 = alpha_0_deg*2*pi/360
x = input4fgpg[4,3]
b = input4fgpg[5,3]
a = input4fgpg[6,3]
d = input4fgpg[7,3]
c = input4fgpg[8,3]
e = input4fgpg[9,3]
x_0 = input4fgpg[10,3]
y_0 = input4fgpg[11,3]
seg_circle = int64(input4fgpg[12,3])
seg_involute = int64(input4fgpg[13,3])
seg_edge_r = int64(input4fgpg[14,3])
seg_root_r = int64(input4fgpg[15,3])
seg_center = int64(input4fgpg[16,3])
seg_outter = int64(input4fgpg[17,3])
seg_root = int64(input4fgpg[18,3])


############################
# Involute Curve
# alpha_m = Center Line's Slope [Rad]
alpha_m = pi/z
# alpha_is = Start Angle for Involute Curve
alpha_is = alpha_0 + pi/(2*z) + b/(z*cos(alpha_0)) - (1+2*x/z)*sin(alpha_0)/cos(alpha_0)
# theta_is = Minimum Range of Parameter to Draw Involute Curve
theta_is = sin(alpha_0)/cos(alpha_0) + 2*(c*(1-sin(alpha_0))+x-d)/(z*cos(alpha_0)*sin(alpha_0))
# theta_ie = Maximum Range of Parameter to Draw Involute Curve
theta_ie = 2*e/(z*cos(alpha_0)) + sqrt( ((z+2*(x+a-e))/(z*cos(alpha_0)))^2 - 1 )
# Condition of theta_ie
if alpha_malpha_m && alpha_m>alpha_is+theta_ie-atan(theta_ie)
    e = (z/2)*cos(alpha_0)*( theta_ie - sqrt( (1/cos(alpha_is+theta_ie-alpha_m))^2-1 ) )
end
# x_e, y_e = Location of Tooth's End Point
x_e = x_0 + m*((z/2)+x+a)*cos(alpha_e)
y_e = y_0 + m*((z/2)+x+a)*sin(alpha_e)
# x_e0, y_e0 = Location of Edge Round Center
x_e0 = m*(z/2+x+a-e)*cos(alpha_e) + x_0
y_e0 = m*(z/2+x+a-e)*sin(alpha_e) + y_0
# Parameter Range of Edge Round
theta3_min = atan((Y1[length(Y1)]-y_e0)/(X1[length(X1)]-x_e0))
theta3_max = atan((y_e-y_e0)/(x_e-x_e0))
THETA3 = linspace(theta3_min,theta3_max,seg_edge_r)
X3 = m*e*cos(THETA3)+x_e0
Y3 = m*e*sin(THETA3)+y_e0
#plot(X3,Y3,color="green",linestyle="-")


############################
# Root Round Curve of Tooth
# Condition Check
# alpha_ts = Start Angle of Root Round Curve
# THETA_s = Substitution Variable to plot Root Round Curve
alpha_ts = (2*(c*(1-sin(alpha_0))-d)*sin(alpha_0)+b)/(z*cos(alpha_0)) - 2*c*cos(alpha_0)/z + pi/(2*z)
theta_te = 2*c*cos(alpha_0)/z - 2*(d-x-c*(1-sin(alpha_0)))*cos(alpha_0)/(z*sin(alpha_0))
THETA_t = linspace(0,theta_te,seg_root_r)
if c!=0 && (d-x-c)==0
    # mc를 반지름으로 하는 원호를 그려서 대체하게 됨
    THETA_s = (pi/2)*ones(length(THETA_t))
    elseif c==0 && (d-x-c)==0
    # 루트커브 작도 생략하고, (x_is,y_is) 점을 대칭으로 이뿌리호를 그려서 대체하게 됨
    elseif (d-x-c)!=0
    THETA_s = atan((m*z*THETA_t/2)/(m*d-m*x-m*c))
end
X_t = x_0 + m*( (z/2+x-d+c)*cos(THETA_t+alpha_ts) + (z/2)*THETA_t.*sin(THETA_t+alpha_ts) - c*cos(THETA_s+THETA_t+alpha_ts) )
Y_t = y_0 + m*( (z/2+x-d+c)*sin(THETA_t+alpha_ts) - (z/2)*THETA_t.*cos(THETA_t+alpha_ts) - c*sin(THETA_s+THETA_t+alpha_ts) )
#plot(X_t,Y_t,color="green",linestyle="-")


############################
# Outter Arc
#alpha_em = 2*alpha_m-alpha_e
#x_em = x_0 + m*(z/2+x+a)*cos(alpha_em)
#y_em = y_0 + m*(z/2+x+a)*cos(alpha_em)
THETA6 = linspace(alpha_e,alpha_m,seg_outter)
X6 = m*(z/2+a+x)*cos(THETA6)+x_0
Y6 = m*(z/2+a+x)*sin(THETA6)+y_0
#plot(X6,Y6,color="red",linestyle="-")


############################
# Root Arc
#x_rm = x_0 + m*(z/2+x-d)*cos(alpha_ts)
#y_rm = y_0 - m*(z/2+x-d)*sin(alpha_ts)
THETA7 = linspace(0,alpha_ts,seg_root)
X7 = m*(z/2-d+x)*cos(THETA7)+x_0
Y7 = m*(z/2-d+x)*sin(THETA7)+y_0
#plot(X7,Y7,color="red",linestyle="-")


############################
# Combine Curves
# Not using Root Round Curve (X_t,Y_t)
if c==0 && (d-x-c)==0
    Xc = X7[1:length(X7)-1]
    Yc = Y7[1:length(Y7)-1]
else
    Xc = [ X7[1:length(X7)-1]; X_t[1:length(X_t)-1] ]
    Yc = [ Y7[1:length(Y7)-1]; Y_t[1:length(Y_t)-1] ]
end
# Not using Edge Round Curve (X3,Y3)
if e==0 || alpha_m==alpha_is+theta_ie-atan(theta_ie)
    Xc = [ Xc; X1[1:length(X1)-1] ]
    Yc = [ Yc; Y1[1:length(Y1)-1] ]
else
    Xc = [ Xc; X1[1:length(X1)-1]; X3[1:length(X3)-1] ]
    Yc = [ Yc; Y1[1:length(Y1)-1]; Y3[1:length(Y3)-1] ]
end
# Not using Outter Arc (X6,Y6)
if alpha_e == alpha_m || alpha_m==alpha_is+theta_ie-atan(theta_ie)
    Xc = Xc
    Yc = Yc
else
    Xc = [ Xc; X6[1:length(X6)-1] ]
    Yc = [ Yc; Y6[1:length(Y6)-1] ]
end
#Xc = [ X7[1:length(X7)-1]; X_t[1:length(X_t)-1]; X1[1:length(X1)-1]; X3[1:length(X3)-1]; X6[1:length(X6)-1]]
#Yc = [ Y7[1:length(Y7)-1]; Y_t[1:length(Y_t)-1]; Y1[1:length(Y1)-1]; Y3[1:length(Y3)-1]; Y6[1:length(Y6)-1]]
#plot(Xc,Yc,color="black",linestyle="-",linewidth=2)



############################
# Make Whole One Tooth
Xc2 = Xc[2:length(Xc)]
Yc2 = Yc[2:length(Yc)]
# Reflect Transform
Xcc = cos(2*alpha_m)*(Xc2-x_0) + sin(2*alpha_m)*(Yc2-y_0)
Ycc = sin(2*alpha_m)*(Xc2-x_0) - cos(2*alpha_m)*(Yc2-y_0)
# Location Transform to (x_0,y_0)
Xcc = Xcc + x_0
Ycc = Ycc + y_0
# Invert
Xcc = Xcc[length(Xcc):-1:1]
Ycc = Ycc[length(Ycc):-1:1]
# Combine
Xcc = [Xc; Xcc]
Ycc = [Yc; Ycc]
#plot(Xcc,Ycc,color="black",linestyle="-",linewidth=2)


############################
# Align to Top
align_angle = pi/2-pi/z
X_align = Xcc
Y_align = Ycc
# Rotate
X_align = cos(align_angle)*(Xcc-x_0) - sin(align_angle)*(Ycc-y_0)
Y_align = sin(align_angle)*(Xcc-x_0) + cos(align_angle)*(Ycc-y_0)
# Location Transform to (x_0,y_0)
X_align = X_align + x_0
Y_align = Y_align + y_0
#plot(X_align,Y_align,color="black",linestyle="-",linewidth=2)


############################
# Make Whole Gear
p_angle = 2*pi/z
Xccc = X_align
Yccc = Y_align
Xtemp = X_align[2:length(X_align)]
Ytemp = Y_align[2:length(Y_align)]
for i = 1:z-1
    # Rotate
    Xtemp = cos(p_angle*i)*(X_align[2:length(X_align)]-x_0) - sin(p_angle*i)*(Y_align[2:length(Y_align)]-y_0)
    Ytemp = sin(p_angle*i)*(X_align[2:length(X_align)]-x_0) + cos(p_angle*i)*(Y_align[2:length(Y_align)]-y_0)
    # Location Transform to (x_0,y_0)
    Xtemp = Xtemp + x_0
    Ytemp = Ytemp + y_0
    Xccc = [Xccc; Xtemp]
    Yccc = [Yccc; Ytemp]
end



############################
# Plot Figure
f=figure(figsize=(10,10))
grid("on")
title("FGPG (Fine_Gear_Profile_Generator) $version \n by Dymaxion.Kim")
axis("equal")


############################
# Base, Pitch, Offset, Outter, Root Circle
THETA0 = linspace(0.0,2*pi,seg_circle)
# Base Circle
base_dia = m*z*cos(alpha_0)
X_base = base_dia/2*sin(THETA0) + x_0
Y_base = base_dia/2*cos(THETA0) + y_0
plot(X_base, Y_base, color="cyan", linestyle="--")
# Pitch Circle
pitch_dia = m*z
X_pitch = pitch_dia/2*sin(THETA0) + x_0
Y_pitch = pitch_dia/2*cos(THETA0) + y_0
plot(X_pitch, Y_pitch, color="magenta", linestyle="--")
# Offset Circle
offset_dia = 2*m*(z/2+x)
X_offset = (offset_dia/2)*sin(THETA0) + x_0
Y_offset = (offset_dia/2)*cos(THETA0) + y_0
plot(X_offset, Y_offset, color="red", linestyle="--")
# Outter Circle
outter_dia = 2*m*(z/2+x+a)
X_out = (outter_dia/2)*sin(THETA0) + x_0
Y_out = (outter_dia/2)*cos(THETA0) + y_0
plot(X_out, Y_out, color="brown", linestyle="--")
# Root Circle
root_dia = 2*m*(z/2+x-d)
X_root = (root_dia/2)*sin(THETA0) + x_0
Y_root = (root_dia/2)*cos(THETA0) + y_0
plot(X_root, Y_root, color="grey", linestyle="--")


############################
# Center Line of Tooth
X_m = linspace(x_0, (m*(z/2+a+x))*cos(alpha_m)+x_0, seg_center)
Y_m = (X_m-x_0)*tan(alpha_m) + y_0
plot(X_m,Y_m,color="orange",linestyle="--")


############################
# Shaft
THETA8 = linspace(0,2*pi,2*z)
if ad  # Internal Gear
    d_shaft = m*(z+x+a)+m*(4*(a+d))
    elseif a==d  # No correct
    #d_shaft = m*(z+x-d)-m*(2*(a+d))
    d_shaft = 0
end
X_shaft = (d_shaft/2)*sin(THETA8) + x_0
Y_shaft = (d_shaft/2)*cos(THETA8) + y_0
plot(X_shaft, Y_shaft, color="black", linestyle="-", linewidth=2)


############################
# Plot Whole Gear
plot(Xccc,Yccc,color="black",linestyle="-",linewidth=2)
char_scale = 0.03*m*z
annotate(["Module = $m mm"],xy=(x_0,y_0+5*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Teeth Number = $z ea"],xy=(x_0,y_0+4*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Pressure Angle = $alpha_0_deg DEG"],xy=(x_0,y_0+3*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Offset Factor = $x"],xy=(x_0,y_0+2*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Backlash Factor = $b"],xy=(x_0,y_0+1*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Addendum Factor = $a"],xy=(x_0,y_0+0*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Dedendum Factor = $d"],xy=(x_0,y_0-1*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Radius Factor of Edge Round of Hob = $c"],xy=(x_0,y_0-2*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Radius Factor of Edge Round of Tooth = $e"],xy=(x_0,y_0-3*char_scale),ha="center",va="center",color="black",fontsize="x-small")
annotate(["Center Position [mm] = ($x_0,$y_0)"],xy=(x_0,y_0-4*char_scale),ha="center",va="center",color="black",fontsize="x-small")
legend(["Base Circle Dia = $base_dia", "Pitch Circle Dia = $pitch_dia", "Offset Circle Dia = $offset_dia",
    "Outter Circle Dia = $outter_dia", "Root Circle Dia = $root_dia", "Center Line"], loc="lower left", fontsize="x-small")
savefig("case01.svg")





############################
# Figuer 2
f2=figure(figsize=(10,10))
grid("on")
title("FGPG (Fine_Gear_Profile_Generator) $version \n by Dymaxion.Kim")
axis("equal")


############################
# Base, Pitch, Offset, Outter, Root Circle 2
THETA9 = linspace(atan(X_align[length(X_align)]/Y_align[length(Y_align)]),
                  atan(X_align[1]/Y_align[1]), int(seg_circle/10))
# Base Circle
X_base9 = base_dia/2*sin(THETA9) + x_0
Y_base9 = base_dia/2*cos(THETA9) + y_0
plot(X_base9, Y_base9, color="cyan", linestyle="--")
# Pitch Circle
X_pitch9 = pitch_dia/2*sin(THETA9) + x_0
Y_pitch9 = pitch_dia/2*cos(THETA9) + y_0
plot(X_pitch9, Y_pitch9, color="magenta", linestyle="--")
# Offset Circle
X_offset9 = (offset_dia/2)*sin(THETA9) + x_0
Y_offset9 = (offset_dia/2)*cos(THETA9) + y_0
plot(X_offset9, Y_offset9, color="red", linestyle="--")
# Outter Circle
X_out9 = (outter_dia/2)*sin(THETA9) + x_0
Y_out9 = (outter_dia/2)*cos(THETA9) + y_0
plot(X_out9, Y_out9, color="brown", linestyle="--")
# Root Circle
X_root9 = (root_dia/2)*sin(THETA9) + x_0
Y_root9 = (root_dia/2)*cos(THETA9) + y_0
plot(X_root9, Y_root9, color="grey", linestyle="--")


############################
# Plot only One Tooth
plot(X_align,Y_align,color="black",linestyle="-",linewidth=1)
plot(X_align,Y_align,"b.")
base_radius = base_dia/2
pitch_radius = pitch_dia/2
offset_radius = offset_dia/2
outter_radius = outter_dia/2
root_radius = root_dia/2
xmm = x*m
emm = e*m
bmm = b*m
cmm = c*m
annotate(["Base Radius = $base_radius mm"],xy=(0,base_dia/2),ha="center",va="bottom",color="cyan",fontsize="x-small")
annotate(["Pitch Radius = $pitch_radius mm"],xy=(0,pitch_dia/2),ha="left",va="bottom",color="magenta",fontsize="x-small")
annotate(["Offset Radius = $offset_radius mm"],xy=(0,offset_dia/2),ha="right",va="bottom",color="red",fontsize="x-small")
annotate(["Outter Radius = $outter_radius mm"],xy=(0,outter_dia/2),ha="right",va="bottom",color="brown",fontsize="x-small")
annotate(["Root Radius = $root_radius mm"],xy=(0,root_dia/2),ha="center",va="bottom",color="grey",fontsize="x-small")
annotate(["Offset = $xmm mm"],xy=(0,offset_dia/2),ha="right",va="top",color="red",fontsize="x-small")
annotate(["Radius of Hob Edge = $cmm mm"],
        xy=(X_align[seg_root+int(seg_root_r/2)],Y_align[seg_root+int(seg_root_r/2)]),
        ha="right",va="top",color="blue",fontsize="x-small")
annotate(["Radius of Edge = $emm mm"],
        xy=(X_align[seg_root+seg_root_r+seg_involute],Y_align[seg_root+seg_root_r+seg_involute]),
        ha="left",va="bottom",color="blue",fontsize="x-small")
annotate(["Backlash = $bmm mm"],
        xy=(X_align[seg_root+seg_root_r+int(seg_involute/2)],Y_align[seg_root+seg_root_r+int(seg_involute/2)]),
        ha="left",va="bottom",color="blue",fontsize="x-small")
amm = a*m
dmm = d*m
hmm = amm+dmm
tmm = 2*X_align[seg_root+seg_root_r]
#temm = 2*X_align[seg_root+seg_root_r+seg_involute+int(seg_edge_r/2)]
temm = 2*X_align[seg_root+seg_root_r+seg_involute]
annotate(["Addendum = $amm mm"],xy=(X_out9[1],Y_out9[1]),ha="left",va="top",color="black",fontsize="x-small")
annotate(["Dedendum = $dmm mm"],xy=(X_root9[1],Y_root9[1]),ha="left",va="top",color="black",fontsize="x-small")
annotate(["Tooth Height = $hmm mm"],xy=(X_offset9[1],Y_offset9[1]),ha="left",va="top",color="black",fontsize="x-small")
annotate(["Approx Tooth Thickness = $tmm mm"],xy=(0,base_dia/2),ha="center",va="top",color="black",fontsize="x-small")
annotate(["Approx Tooth End Thickness = $temm mm"],xy=(0,outter_dia/2),ha="center",va="top",color="black",fontsize="x-small")

savefig("case02.svg")





############################
# Save case.csv
writecsv("case.csv",[Xccc Yccc])


############################
# Save case.geo
fileout = open("case.geo", "w")
println(fileout, "cl__1 = 1;")
num_Point_gear = length(Xccc)
num_Line_gear = num_Point_gear-1
num_Point_shaft = num_Point_gear + length(X_shaft) -1
num_Line_shaft = num_Point_shaft-1
for i=1:num_Point_gear
    println(fileout, "Point(",i,") = {",Xccc[i],", ",Yccc[i],", ",0,", ","cl__1};")
end
for i=num_Point_gear+1:num_Point_shaft
    println(fileout, "Point(",i,") = {",X_shaft[i-num_Point_gear],", ",Y_shaft[i-num_Point_gear],", ",0,", ","cl__1};")
end
for j=1:num_Line_gear
    println(fileout, "Line(",j,") = {",j,", ",j+1,"};")
end
println(fileout, "Line(",num_Line_gear+1,") = {",num_Line_gear+1,", ",1,"};")
for j=num_Line_gear+2:num_Line_shaft
    println(fileout, "Line(",j,") = {",j,", ",j+1,"};")
end
println(fileout, "Line(",num_Line_shaft+1,") = {",num_Line_shaft+1,", ",num_Line_gear+2,"};")
# Combine
array_LineLoop = [1:num_Line_shaft+1]
array_LineLoop[num_Line_gear+2:num_Line_shaft+1] = -array_LineLoop[num_Line_gear+2:num_Line_shaft+1]
array_LineLoop = string(array_LineLoop)
array_LineLoop = array_LineLoop[2:length(array_LineLoop)-1]
println(fileout, "Line Loop(",num_Line_shaft+3,") = {",array_LineLoop,"};")
println(fileout, "Plane Surface(",num_Line_shaft+3,") = {",num_Line_shaft+3,"};")
#Physical Line(13) = {8, 9};
#Physical Surface(14) = {11};
array_PhysicalLine = [num_Line_gear+2:num_Line_shaft+1]
array_PhysicalLine = string(array_PhysicalLine)
array_PhysicalLine = array_PhysicalLine[2:length(array_PhysicalLine)-1]
#println(fileout, "Physical Line(",num_Line_shaft+4,") = {",array_PhysicalLine,"};")
# Position for Normal Force
#position_NormalForce = (length(Xcc)-1) - (seg_root+seg_root_r-2) - (seg_involute-3)
#println(fileout, "Physical Line(",num_Line_shaft+5,") = {",position_NormalForce,"};")
# Fixed
#println(fileout, "Physical Surface(",num_Line_shaft+6,") = {",num_Line_shaft+3,"};")
println(fileout, "Mesh.ElementOrder = 2;")
println(fileout, "Mesh.RecombineAll = 1;")
close(fileout)


############################
# Save case.scr
fileout = open("case.scr", "w")
println(fileout, "spline")
for i=1:num_Point_gear
    println(fileout, Xccc[i],",",Yccc[i],",0")
end
println(fileout, Xccc[1],",",Yccc[1],",0")
println(fileout, "  ")
println(fileout, "circle")
println(fileout, x_0,",",y_0)
println(fileout, d_shaft/2)
close(fileout)


############################
# Save case.sif

fileout = open("case.sif", "w")
println(fileout, "Header")
println(fileout, "  CHECK KEYWORDS Warn")
println(fileout, "  Mesh DB \".\" \".\"")
println(fileout, "  Include Path \"\"")
println(fileout, "  Results Directory \"\"")
println(fileout, "End")
println(fileout, "")
println(fileout, "Simulation")
println(fileout, "  Max Output Level = 5")
println(fileout, "  Coordinate System = Cartesian")
println(fileout, "  Coordinate Mapping(3) = 1 2 3")
println(fileout, "  Simulation Type = Steady state")
println(fileout, "  Steady State Max Iterations = 1")
println(fileout, "  Output Intervals = 1")
println(fileout, "  Timestepping Method = BDF")
println(fileout, "  BDF Order = 1")
println(fileout, "  Solver Input File = case.sif")
println(fileout, "  Post File = case.ep")
println(fileout, "  Coordinate Scaling = Real 0.001")
println(fileout, "End")
println(fileout, "")
println(fileout, "Constants")
println(fileout, "  Gravity(4) = 0 -1 0 9.82")
println(fileout, "  Stefan Boltzmann = 5.67e-08")
println(fileout, "  Permittivity of Vacuum = 8.8542e-12")
println(fileout, "  Boltzmann Constant = 1.3807e-23")
println(fileout, "  Unit Charge = 1.602e-19")
println(fileout, "End")
println(fileout, "")
println(fileout, "Body 1")
println(fileout, "  Target Bodies(1) = 1")
println(fileout, "  Name = \"Body 1\"")
println(fileout, "  Equation = 1")
println(fileout, "  Material = 1")
println(fileout, "End")
println(fileout, "")
println(fileout, "Solver 1")
println(fileout, "  Equation = Result Output")
println(fileout, "  Single Precision = True")
println(fileout, "  Procedure = \"ResultOutputSolve\" \"ResultOutputSolver\"")
println(fileout, "  Save Geometry Ids = True")
println(fileout, "  Output File Name = case_output")
println(fileout, "  Output Format = Gmsh")
println(fileout, "  Exec Solver = After Timestep")
println(fileout, "End")
println(fileout, "")
println(fileout, "Solver 2")
println(fileout, "  Equation = Linear elasticity")
println(fileout, "  Procedure = \"StressSolve\" \"StressSolver\"")
println(fileout, "  Variable = -dofs 2 Displacement")
println(fileout, "  Exec Solver = Always")
println(fileout, "  Stabilize = True")
println(fileout, "  Bubbles = False")
println(fileout, "  Lumped Mass Matrix = False")
println(fileout, "  Optimize Bandwidth = True")
println(fileout, "  Steady State Convergence Tolerance = 1.0e-5")
println(fileout, "  Nonlinear System Convergence Tolerance = 1.0e-7")
println(fileout, "  Nonlinear System Max Iterations = 20")
println(fileout, "  Nonlinear System Newton After Iterations = 3")
println(fileout, "  Nonlinear System Newton After Tolerance = 1.0e-3")
println(fileout, "  Nonlinear System Relaxation Factor = 1")
println(fileout, "  Linear System Solver = Iterative")
println(fileout, "  Linear System Iterative Method = BiCGStab")
println(fileout, "  Linear System Max Iterations = 500")
println(fileout, "  Linear System Convergence Tolerance = 1.0e-10")
println(fileout, "  BiCGstabl polynomial degree = 2")
println(fileout, "  Linear System Preconditioning = Diagonal")
println(fileout, "  Linear System ILUT Tolerance = 1.0e-3")
println(fileout, "  Linear System Abort Not Converged = False")
println(fileout, "  Linear System Residual Output = 1")
println(fileout, "  Linear System Precondition Recompute = 1")
println(fileout, "End")
println(fileout, "")
println(fileout, "Equation 1")
println(fileout, "  Name = \"Equation 1\"")
println(fileout, "  Calculate Stresses = True")
println(fileout, "  Active Solvers(2) = 1 2")
println(fileout, "End")
println(fileout, "")
println(fileout, "Material 1")
println(fileout, "  Name = \"Steel (carbon - generic)\"")
println(fileout, "  Heat Conductivity = 44.8")
println(fileout, "  Youngs modulus = 200.0e9")
println(fileout, "  Mesh Poisson ratio = 0.285")
println(fileout, "  Heat Capacity = 1265.0")
println(fileout, "  Density = 7850.0")
println(fileout, "  Poisson ratio = 0.285")
println(fileout, "  Sound speed = 5100.0")
println(fileout, "  Heat expansion Coefficient = 13.8e-6")
println(fileout, "End")
println(fileout, "")
println(fileout, "Boundary Condition 1")
println(fileout, "  Target Boundaries(47) = 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 ")
println(fileout, "  Name = \"Fixed\"")
println(fileout, "  Displacement 2 = 0")
println(fileout, "  Displacement 1 = 0")
println(fileout, "End")
println(fileout, "")
println(fileout, "Boundary Condition 2")
println(fileout, "  Target Boundaries(1) = 52 ")
println(fileout, "  Name = \"NormalForce\"")
println(fileout, "  Normal Force = -100")
println(fileout, "End")
println(fileout, "")
close(fileout)


############################
# Save case.bat
fileout = open("case.bat", "w")
println(fileout, "gmsh -2 case.geo")
println(fileout, "ElmerGrid 14 2 case.msh")
println(fileout, "move .\\case\\*.* .\\")
println(fileout, "rmdir case")
println(fileout, "ElmerSolver case.sif")
println(fileout, "gmsh case_output.msh")
close(fileout)


############################
# Save case.sh
fileout = open("case.sh", "w")
println(fileout, "#!/bin/sh")
println(fileout, "gmsh -2 ./case.geo")
println(fileout, "ElmerGrid 14 2 case.msh")
println(fileout, "mv ./case/* ./")
println(fileout, "rmdir case")
println(fileout, "ElmerSolver ./case.sif")
println(fileout, "gmsh ./case_output.msh")
close(fileout)
if OS_NAME == "Linux"
    run(`chmod 751 case.sh`)
end


이것을 실행하려면 우선 Julia가 실행 가능한 환경이어야 한다.
자신의 PC에 Julia가 깔려있지 않고, 또 깔고 싶지 않다면 http://juliabox.org 사이트에서 클라우드 서비스를 제공해 주므로 거기서 소스코드를 복사해서 붙여넣은 후 실행해도 된다.
실행 후 생성된 파일들을 다운로드 받을 수 있기 때문에 전혀 문제가 안된다.

(사실 이것도 불편하기 때문에, 추후에 윈도우용 및 리눅스용 독립 실행파일을 생성해서 제공해 주려고 한다.  아직 Julia v0.3.9 에서는 독립 실행 파일 생성이 불가능하고, 조만간 나올 Julia v0.4 부터는 독립실행파일을 생성해 줄 수 있는 패키지가 안정화되어 나올 예정이기 때문에 시간이 좀 필요하다.)

그리고, 이 프로그램은 기어 파라미터를 설정해 준 별도의 파일을 통해 입력받도록 해 놨기 때문에 입력용 파일을 따로 작성해 둬야 한다.  파일 이름은 input4fgpg.csv 로 해야 한다.
이 파일은 콤마로 필드가 구분된 텍스트파일이다.  엑셀 같은 스프레드 시트에서 읽으면 다루기 쉽다.

내용은 아래와 같다.



Module,m,1
Teeth Number,z,24
Pressure Angle [Deg],alpha_0_deg,20
Offset Factor,x,0.2
Backlash Factor,b,0.025
Addendum Factor,a,1.2
Dedendum Factor,d,1.0
Radius Factor of Edge Round of Hob,c,0.3
Radius Factor of Edge Round of Tooth,e,0.2
Center of Gear,x_0,0
Center of Gear,y_0,0
Segmentation Numbers,seg_circle,360
Segmentation Numbers,seg_involute,20
Segmentation Numbers,seg_edge_r,4
Segmentation Numbers,seg_root_r,10
Segmentation Numbers,seg_center,2
Segmentation Numbers,seg_outter,6
Segmentation Numbers,seg_root,6



기어 사양을 변경하고 싶다면, 위 내용 중에 다른 것은 건드리지 말고 매 행의 맨 마지막 필드의 숫자만 적절히 바꿔주면 된다.
 기어 사양에 관한 자세한 설명은 나중에 버전업 하고 나서 쓸만하게 되면 자세한 도큐먼트를 작성하려고 하므로 일단 생략한다.


아무튼 PC에 Julia 환경이 구축된 경우의 작업 예시를 순서대로 설명해 보겠다.
본 예제에서는 윈도우7 환경이다.




일단 작업하려고 하는 디렉토리를 만들고, 소스코드 및 입력파일을 위와 같이 넣는다.
Juliabox.org 에서 작업할 경우도 역시 마찬가지다.  작업하려는 디렉토리를 만들고 똑같이 넣어두면 된다.



 FGPG_V11.jl 소스코드를 실행하기 위해서는 크게 3가지 방법이 있다.
그냥 cmd 즉 소위 말하는 도스창에서



julia FGPG_V11.jl 



이렇게 커맨드를 직접 때려주는 방법이 있고, 또 다른 방법은 위 그림과 같이 JUNO IDE를 실행시켜서 여기서 실행해 주는 방법이 있다.
마지막으로는 Juliabox.org의 웹브라우저 상에서의 개발환경에서 실행해 줄 수 있다.  Juliabox.org는 기본적으로 IPython Notebook 기반이기 때문에, IJulia Notebook을 자신의 PC에 설치해서 똑같이 해도 된다.

본 예제에서는 위 그림 처럼 JUNO 상에서 실행한다.
 실행 전에 input4fgpg.csv 파일도 열어서 내용 확인하고 적절히 수정해 준 후에, 다시 재 실행하면서 기어 형상 변화를 확인해 갈 수 있을 것이다.


JUNO 에서 소스를 실행하려면, 아래 그림처럼 메뉴 중에서 "Eval All"을 선택해 주면 된다.


참고로, 본 소스코드는 외부 패키지는  PyPlot만 사용한다.
가급적 외부 의존성을 줄여서 간단하게 만들기 위해서이다.
Julia는 JIT 방식이기 때문에, 최초 실행시에는 로딩 및 내부 컴파일 시간이 좀 걸린다.
하지만 두 번째 실행 부터는 상당히 빠르게 수행된다.

실행이 완료되면, 우선 아래와 같은 2개의 그림이 별도 창으로 뜬다.



이것을 보면서 기어의 형상 및 결과로 나오는 주요 치수들을 확인할 수 있다.
기어 사양이 적절치 않다면, input4fgpg.csv 안의 파라미터 값들을 조절해 주면서 반복 실행해 주면 된다.

아울러, 실행이 완료되면 작업 디렉토리 안에 여러 가지 파일들이 만들어져 있음을 볼 수 있다.


우선 case.csv 파일은, 생성된 기어 형상의 원시(Raw) 데이타이다.
이것을 엑셀에서 읽어서 그래프로 그대로 그려줄 수 있다.
또는 Matlab이나 Octave, Scilab 등에서 읽어서 이용할 수도 있다.


case01.svg 및 case02.svg 파일들은 앞서 나타났던 기어 형상 그림들이 파일로 저장된 것이다.
벡터 그림 파일이기 때문에, Inkscape 또는 Adobe Illustrator 같은 것으로 읽어서 활용할 수 있다.
아래의 그림은 Inkscape에서 읽어본 것이다.


case.scr 파일은 AutoCAD Script 파일이다.
AutoCAD 대신 DraftSight에서도 실행 가능하기 때문에, DraftSight에서 실행한 예를 아래 그림과 같이 볼 수 있다.


DraftSight를 실행해서 빈 화면이 나오며느, 화면 아래쪽에 "도면요소 스냅"이 보통 기본적으로 눌러진 상태로 되어 있다.  이것을 마우스로 한 번 눌러줘서 해제 상태로 바꾼다.


"도면요소 스냅"을 해제하는 이유는, 눌러진 상태에서 case.scr 파일을 실행시키면 제대로 그려지지 않고 이상하게 나오기 때문이다.  AutoCAD 에서도 똑같다.



아무튼, 이렇게 실행하면 약간의 수초~십수초 정도의 시간을 소요한 후에 기어 형상이 그려져 있음을 확인할 수 있다.
기어의 치형은 하나의 Spline으로 그려지도록 해 놨다.
또 Addendum 및 Dedendum 값을 비교해서 Internal & External Gear를 자동 판별해서 그려지도록 해 놨다.


이것을 case.dxf로 저장한다.  버전은 R2000~2002 버전으로 적당히 옛날 버전으로 해 두는 습관이 좋겠다.  왜냐면 구버전일수록 여러 소프트웨어를 가리지 않고 잘 열리기 때문이다.

한가지 팁은, R12 버전의 *.dxf 파일로 저장시키면 Spline이 많은 짧은 직선으로 구성된 Polyline으로 바꿔져서 저장된다.  R12 버전에서는 Spline 사양이 없기 때문이다.
이런 특징을 이용해서, Spline 궤적을 생성할 수 없는 공작기계로 가공할 경우에 (구형 와이어 방전 가공기나 밀링머신들은 Spline 궤적 보간 기능이 없고, 오로지 원호 보간 기능만 지원된다.  최신 기계들은 대부분 Spline 보간 기능이 있다.) 대응 가능할 것이다.



한편, case.bat 및 case.sh 파일은 각각 윈도우 및 리눅스용 해석 스크립트이다.
이 파일들을 실행시키면 PC에 이미 Gmsh 및 Elmer가 설치되어 있고 또 path가 잡혀 있는 경우에 한해서 이상없이 FEM 해석이 실시될 것이다.


Batch 파일의 스크립트 내용은 별것 없다.  위 그림과 같다.
순서대로 명령을 실행하는 것이다.
리눅스용 case.sh 파일 역시 같은 동작을 하도록 리눅스용 Bash 쉘에 맞춰서 내용이 들어있다.

gmsh -2 case.geo 명령은, case.geo 형상 파일을 이용해서 gmsh 프로그램으로 2차원 매쉬를 생성하라는 것이다.
매쉬 생성에는 수십초 이상이 소요될 것이다.  커맨트창이 떠서 진행 상황이 확인될 것이다.


gmsh 작업이 끝나면, case.msh 파일이 생성된다.

참고로, case.msh로 만들어지는 매쉬의 특성은, Delaunay 삼각형 자동 생성 알고리즘으로 2nd Order 조건으로 먼저 생성된 후에, Recombine 옵션을 줘 놨기 때문에 삼각형들을 합쳐서 사각형으로 바꿔준다.  따라서 결과물로 나오는 매쉬는 2nd Ordered Quad Mesh가 된다.

매쉬의 조밀함은 input4fgpg.csv 내용 중에서 Segmentation 값들을 올려주면 더 조밀하게 할 수 있다.  대신 해석시간은 기하급수적으로 증가될 것이다.

아무튼 이어서  곧바로 ElmerGrid 명령이 실행된다.  ElmerGrid는 case.msh 파일을 Elmer 전용의 매쉬 포멧으로 바꿔준다.  Elmer용 매쉬 파일은 단일 파일이 아니고, 속성별로 4개의 파일로 저장된다.


따라서 매쉬 생성 결과는, 위 그림과 같이 나타난다.
이어서 곧바로 ElmerSolver가 작동해서 해석을 시작한다.
각종 해석 조건을 설정한 ElmerSolver용 입력파일인 case.sif 파일은 이미 FGPG_V11.jl이 만들어 뒀다.
재질(Carbon Steel), 경계조건의 위치, 힘의 강도(100N)은 상수로 고정시켜 놨기 때문에 소스를 보고 수정해야 바꿀 수 있다.  일단은 잘 되는지 보기 위해 편의상 상수로 뒀다.


아무튼 이에 따라 ElmerSolver가 수행된다.  커맨드창에 나타나는 숫자들은 수렴 오차이다.  수렴오차를 계속 줄여가다가 설정된 오차값 안에 들어오면 해석이 완료되도록 되어 있다.


한참 기다리면 최종적으로 완료된다.  본 예제에서는 해석시간이 406.28초 걸렸다.


이제 어떤 파일이 생겼나 확인해 보면, case_output.msh 파일이 생겨나 있는 것을 확인할 수 있다.  물론 ElmerSolver 결과 파일인 case.ep 역시 함께 생성되어 있다.
이들 파일이 생성된 후에 자동으로 Post Processor로서 gmsh가 실행되어 결과 파일을 띄워준다.


gmsh는 처음 다뤄보는 사람이면 GUI 체계가 좀 생소해서 막막할 수 있는데, 기능들 자체는 매우 단순하다.  이리저리 보면서 건드려 보면 쉽게 습득 가능할 것이다.
일단 본 화면에서는 기본적으로 결과데이타 8가지가 한꺼번에 겹쳐서 보이므로 번잡스럽다.
6번이 VonMises 응력 데이타이므로, 이것을 제외하고 전부 Uncheck 한다.


일단 메뉴에서 Option을 선택해서 화면 상태를 조정해 줄 수 있도록 옵션을 건드려 본다.


Mesh의 Visability 중에서 Surface Edges, Volume Edges를 Uncheck 하면 좀 깔끔해진다.



그리고, Filled iso-values를 선택해서 등고선식으로 보이도록 해 본다.




등고선 단계는 50단계 정도로 해 본다.


그러면 좀 볼 만 하다.  다만 몇 개의 거슬리는 포인트들이 보인다.


거슬리는 포인트는 위 그림처럼 Points를 Uncheck 하면 안보이게 된다.



결과 확인.  그림은 캡춰하거나 다른이름으로 저장할 때 그림포멧으로 지정하면 된다.


이제 7번 Displacement를 본다.



상태 확인하고...


등고선식으로 보이도록 해 보고...


Displacement Factor를 크게 높여서 변형되는 형상을 과장해서 확인해 본다.
이런 식으로 결과 확인이 가능하다.


한 편, 앞서 만들어둔 기어 형상 2D CAD 파일인  case.dxf 파일을 3D CAD에서 읽어들여 모델링해 본다....

일단 FreeCAD에서 잘 되는지 확인.


FreeCAD를 최초로 띄우면 위 그림처럼 나온다.



빈 파일에 case.dxf를 Import 시킨다.


최초에 FreeCAD에는 dxf Reader가 내장되어 있지는 않으나, 우와 같은 창이 뜨면 Yes 해 주면 자동으로 스크립트를 설치해서 읽어들인다.  dxf Reader 부분의 저작권 조건이 달라서 FreeCAD 설치본에 포함하지 못했다고 한다.  (FreeCAD v0.15 기준)


읽어들이는데 성공하면 위 그림과 같다.
간단하게는 이것을 Extrude 시켜서 모델링 하면 된다.


일단은 외곽 라인 부분을 Extrude 해 주는데 일단 Solid가 생성되도록 체크해 준다.


또 기어 치형 부분도 Solid로 Extrude 해 준다.


마지막으로, 생성된 2개의 Extrude Feature를 Boolean Cut 해 준다.
그러면 이상없이 만들어진 모습을 볼 수 있다.
이 파일을 *.step 포멧으로 저장 가능하다.

한편, 프로페셔널하게 쓰는 상용 3D CAD에서도 잘 되는지 확인해 본다...


잘 된다.  모델링 과정 설명은 생략한다.  대충 해 본 거라서..
재밌게 응용해 보기 위해 각도를 살짝 틀어서 Scew를 줘 봤다.  헬리컬기어 느낌이 난다.

이런식으로 응용하면, 상당히 엄밀한 Bevel Gear 및 Bevel Spiral Gear 모델링도 가능할 것이다.




추후에 본 프로그램의 버전업 계획은 아래와 같다.


1. 언더컷 자동 감지 및 겹치는 라인 자동 제거
2. 이끝단 살 소멸 자동 감지 및 겹치는 라인 자동 제거
3. 치형 수정(End Tip Relief) 기능 추가 (Linear Type)
4. 해석 속도 증가를 위해 기어 전체를 해석하지 않고, 3개 정도의 Teeth만 살려서 해석할 수 있도록 수정
5. Theory 및 User Manual 작성


요즘 시간이 점점 쪼들리고 있는데, 신속하게 가능할지는 잘 모르겠다.

이상 끝!


2015년 5월 21일 목요일

네이버까페 오로카 운영자 표윤석씨 강의를 듣다 (ROS 관련)




2011년도에 이런 글을 kldp에 쓴 적이 있는데...

Dymaxion.Kim: 로봇용 소프트웨어 플랫폼 최신 동향 관련

그전부터 주시하던 ROS 및 로봇 미들웨어 동향 관련한 글이다.


이후 국내에서도 결국 ROS가 힘을 쓰기 시작하려고 하는 경향이 생겼다.

이런 흐름을 주도한 것은 유진로봇 쪽이 가장 활발했는데,

듣기로는 그 회사의 개발자 중에서 ROS 전담 개발자가 있다고 한다.

현명한 전략이었다.


내가 재직하는 회사에서는 오픈소스 개념을 이야기하면 비현실적이라고 핀잔 듣는 분위기였으나 오히려 오픈소스화 함으로써 마케팅 효과가 크게 일어난 경우의 좋은 증거가 된다.

내가 보는 세상과 나를 업무적으로 통제하는 사람의 세상이 너무 다르니, 이런 저런 방해를 받아 아무것도 못하고 3년을 그냥 허송해 버린 듯 해서 심히 괴롭다.  한 사람 겨우 극복하면 어디서 듣도보도 못한 사람이 또 나타나 차례대로 방해하는 식이니 원.

아무튼 ...


네이버까페 오로카( http://cafe.naver.com/openrt ) 운영을 열심히 하는 표윤석씨가 우리 회사에 와서 ROS 소개 강의를 2시간 동안 해 줬는데 인상깊게 잘 들었다.

(이 까페에 내가 언제 가입했나 보니 2014년이구낭..  활동은 안하고 그냥 눈팅만 좀 하려고 한건데, 상당히 정성들인 양질의 컨텐츠가 많아서 그냥 눈팅만 하기가 미안할 지경이다.)

열정이 있고 젊음이 느껴져서 더 좋았던 것 같다.

표윤석씨는 예전에 광운대 학생 시절에도 동아리 활동으로 대회 나가기도 하는 등 꽤 잘 검색되던 사람이라서 그 존재는 오래전부터 인지하고 있었는데, 잘 성장해서 지금은 일본에서 로봇 소프트웨어 쪽으로 전공해서 박사과정 밟고 있는 모양이다.



ROS에 관한 기본 개념이나 학계/업계 동향 같은 이야기는 전부 다 아는 것들이라 별로 인상적이지는 않았지만, 최근의 ROS 완성 정도를 확인할 수 있었던 기회였던 것 같다.

개인적인 의견으로, ROS는 아직 아카데믹한 성격이 더 강한 상태이기 때문에 실제 서비스용 로봇의 주력 프레임웍으로 쓰기에는 신뢰성에서 문제가 있다고 본다.

또 요구 시스템 사양이 높기 때문에 (우분투 서버 위에 이것저것 막 올려야 되고, 기본적으로 리얼타임이 아니라서 리얼타임 흉내라도 내려면 일단 수행속도가 나와야 되니..)  로봇에 이걸 사용하려면 로봇 자체가 꽤 중량급이 된다.

즉 좋은 도구나 라이브러리가 많이 있고, 여러 제품들이 지원되니까, 이를 돌려주는 소프트웨어 코딩 생산성이 비약적으로 높아지는 장점이 있는 대신, 시스템이 너무 무겁다는 단점이 있다.

경량화하기 위해 소스나 패키지를 건드리기 시작하면 꽤 골치아플 것이다.


대신 내가 추구하는 방향은 엄청나게 저렴한 시스템이어야 하기 때문에, 사실 ROS는 안 맞는다고 봐도 될 것 이다.  다만 클라우드화된 메인 로봇 통제 서버의 미들웨어로 ROS를 사용하면 적절하겠다 싶다.즉 로봇 클라이언트 단에 ROS를 넣을 게 아니고, 서버 단에다가 심어넣고 돌려야 적절하지 않나 하는 것이다.


로봇 클라이언트 단에 심어넣을 것은 ROS가 아니고 Node.js라고 본다...

(내 의견과 동일한 의견을 가진 개발자가 존재할지 궁금하다.)


이와 관련한 꽤 자세한 제안을 작년에 회사에다 했지만, 역시나 기술동향에 깜깜한 사람들이 그 의미를 충분히 이해하지 못하였고, 감정싸움에 사내 정치놀음에 매몰된 잘못된 사람들과 회사가 방향을 잃어가면서 내가 더이상 어떻게 기여할 방법을 찾지 못했다.

소프트웨어 쪽에다가 설계자가 뭐라 할 입장도 못되고 해서...
그냥 손 놓고 보고 있는 중이다.

아무튼 그쪽은 내가 뭐라 해서 일이 될 분위기는 아니니 일단 신경 끄고 내 쪽만 신경쓰자.
일단은.  망하면 망하게 한 사람이 책임 지겠지.



오늘 표윤석씨의 설명을 통해 몇가지 알아낸 사항은 다음과 같다.


(1) ROS에 심어넣어진 FK,IK 쪽은 유럽의 Orocos인데, 실제 이것을 활용한 결과를 보니 IK 동작중 Singular Point 근방에서 매우 심하게 발산한다.  표윤석씨 말로는 파라미터 설정 잘 하면 잡을 수 있을 거라고는 하는데, 발산 양태를 보면 영락없이 Jacobian Transpose 방법을 쓴 것 같은데 정말 그렇다면 발산 진동을 직접 잡을 방법은 없을 것이다.  결론적으로 IK 쪽이 상당히 허접하게 들어가 있다는 것이 확인되었다.

우리 회사에서 기존에 개발된 IK 알고리즘은 상당히 안정적인데 개발자가 다 퇴사해버린 상태라 사실상 기술 망실 상태나 마찬가지.  코딩으로 구현해서 ROS에 심어넣어 주면 꽤 쓸만한 기여가 될 수 있을 것이다.
내가 핵심 Theory를 이해하고 있기는 하지만, 코딩을 직접 할 수는 없고....
관심있는 개발자가 있다면 Notation을 줘서 패키지화 할 수 있을 것 같은데....
주변에 쓸만한 개발자가 잘 눈에 안띈다는게 함정이다.


(2) ROS에는 아직 Dynamics가 제대로 들어가 있지 못한 듯 하다.   물론 Orocos를 품었으니 Command 상태로는 사용할 수 있겠지만, 다른 GUI나 도구들과 제대로 연계된 상태가 아닌 듯 하다.  또한 IK가 허접하니 역동역학도 믿을만 할지 모르겠다.

ROS를 산업표준으로 만들기 위한 메이저 산업용 로봇 매니퓰레이터 제조사들이 이미 컨소시움을 구성해서 개발을 지원중이라고 하는데...
그 회사들이 자사의 핵심 기술을 오픈소스에다가 그대로 풀어넣을리 없고 그냥 개발자들 돈이나 좀 쥐어줘서 오픈소스 생태계 형성해 주는 수준 같다.

결론적으로 아직 ROS상의 오픈소스 수준은 메이저 업체들이 보유한 기술과 격차가 크다.



우리나라 OPROS, 일본의 OpenRTM은 둘 다 안습이니 그냥 넘어가자..
어디서나 그렇지만 이쪽 분야도 결국 정치하는 놈들이 문제다.



2013년 11월 6일 수요일

URAI 2013 학회에서 공개된 삼성 휴머노이드


제주도에 URAI 2013 학회가 있었는데, 회사 상무님이 나를 끌고가셨다.
학술세계에서는 아무래도 나로서는 좀 불편할 수 밖에 없지만
(학계에서의 목표와 업계에서의 목표가 다르기 때문에)
그래도 작은 수확이라도 얻어가면 좋겠다는 생각을 했다.
마침 삼성 종합기술원에서
로봇팀을 이끌고 있다는 노경식 박사의 발표 세션이 있는걸 발견하고
가서 주욱 들어 보았다.
1시간여 분량의 동영상을 팔아프게 핸드폰으로 찍었는데,
그 중에서 그나마 볼 만 한 부분만 발췌해 보았다.


Upper Body & Fingers

삼성이 휴머노이드를 한 히스토리를 잠깐 상고해 보면....

2000년 전후해서,
삼성 소프트웨어 맴버쉽의 한 팀이 이족보행로봇을 개발하려고 시도한 적이 있었다.
그때 곽노산씨라는 서울대 대학원생이었던 분이 기억난다.
자신들이 하던 공부 내용을 인터넷에 공개도 하고 해서
고맙게 일독했던 기억이 있다.

그런데...
그 팀의 이족보행 로봇 프로젝트는 끝까지 가지 못하고 도중에 드랍되고 말았다.
삼성에서 예산지원을 받기 위해 중간 심사를 거쳐야 했나 본데
아마 짤려버렸나 보다.

그 시기에는 아직 제대로 된 이족보행 로봇이 한국에 존재하지 않았기 때문에
나름대로 굉장히 선구적인 시도였는데
그 소프트웨어 맴버쉽 예산 배정을 맡은 삼성 담당 직원의 시야가
넓지 못했었지 않나 싶다.


Walking & Stability

아무튼 그 이후에,
내가 로봇 업계에 들어오고 나서 처음 한 게 KIST에 파견나가서
우리 제품을 설계하는 거였는데
(3개월만에 설계 제작 끝내고 전시회에 나가는 거였음.  글자그대로 미친 일정..)
2006~7년 전후해서다.

이때 KIST의 유범재 박사팀에서 마루,아라라는 휴머노이드를 하고 있었는데
삼성이 여기에 붙어서 국책과제를 하나 시작했다.
당시 KIST 연구원들의 이야기로는 삼성 엔지니어들이랑 사이가 별로 좋은 편은 아니었다고.
원래 엔지니어라는 인간들이 다들 고집이 센 편이라서 그랬을 것이다.


Hands & Arms with Impedance Control

KIST에서의 이족보행 연구에서 주목할 부분은
당시 책임급이었던 김도익 박사가 참여했던 논문에서 볼 수 있었다.
ZMP를 가지고 노는 전통적인 일본식 방법론에서
좀 더 단순하게 COM 포인트를 가지고 동역학 문제를 기구학 문제로 환원하여
실시간 연산을 시키는 방식이었다.  때문에 문제를 단순화하여 구현을 했다는 거다.

관절의 하위제어기는 H-Infinity를 활용한 강인제어기를 사용하고 있었는데
다시말해 Force Feedback은 없었던 것이다.

이후에 삼성이 KIST와의 국책과제를 끝내고, 노경식 박사팀이 비공개로 계속 연구를 해 왔나보다.
발표된 내용을 보니, 예전의 KIST 방식에서 잘 발전된 진일보한 모습을 보여준다.
우선 딱 보기에도 우수하다.
완성도 측면에서도 그렇고, 보행시 무릎을 완전히 펴고 직립보행을 하는 것도 그렇다.
기구학적인 Singularity 문제를 회피하기 위한 모종의 술수를 쓴 것 같다.


SLAM with 3D Map by Vision

삼성 휴머노이드 Roboray의 특징은,
단순히 이족보행 뿐만 아니라
휴머노이드에게 필요한 모든 매커니즘을 제대로 다 구현해 넣었다는 점인 것 같다.
또 소프트웨어 부분에서도 SLAM 등의 기술을 적용하여 제대로 보여줬다.
한마디로 완성도가 높은 것이다.

대략적인 스펙은 뭐 뻔하지만...
리눅스 기반에 실시간 미들웨어 집어넣어 주고
제어 아이터레이션 타임은 1kHz 또는 10kHz 수준.
관절은 54자유도였던가 56자유도 였던가 암튼 그쯤 되고.
감속기는 하모닉.

메커니즘상의 특징은,
무릎을 펴고 걷는 직립보행을 구현하기 위해
발에도 관절을 하나 더 넣어 줬다는 거다.

기구설계적인 측면에서는
실험실 작품 답게 사실 별 매력은 없었다.
상품화가 불가능한 설계에서 벗어난 수준은 아니다.


노경식 박사팀의 연구원들.
대략 40~50명 정도 되는 것 같이 보인다.


Youtube에서 Roboray 찾아보니 비교적 깨끗한 화질의 데모가 있는데
함께 참고해 본다.



아무튼 완성도가 높은 이유는 무엇일까?
위 사진을 보니깐 그 이유를 이해할 수 있을 것 같았다.
회사로부터의 실적압박, 영업요구 대응, 연구비 펀딩작업 등에서 해방된 상태의
순수한 연구인원이 충분한 것이다.
대학연구실의 소규모 도제식 시스템과도 차이가 있고,
국책연구소 특유의 관료주의나 예산따내기 압박 및 중복된 과제들에 짓눌린 것도 아니며,
다른 중소기업들처럼 실적에 목을 메는 상황인 것도 아니다.
아주 부러운 환경이라고 생각된다.


Robot Surgery

동 발표에서 직장암 수술로봇 결과물도 보여주었다.
다빈치 로봇의 그리퍼나 메커니즘을 흉내낼 수 없는 이유가,
그 회사에서 특허 장벽을 엄청나게 빽빽하게 포스팅해 놨기 때문에
특허회피가 굉장히 어렵다고 한다.
삼성 수술로봇의 경우에는 다빈치와 확연히 구분되는 방식이다.
동작 동영상을 보니 재미있는 구조다.
양쪽의 집게를 이용해서 봉합 작업을 하는 장면이 아기자기했다.


삼성 발표세션 말고 다른 것은 바빠서 참관하지 못했다.
일본 것도 있었는데 못 봐서 아쉽긴 하다.

포스터 세션 쪽에는 소소한 재미있는 것들이 있었는데 역시 자세히 보지는 못했다.

우리 포스터.

포스터는 출력소에서 2만원 주고 출력했는데,
종이가 아니고 비닐재질에 방수잉크라서 아주 좋았다.



어떤 일본 교수가 흥미를 보여줬는데, 요즘 자동차 회사들이 종종 발표하곤 하는
Personal Mobility와 비슷한 점에서 그러지 않았나 싶기도 하고
또는 컨셉이 실버마켓을 노린 것이기 때문에 그런 것 같기도 했다.

기타 자세한 내용은 생략.



2013년 10월 27일 일요일

2013 로봇월드 전시회 사진 모음


오랜만에 로봇월드 전시회에서 참가해서 일을 해 보니 여러가지로 마음이 복잡하다.





부스 디자인이나 설치 과정에서 주어진 조건 하에서 큰 이상없이 잘 해 주신 것 같다.


 

 

타회사들과 큰 차별성이 없는 제품들은 기본으로 깔아놓고.


 

스타게이저 표지를 붙이기 위한 스탠드도 세워두어져 있다.



 


 

실버로봇의 시연은 큰 문제가 다행히 없었는데, 어린이들이 그걸 타겠다고 엄마 아빠 손잡고 줄 서서 기다리는 걸 보고서는 놀랐었다.  의외로 관람객들이 줄을 엄청 잘 서고, 질서도 잘 지키는 것도 놀라웠고...  어린이들이 로봇을 운전하는데 생각보다 척 보더니 엄청 잘 하는 것에도 놀랐다.  거의 사고 위험이 없었다.
바닥 재질은 유리판으로 되어 있었는데, 옴니휠 캐스터 적용한 덕분에 큰 이상없이 잘 작동되었다.  진동은 느껴지지만 전시장의 소음 때문에 로봇에서 발생하는 소음은 전혀 느껴지지 않았다.  다행이라 해야하나...



 



 



 

분홍색 버전의 시제품은 실작동 시키지 않고 세워두기만 했는데 생각보다 사진을 찍어가는 사람들이 많았다.  과제 심사때 디자인에 대한 지적이 있었는데, 실물에 대한 반응은 디자인이 차별성이나 우월성을 사람들이 직관적으로 느끼는 듯 했다.



 

바로 옆 부스의 유진로봇에서 과제로 만들었다는 시제품인데, 외관 스타일로만 놓고 비교해 봤을 때 ED 실버로봇 쪽이 더 예쁘게 보이는 건 사실이니까...
기성품 전동휠체어에서 떼어낸 의자와 급하게 만든 각관 프레임을 보니 유진로봇의 개발자들도 꽤 갑갑한 상황이겠구나 하는 추측이 들었다.




아로는 다른 기능은 다 포기하고 웨이포인트를 찍으면서 돌아다니는 동작만 시켰는데, 유로보 영어학습 게임을 보는 디스플레이 관람공간과 동선이 겹쳐서 운영상 어려움이 있었다.
부스 디자인에서 로봇 시연의 기능적인 부분들을 더 고려했으면 더 좋았겠다는 아쉬움이 있었다.


  

수술로봇, 의료로봇 어쩌구 하면서 유행 타는 모습을 볼 수 있다.
실제로 살아남아 수익을 발생시키는 회사는 나중에 얼마나 될 지 궁금하다.




퍼스텍에서는 매니퓰레이터를 결국 직접 만들었나보다.
실제 작동을 시키지 않는 걸로 봐서는 신뢰성 등에서의 문제가 아직 해결되지 못한건가 싶기도 하고...  아무튼 퍼스텍 분이 날 기억하시더라..



 



 

양팔과제.  잘 되었으면 한다.