Webtextures Loader

Embed Size (px)

Citation preview

  • 8/12/2019 Webtextures Loader.

    1/16

  • 8/12/2019 Webtextures Loader.

    2/16

    @last_shadow_info_json = false

    # Create our dialog. keys = { :dialog_title => title, :scrollable => false, :preferences_key => WT_DIALOG_REGISTRY_KEY, :height => WT_DIALOG_HEIGHT, :width => WT_DIALOG_WIDTH, :min_height => WT_DIALOG_MIN_HEIGHT, :min_width => WT_DIALOG_MIN_WIDTH, :left => WT_DIALOG_X, :top => WT_DIALOG_Y, :resizable => true, :mac_only_use_nswindow => true} @dialog = UI::WebDialog.new(keys)

    @dialog.set_background_color('000000') @dialog.set_html('')

    # Attach all of our callbacks. @dialog.add_action_callback("grab") { |d, p| grab(p) } @dialog.add_action_callback("agree_to_eula") { |d, p| agree_to_eula(p) } @dialog.add_action_callback("store_ui_state") { |d, p| store_ui_state(p) }

    @dialog.add_action_callback("pull_ui_state") { |d, p| pull_ui_state(p) } @dialog.add_action_callback("get_flash") { |d, p| get_flash(p) } @dialog.add_action_callback("pull_selected_shape") { |d,p| pull_selected_shape(p) } @dialog.add_action_callback("open_url") { |d, p| open_url(p) } @dialog.add_action_callback("open_eula") { |d, p| open_eula() }

    # A hash to store arbitrary state about the WebDialog's embedded html UI. @ui_state = {}

    # Version string that will be written as an attribute onto any created # material.

    @version_ruby = "1.0.0"

    # Place where we will save the texture to. if @is_mac @image_path = temp_directory + '/temp.jpg' else @image_path = temp_directory + '\\temp.jpg' end

    # Where to load the url from. # TODO(scottlininger): Replace the default URL with the live one # once it's live. @url = Sketchup.get_datfile_info 'WEB_TEXTURES',

    'http://sketchup.google.com/3dwarehouse/skptextures' @dialog.set_url(@url) show() end

    # A callback that opens a url in the default browser. # # Args: # url: The URL

  • 8/12/2019 Webtextures Loader.

    3/16

    # # Returns: # Nothing # def open_url(url) UI.openURL(url) end

    # A callback that opens the SketchUp EULA in the default browser. # # Args: # None # # Returns: # Nothing # def open_eula() if Sketchup.is_pro? url = Sketchup.get_datfile_info 'EULA_PRO', 'http://sketchup.com/intl/' + Sketchup.get_locale + '/redirects/gsu8/eula_pro.html' else url = Sketchup.get_datfile_info 'EULA',

    'http://sketchup.com/intl/' + Sketchup.get_locale + '/redirects/gsu8/eula.html' end UI.openURL(url) end

    # A callback that allows javascript to get a string describing the shape # of the currently selected face. If anything but a single face is selected, # then a simple rectangle will be sent. By default, it replies to the # javascript by setting a global js variable called 'shapeString', but # if an optional param called oncomplete is passed, then it will call # that function instead.

    # # Args: # params: The params string that was sent as part of the callback. It # may have an optional "oncomplete" param. # # Returns: # Nothing # def pull_selected_shape(params) params = query_to_hash(params)

    # Figure out how many faces are selected. selection = Sketchup.active_model.selection

    faces = [] for entity in selection faces.push entity if entity.typename == "Face" end

    # Generate a string describing the shape if only one is selected. # Otherwise just describe a rectangle shape. uv_strings = [] if faces.length == 1 corners, vertex_uvs = get_uvs(faces[0])

  • 8/12/2019 Webtextures Loader.

    4/16

    for uv in vertex_uvs uv_strings.push(uv['u'].to_s + ',' + uv['v'].to_s) end else uv_strings.push('0,0') uv_strings.push('1,0') uv_strings.push('1,1') uv_strings.push('0,1') end shape_string = uv_strings.join(':');

    # Figure out the width and height if only one face is selected. # Otherwise just describe a square. if corners.to_s != "" width = corners[0].distance corners[1] height = corners[1].distance corners[2] else width = 1 height = 1 end

    # Walk the selection and build a string describing the geometry. This # will be encoded as a nested JSON array of x, y, z locations, like this: #

    # [ # [{x:0, y:0, z:0}, {x:1, y:1, z:0}, {x:1, y:0, z:1}], // face 1 # [{x:0, y:0, z:2}, {x:1, y:1, z:2}, {x:1, y:0, z:3}], // face 2 # [{x:0, y:0, z:4}, {x:1, y:1, z:4}, {x:1, y:0, z:5}] // etc... # ] bb = Geom::BoundingBox.new() geometry_json = '[' if faces.length

  • 8/12/2019 Webtextures Loader.

    5/16

    end @dialog.execute_script(cmd)

    end

    # A callback that tells SketchUp that the user has agreed to our EULA. This # will set a registry value to record that fact as a unix timestamp. # # Args: # params: The params string that was sent as part of the callback. It # may have an optional "oncomplete" param. # # Returns: # Nothing # def agree_to_eula(params) params = query_to_hash(params) Sketchup.write_default WT_REGISTRY_SECTION, WT_REGISTRY_KEY, Time.now.to_i @agreed_to_eula = true end

    # A callback that allows the web dialog's Javascript to send down an

    # arbitrary JSON state string that will be sent back up to the dialog # should it be closed and reopened. # # Each JSON string is stored by key in the @ui_state hash. This allows for # different sections of the WebDialog UI to store different states # without clobbering each other. For example, Street View might want # to store the current yaw, pan, and zoom, while a Picasa photo # picker might want to store the current photo URL being viewed. # # Args: # params: The params string that was sent as part of the callback. It is # expected to contain a param called "key" and "state" that has # the JSON string we care to store.

    # # Returns: # Nothing # def store_ui_state(params) params = query_to_hash(params) @ui_state[params['key']] = params['state'] end

    # A callback that allows the web dialog's Javascript to request the # JSON state that was sent down to Ruby via store_state. # # Args:

    # params: The params string that was sent as part of the callback. # # Returns: # Nothing # def pull_ui_state(params) params = query_to_hash(params)

    json = generate_ui_state_json()

  • 8/12/2019 Webtextures Loader.

    6/16

    # Execute a JS command to reply. if params['oncomplete'] != nil cmd = params['oncomplete'] + '(' + json + ')' else cmd = "uiState = " + json end @dialog.execute_script(cmd) end

    # A callback that tells the user they need to install flash. This has # different behavior mac vs. pc. On mac, we show them some messages # and send them to Adobe to run the install. On PC, we tell them what's # happening and to expect an ActiveX install box. # # Args: # params: The params string that was sent as part of the callback. # It could contain 'message' or 'message2', which defines what # messages to show the user, and 'url' which defines where to open # the user's browser on Mac to should they click the 'yes' option. # If none of these is passed down, we use default values. # # Returns: # Nothing

    # def get_flash(params) params = query_to_hash(params)

    if @is_mac

    # Show a Yes/No message box. if params['message'] != nil msg = params['message'] else msg = @strings.GetString("Photo Textures requires the latest" + " version of the Flash player. Would you like to install it now?") end

    response = UI.messagebox(msg, MB_YESNO);

    # If they said yes, open the install URL in their default browser. if response == 6 # YES if params['message2'] != nil msg = params['message2'] else msg = @strings.GetString("We will now send you to an installation" + " page for Flash player. Once you are done with the install," + " please restart SketchUp.") end response = UI.messagebox(msg, MB_OKCANCEL);

    if response == 1 # OK if params['url'] != nil url = params['url'] else url = Sketchup.get_datfile_info 'INSTALL_FLASH', 'http://get.adobe.com/flashplayer/' end UI.openURL url end end

  • 8/12/2019 Webtextures Loader.

    7/16

    @dialog.close() else if params['message'] != nil msg = params['message'] else msg = @strings.GetString("Photo Textures requires the latest" + " version of the Flash player. An installation box for this should" + " appear shortly. (If it does not, please visit www.flash.com to" + " install.) Once you have agreed to the installation, you may need" + " to restart SketchUp.") end UI.messagebox(msg) end end

    # Generates a JSON string representing the current shadow_info # # Args: # None # # Returns: # json: string representing our current shadow_info. # def generate_shadow_info_json()

    shadow_info = Sketchup.active_model.shadow_info return '"shadow_info":{ ' + '"city": "' + shadow_info["City"] + '", ' + '"country":"' + shadow_info["Country"] + '", ' + '"lat": "' + shadow_info["Latitude"].to_s + '", ' + '"lng": "' + shadow_info["Longitude"].to_s + '" ' end

    # Generates a JSON string representing our complete UI state. # # Since many of our web textures UI ideas involve some notion of geolocation, # this callback will always report on the that info inside a key called

    # 'shadow_info'. By default, it replies to the JavaScript by setting a global # js variable called 'uiState', but if an optional param called oncomplete # is passed, then it will call that function instead. # # Args: # None # # Returns: # json: string representing our current ui state. # def generate_ui_state_json()

    # Build out a JSON string of all of our state info.

    json = '{'

    if @agreed_to_eula json += '"agreedToEula":"' + @agreed_to_eula.to_s + '",'; end

    # Figure out the lat/lng of the center point of the current selection. # This will be passed up to the WebDialog so we can use it to improve # pose guessing. selection = Sketchup.active_model.selection

  • 8/12/2019 Webtextures Loader.

    8/16

    if selection.length > 0 && selection.length

  • 8/12/2019 Webtextures Loader.

    9/16

    # corners that bound the face and map to those instead.) # # The string will be four x,y local texture coordinates separated by colons. # A typical string might look like this... '10,90:120,100:120,5:10,5' # # The first corner in the list (c0) is the bottom left, and they go # counter-clockwise from there. The x,y origin (0,0) is located at the top # left of the texture image. # # c3----------c2 # | | # c0---- | # ------c1 # # Args: # params: The params string that was sent as part of the callback. # # Returns: # Nothing, but it does call a Javascript method called onGrabComplete() # when it is complete. # def grab(params) begin params = query_to_hash(params)

    # Make a list of the faces to texture. faces_to_texture = [] selection = Sketchup.active_model.selection for face in selection if face.typename == "Face" faces_to_texture.push(face) end end

    # Bail out if there are no selected faces. if faces_to_texture.length == 0 UI.messagebox(@strings.GetString("Please select one or more faces" +

    " in your SketchUp model that you would like to photo texture" + " and try again.")) if params['oncomplete'] != nil @dialog.execute_script(params['oncomplete']) end return end

    op = @strings.GetString("Apply Photo Texture") Sketchup.active_model.start_operation op, true

    # Capture the screen and create the material. if params['compression'] == nil

    params['compression'] = 75 end if params['top_left_x'] == nil @dialog.write_image(@image_path, params['compression'].to_i) else @dialog.write_image(@image_path, params['compression'].to_i, params['top_left_x'].to_i, params['top_left_y'].to_i, params['bottom_right_x'].to_i, params['bottom_right_y'].to_i)

  • 8/12/2019 Webtextures Loader.

    10/16

    end

    file = @image_path.gsub(/\\/, '/') materials = Sketchup.active_model.materials m = materials.add @strings.GetString("Photo Texture") m.texture = file texture = m.texture

    if params['texture_width'] == nil texture.size = WT_DEFAULT_TEXTURE_WIDTH else texture.size = params['texture_width'].to_f end

    pixel_width = texture.image_width.to_f pixel_height = texture.image_height.to_f

    # Attach some attributes to the material so we can view on 3D Warehouse. m.set_attribute "web_textures", "version_ruby", @version_ruby m.set_attribute "web_textures", "ui_state", generate_ui_state_json() m.set_attribute "web_textures", "created", Time.now.to_i

    # If a region param was passed that defines some UV mapping info, then

    # do UV mapping. Otherwise, just paint all faces with the untransformed # texture. if params['region'] != nil uvs = [] corners = params['region'].split(':') for corner in corners u, v = corner.split(',') u = u.to_f v = v.to_f u = u / pixel_width v = (pixel_height - v) / pixel_height uvs.push(u.to_s + ',' + v.to_s) end

    if faces_to_texture.length == 1 # Apply the texture to the side of the face the camera is looking at. # TODO(scottlininger): Could be better to rewrite to use the plane # equation. See http://mondrian.corp.google.com/file/11865235 for # commentary. face = faces_to_texture[0] camera_direction = Sketchup.active_model.active_view.camera.direction angle = face.normal.angle_between camera_direction if angle.radians < 90 uv_texture(face, m, uvs, false, true) else uv_texture(face, m, uvs, true, false)

    end else # Apply the texture to the front of all selected faces. for face in faces_to_texture uv_texture(face, m, uvs, true, false) end end

    else # Paint the texture onto all selected faces.

  • 8/12/2019 Webtextures Loader.

    11/16

    for face in faces_to_texture face.material = m end end

    if params['oncomplete'] != nil @dialog.execute_script(params['oncomplete']) end

    # Delete the temporary jpg file. File.delete(file)

    Sketchup.active_model.commit_operation

    rescue Exception => e puts "#{e.class}: #{e.message}" UI.messagebox( @strings.GetString("There was an error pulling in the texture.") + "\n" + @strings.GetString("Please try again.") + "\n\n" + "#{e.class}: #{e.message}") if params['oncomplete'] != nil @dialog.execute_script(params['oncomplete']) end

    end end

    # UV Textures a face, meaning it applies a texture and positions it to match # four coordinate pairings passed in. Each "corner" of the face will # get a corresponding u,v coordinate local to the texture itself, and # SketchUp will scale and skew the texture so that the u,v location matches # with each corner. # # Args: # face: The face to texture. # material: The Material object to apply. It must already contain the

    # texture. # uvs: An 4-element array of strings. Each string is a single u,v # coordinate such as "0,0" or ".25,1.0" # do_front: If true, apply to front of face. # do_back: If true, apply to back of face. # # Returns: # Nothing def uv_texture(face, material, uvs, do_front=true, do_back=false) corners, vertex_uvs = get_uvs(face) pts = [] for i in 0..3 pts

  • 8/12/2019 Webtextures Loader.

    12/16

    back_pts[2] = pts[0] back_pts[3] = pts[3] back_pts[4] = pts[6] back_pts[5] = pts[5] back_pts[6] = pts[4] back_pts[7] = pts[7] face.position_material material, back_pts, false end end

    # Turns a query string into a hash. So something like "x=100&z=2" will be # translated into { x:"100", z:"2" }. # # Args: # data: The string to process. # # Returns: # param_hash: The nice name/value paired hash. def query_to_hash(data) param_pairs = data.to_s.split('&') param_hash = {} for param in param_pairs name, value = param.split('=')

    param_hash[name] = value end return param_hash end

    # Pops open the dialog. # # Args: # None. # # Returns: # Nothing

    def show(force_refresh = false)

    # If we don't think we're connected, or if it's the first time we've # launched the dialog, then ask SketchUp if we're online. if @is_online == false @is_online = Sketchup.is_online end

    # If we're still offline, show a message. if @is_online == false UI.messagebox(@strings.GetString("Photo Textures requires a connection " + "to the internet and yours appears to be down. Please reset " + "your connection and try again."))

    return end

    # If the geo location has changed, force a refresh of the dialog. if @dialog.visible? if force_refresh == true @dialog.execute_script('refresh()'); end end

  • 8/12/2019 Webtextures Loader.

    13/16

    if @dialog.visible? == false if @is_mac # Mac has refresh issues with flash, so reset URL. @dialog.set_url(@url) @dialog.show_modal else @dialog.show end end

    if @is_mac # Force focus on the mac. @dialog.bring_to_front end

    end

    # There are two things that we calculate in this function: first, the # 4 "corners" in model space that define a rectangular bounding poly of the # underlying face. Second, an array of the uv points for each vertex in the # face, relative to that bounding poly. # # Args:

    # face: the face to calculate corners and vertex uvs for # # Returns: # corners: An Array of Point3d objects describing the four "corners" # surrounding the face. These may or may not be vertices. For # example, a circular face or a diamond will have 4 corners # where none of them match up with a vertex. A square face # will have all 4 corners overlap with a vertex. # vertex_uvs: An Array of hashes. Each hash contains a "u" and a "v" # member, so that you'll get something like this: # [ {u:0,v:1}, {u:0.75,v:.25}, {u:0.25,v:.75}, {u:0.25,v:.75}] # def get_uvs(face)

    # Get the axes for a plane that the face is on, with # the x axis parallel to the ground plane and the z axis # corresponding to the face normal. xaxis, yaxis, zaxis = face.normal.axes

    # Calculate points that define a "bottom" and a "left" # direction, as would be viewed by a person looking from # the camera toward the face with their feet pointing # downward. (In the case of a face that is parallel to # the ground, this method will return "bottom" as being # in the negative y direction.) far_left = xaxis.reverse

    far_left.length = WT_VERY_LARGE_NUMBER left = face.vertices[0].position left.offset! far_left

    far_bottom = yaxis.reverse far_bottom.length = WT_VERY_LARGE_NUMBER bottom = face.vertices[0].position bottom.offset! far_bottom

    # Figure out which face vertices define the 4 edges of

  • 8/12/2019 Webtextures Loader.

    14/16

  • 8/12/2019 Webtextures Loader.

    15/16

    # # Returns: # string: a JSON-friendly version suitable for parsing in javascript def clean_for_json(value) value = value.to_s value = value.gsub(/\\/,'\') value = value.gsub(/\"/,'"') value = value.gsub(/\n/,'\n') if value.index(/e-\d\d\d/) == value.length-5 value = "0.0"; end return value end

    # Returns the system's temporary directory. # # Args: # none # # Returns: # string: the pull path to the temp directory. def temp_directory if @temp_dir

    return @temp_dir end tmp = '.' for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], ENV['USERPROFILE'], '/tmp'] if dir and File.directory?(dir) and File.writable?(dir) tmp = dir break end end @temp_dir = File.expand_path(tmp) return @temp_dir end

    end

    ## Set up the UI hooks for the standard grab texture functionality.#####if (not $wt_loaded)

    # Create the context menu item.

    UI.add_context_menu_handler do |context_menu| selection = Sketchup.active_model.selection has_faces = false for entity in selection if entity.typename == "Face" has_faces = true break end end if has_faces

  • 8/12/2019 Webtextures Loader.

    16/16

    context_menu.add_separator context_menu.add_item($wt_strings.GetString("Add Photo Texture")) { if not $wt_instance $wt_instance = WebTextures.new($wt_strings.GetString("Photo Textures"), $wt_strings) else $wt_instance.show(true) end } end end

    # Create the Windows > Web Textures menu item. menu = UI.menu("Windows") menu_text = $wt_strings.GetString("Photo Textures") cmd = UI::Command.new(menu_text) { if not $wt_instance $wt_instance = WebTextures.new($wt_strings.GetString("Photo Textures"), $wt_strings) else $wt_instance.show() end }

    cmd.tooltip = menu_text menu.add_item(cmd) $wt_loaded = trueend