# # Goldilocks v2.0 # Ensure textures are not too big and not too small for a specific Viewpoint # Collate Geometry Density to spot inappropriate mesh density # # Created by Adam Billyard on 21/10/2010. # Copyright (c) 2010 Billyard Enterprises. All rights reserved. # module Goldilocks class BarChartDialog < UI::WebDialog @@images = Hash.new @@images["red"] = "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAAQCAYAAADXnxW3AAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGF VM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2h B/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq /IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog8 36Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbI EL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp +DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd 70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+ KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8 muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn /WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq8 9S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5z rgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl 12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAA CXBIWXMAAAsSAAALEgHS3X78AAAATElEQVQIHR3GwQ2AIBAEwM0CH4E2fFq2tmATdqImPJQQz5yH n8lgRVDs86KU4wSlFPC9blBrNVozHgG2cVK6OIA+RTB0XEp9hs8/GR+WEBlii24h4AAAAABJRU5E rkJggg==" @@images["green"] = "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAAQCAYAAADXnxW3AAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGF VM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2h B/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq /IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog8 36Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbI EL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp +DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd 70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+ KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8 muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn /WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq8 9S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5z rgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl 12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAA CXBIWXMAAAsSAAALEgHS3X78AAAAUUlEQVQIHR3HuQ2AMBAEwNViH28pRBRASg1UQ48UQUAEkR9s w4lkpMG4Ti/nbQH95cBwe/BxEcwhKTGDJRXw3A+wqg1oGgvaTjGtIr1ofwbBBwuTF4+Bs1H6AAAA AElFTkSuQmCC" @@images["gold"] = "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAAQCAIAAABY/YLgAAAC7mlDQ1BJQ0MgUHJvZmlsZQAAeAGF VM9rE0EU/jZuqdAiCFprDrJ4kCJJWatoRdQ2/RFiawzbH7ZFkGQzSdZuNuvuJrWliOTi0SreRe2h B/+AHnrwZC9KhVpFKN6rKGKhFy3xzW5MtqXqwM5+8943731vdt8ADXLSNPWABOQNx1KiEWlsfEJq /IgAjqIJQTQlVdvsTiQGQYNz+Xvn2HoPgVtWw3v7d7J3rZrStpoHhP1A4Eea2Sqw7xdxClkSAog8 36Epx3QI3+PY8uyPOU55eMG1Dys9xFkifEA1Lc5/TbhTzSXTQINIOJT1cVI+nNeLlNcdB2luZsbI EL1PkKa7zO6rYqGcTvYOkL2d9H5Os94+wiHCCxmtP0a4jZ71jNU/4mHhpObEhj0cGDX0+GAVtxqp +DXCFF8QTSeiVHHZLg3xmK79VvJKgnCQOMpkYYBzWkhP10xu+LqHBX0m1xOv4ndWUeF5jxNn3tTd 70XaAq8wDh0MGgyaDUhQEEUEYZiwUECGPBoxNLJyPyOrBhuTezJ1JGq7dGJEsUF7Ntw9t1Gk3Tz+ KCJxlEO1CJL8Qf4qr8lP5Xn5y1yw2Fb3lK2bmrry4DvF5Zm5Gh7X08jjc01efJXUdpNXR5aseXq8 muwaP+xXlzHmgjWPxHOw+/EtX5XMlymMFMXjVfPqS4R1WjE3359sfzs94i7PLrXWc62JizdWm5dn /WpI++6qvJPmVflPXvXx/GfNxGPiKTEmdornIYmXxS7xkthLqwviYG3HCJ2VhinSbZH6JNVgYJq8 9S9dP1t4vUZ/DPVRlBnM0lSJ93/CKmQ0nbkOb/qP28f8F+T3iuefKAIvbODImbptU3HvEKFlpW5z rgIXv9F98LZua6N+OPwEWDyrFq1SNZ8gvAEcdod6HugpmNOWls05Uocsn5O66cpiUsxQ20NSUtcl 12VLFrOZVWLpdtiZ0x1uHKE5QvfEp0plk/qv8RGw/bBS+fmsUtl+ThrWgZf6b8C8/UXAeIuJAAAA CXBIWXMAAAsSAAALEgHS3X78AAAAS0lEQVQIHQFAAL//AG9AFwDGtYAC9e/uAvfz8gL69/cC+/j5 Avz7+wL+/PwDMBQBAgUGBQIHCAYCBwkHAgYJCAIHCggCCAoKAggLC7QHFTTcCb0/AAAAAElFTkSu QmCC" def initialize(*xargs) super add_action_callback("onResize") {|d,p| d.set_size(p.split(',')[0].to_i,p.split(',')[1].to_i)} add_action_callback("onClose") {|d,p| d.close} @bars = '' @high = 100 end def setscale(high) @high = high * 1.25 end def addbar(name, info, value, color) klass = @bars=='' ? 'first' : '' path = "url(#{File.dirname(__FILE__) + '/'}bar#{color}.png)" path = @@images[color] @bars += %[
#{info}
#{name.gsub(/(<)/,'<').gsub(/(>)/,'>')} #{(value * 10).round * 0.1} ] end def show(col1, col2) html = %[
v2.0
#{Time.new.strftime("%Y-%m-%d %H:%M")}
#{@bars}
#{col1} #{col2}
] set_html(html) super() end end def Goldilocks.findclosestfeature(face, p) bestd = 1e20 testp = p.project_to_plane(face.plane) if face.classify_point(testp) < 8 bestv = testp - p bestd = bestv.length end for e in face.edges line = e.line testp = p.project_to_line(line) dot = line[1].dot(testp - line[0]) if dot < 0 testp = line[0] elsif dot > 1 testp = line[0] + line[1] end testv = testp - p testd = testv.length if testd < bestd bestv = testv bestd = testd end end return bestv end def Goldilocks.walkFaces(container, xform, defmat, stats, &block) leye = Sketchup.active_model.active_view.camera.eye.transform(xform.inverse) lat = Sketchup.active_model.active_view.camera.zaxis.transform(xform.inverse).normalize for ent in (container.respond_to?("entities") ? container.entities : container) if ent.kind_of?(Sketchup::Face) and ent.visible? and ent.layer.visible? #frustum culling bestv = findclosestfeature(ent, leye) zdist = lat.dot(bestv) next if zdist < 0 inheritmat = defmat inheritmat = ent.material if ent.material != nil if inheritmat and inheritmat.texture ratio = block.call(ent, xform, zdist, inheritmat) if stats[inheritmat] stats[inheritmat] = ratio if ratio < stats[inheritmat] else stats[inheritmat] = ratio end end inheritmat = defmat inheritmat = ent.back_material if ent.back_material != nil if inheritmat and inheritmat.texture ratio = block.call(ent, xform, zdist, inheritmat) if stats[inheritmat] stats[inheritmat] = ratio if ratio < stats[inheritmat] else stats[inheritmat] = ratio end end end inheritmat = defmat if ent.kind_of?(Sketchup::Group) and ent.visible? and ent.layer.visible? inheritmat = ent.material if ent.material != nil walkFaces(ent, xform * ent.transformation, inheritmat, stats, &block) end if ent.kind_of?(Sketchup::ComponentInstance) and ent.visible? and ent.layer.visible? inheritmat = ent.material if ent.material != nil walkFaces(ent.definition, xform * ent.transformation, inheritmat, stats, &block) end end end def Goldilocks.texreport(width=Sketchup.active_model.active_view.vpwidth, height=Sketchup.active_model.active_view.vpheight) unitvp = Math::sqrt(width * height) # reset stats = Hash.new w2t = Hash.new Sketchup.active_model.materials.each {|mat| if mat.texture w2t[mat] = Math::sqrt(mat.texture.image_width * mat.texture.image_height) / Math::sqrt(mat.texture.width * mat.texture.height) #puts "#{mat.name} = #{w2t[mat]} (#{mat.texture.image_width} texels over #{mat.texture.width} inches)" end } # record amodel = Sketchup.active_model amodel = Sketchup.active_model.selection if Sketchup.active_model.selection.length > 0 walkFaces(amodel, Geom::Transformation.new, nil, stats) {|face,xform, zdist, mat| # 1 unit of face subtends how much texture tsize = Math::sqrt(face.area) * w2t[mat] # 1 unit of face subtends how much viewport psize = Math::sqrt(face.area) * (unitvp / zdist) # how closely matched? tsize / psize } # report sorted = stats.sort {|a,b| b[1]<=>a[1]} if sorted.length > 0 mpath = File.basename(Sketchup.active_model.path) mpath = 'Untitled' if mpath.length == 0 dialog = BarChartDialog.new("Goldilocks Result - #{mpath}", false, nil, 100, 100, 200,200, true) dialog.setscale(sorted[0][1]) sorted.each {|line| if line[1] > 0.0 info = "#{line[0].texture.image_width}x#{line[0].texture.image_height}" if line[1] < 0.5 dialog.addbar(line[0].name, info, line[1], "gold") elsif line[1] > 8.0 dialog.addbar(line[0].name, info, line[1], "red") else dialog.addbar(line[0].name, info, line[1], "green") end end } dialog.show("Material Name","Texture Resolution") true else false end end def Goldilocks.bv(ent, xform) v = (ent.bounds.max - ent.bounds.min).transform(xform) return v.x * v.y * v.z end def Goldilocks.walkInstances(container, xform, path, stats) ecount = 0 for ent in (container.respond_to?("entities") ? container.entities : container) if ent.kind_of?(Sketchup::Face) and ent.visible? and ent.layer.visible? ecount += ent.edges.length elsif ent.kind_of?(Sketchup::Group) and ent.visible? and ent.layer.visible? ipath = path+"/"+ (ent.name !=""? ent.name : "Group") iecount = walkInstances(ent, xform * ent.transformation, ipath, stats) stats[ipath] = [bv(ent, xform) , iecount] ecount += iecount elsif ent.kind_of?(Sketchup::ComponentInstance) and ent.visible? and ent.layer.visible? ipath = path+"/"+ent.definition.name iecount = walkInstances(ent.definition, xform * ent.transformation, ipath, stats) stats[ipath] = [bv(ent.definition, xform) , iecount] ecount += iecount end end return ecount end def Goldilocks.geomreport() # collect stats = Hash.new amodel = Sketchup.active_model amodel = Sketchup.active_model.selection if Sketchup.active_model.selection.length > 0 ecount = walkInstances(amodel, Geom::Transformation.new, 'Model', stats) stats['Model'] = [bv(amodel, Geom::Transformation.new) , ecount] stats.delete_if { |key,value| value[0] < 0.00001 } stats.delete_if { |key,value| value[1] < 20 } # normalization oovol = stats['Model'][1] / stats['Model'][0] stats.each {|line| line[1][0] *= oovol} # report sorted = stats.sort {|a,b| b[1][1]/b[1][0] <=> a[1][1]/a[1][0]} if sorted.length > 0 mpath = File.basename(Sketchup.active_model.path) mpath = 'Untitled' if mpath.length == 0 dialog = BarChartDialog.new("Goldilocks Result - #{mpath}", false, nil, 800, 600, 200,200, true) dialog.setscale(sorted[0][1][1]/sorted[0][1][0]) sorted.each {|line| edensity = line[1][1]/line[1][0] info = "#{line[1][1]}" if edensity < 0.9 dialog.addbar(line[0], info, edensity, "gold") elsif edensity > 1.1 dialog.addbar(line[0], info, edensity, "red") else dialog.addbar(line[0], info, edensity, "green") end } dialog.show("Component Path", "Edge Density") true else false end end end if ( not file_loaded?(__FILE__) ) tools = UI.menu("Tools") tools.add_separator tools.add_item("Goldilocks Texture..") { Goldilocks.texreport } tools.add_item("Goldilocks Geometry..") { Goldilocks.geomreport } UI.add_context_menu_handler do |menu| menu.add_separator menu.add_item("Goldilocks Texture..") { Goldilocks.texreport } end UI.add_context_menu_handler do |menu| menu.add_separator menu.add_item("Goldilocks Geometry..") { Goldilocks.geomreport } end file_loaded(__FILE__) end