Apply changes when click on the element

Hello guys, need some help.

Scenario:

Have two entities selected, and when I click in my plugin need to calculate the sum of both selected LenX and apply this value on the next entity that i’ll click. Like image below:

My code until now:

selections = Sketchup.active_model.selection

lenx_total = 0.to_i

if selections.empty?
  UI.messagebox('Select at least one component.')
else
    selections.each do |sel| 
      lenx_total = lenx_total + sel.definition.get_attribute('dynamic_attributes', '_lenx_nominal')
  end
end
 
# All this is for test only, where i'm stuck and trying to discover something
model.find_entity_by_id(18618).definition.set_attribute 'dynamic_attributes', '_lenx_nominal', lenx_total
new_def = model.find_entity_by_id(18618).definition
model.place_component new_def

If you have another solution to the problem, please share.
Thanks for your time.

You want to make an interactive “tool”. That won’t work purely with preselection, because after running the plugin from the toolbar button, it won’t have access to what you select afterwards.

(You could of course just require the user to select the components in the correct order, or make two toolbar buttons, first step, second step, and make sure the user has executed the first step, then selected the target component before executing the second step.)

To make an interactive tool, you need to write a Tool class (any class that implements those methods from Sketchup::Tool that you need), and then you tell SketchUp to select this tool:

command = UI::Command.new("MyTool"){
  begin
    Sketchup.active_model.select_tool( MyTool.new )
  rescue Exception => e
    puts e.message
    puts e.backtrace
  end
}

For the tool class, take a look at the line tool example from Extension Warehouse.
You need only a basic select tool with three states. On mouse click, you pick the group/component below the cursor using a pickhelper. If it is a correct entity, you save it in an instance variable and proceed into the next state. In the last state, you process the selected components (like summing up the lengths and applying them to the third component).

class MyTool

  def activate(view)
    @pickhelper = view.pick_helper
    reset()
  end

  def reset
    @component1 = nil
    @component2 = nil
    @component3 = nil
    @state = 0
  end

  def onLButtonDown(flags, x, y, view)
    @pickhelper.do_pick(x, y)
    if @pickhelper.picked_element.is_a?(Sketchup::ComponentInstance)
      case @state
        when 0
          @component1 = @pickhelper.picked_element
          @state = 1
        when 1
          @component2 = @pickhelper.picked_element
          @state = 2
        when 2
          @component3 = @pickhelper.picked_element
          process(@component1, @component2, @component3)
          @state = 0
      end
    end
  end

  def process(component1, component2, target_component)
    # ...
  end
end
2 Likes

Thanks a lot man,
I didn’t know the Sketchup::Tool. Very interesting.

I’m having problem to understand the states of the tool. How could I stop an execution of a tool?
Ex:
I click in my tool, select a component and want to stop my tool in that moment (deactivate).
But, if I call deactivate nothing happens, my tool still remains selected, even if set @state = 0, nothing happens.

*I know that the deactivate method is an “observer” method that is call when the tool is deactivated, but I thought that maybe calling him, would stop the tool.

The question is, how could I stop the execution of my tool anytime I want and change to select tool, for example?

Thanks again.

deactivate is a “virtual method” or “fill-in method” or “hook”. It’s not supposed to be called by you but by SketchUp, and you just implement it if you want something specific to happen when the user has deactivated the tool. That’s why it didn’t seem to work.

When you exit a tool, a different tool must be the selected tool, obviously we want the previous tool. To achieve that, you don’t call model.select_tool(…) but you push your tool onto the tool stack and later you pop it from the tool stack, and then the previous tool will be again on top of the stack:
model.tools.push_tool(MyTool.new)

model.tools.pop_tool

deactivate is a “virtual method” or “fill-in method” or “hook”. It’s not supposed to be called by you but by SketchUp, and you just implement it if you want something specific to happen when the user has deactivated the tool. That’s why it didn’t seem to work.

Alright, understood.

To achieve that, you don’t call model.select_tool(…) but you push your tool onto the tool stack and later you pop it from the tool stack, and then the previous tool will be again on top of the stack:
model.tools.push_tool(MyTool.new)
model.pop_tool

About that, I’ve tried:

model.tools.push_tool(MyTool.new)
model.pop_tool

But got:

Error: #< NoMethodError: undefined method `pop_tool’ for #< Sketchup::Model:0x8e29a68>>

Then I tried:

model.tools.push_tool(MyTool.new)
model.tools.pop_tool

And Sketchup crashes. lol.

What I’m doing is:

def onLButtonDown(flags, x, y, view)
  #do all my stuffs... including set model variable, etc..

  model.tools.push_tool(MyTool.new)
  model.tools.pop_tool
end

