How to tell if V-Ray render has finished

Hi there!
I’m working on automating rendering with V-Ray but have run into the problem that it’s not possible to know when a render has completed.

Currently i render the scene, then wait 20 seconds, change the material of the model and then render again, but this is a very ineficcient system if the render completes in 5 seconds.

So far i’ve tried the live_scene.renderer? boolean and several VRay::Renderer::IDLE_ variables to no luck.

The problem is that the boolean does not change when rendering loop from the extension, so the code always times out, even if the render completes way sooner.

Here is some example code:

model = Sketchup.active_model
model.start_operation("Running VRay render", true)
live_scene = VRay::LiveScene.active
while live_scene.rendering?
  puts "Sleeping"
  sleep 1 # Timeout the code, i know in python this is helpfull for letting other threads work, but unsure of the impact in ruby
  i += 1
  if i > 20
    puts "Timeout rendering"
    break
  end
end
model.commit_operation

You cannot use Ruby’s global Kernel#sleep method within embedded Ruby. It stops everything.
The Ruby code must return to the main application loop in order for SketchUp to do anything more.

You might get by using the SketchUp API’s UI::start_timer method. (It wraps C++ timers.)

Secondly (FYI), the snippet above does not actually modify the model, so the undo operation is frivolous except for suppressing UI redraws. This won’t happen as again, the code must return to the main loop, so the undo operation would get closed.

1 Like

I see. I’ve now experimented with UI.start_timer now, but there is no obvious way to get the live_scene.rendering? to change while in the loop even then.

The general solution i’m trying to get working is along the lines of:

live_scene = VRay::LiveScene.active
array_materials.each do |materialjpg| # For every material in array of materials
  Sketchup.active_model.selection.each do |entity| # On the selected entity (assuming you only select one thing to change)
    entity.material = material_handler.set material(materialjpg) # Simplification of applying material to entity
  
    # render the model with new material
    live_scene.render
  
    # Start loop to stop code from progressing while rendering is in progress
    while live_scene.rendering?
    # Find something clever to do while the render completes in around 7-10 seconds
    end
    puts "render complete!"
    # go to next material and run loop again
  end
end

Again, this is a simplification of my code, and it all seems to work except for the rendering status bit.

The problem is that no matter how i form the loop, the live_scene.rendering? boolean never changes to false.
The only way it does change to false is if i continously enter live_scene.rendering? in the console.

Am i missing something clever or is there no way to optimize the rendering process short of checking if there has appeared a new image in the render destination folder?

Thank you again for clearing this up.

I don’t have a solution (I’ve fiddled with similar ideas in the past without success), but I can tell you that the reason it works from the Ruby Console is that the console does not have Ruby continuously in control. It returns control to the SketchUp GUI engine while waiting for you to type the next command. That gives the live_scene a chance to update its rendering? value.

2 Likes

Possibly because sometimes Ruby procs encapsulate the current state of things, so it may be getting the return value once when it processes the block prior to starting up the timer loop.

Sometimes we can force the proc to get a new value by using a method call instead of a variable reference. So I wrapped the @live_scene.rendering? call inside a rendering? method in hopes the loop procs will call it each time through.


Here is my attempt.
I don’t have a Vray license to test it, and I don’t have your material_handler object.
I assumed in the render_command method that there would exist material_handler.get_materials to return the array of materials. (If not adjust the code.)
So running on a selection gives …

Error: #<NameError: undefined local variable or method `material_handler'
 for Example::BatchRender:Module>
"Example_VRay_BatchRender_1.0.0.rb":17:in `render_command'
"Example_VRay_BatchRender_1.0.0.rb":227:in `block in <module:BatchRender>'

ADD: You’ll also need to fix material_handler access in the apply_material() method.

Otherwise it loads without syntax errors.

Example_VRay_BatchRender_1.0.0.rb (7.9 KB)


# encoding: UTF-8

