Animation in a Draw Tool

I’m trying to add in an animation into my tool as the final step before it creates some geometry. However I’m not even sure this is possible.

Here is a snippet of the code:

def draw(view)
    @drawn = false
    
    # Show the current input point
    if( @ip.valid? && @ip.display? )
        @ip.draw(view)
        @drawn = true
    end

    case @state

 when STATE_PICK_FINAL_WALL

	puts "draw final flooring animation"


	if @perim_face && @Validfloor
	
	    unless @drawnonce
		
		view.line_width = 1
		color1 = Sketchup::Color.new(0x66ffff)
		color1.alpha = 85
		view.drawing_color = color1
	
		
		# view.draw(GL_POLYGON, @perim_pts)

		puts "draw final flooring animation2"

		# view = Sketchup.active_model.active_view
		steps = 10.0
		cycles = 0
		stepsize = @Wallhgt/steps
		
		floorz = @Wallhgt
		
									
		anim_timer = UI.start_timer(0.02, true) {

			# view.refresh
			# view.invalidate

			face_pts = []
			for index in 0 ... @perim_pts.size
				pti = @perim_pts[index].clone
				face_pts[index] = [pti.x, pti.y, floorz]
			end
			
			view.draw(GL_POLYGON, face_pts)
								
			
			cycles += 1
			floorz = floorz - stepsize
	
			if cycles >= steps
				puts "step #{cycles} final"
				UI.stop_timer(anim_timer)	
			end

			puts "step #{cycles}"
		}

		@drawnonce = true

		puts "final_calc"

		self.calculate_obj
		# Sketchup.active_model.select_tool(nil)

	    end

	end
 @drawn = true
    end

Sorry the indentation got all messed up somehow.

The timer seems to run asynchronously so it kind of messes things up somehow. I want the animation to finish before if makes the final call to the method: calculate_obj

I have never tried to do anything this crazy within the draw method of a tool, so I’m completely out of my comfort zone here and beyond my experience and knowledge, I’m just kind of throwing mud at the wall right now and maybe something might “work”.

Trying out different loops but I can’t figure it out. The animation would be cool if I can figure it out but it’s not the end of the world I guess.

I threw mud at the wall:

Animation?
class AnimationTool
  def initialize(perim_pts, wall_height)
    @perim_pts = perim_pts
    @Wallhgt = wall_height
    @state = :pick_final_wall
    @drawnonce = false
  end

  def activate
    @cycles = 0
    @floorz = @Wallhgt
    @steps = 10.0
    @stepsize = @Wallhgt / @steps

    # Start the animation timer to trigger continuous drawing
    @anim_timer = UI.start_timer(0.02, true) do
      puts "Timer triggered, calling draw"
      Sketchup.active_model.active_view.invalidate
    end
  end

  def deactivate(view)
    # Stop the animation timer when the tool is deactivated
    puts "Deactivating tool and stopping timer"
    UI.stop_timer(@anim_timer) if @anim_timer
  end

  def draw(view)
    puts "Drawing called with current floor height: #{@floorz}"
    
    # Set up colors and line width for the animation
    view.line_width = 1
    color1 = Sketchup::Color.new(0x66ffff)
    color1.alpha = 85
    view.drawing_color = color1

    if @state == :pick_final_wall
      # Temporarily draw the animated polygon face at the current level
      face_pts = @perim_pts.map { |pti| [pti.x, pti.y, @floorz] }
      view.draw(GL_POLYGON, face_pts)
      puts "Temporary face drawn at height #{@floorz}"

      @cycles += 1
      @floorz -= @stepsize
      puts "Cycle #{@cycles} completed, new floor height: #{@floorz}"

      if @cycles >= @steps
        puts "Animation complete after #{@cycles} cycles"
        UI.stop_timer(@anim_timer)
        add_final_floor_face  # Add a single, final face after animation
        @state = :done
        Sketchup.active_model.select_tool(nil)  # Automatically deactivate the tool
      end
    end
  end

  def add_final_floor_face
    # Add a single floor face at the final level in the model
    final_face_pts = @perim_pts.map { |pti| [pti.x, pti.y, 0] }  # Floor at ground level
    model = Sketchup.active_model
    entities = model.active_entities
    entities.add_face(final_face_pts)
    puts "Final floor face created at ground level"
    calculate_obj
  end

  def calculate_obj
    # Placeholder method for final calculation
    puts "Final calculation executed"
  end
end

# Test setup: define perimeter points and wall height
perim_pts = [
  Geom::Point3d.new(0, 0, 0),
  Geom::Point3d.new(10, 0, 0),
  Geom::Point3d.new(10, 10, 0),
  Geom::Point3d.new(0, 10, 0)
]
wall_height = 10.0

# Create and activate the tool
tool = AnimationTool.new(perim_pts, wall_height)
Sketchup.active_model.select_tool(tool)

1 Like

This should not be done within the draw() method *because it gets called alot:

    color1 = Sketchup::Color.new(0x66ffff)
    color1.alpha = 85

Do as many object assignments as possible to @vars in initialize() or activate().

1 Like

Hopefully not muddying with bad examples…