Maybe I’m missing something…
Anyway, I’m trying to modify the way the tool works. Instead of change to previous tool, I’ll change cursor icon or something like that.

Thanks for all your help… if my code have something that could be the problem, please let me know.
Don’t want to stop this topic here, would like to have an answer for that.

That was indeed a typo (model.tools.pop_tool).

As I wrote (maybe not clearly enough), the idea is to select your tool by pushing it onto the tool stack on top of the currently selected tool, and then after using your tool, you pop it from the tool stack when you don’t want it to be selected anymore.
So initially, instead of using model.select_tool(MyTool.new), you do:

model = Sketchup.active_model
tool = MyTool.new(model)
model.tools.push_tool(tool)

And then within this tool, when processing is finished and you want it not to be selected anymore, you pop it:

class MyTool
  def initialize(model)
    @model = model
    # Initialize more if you want…
  end

  # (More methods…)

  def process
    # Do what you want…
    # Processing is finished: Unselect this tool
    @model.tools.pop_tool
  end
end

(Here we set the reference to the model fixed when as soon as the tool is selected.)

You say SketchUp crashes, which SketchUp version do you use? Is it the latest version?
This method used to be crashy under the condition when the last tool is popped from the tool stack (when plugin code unintentionally calls pop_tool when only one tool was on the stack). We had to keep track whether our tool was still on the stack (>= 2 tools on the stack, safe to call pop_tool). But this was fixed in SketchUp 2014 or 2015 (???).

Ah OK, all makes sense now.

Everything you say is what’s happening: about crashes with empty stack, etc.
After read your explanation I’ve seen all my mistakes. i’m not considering the scope of the model for the tools attribute, and thought that when doing Sketchup.active_model, i’m getting the model itself again (is that what happens?).

When doing injection with model and setting up with @ in initialize method (just like you mentioned), everything works like a charm.

many thanks @Aerilius, just learned a lot with you.

*I’m using SU 2015 by the way.

I have not seen (and could not reproduce) such crashes in SketchUp 2015, but it could be that apart from the fixed crash on empty tool stack it is not stable to push&pop tools immediately. When you implemented it as I described, there is a lot of time between tool selection, user interaction and processing + unselection. That made the difference.

As for Sketchup.active_model, it literally gives “the active model” (of … models). It is commonly assumed this method always returns the same model, because the amount of models open at a time in the Windows version of SketchUp is 1 (where as the OS X version has multiple). Just one example, but it applies to many other cases where it is important to look at what the documentation guarantees and what the mind unconsciously assumes.

something1 = unknown_implementation(argument1)
something2 = unknown_implementation(argument1)
# something1 == something2 ?

In (math-like) functional languages yes.
In object oriented languages with program state and mutable environment and random method, we can not know. Maybe the return value depends on random or on a counter that is increased with every call, maybe the method asks the environment for the current system time&date etc. If you would want to re-use something1, keep a variable/reference to it, so its usage can be tracked and followed through your whole program.

1 Like

Sketchup.active_model.select_tool(nil)
deactivates the active model’s current tool, and activates the SelectionTool for the active model.

SketchUp Ruby API: Sketchup::Model#select_tool() said:

The select tool is activated if you pass nil to the select_tool method.

Note that on OSX multiple models may be open, and each model has it’s own toolstack, which should contain tool instances that are unique to that specific model, as the tool instance is likely to have instance variables that reference model specific objects (such as the selection set, the model reference itself, etc.)

1 Like

Here’s my SU about page (I presumed that version 15 == 2015):
http://imageshack.com/a/img633/3797/104YdG.png

Never thought that SU could have two or more models, and can’t imagine how this would be applied in production.

Understood. very useful tip, thanks again. =D

Man, I have to say that I read it and didn’t notice. lol. That’s a lesson to pay more attention in documentation.

Very complex and enlightening (is that term correct?). But, can’t imagine a way to work with multiple models and why is that necessary in any moment.

Thanks for the explanations anyway.

Well, @DanRathbun and @Aerilius, while I’m navigating in forums (here and sketchucation) and making some tests with onMouseMove and puts things out, I saw that I’m not understanding the concept of the model. I’m really having some struggle to understand various concepts and definitions that is difficulting my learning about things on sketchup.

Perhaps this is happening because I started dealing with Dynamic Components at first time that I had contact SU Ruby API here in work.

I’ll study more before doing a lot of questions here.
Thanks for your time. Cheers. =)

Very interesting comments here.

I previously reported a bug about this - on Mac, SketchUp does not always isolate tools between multiple open models. I have an example in which a tool paints the view background a distinct color in each model, and when you change the active model it affects all open models! So if you rely on having separate toolstacks, beware.

1 Like