Changing Context with the API

I’m trying to figure out how to open up a group (change context) so that the user is presented with a group that is already active and then they can proceed to edit the geometry (lines and face) within this group.

I’m not seeing a way to do this with the API, but the documentation is so vast I’m sure I’m probably missing it. Can someone lead me in the right direction?

I’m working on the foundation plugin right now and I am trying to provide a means whereby the user can define a new outline for the (polygon) slab-on-grade foundation.

The simplest thing that comes to mind is push all of the points that define the outline/perimeter of the foundation to a group that contains the face constructed from these points. Then the user can modify this face/lines as much as they like and once they are satisfied they click the save settings button of the accompanying HTML menu which then activates the ruby code that grabs this newly edited face and its points and regens the foundation.

Per this thread:

It looks like it may not be possible…

close_active will allow me to get back to the root of the model.

active_path will give me a way of checking whether I need to use the previous method to jump back to the root of the model.

Found this gem by Eneroth:

However I don’t see a way of passing a group or component to a method which will then allow me to open up that entity for editing. There has got to be a way to do this, otherwise this is a major hole in the API.

It is! It has been requested for years but apparently it’s quite hard to get it to work the way the backend is written. There are a few hacks like selecting a component and sending a virtual keystroke to enter through an OS API but then you have to implement it for Windows and MacOS separately and may need to update it if those OS APIs ever changes. I’m personally avoiding that kind of extra work.

2 Likes

FYI, the logged feature request (has posted “hacks” for Windows) …

And it’s worth mentioning that voting for issues using the :+1: symbol helps SU staff see what to prioritize.

1 Like

I’m trying to see if I can get the window hack to work but this line is problematic:

require 'win32ole'
UTIL = WIN32OLE::new(WScript.Shell)

It is giving me an error:

Error: #<NameError: uninitialized constant WScript>

or this error:

dynamic constant assignment

This seems to work for Windows:

    ######################
	#
	# Open Group for Editing (Windows Only)


	if Sketchup.platform == :platform_win

    		ss = Sketchup.active_model.selection
		    ss.clear
      		ss.add(@maingroup)

    		if ss.length > 1
			UI.messagebox "Selection must be limited to one (1) Foundation Assembly."
		else
			if ss.empty?
        			UI.messagebox "No Foundation Assembly Selected."
        		else
				first_sel = ss[0]
				if first_sel.respond_to?(:definition)
					require "win32ole"
  					win32util = WIN32OLE.new('WScript.Shell')
					win32util.SendKeys('{ENTER}')
				end
      			end	
      		end
	else
		# Mac Solution?
	end

The only problem I’m having now is that right before I run this section of code I’m opening up an HTML menu so the focus is taken off of the main SU window. Now I just need to figure out how to bring the focus back to the main window, and also figure out a solution for MacOS.

Well the error message tells you exactly what you’re doing wrong. There is no Ruby constant (class or module) named WScript, ie yet defined within the Ruby ObjectSpace.

The first thing to do when you get such an error, would be to look up the WIN32OLE constructor method …

… which shows it is a server name (ie, a String object.)

This is a recent Ruby no-no … ie, most SketchUp versions using Ruby 2+.
So Ruby no longer allows the assignment (or reassignment) of constants from within methods.
Or at the least it is warning you, and soon will not be allowed in upcoming versions.

Another feature request “old as the hills” that has not yet been given us. Various hacks exist. (Opening a 1px x 1px webdialog offscreen, and then closing it returns focus to the main application window.)

I’ve been chatting with @john_drivenupthewall how to send an enter (or return) to mac. He uses a utility called “cliclick”.

I use this in some [on a mac]

def focus_vp
  model = Sketchup.active_model.path
  Sketchup.open_file(model)
end

john

1 Like

I grabbed that and then realised the next methods may also be relevant…

    def edit_comp
      focus_vp
      cliclick = File.join(File.dirname(__dir__), 'bin', 'cliclick')
      %x["#{cliclick}" kd:shift t:C ku:shift]
    end

    def edit_grp
      focus_vp
      cliclick = File.join(File.dirname(__dir__), 'bin', 'cliclick')
      %x["#{cliclick}" kd:shift t:G ku:shift]
    end

#EDIT:
%x["#{cliclick}" kp:return] # will work for either

john

2 Likes

If you are not within an active tool, then you just need to activate a dummy tool and then reset it to the previous tool. This is very fast.

class DummyTool ; end

then