Animakinda Bettermaybe
class AnimationTool
  def initialize(perim_pts, wall_height)
    @perim_pts = perim_pts
    @Wallhgt = wall_height
    @state = :pick_final_wall
    @drawnonce = false
  end

  def activate
    @cycles = 0
    @floorz = @Wallhgt
    @steps = 10.0
    @stepsize = @Wallhgt / @steps

    @line_width = 1
    @color1 = Sketchup::Color.new(0x66ffff)
    @color1.alpha = 85

    # Start the animation timer to trigger continuous drawing
    @anim_timer = UI.start_timer(0.02, true) do
      puts "Timer triggered, calling draw"
      Sketchup.active_model.active_view.invalidate
    end
  end

  def deactivate(view)
    # Stop the animation timer when the tool is deactivated
    puts "Deactivating tool and stopping timer"
    UI.stop_timer(@anim_timer) if @anim_timer
  end

  def draw(view)
    puts "Drawing called with current floor height: #{@floorz}"
    
    view.line_width = @line_width
    view.drawing_color = @color1

    if @state == :pick_final_wall
      face_pts = @perim_pts.map { |pti| [pti.x, pti.y, @floorz] }
      view.draw(GL_POLYGON, face_pts)
      puts "Temporary face drawn at height #{@floorz}"

      @cycles += 1
      @floorz -= @stepsize
      puts "Cycle #{@cycles} completed, new floor height: #{@floorz}"

      if @cycles >= @steps
        puts "Animation complete after #{@cycles} cycles"
        UI.stop_timer(@anim_timer)
        add_final_floor_face
        @state = :done
        Sketchup.active_model.select_tool(nil)
      end
    end
  end

  def add_final_floor_face
    final_face_pts = @perim_pts.map { |pti| [pti.x, pti.y, 0] }  
    model = Sketchup.active_model
    entities = model.active_entities
    entities.add_face(final_face_pts)
    puts "Final floor face created at ground level"
    calculate_obj
  end

  def calculate_obj
    # Placeholder method for final calculation
    puts "Final calculation executed"
  end
end

# Test setup: define perimeter points and wall height
perim_pts = [
  Geom::Point3d.new(0, 0, 0),
  Geom::Point3d.new(10, 0, 0),
  Geom::Point3d.new(10, 10, 0),
  Geom::Point3d.new(0, 10, 0)
]
wall_height = 10.0

# Create and activate the tool
tool = AnimationTool.new(perim_pts, wall_height)
Sketchup.active_model.select_tool(tool)

1 Like

Wow, very cool. Let me try this out but I’m going to study it first so I can understand why it is working and mine is not.

I was alternatively just going to move the animation outside of the tool and actually animate the geometry itself (ie. translate it) since I have a pretty good handle on how to do that already but now I want to give this a further try. I learn something new ever day.

You should have a look at the Sketchup::Animation abstract class and its nextFrame method. This is the standard way to perform animation within an interactive tool (or even without).

Basically,

  • to start an animation, you do: view.animation = self (i.e. the interactive tool). You can instead use a dedicated class object if you wish, as the Sketchup::Animation class is abstract. It just needs to have the method nextFrame(view) implemented.
  • Then implement a method nextFrame(view) in your tool where you move the camera and objects and use view.show_frame to refresh the view
  • to stop the animation, just do: view.animation = nil
3 Likes

As to why the original didn’t work, the proc being called via UI.start_timer() is not executing within the tool’s draw() method.

Class: Sketchup::View
Overview
This class contains methods to manipulate the current point of view of the model. The drawing methods here (draw_line, draw_polyline, etc) are meant to be invoked within a tool’s Tool.draw method. Calling them outside Tool.draw will have no effect.

1 Like

A nextFrame attempt:

Next Attempt
class AnimationTool
  def initialize(perim_pts, wall_height)
    @perim_pts = perim_pts
    @wall_height = wall_height
    @finalized = false 
    @color = Sketchup::Color.new(144, 238, 144)  
    @color.alpha = 128  
  end

  def activate
    @cycles = 0
    @floorz = @wall_height
    @steps = 20.0
    @stepsize = @wall_height / @steps

    @current_entities = [] # Track temporary entities
    view = Sketchup.active_model.active_view
    view.animation = self
    puts "Animation started"
  end

  def nextFrame(view)
    # Exit if animation is complete
    return false if @animation_complete 

    # Clear temporary entities from the previous frame
    cleanup_temporary_entities

    face_pts = @perim_pts.map { |pti| [pti.x, pti.y, @floorz] }
    model = Sketchup.active_model
    entities = model.active_entities
    face = entities.add_face(face_pts)

    if face
      face.material = @color
      face.back_material = @color
      @current_entities << face
      face.edges.each { |edge| @current_entities << edge }
    end

    view.show_frame
    sleep(0.1) # Animation rate

    @cycles += 1
    @floorz -= @stepsize

    if @cycles >= @steps && !@finalized
      finalize_floor_face(view)
      return false 
    end

    true
  end

  def cleanup_temporary_entities
    @current_entities.each(&:erase!) if @current_entities
    @current_entities.clear
  end

  def finalize_floor_face(view)
    return if @finalized 
  
    view.animation = nil
    cleanup_temporary_entities
  
    final_face_pts = @perim_pts.map { |pti| [pti.x, pti.y, 0] }
    entities = Sketchup.active_model.active_entities
    final_face = entities.add_face(final_face_pts)
    final_face.material = @color
    final_face.back_material = @color
  
    puts "Final floor face created and final calculation executed"
    
    @finalized = true
    @animation_complete = true
  
    Sketchup.active_model.select_tool(nil)
  end
end

# Test setup: define perimeter points and wall height
perim_pts = [
  Geom::Point3d.new(0, 0, 0),
  Geom::Point3d.new(10, 0, 0),
  Geom::Point3d.new(10, 10, 0),
  Geom::Point3d.new(0, 10, 0)
]
wall_height = 20.0

# Create and activate the tool
tool = AnimationTool.new(perim_pts, wall_height)
Sketchup.active_model.select_tool(tool)

Normally, in a tool, the animation of proposed geometry is linked to the onMouseMove callback. As the mouse moves, in the callback the inputpoint would be used to calculate proposed position of say a face, and view.invalidate() is called from the onMouseMove callback after the calculations, which in turn triggers the draw() method to show the proposed geometry outline.

1 Like