How exactly do I transform a selected group

If I select a sub group, they rotate. But if I leave the main group, they stop spinning. what exactly happened.

def spin(arr)
	x = 0
	my_timer = UI.start_timer(0.02,true){
		total = 360
		step = 10
		interval = total/step #=36
		
		i = x/interval
		x += 1

		if arr.size > 1
			UI.stop_timer my_timer if i == arr.size
		else
			UI.stop_timer my_timer if x == interval
		end
		
		target = arr[i]
		if target != nil
			rot = Geom::Transformation.rotation(target.bounds.center, [0,0,1], step.degrees)
			target.transform! rot
		end
	}
end

mod = Sketchup.active_model # Open model
ent = mod.entities # All entities in model
sel = mod.selection # Current selection
arr_target = sel
spin(arr_target)

(1) You should be using #move! for animations, not #transform! as it creates many undo steps.

(2) Probably it is better to use an abstract Animation class than a timer block.


Most likely because your arr reference in the method is pointing at the model.selection object, which is cleared (has 0 entities) when you “leave the main group”.

A Sketchup::Selection object is a thinly wrapped C++ collection, not a Ruby Array,

You can try making an array copy of the selection object like so:

# Make an array copy of the selection:
arr_target = sel.to_a

# Pass the array into the method:
spin(arr_target)

The #to_a method comes from the Ruby Enumerable module that is mixed into many of the API collection classes.

1 Like

thank you @DanRathbun
When I was still in the main group with different axes with the model, they rotated correctly. But when I left the main group, they rotated incorrectly.

def spin(arr)
	view = Sketchup.active_model.active_view
	x = 0
	my_timer = UI.start_timer(0.02,true){
		total = 360
		step = 10
		interval = total/step #=36
		
		i = x/interval
		x += 1

		if arr.size > 1
			UI.stop_timer my_timer if i == arr.size
		else
			UI.stop_timer my_timer if x == interval
		end
		
		target = arr[i]
		if target != nil
			tran = target.transformation
			rot = Geom::Transformation.rotation(target.bounds.center, [0,0,1], (step).degrees)
			#target.transform! rot #depreceat
			target.move! rot*tran
			view.refresh
		end
	}
end

mod = Sketchup.active_model # Open model
ent = mod.entities # All entities in model
sel = mod.selection # Current selection
#Make an array copy of selection
arr_target = sel.to_a

#Pass the array into the mthod:
spin(arr_target)

You are rotating about an axis [0,0,1] aka Z_AXIS
But the group’s z axis might not be the same.
?
You can get the group’s transformation z axis and use that instead…
axis = group.transformation.zaxis
??
But your code appears to rotate a selection which can include many things…
So the z axis should be that of the ‘context’.
So something like axis = sel[0].parent.transformation.zaxis might be appropriate…
???

2 Likes

I think you miss what i want. I want to rotate subgroup about an global Axis. That happen when i still in main group. But when i out from main group contex, subgroup rotated incorrectly. That all make me think what are all different from active_contex. Like as @DanRathbun say. Sel collection is one off them.

There are no
sel[0].parent.transformation.zaxis = nil
That become error.
it must :
sel[0].parent.instances[0].transformation.zaxis if !se[0].parent.is_a?(Sketcthup::Model)

@DanRathbun
Sketchup very slow :melting_face:

class FloatUpAnimation
  def nextFrame(view)
	mod = Sketchup.active_model # Open model
	ent = mod.entities # All entities in model
	sel = mod.selection # Current selection
	arr_target = sel.to_a
	arr_target.each{|e|
		if e != nil
			step = 10
			vec= Z_AXIS
			tran = e.transformation
			rot = Geom::Transformation.rotation(e.bounds.center, vec, (step).degrees)
			e.move! rot*tran
			view.refresh
		end
	}
  end
end

# This adds an item to the Camera menu to activate our custom animation.
UI.menu("Camera").add_item("Run Float Up Animation") {
  Sketchup.active_model.active_view.animation = FloatUpAnimation.new
}

Well, it depends upon the number of steps (ie, the smaller the angle, the slower the rotation).

You also need to do a little as possible in the next_frame() method. Do as much calculation ONCE in the class’ initialize method and assign results to @instance_variables that can be accessed from the next_frame() method.

Try this …

Seklik_RotateSelection_main.rb (3.6 KB)

The file contents for those not wanting to download the Ruby file ... (click here)
# encoding: UTF-8

