Loading components from a File


#1

With this wall plugin I am loading more third party components from a file than ever before. My tried and true load method is the following:

def load_holdown_hardware (holdowntype)

	model = Sketchup.active_model()
	definitions = model.definitions

	this_dir=File.dirname(__FILE__)
	# Fix for ruby 2.0
	if this_dir.respond_to?(:force_encoding)
		this_dir=this_dir.dup.force_encoding("UTF-8")
	end

	filename1 = File.join(this_dir,"components/#{holdowntype}.skp")		
  	@hd_componentdefinition = definitions.load(filename1)

end

The plugin rebuilds the entire wall panel and tries to reload any components it need with every edit. I am wondering if there is penalty for reloading a component if it already exists in the model (should I be check for this) or does SketchUp essentially check for you and if its already loaded it ignores the command? This is what it appears to doing since older versions of the component do not seem to be overwritten with new ones once they are already present.


#2

I cannot test, but I would expect SketchUp always loads the component from the file because this would be the only method if you actually want to reload it. Of course there is a penalty (harddrive access etc.).

If you just want to insert another instance, you should better load the definition once and then insert instances.
There are several techniques:

  • Check whether it is already loaded and then return the already loaded definition
loaded_definition = definitions.find{ |definition| definition.path == definition_filepath }
loaded_definition = definitions.load(definition_filepath) if loaded_definition.nil?
  • Keep a cache of all definitions that you load
# (in the class initializer)
@loaded_definitions_cache = {}
# (in the loader method)
unless @loaded_definitions_cache.include?(definition_filepath)
  @loaded_definitions_cache[definition_filepath] = definitions.load(definition_filepath)
end
loaded_definition = @loaded_definitions_cache[definition_filepath]

  • Why are you using an instance variable @hd_componentdefinition, this would overwrite whatever the variable had before. Better let the method return the component definition to get predictable behavior. If you unnecessarily write to instance variables and loose overview from where (e.g. if the class is not modelled as a state machine), you will introduce bugs.

  • If there won’t be a filename2, then it’s now the best time to give your variables a more expressive name (a good name avoids any possible risk of confusion, here it is actually a path not just a file name).


#3

Loading components from files in with the Ruby API is a mess. SketchUp replaces an existing definition rather than loading a new one if the associated path is the same. It also refuses to reload in some situations. I can’t even remember what it does as it doesn’t make much sense but I think it happens when the components hasn’t been modified since it was loaded, regardless of whether the external file has been modified. Workarounds consist of making arbitrary changes to components, e.g. draw a cpoint, and save out files to temporary locations just to disassociate them with the load path.

Bugtracker issue: https://github.com/SketchUp/api-issue-tracker/issues/50


#4

On a similar note I am trying to implement a system whereby the designer can select from any number of pre-configured windows or doors that are stored in a sub-folder of my plugin.

With my window and door edit menus I have gone the html route. The problem I see is that if the number of files in the sub-folder(s) get large then this could create problems in passing the data between ruby and the html (the list of files).

My initial idea is to have ruby gather of the file names in the sub-folder then pass this to the html form via javascript. Javascript then populates the appropriate select input (options) with the file names so that the user can select from them.

Perhaps there is a better and more efficient way to handle this?


#5

JavaScript can nowadays handle a lot… it is more of a question how large the selection is and how you present it to the user.

It is better to define something in one single place and pass it around, than having many code places that the developer needs to update and keep consistent (e.g. hard-coded names). If your GUI does not need to know anything about the components but just displays whatever data you pass to it, it is more generic and flexible.


#6

All the GUI needs to know is the list of names, it is not doing any sort of parsing or applying any logic to them to sort them etc…

I guess I just need to test it out.

It just seems that somewhere there is a limitation on the length of string you can pass to html via ruby, so if the names themselves are long or if there is 100+ files in the sub-folders I may hit this limit.

Once the GUI knows the file name it passes it back to Ruby and loads that component into the model, so it really is fairly straight forward.

My biggest concern is the back and forth between the Ruby and HTML so that it is robust and doesn’t break.

I already have issues where it takes a while for Ruby to pass the data to the html and sometimes it doesn’t get there in time, my solution has been to use the javacript setTimeout function like this:

if (v12 == 'YES')
	{
		setTimeout(function(){
			callRubyADV('Update');
		},200);
	}

Even this does not seem to be absolutely bullet proof, it probably has something to do with the asynchronous nature of javascript.


#7

Practically there is not such a limit in HtmlDialog. For example Ruby Console+ can pass megabytes of text files as strings (historically there had only been a URL length limit from webdialog to Ruby).

For back-and-forth messaging you need to take care not to rely on synchronous programming (assuming one command is executed after the previous one is completed). The “bridge” library (e.g. in Ruby Console+) makes it very easy with promises and callbacks.

If an application needs timers to work-around issues, it is a sign that it has not the right design. Think about it, how do you know the right duration of the timer? There is no answer! The right duration is to wait until the data is there, and this is best done with asynchronous programming, like with callbacks.

Don’t do this: if you mean something with two possible values, use a boolean (true/false), no slow string comparison (and strings can have infinite possible values and spellings). Besides that, the variable name v12 would not help a reader understand anything…


#8

I guess that is my BIG question then, how do I force the javascript/html to wait for the data from Ruby to arrive.

I am sending multiple streams of data from Ruby (basic options, advanced options, trim options, casing options, installation options etc)

I think the problem is that it all needs to arrive before the the page renders otherwise it doesn’t properly populate or perhaps the page hasn’t rendered yet and therefore the javascript cannot cannot populate the fields within the html.

I see the issue pop up intermittently but I can’t seem to put my finger on it.


#9

I wouldn’t use timers for this. When a JS function makes a callback to Ruby, Ruby can do an execute_script and call another JS function when ready.


#10

What different method would you suggest. I’m actually at wits end right now with this issue since it is an intermittent problem and I can’t seem to find the exact reason why sometime some of the parameter fields in the html form don’t get populated.

The html form in its entirety is here:

http://design.medeek.com/test/web_dialog_editwindow.html

The ruby code that connects to the html and talks to it is below:

############################################
#
# Medeek Design Inc. - Medeek Wall Plugin - Copyright 2016-2018
#
# Name:  MEDEEK_EDIT_WINDOW
# http://design.medeek.com
# Author: Nathaniel P. Wilkerson PE
# Created:  Apr. 11, 2018
# Last Revision:  May 19, 2018
# Version 1.0.0
# Company: Medeek Design Inc.
#
#  TOOL TO EDIT WINDOWS
#
#
# Place this file in your Google SketchUp plugins directory
#
#############################################



# First we pull in the standard API hooks.

# require 'sketchup.rb'
# require 'extensions.rb'
# require 'langhandler.rb'



# Define Module Hierarchy

module Medeek_Engineering_Inc_Extensions

	module MedeekWallPlugin

	module Wall


##############################
#
# Class Methods of Plugin
#
##############################

class MedeekMethods
  	class << self

# include Math




######################################################
#
# EDIT WINDOWS
#
######################################################