module Example #  <----<<< Change to YOUR UNIQUE toplevel namespace name !!!
  module BatchRender

    extend self

    VERSION ||= '1.0.0'

    @command_active = false
    @render_time_adjustment = true


    # CONTROL METHODS:

    # The method that is fired by a (menu or toolbar) UI command request.
    # (See run once code block at bottom of this file.)
    def render_command()
      unless @command_active
        @array_entities = Sketchup.active_model.selection.to_a
        @array_materials = material_handler.get_materials
        @command_active = true
        start_render(@array_entities, @array_materials)
      else
        UI.messagbox("Batch render already in progress!",MB_OK)
      end
    end

    # Start the render.
    # @param array_entities [Array(Sketchup::Entity)]
    # @param array_materials [Array(Sketchup::Material)]
    # @param interval [Float] an optional override to set the interval
    # for the render checks (how often the render loop proc runs.)
    # If not set will be 5 secs.
    # @param control [Float] an optional override to set the interval
    # for the control checks (how often the control loop proc runs.)
    # If not set will be interval times the size of the array_entities.
    def start_render(
      array_entities, array_materials, interval = 0, control = 0
    )
    
      # Make a copy of the materials array:
      @matls = array_materials.dup
      # Make a copy of the entities array:
      @ents = array_entities.dup
      #
      @tid = 0
      @control_id = 0
      @start_time = 0
      @stop_time  = 0
      #
      @render = false # This controller's render state
      @render_start = 0
      @render_stop  = 0
      @render_time  = 0
      #
      if @render_check_interval.nil? || interval > 0
        @render_check_interval =( interval > 0 ? interval : 5.0 )
      end
      if @control_loop_interval.nil? || control > 0
        if control > 0
          @control_loop_interval = control
        else
          @control_loop_interval = @render_check_interval * @ents.size
        end
      end
      #
      @live_scene = VRay::LiveScene.active
      puts "Starting Batch render job:"
      control_loop()
      #
    end ###

    # Starts the control loop that controls the material interation.
    def control_loop(interval = @control_loop_interval)
      @start_time = Time.now.to_i
      @control_id = UI.start_timer(interval,true) {
        next if @tid > 0 || rendering?()
        # Each material:
        matl = next_material()
        if matl.nil? # We are done
          UI.stop_timer(@control_id)
          @stop_time = Time.now.to_i
          sec = @stop_time - @start_time
          puts "Batch rendering complete. (#{sec/60.0} mins)"
          @command_active = false
        else
          puts "Starting material renders for \"#{matl.display_name}\"."
          render_loop(matl)
        end
      }
    end ###

    # Starts the render loop that controls the iteration for entity renders.
    def render_loop(matl)
      #
      if @render_time > 0 && @render_time_adjustment
        # Adjust the interval for the next material renders
        interval = @render_time + 0.25
      else
        interval = @render_check_interval
      end
      #
      @tid = UI.start_timer(interval,true) {
        next if rendering?()
        if @render # will be false initially
          @render_stop = Time.now.to_i
          @render = false
          @render_time = sec =( @render_stop - @render_start )
          if sec > 60.0
            puts "  Scene render complete. (#{sec/60.0} mins)"
          else
            puts "  Scene render complete. (#{sec} sec)"
          end
        end
        # Each entity:
        entity = next_entity()
        if entity.nil? # We are done with all entities for this pass.
          UI.stop_timer(@tid)
          @tid = 0
          puts "Material render complete for \"#{matl.display_name}\"."
          reload_entities() # for next material pass
        else
          apply_material(entity, matl)
          @render = true
          @live_scene.render
          @render_start = Time.now.to_i
        end
      }
    end ###

    # A method to get whether the VRay live scene is rendering?
    # @return [Boolean]
    def rendering?
      return false if @live_scene.nil?
      @live_scene.rendering?
    end

    # SUPPORT METHODS:

    # Apply material to an entity.
    # @param entity [Sketchup::Entity]
    # @param matl [Sketchup::Material]
    def apply_material(entity, matl)
      entity.material = material_handler.set_material(matl)
    end ###

    # Returns the timer ID for @control_id.
    def control_id
      @control_id
    end ###

    # Returns the current setting for @control_loop_interval.
    def control_interval
      @control_loop_interval
    end ###

    # Set the @control_loop_interval. Once the control loop begins
    # the interval cannot be changed. So this must be set prior to
    # starting the render job.
    # @param num [Numeric] number of secs.
    def control_interval=(num)
      @control_loop_interval = num.to_f
    end ###

    # Getter for the live scene.
    def live_scene
      @live_scene
    end

    # Remove and return the next material in the list.
    # @return [Sketchup::Material,nil] The material to render with,
    # or nil if the array is empty.
    def next_material
      @matls.shift
    end ###

    # Remove and return the next entity in the list.
    # @return [Sketchup::Entity,nil] The entity to render with,
    # or nil if the array is empty.
    def next_entity
      @ents.shift
    end ###

    # Reload a new copy of the entities array into @ents
    # to be used with the next material renders.
    def reload_entities
      @ents = @array_entities.dup
    end

    # Returns the current setting for @render_check_interval.
    def render_check_interval
      @render_check_interval
    end ###

    # Set the @render_check_interval. Once the render loop begins
    # the interval cannot be changed. So this is usually set prior to
    # starting the render job.
    # @param num [Numeric] number of secs.
    def render_check_interval=(num)
      @render_check_interval = num.to_f
    end ###

    # Return the flag for render check interval adjustment.
    def render_time_adjustment
      @render_time_adjustment
    end ###

    # Set the flag for render check interval adjustment.
    # True will adjust the @render_check_interval to the previous
    # render time + a quarter second. False to switch off and
    # just use the @render_check_interval as set throughout the batch job.
    def render_time_adjustment=(arg)
      @render_time_adjustment =(arg ? true : false)
    end ###

    # Stop the timer loop gvien by an ID (default = @tid)
    # @param tid [Integer] a timer id.
    def stop_render(tid = @tid)
      UI.stop_timer(tid)
      @tid = 0 if tid == @tid
    end ###

    # Returns the timer ID for @tid.
    def tid
      @tid
    end ###


    # RUN ONCE UPON STARTUP CODE:
    if !@loaded
      @loaded = true
      
      # Define UI objects here:
      render_cmd = UI::Command.new("Batch Render") {
        render_command()
      }.set_validation_proc {
        if Sketchup.active_model.selection.empty?
          MF_DISABLED | MF_GRAYED
        else
          MF_ENABLED
        end
      }
      render_cmd.large_icon = "render_btn.png"
      render_cmd.small_icon = "render_btn.png"
      
      render_cmd.tooltip = "Batch render selection."
      render_cmd.status_bar_text = "Batch render selection."
      render_cmd.menu_text = "Batch render selection."

      toolbar = UI::Toolbar.new("Batch Render")
      toolbar.add_item(render_cmd)
      toolbar.show

      UI.menu("Plugins").add_item(render_cmd)

    end # run once code


  end # extension submodule
end # outer namespace module
1 Like