module Seklik # top-level namespace module
  module RotateSelection

    @default_step ||= 10 # angle in degrees
    @default_spin ||= 1  # number of rotations

    # A custom abstract animation class. (Abstract classes have no API superclass
    # from which they inherit specific functionality. They will be like any other
    # standalone Ruby class whose ancestors are: Object, Kernel, & BasicObject.)
    class Animation

      # The initialize method is automatically called by Ruby from a class'
      # ::new() constructor method after it instantiates the new instance.
      # So this method is evaluated within the new instance object. It receives
      # any arguments that were passed into the class constructor method.
      def initialize(
        sel,     # current selection object
        arr,     # the array copy of all selected objects
        targets, # transformable objects from current selection
        step = 10,
        rotations = 1
      )
        @sel = sel
        @arr = arr
        @targets = targets
        @step = step
        @stop = rotations * 360
        angle = step.degrees
        # A hash to hold the object rotational transforms:
        @rotate = {}
        # Load the @rotate hash with transformations:
        @targets.each { |e|
          @rotate[e]= Geom::Transformation.rotation(
            e.bounds.center, Z_AXIS, angle
          )
        }
        @position = 0
        @sel.clear
        # next_frame() gets called here ONCE after the view animation object is
        # instantiated by the View#animation=() method call.
      end

      def nextFrame(view)
        @targets.each { |e|
          e.move!( @rotate[e] * e.transformation )
        }
        # Determine whether we've spun enough:
        @position += @step
        # Return false to stop the animation:
        if @position >= @stop
          @sel.add(@arr) # restore the original selection
          return false
        else
          # Show the next frame:
          view.show_frame
          return true
        end
      end

    end # class Animation

    # The method encapsulating the animation command. Commands are best
    # wrapped up within a method so that the code can be reloaded during
    # development which redefines the method if any changes were made.
    # Blocks for UI menu items and commands cannot be redefined at runtime
    # for security reasons. So this is why the block only calls this method.
    def self.rotate_selection
        model = Sketchup.active_model # reference the active model
        sel = model.selection # Current selection
        arr = sel.to_a
        targets = arr.select { |e| e.respond_to?(:transformation) }
        unless targets.empty?
          prompts  = ["Step Angle (degrees)", "Total Rotations"]
          defaults = [@default_step, @default_spin]
          input = UI.inputbox(prompts, defaults, "Rotate Selection")
          return unless input
          step = input[0]
          rots = input[1]
          @default_step = step if @default_step != step
          @default_spin = rots if @default_spin != rots
          view = model.active_view
          view.animation= RotateSelection::Animation.new(sel, arr, targets, step, rots)
        else
          UI.messagebox("No objects selected.")
        end
    end

    unless defined?(@loaded)

      # Adds an item to the Camera menu to activate our custom animation:
      UI.menu("Camera").add_item("Rotate Selection Animation") {
        rotate_selection()
      }

      @loaded = true
    end

  end # extension submodule
end # namespace module
2 Likes

Thank alot @DanRathbun, that is awesome!! great job.
i must lear more your code to make it just stop animation if object(group/component) transformation same before animation.

1 Like

Finally, i finish learn your code @DanRathbun
There are 9 step to do rotate animation
1.stop_animasi
2.get_old_targets
3.get_pos_origin
5.validation_old_target
6.set_new_targets
7.set_pos_origin
8.set_speed == set_step
9.start_animation

I need advice, what is wrong or better way. this is my code
I don’t know, what exactly is the speed of rotation.
increasing the angle, it looks unstable in Sketchup

module SklikAnimation # top-level namespace module
	module RotateSelection
		@default_step ||= 1 # angle in degrees
		class Animation	
			def set_old_targets=(targets)
				@targets = targets
			end

			def set_pos_origin=(pos_origin)
				@pos_origin = pos_origin
			end
			
			def get_old_targets
				@targets
			end

			def get_pos_origin
				@pos_origin
			end
			
			def initialize(targets,pos_origin,step)
				@targets = targets
				@pos_origin = pos_origin
				@step = step
				angle = step.degrees
				# A hash to hold the object rotational transforms:
				@rotate = {}
				# Load the @rotate hash with transformations:
				@targets.each {|e|
					@rotate[e]= Geom::Transformation.rotation(e.bounds.center, Z_AXIS, angle)
				}				
			end
			
			def nextFrame(view)
				@targets.each {|e|
					e.move!( @rotate[e] * e.transformation ) if e.valid?
				}
				view.show_frame
				return true
			end
		end # class Animation
		
		def self.restore_position
			model = Sketchup.active_model # reference the active model
			view = model.active_view
			#stop_animasi
			view.animation = nil
			#get old_target
			old_target = []
			old_target = @animation_rotation.get_old_targets if @animation_rotation != nil
			#get pos_origin
			pos_origin = []
			pos_origin = @animation_rotation.get_pos_origin if @animation_rotation != nil
			#restore_all_target
			old_target.each_index{|i| old_target[i].move! pos_origin[i] if old_target[i].valid? }
			view.invalidate
			#validation_old_target
			old_target = old_target.select { |e| e.valid? }
			return old_target
		end
		
		def self.start_rotation
			model = Sketchup.active_model # reference the active model
			view = model.active_view
			sel = model.selection # Current selection
			arr = sel.to_a.select { |e| e.respond_to?(:transformation) }
			sel.clear
			unless arr.empty?
				#stop_animasi,get old_target,get pos_origin,restore_all_target,validation_old_target
				old_target = restore_position()
				#set_new_targets
				targets = arr + old_target
				targets = targets.uniq
				#set_pos_origin
				pos_origin = []
				targets.each{|e| pos_origin << e.transformation}
				#set_speed == set_step
				step = 1
				#start_animation
				@animation_rotation = RotateSelection::Animation.new(targets,pos_origin,step)
				view.animation = @animation_rotation
			else
				UI.messagebox("No objects selected.")
			end
		end

		def self.stop_rotation
			model = Sketchup.active_model # reference the active model
			view = model.active_view
			sel = model.selection # Current selection
			arr = sel.to_a.select { |e| e.respond_to?(:transformation) }
			sel.clear
			unless arr.empty?
				#stop_animasi,get old_target,get pos_origin,restore_all_target,validation_old_target
				old_target = restore_position()
				#set_new_targets
				targets = old_target
				arr.each{|e| targets.delete(e)}
				#set_pos_origin
				pos_origin = []
				targets.each{|e| pos_origin << e.transformation}
				#set_speed == set_step
				step = 1
				#start_animation
				@animation_rotation = RotateSelection::Animation.new(targets,pos_origin,step)
				view.animation = @animation_rotation
			else
				UI.messagebox("No objects selected.")
			end
		end

		def self.stop_all_rotation
			restore_position()
			@animation_rotation.set_old_targets = [] if @animation_rotation != nil
			@animation_rotation.set_pos_origin = [] if @animation_rotation != nil
		end

		def self.set_speed
			model = Sketchup.active_model # reference the active model
			view = model.active_view
			prompts  = ["Step Angle (degrees)"]
			defaults = [@default_step]
			input = UI.inputbox(prompts, defaults, "Set Speed Rotation")
			if input
				#set_speed == set_step
				step = input[0]
				@default_step = step if @default_step != step
				#stop_animasi,get old_target,get pos_origin,restore_all_target,validation_old_target
				old_target = restore_position()
				#set_new_targets
				targets = old_target
				#set_pos_origin
				pos_origin = []
				targets.each{|e| pos_origin << e.transformation}
				#start_animation
				@animation_rotation = RotateSelection::Animation.new(targets,pos_origin,step)
				view.animation = @animation_rotation
			end
		end
		
		unless defined?(@loaded)
			# Adds an item to the Camera menu to activate our custom animation:
			UI.menu("Camera").add_item("Start Rotate Selection") {start_rotation()}
			UI.menu("Camera").add_item("Stop Rotate Selection") {stop_rotation()}
			UI.menu("Camera").add_item("Stop All Rotation") {stop_all_rotation()}
			UI.menu("Camera").add_item("Set Speed Rotation") {set_speed()}
			@loaded = true
		end
	end # extension submodule
end # namespace module

After I ran the animation a few times, Sketchup seemed to start responding slowly. Unlike the beginning of opening SketchUp. What exactly happened?

See View#average_refresh_time()
… you can multiply by the number of steps in a rotation.

I don’t know.


But I really think it is easier if you have a conditional expression within the next_frame() callback method to cause the animation to stop by returning false, even if it is only checking a @continue variable set elsewhere in the code.

I’m really sorry :pray: :pray: :pray: @DanRathbun , How to realize it in code ?
then, how to stop animation before Sketchup close or save or crash.??

I showed you how to stop the animation in the code I posted above.

And I don’t know why or how you want to know how long the animation takes.

At some point you will need to figure out the code yourself. I can’t do everything for you as I have my own projects.

1 Like

Hehehe…You are not like AI, but better than AI @DanRathbun
Thank you very much for the help.