Draw model while moving mouse in custom tool

So basically I would like to recreate MoveTool/ComponentTool functionality and show model position while moving mouse. I know that I could use place_component but this solution changes tool to ComponentTool and I need to change logic in onMouseMove so that solution cannot be used.

Is there way to draw model from RubyApi on specific position without placing it? Or another question is it possible to recreate MoveTool/ComponentTool from RubyAPI?

So you saw my example here ? Did you get any ideas from this ?

Just checked it. And there is similiar problem as with my own solution using place_component.

I would like to have moving functionality but with possibility to add some logic to handlers (onMouseMove, onLButtonDown) but place_component change tool to ComponentTool and as this is native tool I cannot add my own logic.

That’s why I wanted to recreate moving tool functionality but as I see it cannot be done to create your own custom tool which works exactly as native ComponentTool or MoveTool without using place_component (so in fact changing tool to ComponentTool but from code). Am I right?

For this discussion, the term should not be “model” but component instance.


One way is to place the instance in the model.active_entities (usually at the local ORIGIN,) and then transform the instance to follow the mouse as SketchUp calls your custom tool onMouseMove() callback method.

Take care to remove the instance (or fire a Sketchup::undo) if the user cancels and/or deactivates the tool.

This is not a new idea. There may even be some example plugins lying around over at SketchUcation.
I remember it being discussed over there in the past.


You can also draw to the screen within a tool. So you could actually be drawing a facsimile of the component (that is still only just a definition, but not an actual placed instance,) on the end of the cursor as your tool moves it.

This can be done because it is the definition that “owns” the entities collection, not instances. So you can read and explore the definition’s entities before a real instance is actually placed into the model’s entities collection.

The API tool class is abstract. This means that there is not a defined superclass for it.
The methods described in the API are optional callback methods, but since it’s your custom class, you may also define other internal methods to help organize the code (as long as they have names other than the API callback methods.)

There are several examples of tools by the SketchUp Team in the Extension Warehouse.

This one is a must read:

There are also others … Window Maker, Bezier Curve Tool, Rotate Rectangle Tool, etc.

Check out there extensions listing: http://extensions.sketchup.com/en/user/48/store

ADD: The Rotate Rectangle Tool is one that draws facsimile geometry to the screen before any real geometry is added to the model.

Thank you very much, that post was very helpful!

1 Like

No problem.

I suggest you write a tool that loads a 1m cube component into the model’s definition list, then reads that definition to get faces, and recreate those 6 faces on the screen attached to the mouse cursor.
When clicked then an instance of that cube is placed at the click point.

When you get that working, you can use it as a superclass to tweak further to do the other nifty ideas you have.

Ensure me if I’m right. When my definition will be already put in definitions list then I should create instance of that definition (using add_instance method) and then in OnMouseMove handler transform that instance position to “mouse cursor position” (probably some more calculation will be needed than just transform to x,y of cursor).

One way is to place the instance in the model.active_entities (usually at the local ORIGIN,)

What do you mean by ‘place the instance in the model.active_entities’. I’m not sure do I understand this.

Users do not always edit at the top level model entities collection.

model.entities

The user can double-click to enter into the editing context of a group or a component. So …

model.active_entities

… will return the entities collection object for the definition that they are contextually “within”.

Yes if you wish to do the easier of the two. Use the global IDENTITY transform to initially place the instance …

@inst = model.active_entities.add_instance(cdef,IDENTITY)

Again yes. SketchUp sends your onMouseMove() callback the x,y screen coordinates.

You need a 3D position to move the instance to, so use InputPoint.position

def onMouseMove( flags, x, y, view )
  ip = view.inputpoint( x,y )
  point = ip.position
  @inst.move!( Geom::Transformation::translation(point) )
end

I think you’ll want to be using the #move!() method as it does not record an undo operation with each transforrm.

You’ll need to wrap up the whole thing inside 1 single undo operation anyway. See:
Sketchup::Model#start_operation()

You may need to adjust the transformation if inside other editing context, but try it first at top model level, then worry about the edit path later.

Two questions:

  • Will you need to do a view.invalidate to get SketchUp to display the newly moved ComponentInstance?
  • Is this likely to cause sluggish performance of the Tool (I have not tried this technique, only used the #draw method to create basic OpenGL items in the Tool)?

I’m doing a quick test now.

YES

It is quite fast with small components from the distro library … try it …

# encoding: UTF-8

module Testing

  @@loaded ||= false

  class DragTool

    def activate
      @model = Sketchup.active_model
      choice = get_definition()
      if choice
        @cancelled = @placed = false
        @model.start_operation("DragInsert Component")
        cdef  = @model.definitions.load(choice)
        @inst = @model.active_entities.add_instance(cdef,IDENTITY)
      else
        @model.select_tool(nil)
      end
    end

    def deactivate(view)
      if @placed && !@cancelled
        @model.commit_operation
      else
        @model.abort_operation
      end
    end

    def get_definition()
      path = Sketchup.find_support_file("Components/Components Sampler")
      choice = UI.openpanel("Choose a component ...",path,"*.skp")
    end

    def onCancel(reason, view)
      puts "Cancel reason: #{reason}"
      @cancelled = true
      @model.select_tool(nil)
    end

    def onLButtonDown( flags, x, y, view )
      ip = view.inputpoint( x,y )
      point = ip.position
      @inst.move!( Geom::Transformation::translation(point) )
      view.invalidate
      @placed = true
      @model.select_tool(nil)
    end

    def onMouseMove( flags, x, y, view )
      ip = view.inputpoint( x,y )
      point = ip.position
      @inst.move!( Geom::Transformation::translation(point) )
      view.invalidate
    end

  end

  if !@@loaded
    UI.add_context_menu_handler {|popup|
      popup.add_item("DragInsert Component Tool") {
        Sketchup.active_model.select_tool(DragTool::new)
      }
    }
    @@loaded = true
  end

end

Thanks Dan, that is good news. I suppose that’s because all of the heavy lifting is done by compiled app code not the Ruby script.

Wow! You are quick :slight_smile: It works really good and it is exactly what I wanted. Thank you very much once more.

1 Like

Updated the example above to wrap it in an undo operation.

… and using #move! helps a lot also I think. Since I wrapped it in an operation, it is not necessary to record undos for the mouse moves.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.

@JPCodaLot, In this other example …
[code] undoable place_component() "MoveTool" clone
… I used a trick that wraps the selection in a temporary group so that primitives could also be moved.

For a tool that moves drawing elements you don’t want to put a whole bunch of transformations on the undo stack (if possible) whilst dragging with the mouse.

But you might want to put the last position on the undo stack if it’s move.
To do this just re transform to the last point making a call to #transform! instead of #move!.
The rotates you will want recorded so use #transform! for those.