Skip to content

Instantly share code, notes, and snippets.

@amiller
Created April 13, 2012 09:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amiller/2375263 to your computer and use it in GitHub Desktop.
Save amiller/2375263 to your computer and use it in GitHub Desktop.
Sketchup OBJ Exporter (put in "%APPDATA%/Google Sketchup 8/Sketchup/Plugins" http://sketchuptips.blogspot.com/2009/01/wavefront-obj-exporter-with-materials.html
#-----------------------------------------------------------------------------
#
# WaveFront .Obj File Exporter for Google SketchUp
# Author: M van der Honing, mvdhoning@noeska.com
#
# Based on ZbylsXExporter by Zbigniew Skowron, zbychs@gmail.com
#
# I'd like to thank Jonathan Harris and Erwan de Cadoudal for their exporters.
# They helped me a lot.
#
#-----------------------------------------------------------------------------
module ObjExporter
module_function
Meter_to_inch = 39.3700787
Inch_to_meter = 1.0 / Meter_to_inch
#-----------------------------------------------------------------------------
def reload
pfile = Sketchup.find_support_file "ObjExporter.rb", "Plugins"
load pfile
end
#-----------------------------------------------------------------------------
def obj_header()
text = "#Exported from google sketchup"
end
#-----------------------------------------------------------------------------
def directx_def_material()
#Ka
#Kd
#Ks
#Ns
text = "newmtl Default_Material
Ka 0.000000 0.000000 0.000000
Kd 1.0 1.0 1.0
Tr 1.0
Ks 0.000000 0.000000 0.000000
Ns 3.2
"
end
#-----------------------------------------------------------------------------
def directx_material(name, color, textureFile, alpha)
# #{color.red / 255.0};#{color.green / 255.0};#{color.blue / 255.0};#{color.alpha / 255.0};;
col = format("%8.4f %8.4f %8.4f", 1.0, 1.0, 1.0)
if !textureFile
col = format("%8.4f %8.4f %8.4f",
color.red / 255.0, color.green / 255.0, color.blue / 255.0)
end
specular = format("%8.4f", 3.2)
tex = ""
tex = " map_Kd #{textureFile}" if textureFile
text = "newmtl #{name}
Ka 0.000000 0.000000 0.000000
Kd #{col}
Tr #{alpha/255.0}
Ks 0.000000 0.000000 0.000000
Ns #{specular}
#{tex}
"
end
#-----------------------------------------------------------------------------
def out_tab(f, points, del = "", extra = "")
cnt = points.size
return if cnt < 1
for i in 0..(cnt - 2)
p = points[i]
f.puts(" #{p}#{del}")
end
p = points[cnt - 1]
f.puts(" #{p}#{extra}")
f.puts("# #{cnt} elements")
end
#-----------------------------------------------------------------------------
def out_point(p)
print "Kuozaa Too!\n" if (!p)
return "nil" if (!p)
px = p.x * Inch_to_meter
py = p.z * Inch_to_meter
pz = p.y * Inch_to_meter
#text = "#{px.inspect};#{py.inspect};#{pz.inspect};"
text = format("v %8.4f %8.4f %8.4f", px, py, pz)
end
#-----------------------------------------------------------------------------
def out_normal(p)
print "Kuozaa Noo!\n" if (!p)
return "nil" if (!p)
px = p.x
py = p.z
pz = p.y
#text = "#{p.x.inspect};#{p.y.inspect};#{p.z.inspect};"
text = format("vn %8.4f %8.4f %8.4f", px, py, pz)
end
#-----------------------------------------------------------------------------
def out_uv(u, v)
#text = "#{u.inspect},#{v.inspect};"
text = format("vt %8.4f %8.4f 0.0000", u, v)
end
#-----------------------------------------------------------------------------
#TODO: support toevoegen voor uv en normal indices
def out_face(f, face)
face = face.reverse
cnt = face.size
#return if cnt < 3
#f.printf(" #{cnt};")
#check if material change is needed
#mats = []
# for n, mat in materials
# mats.push(n)
# end
#m = face[1]
#midx = mats.index(m)
#f.puts("mat #{midx};")
f.printf("f ")
for i in 0..(cnt - 2)
p = face[i]
f.printf("#{p[0]+1}/#{p[1]+1}/#{p[2]+1} ")
end
p = face[cnt - 1]
f.printf("#{p[0]+1}/#{p[1]+1}/#{p[2]+1}")
end
#-----------------------------------------------------------------------------
def out_faces(f, faces, materials)
pm = ""
mats = []
for n, mat in materials
mats.push(n)
end
cnt = faces.size
return if cnt < 1
f.puts("# #{cnt} faces")
for i in 0..(cnt - 2)
fc = faces[i]
m = fc[1]
if m!=pm then
f.puts("usemtl #{m}")
pm = m
end
out_face(f, fc[0]);
f.puts("")
end
fc = faces[cnt - 1]
out_face(f, fc[0]);
#out_face_ns(f, fc[0]);
# f.puts(";;")
end
#-----------------------------------------------------------------------------
def out_face_ns(f, face)
face = face.reverse
cnt = face.size
#return if cnt < 3
f.printf(" #{cnt};")
for i in 0..(cnt - 2)
p = face[i]
f.printf("#{p[2]},")
end
p = face[cnt - 1]
f.printf("#{p[2]}")
end
#-----------------------------------------------------------------------------
def out_normals(f, faces)
cnt = faces.size
return if cnt < 1
f.puts(" #{cnt};")
for i in 0..(cnt - 2)
fc = faces[i]
out_face_ns(f, fc[0]);
f.puts(";")
end
fc = faces[cnt - 1]
out_face_ns(f, fc[0]);
f.puts(";;")
end
#-----------------------------------------------------------------------------
def out_face_materials(f, faces, materials)
#mats = materials.keys #is this ok?
mats = []
for n, mat in materials
mats.push(n)
end
f.puts(" #{mats.size};")
cnt = faces.size
return if cnt < 1
f.puts(" #{cnt};")
for i in 0..(cnt - 2)
fc = faces[i]
m = fc[1]
midx = mats.index(m)
f.puts(" #{midx},")
end
fc = faces[cnt - 1]
m = fc[1]
midx = mats.index(m)
f.puts(" #{midx};")
mats.each { |m|
f.puts(" { #{m} }")
}
end
#-----------------------------------------------------------------------------
def out_materials(f, materials)
for n, mat in materials
if n == "Default_Material"
f.puts(directx_def_material())
else
f.puts(directx_material(mat[0], mat[1], mat[2], mat[3]));
end
end
end
#-----------------------------------------------------------------------------
def processFileName(fname)
slash = "\\"
dir = "c:\\temp"
sidx = fname.rindex(slash)
if (!sidx)
slash = "/"
dir = "/tmp"
sidx = fname.rindex(slash)
end
name = "Untitled.obj"
if (sidx)
dir = fname[0..(sidx-1)]
name = fname[(sidx+1)..-1]
didx = name.rindex('.')
name += ".obj" if !didx
name[didx..-1] = ".obj" if didx
end
return [name, dir, slash]
end
#-----------------------------------------------------------------------------
def exportXFileUI
# Create the WebDialog instance
fname = Sketchup.active_model.path
name, dir, slash = processFileName(fname)
my_dialog = UI::WebDialog.new(".Obj File Exporter", true, ".Obj File Exporter",
600, 600, 200, 200, true)
# Attach an action callback
my_dialog.add_action_callback("browse") { |web_dialog, params|
#puts params
a = params.split(',');
outDir = a[0]
outFile = a[1]
outDir += slash if outDir[-1] != slash[0]
#puts(outDir, outFile)
fname = UI.savepanel("Export to...", outDir, outFile)
fname = UI.savepanel("Export to...") if !fname
if fname
name, dir, slash = processFileName(fname)
dir = dir.gsub(/['"\\]/) { '\\'+$& }
my_dialog.execute_script(
"document.getElementById('outDir').value = '#{dir}'");
my_dialog.execute_script(
"document.getElementById('outFile').value = '#{name}'");
end
}
my_dialog.add_action_callback("ruby") { |web_dialog, params|
#puts params
# display ruby panel for messages
Sketchup.send_action "showRubyPanel:"
}
# Attach an action callback
my_dialog.add_action_callback("export") { |web_dialog, params|
#puts params
a = params.split(',');
#UI.messagebox("Ruby says: Your javascript has asked for " + a)
#my_dialog.execute_script(
# "document.getElementById('ala').innerHTML= '#{a.to_s}'");
outDir = a[0]
outFile = a[1]
smartFaces = a[2] == "true"
front = a[3] == "true"
back = a[4] == "true"
uv = a[5] == "true"
sel = a[6] == "true"
tex = a[7] == "true"
outName = outDir + slash + outFile
#print(outName, outDir, outFile, smartFaces, front, back, uv, sel, tex)
my_dialog.execute_script(
"document.getElementById('console').innerHTML = ''");
my_dialog.execute_script(
"document.getElementById('progress').innerHTML = 'Exporting...'");
res = exportXFile(outName, outDir, smartFaces, front, back, uv, sel, tex,
lambda {|forward, text|
if forward
puts text
text = text.gsub(/['"\\]/) { '\\'+$& }
text = text.gsub(/[\n]/) { '<br>' }
text += '<br>'
my_dialog.execute_script(
"document.getElementById('console').innerHTML += '\\n#{text}'");
else
print text
#my_dialog.execute_script(
# "document.getElementById('progress').innerHTML += '#{text}'");
end
})
my_dialog.execute_script(
"document.getElementById('progress').innerHTML = '#{res ? 'Exported' : 'Not exported!'}'");
}
html="
<html>
<head>
<title>WorldEd Exporter</title>
<script>
function callRuby(actionName, params) {
query = 'skp:'+ actionName + '@' + params;
window.location.href = query;
}
function rubyFunc() {
callRuby('ruby', '')
}
function browseFunc() {
outputDir = document.getElementById('outDir').value
outputFile = document.getElementById('outFile').value
callRuby('browse', outputDir + ',' + outputFile)
}
function exportFunc() {
outputDir = document.getElementById('outDir').value
outputFile = document.getElementById('outFile').value
smart = document.getElementById('smartFaces').checked
front = document.getElementById('frontFaces').checked
back = document.getElementById('backFaces').checked
uv = document.getElementById('useUVHelper').checked
sel = document.getElementById('exportSelected').checked
tex = document.getElementById('exportTextured').checked
callRuby('export', outputDir + ',' + outputFile + ',' + smart + ',' + front + ',' + back + ',' + uv + ',' + sel + ',' + tex)
}
</script>
</head>
<body>
<input type='button' onclick='exportFunc()' value='Export .Obj File'>
<input type='button' onclick='rubyFunc()' value='Show Console'><br>
<div>
<div id='progress' style='color: red'>
</div>
Output directory:<br>
<input id='outDir' type='text' value='#{dir}' size='60'><br>
Output file:
<input type='button' onclick='browseFunc()' value='Browse'><br>
<input id='outFile' type='text' value='#{name}' size='60'><br>
<input id='smartFaces' type='checkbox' value='Smart faces' checked='checked'> Guess which faces to export<br>
<input id='frontFaces' type='checkbox' value='Front faces'> Export front faces<br>
<input id='backFaces' type='checkbox' value='Back faces'> Export back faces<br>
<input id='useUVHelper' type='checkbox' value='Use UVHelper' checked='checked'> Use UVHelper<br>
<input id='exportSelected' type='checkbox' value='Export Selected'> Export selected only<br>
<input id='exportTextured' type='checkbox' value='Export Textured'> Export textured only<br>
<hr>
<div id='console' style='color: grey; font-family: monospace; font-size: smaller'>
</div>
</div>
</body>
</html>
"
#print html
my_dialog.set_html(html)
my_dialog.show {
#my_dialog.execute_script(
# "document.getElementById('ala').innerHTML = '<b>Hi There!</b>'");
}
end
#-----------------------------------------------------------------------------
def exportXFileEngine(entities, trans, tw,
materials, indexer, points, uvs, normals, faces,
smartInfo, print_callback)
entities.each { |ent|
next if ent.hidden? or not ent.layer.visible?
case ent.typename
when "ComponentInstance"
print_callback.call(true, "Exporting Component: " + ent.definition.name + " " + ent.name)
exportXFileEngine(ent.definition.entities, trans*ent.transformation, tw,
materials, indexer, points, uvs, normals, faces,
smartInfo, print_callback)
#print_callback.call(true, "Done.")
when "Group"
print_callback.call(true, "Exporting Group: " + ent.name)
exportXFileEngine(ent.entities, trans*ent.transformation, tw,
materials, indexer, points, uvs, normals, faces,
smartInfo, print_callback)
#print_callback.call(true, "Done.")
end
}
ss = entities
ss = ss.select {|ent|
(ent.kind_of? Sketchup::Face) && !ent.hidden? && ent.layer.visible?
}
if ss.empty?
print_callback.call(true, "Nothing to export.")
return false
end
smarty = {}
ss = ss.select {|face|
matF = nil;
matF = face.material
texF = nil
texF = matF.texture if matF
matB = nil;
matB = face.back_material
texB = nil
texB = matB.texture if matB
smart = 0
smart |= 1 if smartInfo["frontFaces"]
smart |= 2 if smartInfo["backFaces"]
if (smartInfo["smartFaces"])
smart |= 1 if texF
smart |= 2 if texB
if smart == 0
smart |= 1 if matF
smart |= 2 if matB
smart |= 3 if smart == 0
end
end
smart &= ~1 if smartInfo["texturedOnly"] && !texF
smart &= ~2 if smartInfo["texturedOnly"] && !texB
smarty[face] = smart if smart != 0
(smart != 0)
}
if ss.empty?
print_callback.call(true, "Nothing to export: no faces chosen")
return false
end
if smartInfo["textureWritePass"]
ss.each {|face|
[true, false].each {|front|
if (smarty[face] & (front ? 1 : 2)) != 0
tw.load(face, front)
end
}
}
return true
end
# Create transformation w/out translation for normals
narray=trans.to_a
narray[12..16]=[0,0,0,1]
ntrans = Geom::Transformation.new(narray)
ss.each {|face|
[true, false].each { |front|
print_callback.call(false, ".")
if (smarty[face] & (front ? 1 : 2)) == 0
next
end
mat = nil;
mat = face.material if front
mat = face.back_material if !front
tex = nil
tex = mat.texture if mat
texFile = nil
if tex
handle = tw.handle(face, front)
f.puts "Texture handle invalid!" if handle < 0
texFile = tw.filename(handle).to_s
end
mname = "Default_Material"
if mat
mname = "_" + mat.name
mname = mname + "-" + texFile if texFile
mname = mname.gsub(/[^a-zA-Z0-9]/, "_")
alpha = 255
alpha = mat.alpha if mat.use_alpha?
materials[mname] = [mname, mat.color, texFile, alpha]
#puts(materials[mname])
else
materials[mname] = [mname, nil, nil, nil]
end
if tex
uvHelp = face.get_UVHelper(true, true, tw)
#retval = tw.write(face, front, fname + tw.filename(handle).to_s)
#retval = (retval == FILE_WRITE_OK)
mus = []
mvs = []
face.outer_loop.vertices.each { |vertex|
pos = vertex.position
if front
uvq = uvHelp.get_front_UVQ(pos).to_a
else
uvq = uvHelp.get_back_UVQ(pos).to_a
end
#mus << (u.x/u.z)
#mvs << (u.y/u.z)
mus << (uvq.x)
mvs << (uvq.y)
puts "#{uvq.x} #{uvq.y} #{uvq.z}" if !(1.0-0.0001 < uvq.z && uvq.z < 1.0+0.0001)
}
minu = mus.min
minv = mvs.min
maxu = mus.max
maxv = mvs.max
#print("#{minu} #{minv} #{maxu} #{maxv}\n")
end
mesh = face.mesh 7
#puts("Mesh:")
for p in (1..mesh.count_polygons)
poly = mesh.polygon_points_at(p)
ipoly = mesh.polygons[p - 1]
#print "Poly #{poly}\n"
theFace = []
for i in 0..(poly.size - 1)
idx = i
idx = (poly.size - 1) - i if !front
pos = poly[idx]
uq = 0.0
vq = 0.0
if tex
if smartInfo["useUVHelper"]
if front
uvq = uvHelp.get_front_UVQ(pos).to_a
else
uvq = uvHelp.get_back_UVQ(pos).to_a
end
uq = (uvq.x)
vq = (uvq.y)
#printf("%8.4f %8.4f %8.4f %8.4f %8.4f %8.4f\n", uq, vq, (maxu - minu), (maxv - minv), mat.texture.width, mat.texture.height)
#printf("%8.4f %8.4f %8.4f\n", uvq.x, uvq.y, uvq.z)
else
ni = ipoly[idx]
ni = - ni if ni < 0
uvss = mesh.uv_at(ni, 1)
uq = uvss.x / mat.texture.width
vq = uvss.y / mat.texture.height
#printf("%8.4f %8.4f %8.4f\n", uvss.x, uvss.y, uvss.z)
end
end
pt = out_point(trans * pos)
uv = out_uv(uq, vq)
ptuv = pt + uv
pidx = indexer.index(ptuv)
if !pidx
pidx = indexer.size
points.push(pt)
uvs.push(uv)
indexer.push(ptuv)
end
ni = ipoly[idx]
ni = - ni if ni < 0
norm = mesh.normal_at(ni)
n = norm
n = n.reverse if (!front)
n = ntrans * n
n = n.normalize
n = out_normal(n)
nidx = normals.index(n)
if !nidx
nidx = normals.size
normals.push(n)
end
theFace.push([pidx, pidx, nidx])
end
#puts "TheFace #{theFace}"
faces.push([theFace, mname])
end
}
}
print_callback.call(false, "\n")
return true
end
#-----------------------------------------------------------------------------
def exportXFile(fname, outDir, smartFaces, export_front_faces, export_back_faces,
use_uv_helper, selectedOnly, texturedOnly, print_callback)
if !fname || !outDir
print_callback.call(true, "Empty file or directory name.")
return false
end
model = Sketchup.active_model
ss = model.active_entities
ss = model.selection if selectedOnly
smartInfo = {}
smartInfo["smartFaces"] = smartFaces
smartInfo["frontFaces"] = export_front_faces
smartInfo["backFaces"] = export_back_faces
smartInfo["useUVHelper"] = use_uv_helper
smartInfo["selectedOnly"] = selectedOnly
smartInfo["texturedOnly"] = texturedOnly
print_callback.call(true, "Exporting textures to: " + outDir)
tw = Sketchup.create_texture_writer
smartInfo["textureWritePass"] = true
exportXFileEngine(ss, Geom::Transformation.new, tw,
nil, nil, nil, nil, nil, nil,
smartInfo, print_callback)
result = tw.write_all(outDir, false)
print_callback.call(true, "Writing textures failed!") if !result
print_callback.call(true, "Analyzing geometry")
smartInfo["textureWritePass"] = false
materials = {}
indexer = []
points = []
uvs = []
normals = []
faces = []
exportXFileEngine(ss, Geom::Transformation.new, tw,
materials, indexer, points, uvs, normals, faces,
smartInfo, print_callback)
print_callback.call(true, "Exporting geometry to: " + fname)
#write material file here before if needed
#TODO remove .obj
fm = File.new (fname.gsub(".obj", ".mtl"), "w")
fm.puts "#Materials exported from Google Sketchup"
fm.puts ""
out_materials(fm, materials)
fm.close;
#write mesh data
f = File.new(fname, "w")
f.puts(obj_header())
#TODO remove path and remove .obj
f.puts "mtllib "+fname.gsub(outDir+"\\","").gsub(".obj", ".mtl")
# out_materials(f, materials)
# f.puts "Mesh mesh_0{"
out_tab(f, points)
out_tab(f, uvs, "", "")
out_tab(f, normals)
# f.puts " MeshMaterialList {"
# out_face_materials(f, faces, materials)
# f.puts " }"
# f.puts " MeshTextureCoords {"
# f.puts " }"
# f.puts " MeshNormals {"
# out_normals(f, faces)
out_faces(f, faces, materials)
# f.puts " }"
# f.puts "}"
f.close
print_callback.call(true, "Done.")
return true
end
#-----------------------------------------------------------------------------
end
if( not file_loaded?("ZbylsXExporter.rb") )
#add_separator_to_menu("Plugins")
plugins_menu = UI.menu("Plugins")
plugins_menu.add_item("OBJ Exporter...") { ObjExporter.exportXFileUI }
end
file_loaded("ObjExporter.rb")
#-----------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment