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