def show_edit_menu_window (wallfamily, wall_group, opening_group_name)

	MedeekMethods.get_window_library_files

	@Wallfamily = wallfamily

	# Loads Parameters from Dictionary

	@Win_geom 		= @Windowattr[0]
	@Win_loc 		= @Windowattr[1]
	@Win_width 		= @Windowattr[2]
	@Win_height 		= @Windowattr[3]
	@Win_header 		= @Windowattr[4]
	@Win_headerhgt 		= @Windowattr[5]
	@Win_sill_num 		= @Windowattr[6]
	@Win_king_num 		= @Windowattr[7]
	@Win_trimmer_num 	= @Windowattr[8]
	@Win_sill_trimmers 	= @Windowattr[9]
	@Win_ro_offset 		= @Windowattr[10]
	@Advwinoptions 		= @Windowattr[11]

	if @Advwinoptions == "YES"
		
		@Win_installoption 	= @Windowattradv[0]
		@Win_trimoption 	= @Windowattradv[1]
		@Win_casingoption 	= @Windowattradv[2]

		if @Win_installoption == "YES"

			@Win_installstyle 		= @Windowattr_install[0]
			@Win_installloc 		= @Windowattr_install[1]

			@Win_framewidth_install 	= @Windowattr_install[2]
			@Win_framedepth_install		= @Windowattr_install[3]
			@Win_sashwidth_install 		= @Windowattr_install[4]
			@Win_sashdepth_install		= @Windowattr_install[5]

			@Win_glassthk_install 		= @Windowattr_install[6]
			@Win_outset_install  		= @Windowattr_install[7]
			@Win_mullions_install  		= @Windowattr_install[8]

			@Wininstallmat			= @Windowattr_install[9]
			@Winframematname		= @Windowattr_install[10]

			@Win_mulliondepth 		= @Windowattr_install[11]
			@Win_mullionwidth 		= @Windowattr_install[12]

			@Win_installmode		= @Windowattr_install[13]
			@Win_file			= @Windowattr_install[14]
			@Win_insertion			= @Windowattr_install[15]


		end

		if @Win_trimoption == "YES"

			@Win_trimstyle 			= @Windowattr_trim[0]
			@Win_trimloc 			= @Windowattr_trim[1]
			@Win_headerwidth 		= @Windowattr_trim[2]
			@Win_jambwidth 			= @Windowattr_trim[3]
			@Win_sillwidth 			= @Windowattr_trim[4]
			@Win_headerthk 			= @Windowattr_trim[5]
			@Win_jambthk 			= @Windowattr_trim[6]
			@Win_sillthk 			= @Windowattr_trim[7]
			@Win_headerext 			= @Windowattr_trim[8]
			@Win_sillext 			= @Windowattr_trim[9]

			@Wintrimmat 			= @Windowattr_trim[10]
			@Wintrimmatname 		= @Windowattr_trim[11]

		end

		if @Win_casingoption == "YES"

			@Win_casingstyle 		= @Windowattr_casing[0]
			@Win_casingloc 			= @Windowattr_casing[1]

			@Win_headerwidth_casing 	= @Windowattr_casing[2]
			@Win_jambwidth_casing 		= @Windowattr_casing[3]
			@Win_apronwidth_casing 		= @Windowattr_casing[4]
			@Win_jambextdepth_casing	= @Windowattr_casing[5]

			@Win_headerthk_casing 		= @Windowattr_casing[6]
			@Win_jambthk_casing  		= @Windowattr_casing[7]
			@Win_stoolthk_casing 		= @Windowattr_casing[8]
			@Win_apronthk_casing 		= @Windowattr_casing[9]
			@Win_jambextthk_casing 		= @Windowattr_casing[10]

			@Win_headerext_casing 		= @Windowattr_casing[11]
			@Win_stoolext_casing  		= @Windowattr_casing[12]
			@Win_apronext_casing 		= @Windowattr_casing[13]
			@Win_stoolproj_casing 		= @Windowattr_casing[14]

			@Win_reveal_casing		= @Windowattr_casing[15]
			@Win_shim_casing  		= @Windowattr_casing[16]

			@Wincasingmat			= @Windowattr_casing[17]
			@Wincasingmatname		= @Windowattr_casing[18]

		end

	end

	######################
	#
	# Html Menu
	#
	######################
	
	# @Wall_length_ft =  @Wall_length/12.0
	# @Wall_length_m = @Wall_length_ft * @ft_m

	# @BuildingLength_ft = 10
	# @BuildingLength_m = @BuildingLength_ft * @ft_m

	if @Wallfamily == "Rectangular"


			if @Licensemode == "trial"
				if defined?(UI::HtmlDialog)
				dlg00 = UI::HtmlDialog.new(
				{
  					:dialog_title => "Edit Window Assembly - Version #{@Pluginversion} (trial version)",
  					:preferences_key => 'editoptions_MEDEEK_WINDOW',
  					:scrollable => true,
  					:resizable => true,
  					:width => 400,
  					:height => 1000,
  					:left => 1200,
  					:top => 100,
  					:min_width => 50,
  					:min_height => 50,
  					:max_width =>1000,
  					:max_height => 1200,
  					:style => UI::HtmlDialog::STYLE_DIALOG
				})
				else
					dlg00 = UI::WebDialog.new("Edit Window Assembly - Version #{@Pluginversion}", true, "editoptions_MEDEEK_WINDOW", 1000, 750, 450, 200, true)
				end
			else
				if defined?(UI::HtmlDialog)
				dlg00 = UI::HtmlDialog.new(
				{
  					:dialog_title => "Edit Window Assembly - Version #{@Pluginversion}",
  					:preferences_key => 'editoptions_MEDEEK_WINDOW',
  					:scrollable => true,
  					:resizable => true,
  					:width => 400,
  					:height => 1000,
  					:left => 1200,
  					:top => 100,
  					:min_width => 50,
  					:min_height => 50,
  					:max_width =>1000,
  					:max_height => 1200,
  					:style => UI::HtmlDialog::STYLE_DIALOG
				})
				else
					dlg00 = UI::WebDialog.new("Edit Window Assembly - Version #{@Pluginversion}", true, "editoptions_MEDEEK_WINDOW", 1000, 750, 450, 200, true)
				end
			end


			#############################################


			dlg00.add_action_callback("PUSHTOHTML_WINDOW_EDIT") {|action_context, params|

				if @Unitstemplate == "metric"
					
					@Win_loc_ftin = @Win_loc.to_l
					@Win_width_m = (@Win_width*@in_mm).round(3)
					@Win_height_m = (@Win_height*@in_mm).round(3)
					@Win_headerhgt_m = (@Win_headerhgt*@in_mm).round(3)
					@Win_ro_offset_m = (@Win_ro_offset*@in_mm).round(3)

					js_command = 'pushdata( "' + opening_group_name.to_s + '|' + @Winfamily.to_s + '|' + @Win_geom.to_s + '|' + @Win_loc_ftin.to_s + '|' + @Win_width_m.to_s + '|' + @Win_height_m.to_s + '|' + @Win_header.to_s + '|' + @Win_headerhgt_m.to_s + '|' + @Win_sill_num.to_s + '|' + @Win_king_num.to_s + '|' + @Win_trimmer_num.to_s + '|' + @Win_sill_trimmers.to_s + '|' + @Win_ro_offset_m.to_s + '|' + @Advwinoptions.to_s + '|' + @Templateunits.to_s + '|' + @Language_db.to_s + '|' + @Licensemode.to_s + '" );'
				else
		
					@Win_loc_ftin = @Win_loc.to_l
					@Win_loc_ftin = @Win_loc_ftin.to_s
					@Win_loc_ftin_fixed = @Win_loc_ftin.gsub(/"/, '\"')
					
					js_command = 'pushdata( "' + opening_group_name.to_s + '|' + @Winfamily.to_s + '|' + @Win_geom.to_s + '|' + @Win_loc_ftin_fixed + '|' + @Win_width.to_s + '|' + @Win_height.to_s + '|' + @Win_header.to_s + '|' + @Win_headerhgt.to_s + '|' + @Win_sill_num.to_s + '|' + @Win_king_num.to_s + '|' + @Win_trimmer_num.to_s + '|' + @Win_sill_trimmers.to_s + '|' + @Win_ro_offset.to_s + '|' + @Advwinoptions.to_s + '|' + @Templateunits.to_s + '|' + @Language_db.to_s + '|' + @Licensemode.to_s + '" );'
				end

				dlg00.execute_script(js_command)	
			}

			dlg00.add_action_callback("PUSHTOHTML_WINDOW_EDIT_ADV") {|action_context, params|

				if @Unitstemplate == "metric"

					js_command = 'pushdata_adv( "' + @Win_installoption.to_s + '|' + @Win_trimoption.to_s + '|' + @Win_casingoption.to_s + '" );'
				else
					js_command = 'pushdata_adv( "' + @Win_installoption.to_s + '|' + @Win_trimoption.to_s + '|' + @Win_casingoption.to_s + '" );'
				end

				dlg00.execute_script(js_command)	
			}


			dlg00.add_action_callback("PUSHTOHTML_WINDOW_EDIT_INS") {|action_context, params|

				if @Unitstemplate == "metric"

					@Win_framewidth_install_m = (@Win_framewidth_install*@in_mm).round(3)
					@Win_framedepth_install_m = (@Win_framedepth_install*@in_mm).round(3)
					@Win_sashwidth_install_m = (@Win_sashwidth_install*@in_mm).round(3)
					@Win_sashdepth_install_m = (@Win_sashdepth_install*@in_mm).round(3)
					@Win_glassthk_install_m = (@Win_glassthk_install*@in_mm).round(3)
					@Win_outset_install_m = (@Win_outset_install*@in_mm).round(3)
					@Win_mulliondepth_m = (@Win_mulliondepth*@in_mm).round(3)
					@Win_mullionwidth_m = (@Win_mullionwidth*@in_mm).round(3)
					
					
					js_command = 'pushdata_ins( "' + @Win_installstyle.to_s + '|' + @Win_installloc.to_s + '|' + @Win_framewidth_install_m.to_s + '|' + @Win_framedepth_m_install.to_s + '|' + @Win_sashwidth_install_m.to_s + '|' + @Win_sashdepth_m_install.to_s + '|' + @Win_glassthk_m_install.to_s + '|' + @Win_outset_install_m.to_s + '|' + @Win_mullions_install.to_s + '|' + @Wininstallmat.to_s + '|' + @Winframematname.to_s + '|' + @Win_mulliondepth_m.to_s + '|' + @Win_mullionwidth_m.to_s + '|' + @Win_installmode.to_s + '|' + @Win_file.to_s + '|' + @Win_insertion.to_s + '|' + @Window_library_string.to_s + '" );'
				else
					js_command = 'pushdata_ins( "' + @Win_installstyle.to_s + '|' + @Win_installloc.to_s + '|' + @Win_framewidth_install.to_s + '|' + @Win_framedepth_install.to_s + '|' + @Win_sashwidth_install.to_s + '|' + @Win_sashdepth_install.to_s + '|' + @Win_glassthk_install.to_s + '|' + @Win_outset_install.to_s + '|' + @Win_mullions_install.to_s + '|' + @Wininstallmat.to_s + '|' + @Winframematname.to_s + '|' + @Win_mulliondepth.to_s + '|' + @Win_mullionwidth.to_s + '|' + @Win_installmode.to_s + '|' + @Win_file.to_s + '|' + @Win_insertion.to_s + '|' + @Window_library_string.to_s + '" );'
				end

				dlg00.execute_script(js_command)	
			}

			dlg00.add_action_callback("PUSHTOHTML_WINDOW_EDIT_TRM") {|action_context, params|

				if @Unitstemplate == "metric"

					@Win_headerwidth_m = (@Win_headerwidth*@in_mm).round(3)
					@Win_jambwidth_m = (@Win_jambwidth*@in_mm).round(3)
					@Win_sillwidth_m = (@Win_sillwidth*@in_mm).round(3)
					@Win_headerthk_m = (@Win_headerthk*@in_mm).round(3)
					@Win_jambthk_m = (@Win_jambthk*@in_mm).round(3)
					@Win_sillthk_m = (@Win_sillthk*@in_mm).round(3)
					@Win_headerext_m = (@Win_headerext*@in_mm).round(3)
					@Win_sillext_m = (@Win_sillext*@in_mm).round(3)
					
					
					js_command = 'pushdata_trm( "' + @Win_trimstyle.to_s + '|' + @Win_trimloc.to_s + '|' + @Win_headerwidth_m.to_s + '|' + @Win_jambwidth_m.to_s + '|' + @Win_sillwidth_m.to_s + '|' + @Win_headerthk_m.to_s + '|' + @Win_jambthk_m.to_s + '|' + @Win_sillthk_m.to_s + '|' + @Win_headerext_m.to_s + '|' + @Win_sillext_m.to_s + '|' + @Wintrimmat.to_s + '|' + @Wintrimmatname.to_s + '" );'
				else
					js_command = 'pushdata_trm( "' + @Win_trimstyle.to_s + '|' + @Win_trimloc.to_s + '|' + @Win_headerwidth.to_s + '|' + @Win_jambwidth.to_s + '|' + @Win_sillwidth.to_s + '|' + @Win_headerthk.to_s + '|' + @Win_jambthk.to_s + '|' + @Win_sillthk.to_s + '|' + @Win_headerext.to_s + '|' + @Win_sillext.to_s + '|' + @Wintrimmat.to_s + '|' + @Wintrimmatname.to_s + '" );'
				end

				dlg00.execute_script(js_command)	
			}

			dlg00.add_action_callback("PUSHTOHTML_WINDOW_EDIT_CAS") {|action_context, params|

				if @Unitstemplate == "metric"

					@Win_headerwidth_casing_m = (@Win_headerwidth_casing*@in_mm).round(3)
					@Win_jambwidth_casing_m = (@Win_jambwidth_casing*@in_mm).round(3)
					@Win_apronwidth_casing_m = (@Win_apronwidth_casing*@in_mm).round(3)
					@Win_jambextdepth_casing_m = (@Win_jambextdepth_casing*@in_mm).round(3)
					@Win_headerthk_casing_m = (@Win_headerthk_casing*@in_mm).round(3)
					@Win_jambthk_casing_m = (@Win_jambthk_casing*@in_mm).round(3)
					@Win_stoolthk_casing_m = (@Win_stoolthk_casing*@in_mm).round(3)
					@Win_apronthk_casing_m = (@Win_apronthk_casing*@in_mm).round(3)
					@Win_jambextthk_casing_m = (@Win_jambextthk_casing*@in_mm).round(3)
					@Win_headerext_casing_m = (@Win_headerext_casing*@in_mm).round(3)
					@Win_stoolext_casing_m = (@Win_stoolext_casing*@in_mm).round(3)
					@Win_apronext_casing_m = (@Win_apronext_casing*@in_mm).round(3)
					@Win_stoolproj_casing_m = (@Win_stoolproj_casing*@in_mm).round(3)
					@Win_reveal_casing_m = (@Win_reveal_casing*@in_mm).round(3)
					@Win_shim_casing_m = (@Win_shim_casing*@in_mm).round(3)
					
					js_command = 'pushdata_cas( "' + @Win_casingstyle.to_s + '|' + @Win_casingloc.to_s + '|' + @Win_headerwidth_casing_m.to_s + '|' + @Win_jambwidth_casing_m.to_s + '|' + @Win_apronwidth_casing_m.to_s + '|' + @Win_jambextdepth_casing_m.to_s + '|' + @Win_headerthk_casing_m.to_s + '|' + @Win_jambthk_casing_m.to_s + '|' + @Win_stoolthk_casing_m.to_s + '|' + @Win_apronthk_casing_m.to_s + '|' + @Win_jambextthk_casing_m.to_s + '|' + @Win_headerext_casing_m.to_s + '|' + @Win_stoolext_casing_m.to_s + '|' + @Win_apronext_casing_m.to_s + '|' + @Win_stoolproj_casing_m.to_s + '|' + @Win_reveal_casing_m.to_s + '|' + @Win_shim_casing_m.to_s + '|' + @Wincasingmat.to_s + '|' + @Wincasingmatname.to_s + '" );'
				else
					js_command = 'pushdata_cas( "' + @Win_casingstyle.to_s + '|' + @Win_casingloc.to_s + '|' + @Win_headerwidth_casing.to_s + '|' + @Win_jambwidth_casing.to_s + '|' + @Win_apronwidth_casing.to_s + '|' + @Win_jambextdepth_casing.to_s + '|' + @Win_headerthk_casing.to_s + '|' + @Win_jambthk_casing.to_s + '|' + @Win_stoolthk_casing.to_s + '|' + @Win_apronthk_casing.to_s + '|' + @Win_jambextthk_casing.to_s + '|' + @Win_headerext_casing.to_s + '|' + @Win_stoolext_casing.to_s + '|' + @Win_apronext_casing.to_s + '|' + @Win_stoolproj_casing.to_s + '|' + @Win_reveal_casing.to_s + '|' + @Win_shim_casing.to_s + '|' + @Wincasingmat.to_s + '|' + @Wincasingmatname.to_s + '" );'
				end

				dlg00.execute_script(js_command)	
			}


			#############################################

			
			dlg00.add_action_callback("EXEC_WINDOW_EDIT") {|action_context, params|

				if params
				
					@Winfamily = "Rectangle"
					@Edit_menu_window = 1
					@Edit_menu = 1
					@Model_operation = 'Edit Window'
					@Windowgroupname = opening_group_name
					@Wall_group = wall_group
					
					@maingroup = @Wall_group
					@maingroup_name = @maingroup.name
	
					this_dir=File.dirname(__FILE__)
					# Fix for ruby 2.0
					if this_dir.respond_to?(:force_encoding)
					this_dir=this_dir.dup.force_encoding("UTF-8")
					end
	
					# Global Settings and Parameters

					Sketchup.load(File.join(this_dir,"MEDEEK_WALL_GLOBALS.rbs"))

					MedeekMethods.check_template_units
					MedeekMethods.check_global_settings
					MedeekMethods.check_layers
					MedeekMethods.check_materials

					Sketchup.load(File.join(this_dir,"MEDEEK_RECT_WALL_DICTIONARY.rbs"))

					# Read Dictionary Attributes
					MedeekMethods.read_dictionary @maingroup

					# Execute Main Menu

					corner1 = [0,0,0]
					corner2 = [1,0,0]
					corner3 = [1,1,0]
					corner4 = [0,1,0]

					@Wall_length_ft =  @Wall_length/12.0
					@Wall_length_m = @Wall_length_ft * @ft_m

					@BuildingLength_ft = 10
					@BuildingLength_m = @BuildingLength_ft * @ft_m

					Sketchup.load(File.join(this_dir,"MEDEEK_RECTANGULAR_WALL.rbs"))

					MedeekMethods.get_rect_wall_parameters

					if @Advwalloptions == "YES"
						Sketchup.load(File.join(this_dir,"MEDEEK_ADVOPTIONS_WALL.rbs"))
						MedeekMethods.get_rect_wall_adv_options

						if @Holdownoption == "YES"
							Sketchup.load(File.join(this_dir,"MEDEEK_WALL_HOLDOWNS.rbs"))
							MedeekMethods.get_holdown_options
						end

						if @Trimoption == "YES"
							MedeekMethods.get_trim_options
						end
					end

					MedeekMethods.check_user_materials
	
					MedeekMethods.main_menu @BuildingLength_ft, @Wall_length_ft, @BuildingLength_m, @Wall_length_m, corner1, corner2, corner3, corner4


					# Final Routines and Methods

					@Winfamilylast = @Winfamily
	

					# Data Logging

					# Sketchup.load(File.join(this_dir,"MEDEEK_WINDOW_LOG.rbs"))
					# MedeekMethods.write_window_log


				end
			}

			dlg00.add_action_callback("GET_WINDOW_EDIT") {|action_context, params|
   		
				if params
					params = params.to_s
					plist = []
					plist = params.split("|")
					# puts "Basic List: #{plist}"

					@Win_geom			= 	plist[0]

					@Win_loc_ftin			= 	plist[1].to_l
					# puts @Win_loc_ftin
					@Win_loc			= 	@Win_loc_ftin.to_f

					@Win_width			= 	plist[2].to_f
					@Win_height			= 	plist[3].to_f
					@Win_header			= 	plist[4]
					@Win_headerhgt			= 	plist[5].to_f
					@Win_sill_num			= 	plist[6].to_i
					@Win_king_num			= 	plist[7].to_i
					@Win_trimmer_num		= 	plist[8].to_i
					@Win_sill_trimmers		= 	plist[9]
					@Win_ro_offset			= 	plist[10].to_f
					@Advwinoptions			= 	plist[11]

					if @Unitstemplate == "metric"
					
						@Win_loc		= 	@Win_loc * @mm_in
						@Win_width		= 	@Win_width * @mm_in
						@Win_height		= 	@Win_height * @mm_in
						@Win_headerhgt		= 	@Win_headerhgt * @mm_in
						@Win_ro_offset		= 	@Win_ro_offset * @mm_in	

					end


					# UI.messagebox("Get Window Edit Fired:  #{@Win_loc_ftin}")
				else
					# Basic Options Input Cancelled
					exit 0
				end
			
			}

			dlg00.add_action_callback("GET_WINDOW_EDIT_ADV") {|action_context, params|

				if params
					params = params.to_s
					plist = []
					plist = params.split("|")
					# puts "Adv. List: #{plist}"
					
					@Win_installoption	= 	plist[0]
					@Win_trimoption		= 	plist[1]
					@Win_casingoption 	= 	plist[2]
					
					if @Unitstemplate == "metric"
						# Nothing here yet
					end
	
					
				else
					# Adv. Options Input Cancelled
					exit 0
				end
			}

			dlg00.add_action_callback("GET_WINDOW_EDIT_INS") {|action_context, params|

				if params
					params = params.to_s
					plist = []
					plist = params.split("|")
					# puts "Install List: #{plist}"
						
					@Win_installstyle	= 	plist[0]
					@Win_installloc		= 	plist[1]
					@Win_framewidth_install	= 	plist[2].to_f
					@Win_framedepth_install	= 	plist[3].to_f
					@Win_sashwidth_install	= 	plist[4].to_f
					@Win_sashdepth_install	= 	plist[5].to_f
					@Win_glassthk_install	= 	plist[6].to_f
					@Win_outset_install	= 	plist[7].to_f
					@Win_mullions_install	= 	plist[8]
					@Wininstallmat		= 	plist[9]
					@Winframematname	=	plist[10]
					@Win_mulliondepth	= 	plist[11].to_f
					@Win_mullionwidth	= 	plist[12].to_f

					# Library Options

					@Win_installmode	=	plist[13]
					@Win_file		=	plist[14]
					@Win_insertion		=	plist[15]

					if @Unitstemplate == "metric"
					
						@Win_framewidth_install		= 	@Win_framewidth_install * @mm_in
						@Win_framedepth_install		= 	@Win_framedepth_install * @mm_in
						@Win_sashwidth_install		= 	@Win_sashwidth_install * @mm_in
						@Win_sashdepth_install		= 	@Win_sashdepth_install * @mm_in
						@Win_glassthk_install		= 	@Win_glassthk_install * @mm_in
						@Win_outset_install		= 	@Win_outset_install * @mm_in
						@Win_mulliondepth		= 	@Win_mulliondepth * @mm_in
						@Win_mullionwidth		= 	@Win_mullionwidth * @mm_in
					
					end
						
				else
					# Install Options Input Cancelled
					exit 0
				end
			}

			dlg00.add_action_callback("GET_WINDOW_EDIT_TRM") {|action_context, params|

				if params
					params = params.to_s
					plist = []
					plist = params.split("|")
					# puts "Trim List: #{plist}"
						
					@Win_trimstyle		= 	plist[0]
					@Win_trimloc  		= 	plist[1]
					@Win_headerwidth	= 	plist[2].to_f
					@Win_jambwidth 		= 	plist[3].to_f
					@Win_sillwidth		= 	plist[4].to_f
					@Win_headerthk		= 	plist[5].to_f
					@Win_jambthk		= 	plist[6].to_f
					@Win_sillthk		= 	plist[7].to_f
					@Win_headerext		= 	plist[8].to_f
					@Win_sillext		= 	plist[9].to_f
					@Wintrimmat		= 	plist[10]
					@Wintrimmatname		= 	plist[11]

					if @Unitstemplate == "metric"
					
						@Win_headerwidth	= 	@Win_headerwidth * @mm_in
						@Win_jambwidth		= 	@Win_jambwidth * @mm_in
						@Win_sillwidth		= 	@Win_sillwidth * @mm_in
						@Win_headerthk		= 	@Win_headerthk * @mm_in
						@Win_jambthk		= 	@Win_jambthk * @mm_in
						@Win_sillthk		= 	@Win_sillthk * @mm_in
						@Win_headerext		= 	@Win_headerext * @mm_in
						@Win_sillext		= 	@Win_sillext * @mm_in
						
					end
						
				else
					# Trim Options Input Cancelled
					exit 0
				end
			}

			dlg00.add_action_callback("GET_WINDOW_EDIT_CAS") {|action_context, params|

				if params
					params = params.to_s
					plist = []
					plist = params.split("|")
					# puts "Casing List: #{plist}"	
						
					@Win_casingstyle		= 	plist[0]
					@Win_casingloc  		= 	plist[1]
					@Win_headerwidth_casing		= 	plist[2].to_f
					@Win_jambwidth_casing		= 	plist[3].to_f
					@Win_apronwidth_casing		= 	plist[4].to_f
					@Win_jambextdepth_casing	= 	plist[5].to_f
					@Win_headerthk_casing		= 	plist[6].to_f
					@Win_jambthk_casing		= 	plist[7].to_f
					@Win_stoolthk_casing		= 	plist[8].to_f
					@Win_apronthk_casing		= 	plist[9].to_f
					@Win_jambextthk_casing		= 	plist[10].to_f
					@Win_headerext_casing		= 	plist[11].to_f
					@Win_stoolext_casing		= 	plist[12].to_f
					@Win_apronext_casing		= 	plist[13].to_f
					@Win_stoolproj_casing		= 	plist[14].to_f
					@Win_reveal_casing		= 	plist[15].to_f
					@Win_shim_casing		= 	plist[16].to_f
					@Wincasingmat			= 	plist[17]
					@Wincasingmatname		= 	plist[18]
					
					if @Unitstemplate == "metric"
					
						@Win_headerwidth_casing		= 	@Win_headerwidth_casing * @mm_in
						@Win_jambwidth_casing		= 	@Win_jambwidth_casing * @mm_in
						@Win_apronwidth_casing		= 	@Win_apronwidth_casing * @mm_in
						@Win_jambextdepth_casing	= 	@Win_jambextdepth_casing * @mm_in
						@Win_headerthk_casing		= 	@Win_headerthk_casing * @mm_in
						@Win_jambthk_casing		= 	@Win_jambthk_casing * @mm_in
						@Win_stoolthk_casing		= 	@Win_stoolthk_casing * @mm_in
						@Win_apronthk_casing		= 	@Win_apronthk_casing * @mm_in
						@Win_jambextthk_casing		= 	@Win_jambextthk_casing * @mm_in
						@Win_headerext_casing		= 	@Win_headerext_casing * @mm_in
						@Win_stoolext_casing		= 	@Win_stoolext_casing * @mm_in
						@Win_apronext_casing		= 	@Win_apronext_casing * @mm_in
						@Win_stoolproj_casing		= 	@Win_stoolproj_casing * @mm_in
						@Win_reveal_casing		= 	@Win_reveal_casing * @mm_in
						@Win_shim_casing		= 	@Win_shim_casing * @mm_in
							
					end

						
				else
					# Roof Return Options Input Cancelled
					exit 0
				end
			}

			

			##########################################

			
			dlg00.add_action_callback("CLOSE_DIALOG") {|action_context, params|
				dlg00.close
			}

			# Find and show our html file
	
			dlg00.set_file File.dirname(__FILE__) + "/html/web_dialog_editwindow.html"
			dlg00.show


	elsif @Wallfamily == "Gable"
		
	else
		UI.messagebox "Invalid wall type selected, action aborted."
        	return	
	end

	##################

	
end



def get_window_library_files

	this_dir=File.dirname(__FILE__)
	# Fix for ruby 2.0
	if this_dir.respond_to?(:force_encoding)
		this_dir=this_dir.dup.force_encoding("UTF-8")
	end

	skpfiles = File.join(this_dir, "library/windows/*.skp")
	@Window_library_list = Dir.glob(skpfiles)

	if @Window_library_list.length > 0

		@Window_library_string = 'NONE'

		regex1 = /\/(?<onlyname>\w+)\.skp/i
		for filename in @Window_library_list do
				
				if matches = filename.match(regex1)
					cleanfilename = matches[:onlyname]
					@Window_library_string << ",#{cleanfilename}"
				end
		end
	else
		# No Custom Window Components in Library
	end

end



end # << self
end # MedeekMethods Class



	###########################
	#
	# Main Entry into Program
	#
	###########################


	end # Wall
	end # MedeekWallPlugin
end # Medeek Module

#11

The whole timer method is obviously not working, I’m trying this jQuery substitute but the problem persists:

if (v1_db == 'YES')
	{
		// setTimeout(function(){
			// var query = 'skp:PUSHTOHTML_DOOR_EDIT_INS@' + 'getadvdata';
			// window.location.href = query;
		// },160);

		
		$( document ).ready(function() {
			var query = 'skp:PUSHTOHTML_DOOR_EDIT_INS@' + 'getadvdata';
			window.location.href = query;
		});
		
	}

I’m sure others have encountered a similar issue before, there must be a reasonably simple solution.


#12

Problem solved, it wasn’t the timer method after all. I was overwriting my variable names in the same function, the resolution is below:

if (v10_db == 'YES')
	{
		setTimeout(function(){
			var query1 = 'skp:PUSHTOHTML_WALL_EDIT_HLD@' + 'getadvdata';
			window.location.href = query1;
		},70);
		
	}
	
	if (v19_db == 'YES')
	{
		setTimeout(function(){
			var query2 = 'skp:PUSHTOHTML_WALL_EDIT_BLK@' + 'getadvdata';
			window.location.href = query2;
		},90);
		
	}

	if (v20_db == 'YES')
	{
		setTimeout(function(){
			var query3 = 'skp:PUSHTOHTML_WALL_EDIT_TRM@' + 'getadvdata';
			window.location.href = query3;
		},110);
		
	}

#13

JQuery ready is much better than a timer as a timer may end too early or to late (with the sensation of extra lag).

Btw, if you use WebDialogs rather than HtmlDialogs you can pass a block of Ruby code to #show to have it executed when the DOM is ready.

In general I would advise you to write shorter methods. If each method does one singe thing only the risk of accidentally overwriting variables more or less disappears. In Ruby it is often recommended to have a maximum of 10 lines per method and extract every identifiable sub-task into its own method. With nicely named methods it can also increase code readability quite a bit. Instead of reading 10 lines of ode and figure out what they do together, or have title comments saying what the next paragraph of code does, you can have one single method call clearly named after what the method does.


#14

As Christina says, timers are not a solution.

Also try to keep the number of shared variables (module or instance variables) low, you have way tooo many. You could group related variables into their own class (like a struct). Actually you would preferably use a class instance rather than a module to ensure it is always properly initialized and subsequent launches of your plugin do not interfere with traces of the previous launch.

In general it is — even if one doesn’t like to say it — better to take the time to learn more of the basics of the technology (Ruby execution flow, JS, synchronous vs. asynchronous, callbacks, classes, variable types) you are using before attempting to go towards a possibly commercial product.


#15

(a) UI::WebDialog can take a hash for it’s constructor method.
I think all the keys are the same. Any of the extra keys used by UI::HtmlDialog will just be ignored by the UI::WebDialog constructor.


(b) If you need to concatenate strings with a pipe delimiter try them from an array and then

opts_str = option_array.join('|')

(c) (I’ve likley told you this before, …)
If you keep your options in hashes, then it is SO MUCH easier to deal with them …

win_opts_str = @window_options.values.join('|')
js_command = "pushdata_adv( '#{win_opts_str}' );"

Hashes can be saved out and restored upon startup using simple loops.

Hashes can be simply converted to and from JSON strings, which can be easily used by BOTH Ruby and Javascript. (And can also be easily saved out to a file and upon startup reloaded and simply restored to a Ruby hash or OpenStruct.)

When ever you see yourself using the same variable prefix over and over (ie, "@Win_") it is a big clue that they all should be in a hash together. Ie… @Win_Opts[:header_width], @Win_Opts[:jamb_width], etc.

win_opt_str = @Win_Opts.values.map { |opt|
  opt.is_a?(Length) ? opt.mm.to_s : opt.to_s 
}.join('|')

The power of an enumerable collection (of data) to reduce the code you have to write cannot be overstated !

Ie, you still are insisting on doing things in the most difficult, verbose, tedious manner.

Amen.


#16

I’ve always insisted I am not programmer, just a man on a mission.

Thank-you for that little nugget of code in (b) and in ©, why didn’t anyone ever suggest this before, that will save me some serious time in assembling these strings and pushing the data over to the html.

Originally when I wrote this chunk of code (probably 2 years ago), I had a handful of parameters needing to be passed, a hash or structure seemed like serious overkill. I agree it is time to upgrade some of this obsolete code.

In other parts of my plugin where I’ve recently started from scratch things are a lot more efficient with hashes and multi-dimensional arrays being the norm,so yes I am fully aware of the power of such structures and without them some of the more complex parts of my code would simply not work.

I guess I’m a creature of habit and unless someone tells me otherwise I won’t fix that which isn’t broken, until it is truly broken.

Generally I don’t like to be picked apart or expose any of my code to the public but in this case some criticism is well worth the knowledge I have just gained. This is why I keep coming back to this forum, slowly but surely my code is tightening up and getting more efficient and less prone to error, the biggest gain however is being able to get more done while working less.


#17

I’ve only used jQuery sporadically in some of my other web based tools, but I will take a more serious look at this library as it appears to have a number of very useful features. I’ve tended to a be a JS purist, relying solely on native javascript for most of my web based code but I’m always willing to look at adding tools to the toolbox.

Breaking the code up into nice little reusable chunks (Methods) does appeal to me. My recent coding trend is to do exactly that, keep the variables local, pass only the data needed into the method, break things up into lots of modular blocks which can be utilized throughout the program. When it comes to creating lots of geometry this sort of methodology is really the only way to go.

When you compare the wall plugin code versus my older truss plugin, it is like night and day. At some point I will need to go back into the truss plugin and rewrite a significant amount of that code just to bring it up to par.


#18

This piece of code appears to be an efficient way of doing it but the problem I am seeing is that once I bring it into javascript I’ve got a string with all my parameters in random order, so I don’t know where anything is, correct me if I’m wrong.


#19

For the record I did via a very verbose PM critique of another plugin in DEC of 2017. (But it likely got lost within too much info at one time.)

It is actually a simple example, but I’d encourage you to instead use JSON.


Going between Ruby Hash, JSON strings and SketchUp attribute dictionaries …

And the most comprehensive of my posts on going from Ruby hashes and arrays --> JS Objects using JSON strings …


Example of Ruby array to JS array:

So basically … from the Ruby-side …

my_ary = [[1,2,3],[4,5,6],[7,8,9]]
html_dlg.execute_script("Data = JSON.parse('#{my_ary.to_json}');")

… and afterward Data is a JS reference for a nested JS array.
(Note you may need to declare Data as a global var set to null first.)


#20

Correct. But see the updates to the previous post.

Hashes enumerate their values in the order that the corresponding keys were inserted.

The randomness is worse in pre-2 Ruby.

You can ensure that values are returned in the sort order of keys by doing this …

key_sorted_values = some_hash.keys.sort.map {|key| some_hash[key] }

Note that calling some_hash.rehash does not help.

When I store my hashes between sessions, I write them out in sorted order, so that they’ll be loaded in order next time.

You could also replace the hash with a sorted one …

new_sorted_hash = {}
old_hash.keys.sort.each {|key| new_sorted_hash[key]= old_hash[key] }

But that is unneeded work if you instead rely upon JSON (as per links above.)