Make ruby wait until component is placed by user

So after reading quite a bit, I’m still unable to figure out how to deal with the asynchronousity of ruby:
My extension builds a component on command and allows the user to place it in his model. If he repeats the command with the same dimensions, a second component is made by the same name but with the addition of ‘#1’, as you know.
I made a cleaning snippet that cleans those # components out if they are equal to the first one.It needs to wait after the placement of the component.
How to tell ruby to wait for it?

I’m not sure I understand exactly …

Actually it as happening if you are adding or loading new component definition to the DefinitionList with the same name.
(BTW: there is bug in SU2022, related to this, where this “numbering” will not happen )

Do you mean you want to watch when a new definition is added to the list?
If yes, there is an Sketchup::DefinitionsObserver for that purpose.

Or do you want to listen when the instance of specific definition added? Use Sketchup::DefinitionObserver


:bulb: Or you check the DefinitionList if the definition with the specific name already exist in it and simple you will not create a new or load it again but use the existing.

This has been discussed before. I remember because I was involved in the discussion and posted several examples. See my old reply here …

1 Like

Hum… I do not experience this bug here with place_component.

Thanks for the clues. Appreciated.

Yes, because

  1. place_component is placing an instance and does not affect the DefinitionList
  2. You are using SU2021 (according to your profile)
    :beers:

ahg… I have been trying to update profile. still looking to find where. :unamused:
I’m using 2022 on Mac M1.

Click on your Avatar top right of the browser window.
image

Click on the head and shoulders icon, then ‘Preferences’:

Then ‘Profile’ within ‘Preferences’, then the License Type and SketchUp Version entries, and reset them.

2 Likes

Thanks John, for some reason I was not seeing the ‘Profile’ option in the preferences. Too tired, I guess.

Back to the topic. I would really like place_component to not switch automaticaly to the Move tool. I often found that annoying while working and it seems to be causing trouble in coding also. Wonder if this what is preventing a normal resume of scripts from the GUI, after the component is put.

@john_mcclenahan and I have faced a similar problem in an extension we are working on, and in our case the behavior of place_component can actually lead to a SketchUp crash (multiple BugSplats submitted, no response yet from the developers). I think the core issue is that place_component in the Ruby API is badly conceived. I think that (like some other API calls such as send_action) it just queues up the action until Ruby relinquishes control to the GUI, rather than suspending the current Tool and reactivating it on completion. There are other native tools such as orbit and pan that suspend/resume the current Tool, so it seems this should have been possible…

Yes indeed, script not resuming is pretty hard on development.

:thinking: code snippet
possible custom “place component” (concept, do not use at it is!)
(Assuming there is a definition already in a model, or you can create your own an pass it as argument)

module Dezmo
  class MyPlaceCompTest
  
    def initialize(definition)
      @ip0 = Sketchup::InputPoint.new
      @defi = definition
      @instance = nil
    end
    
    def onCancel(reason, view)
      puts "MyTool was canceled for reason ##{reason} "
      @instance.erase! if @instance && @instance.valid?
      #The pop_tool method is used to pop the last pushed tool on the tool stack.
      Sketchup.active_model.tools.pop_tool() if reason == 0
      view.invalidate
    end
    
    def deactivate(view)
      puts "Your tool has been deactivated in view: #{view}"
      @instance.erase! if @instance && @instance.valid?
      view.invalidate
    end
    
    def add_instance(tr)
      Sketchup.active_model.active_entities.add_instance(@defi, tr)
    end
    
    def onMouseMove(flags, x, y, view)
      @ip0.pick(view, x, y)
      tr = Geom::Transformation.translation(@ip0.position)
      @instance = add_instance(tr) unless @instance
      ti_inv = @instance.transformation.inverse
      @instance.transform!(tr * ti_inv)
      view.invalidate
    end
      
    def onLButtonDown(flags, x, y, view)
      @instance = nil
      view.invalidate
    end
    
    def draw(view)
      @ip0.draw(view) if @ip0.display?
      view.tooltip = @ip0.tooltip if @ip0.valid?
    end

  end #class
end #module
model = Sketchup.active_model
definition = model.definitions[0]
model.tools.push_tool(Dezmo::MyPlaceCompTest.new(definition))

The problem is that #place_component also closes any open operation and creates a new operation.

It is not likely that this method will change as we discussed in the issue tracker. Rather a new method would need to be implemented. See:

I also posted an example DragTool quite some time (4 years) ago …

1 Like

Oh yes, It is certain that yours will handle a undo stack much better :wink::+1:
While mine just a finger exercise… :blush:

Both of your methods prove the point, I’d say.
A developper should not have to resort to work (hard) around, in this case.
I should post a request on GitHub API issue tracker. Or comment the ones already there.

Please comment in the existing issues.

You can “wait” for the placement with a temporary observer. The most robust way to catch the placement is to listen to the tool change from place_component to move and use the fact that the selection will contain the placed instance. Something like:

module PlaceComponentTest2000

  class PlaceComponentObserver
    
    def initialize()
      Sketchup.active_model.tools.add_observer(self)
    end

    def onActiveToolChanged(tools, tool_name, tool_id)      
      # Ignore orbit and place_component
      return if [10508, 21013].include?(tool_id)
      
      Sketchup.active_model.tools.remove_observer(self)

      sel = Sketchup.active_model.selection
      if sel.length == 1 && sel.first.is_a?(Sketchup::ComponentInstance) 
        PlaceComponentTest2000.post_placement(sel.first)
      end
      
    end
  end

  def self.post_placement(instance)
    puts "The component is placed"
  end

  def self.main
    Sketchup.active_model.place_component(Sketchup.active_model.definitions.first, false)
    PlaceComponentObserver.new
    # execution will resume in post_placement
  end

  main
end
1 Like

Thanks Caul.
Interesting indeed. Studying.

update: That did the trick. Good intro to observers for me.
What appealed to me in that suggestion is that it did not seemed out of reach for my level of knowledge. Which is a little higher now, btw.