Sketchup.active_model.select_tool DummyTool.new
Sketchup.active_model.select_tool nil

or if you think it is more appropriate

Sketchup.active_model.tools.push_tool DummyTool.new
Sketchup.active_model.tools.pop_tool

If you are within your own tool, then you can exit your tool and reenter it, with a special state where you skip the deactivate() and activate(). You of course need to have the instance of your Tool class somewhere.

2 Likes

Here is what I have so far:

def open_group

	######################
	#
	# Open Group for Editing (Windows Only)


	if Sketchup.platform == :platform_win

    		ss = Sketchup.active_model.selection
		ss.clear
      		ss.add(@maingroup)

    		if ss.length > 1
			UI.messagebox "Selection must be limited to one (1) Foundation Assembly."
		else
			if ss.empty?
        			UI.messagebox "No Foundation Assembly Selected."
        		else
				first_sel = ss[0]
				if first_sel.respond_to?(:definition)
					require "win32ole"
					@win32util = WIN32OLE.new('WScript.Shell')
					MedeekMethods.activate_window
					@win32util.SendKeys('{ENTER}')

					# Edit nested group (not working)
					# ss.clear
      					# ss.add(@Temp_outline_group)
					# @win32util.SendKeys('{ENTER}')
				end
      			end	
      		end
	else
		# Mac Solution?
	end

	UI.stop_timer(@opentimer)

end



def activate_window
	caption = MedeekMethods.get_caption
  	@win32util.AppActivate caption
end



def get_caption
	su_version = Sketchup.version.to_i
  	if Sketchup.active_model.path.empty? # new model
		if Sketchup.is_pro?
			if su_version == '17'
				caption = "Untitled - SketchUp Pro 2017"
			elsif su_version == '18'
				caption = "Untitled - SketchUp Pro 2018"
			elsif su_version == '19'
				caption = "Untitled - SketchUp Pro 2019"
			else
				caption = "Untitled - SketchUp Pro"
			end
    		else
			caption = "Untitled - SketchUp Make 2017"
    		end
  	else
    		filename = File.basename(Sketchup.active_model.path)
    		if Sketchup.is_pro?
			if su_version == '17'
				caption = "#{filename} - SketchUp Pro 2017"
			elsif su_version == '18'
				caption = "#{filename} - SketchUp Pro 2018"
			elsif su_version == '19'
				caption = "#{filename} - SketchUp Pro 2019"
			else
				caption = "#{filename} - SketchUp Pro"
			end
    		else
			caption = "#{filename} - SketchUp Make 2017"
    		end
	end

	return caption
end

I have to call the open_group method with a timer because of the HTML dialog so after my dialog is launched I call this:

@opentimer = UI.start_timer(0.25, false) { open_group }

Note that I’m actually trying to edit a nested group with my main group. So I actually need to select the main group, open it then select the group inside and open it.

When I try to add the nested group to the selection set and then press enter it appears to jump me out of the editing of the main group so this is not working.

P.S. For now I’ve just commented out the logic that tries to open up the nested group. At least I can get one level deep. It is then up to the user to click into the nested group to edit it.

I think a quite big question is why you want to open a component. For e.g. a layer panel/outliner it is a required functionality, but perhaps your plugin can be designed to simply not do this.

1 Like

It is not an absolute necessity but it would be convenient to automatically drop the user into the group that needs to be modified.

Once I have this up and running I will post a video demonstrating its use to this thread as well so you can see what I am trying to accomplish.

Why do the user have to modify the group?

1 Like

Inside of this group is a face which defines the outline or perimeter of the slab-on-grade foundation. The user can edit this face to any geometry they like and then hit the update / commit button and the plugin will then utilize this updated face to regenerate the slab-on-grade foundation with the new perimeter.

Technically a user could just recreate their foundation from scratch with a new face (to define the outline) however people always want that parametric ability, so I have to provide it.

I’m also using this method to jump out of the possible nested scenarios prior to regenerating the foundation:

def move_to_root
	path1 = (Sketchup.active_model.active_path || [])
	instance1 = path1.last
	if instance1 == @Temp_outline_group
		Sketchup.active_model.close_active

		path2 = (Sketchup.active_model.active_path || [])
		instance2 = path2.last
		if instance2 == @maingroup
			Sketchup.active_model.close_active
		end
	elsif instance1 == @maingroup
		Sketchup.active_model.close_active
	end
end

This is pretty basic stuff when you have the right methods available to you. It would really be nice if there was a method available to open a group or component rather than having to try and make it work with a hack job.