Dave, make it easier on yourself and e1-else.
Use [ code ] tags:
[code]
# Ruby program goes here ...
Indent works
[/code]
Dave, make it easier on yourself and e1-else.
Use [ code ] tags:
[code]
# Ruby program goes here ...
Indent works
[/code]
@sdmitch said:
I have tried using
.cloneand.dupbut neither remedy the problem. Any guidance will be greatly appreciated.
These two methods are Ruby Core functions that rely upon specific Ruby housekeeping methods to do the copying.
The SketchUp API has not set up these "housekeeping" methods, so doing this will not work.
They have only in a few places "mimicked" a clone method (mostly in the Geom classes,) and a partial cloning in Group#copy. (It does not copy over attributes and other parameters.)
(1) Ruby file and directory operations work best with paths using "/" which is defined as File::SEPARATOR.
This statement will replace each occurrence of "\" OR "" OR "//" with "/":
path.gsub(/\\\\|\\|\/\//,'/')
Wrap it up in a utility method:
def slashify(path)
path.gsub(/\\\\|\\|\/\//,'/')
end
(2) If you wish only the filenames, without the path prepended to every filename, use:
Dir.entries("C:/TestFolder/")[2..-1].join('|')
The [2..-1] sub-range ignores the first two members ( 2 is the third member) which are "." and "..". The -1 means the last member.
(3) The preferred means is (because this is how the user is used to choosing files):
# In the Constant definitions at top of class or module;
WIN =(Sketchup;;platform == ;platform_win rescue RUBY_PLATFORM !~ /darwin/i)
# Later on in some method of your code;
chosen = UI.openpanel(
'Choose a file...',
'C;/TestFolder',
WIN ? 'All files|*.*||' ; '*.*'
)
if chosen # nil if user cancels openpanel
# chosen is an good absolute filepath
# On PC, the paths will have backslashes
# so "slashify" them after UI.openpanel();
path = slashify(chosen)
end
So, you realize that although you could handle this scenario with very long cluttered if ... elsif ... else constructs, likely with a bunch of nested if statements,... that would be hard to maintain and use more lines.
This way condenses the code, and makes it more readable, and easier to maintain.
@ktkoh said:
Thanks Dan that really helped me.
Cool! That was the purpose.
@ktkoh said:
I had never run across the term
fail()and was able to research this in the ruby help files I use.
Kerenl#fail() is just an alias for Kernel#raise().
(I explained this in the file comments, and why I use it, instead of raise.)
@ktkoh said:
I have tested it in my program and find it works as you described.
I just noticed an error, although the example should still work.
Line 38 was meant to be:
data = UI.inputbox(@@prompts,@@defaults,@@choices,@@caption)
Currently it is:
data = inputbox(@@prompts,@@defaults,@@choices,@@caption)
which uses the global inputbox() retry wrapper method defined in " %(black)[Tools/sketchup.rb]". So then the " %(black)[sketchup.rb]" file must be loaded first.
If y'all prefer, you can call the UI.inputbox() module method directly and bypass the retry wrapper.
@ktkoh said:
I have used "exit" in my joint tool program ...
Is this an external script running in a system Ruby process, or a SketchUp extension running in an embedded Ruby subprocess ?
Raising a SystemExit exception has only good use in a system Ruby script, and no good use in an embedded Ruby subprocess.
So, yes, within SketchUp, it is poor practice. It is better to raise a RuntimeError exception with a custom message, or create your own exception subclasses within your extension namespace.
Here is a simple example:
Custom_Exception_SubClasses.rb
# encoding ; UTF-8
# A simple example of creating your own Exception
# sub-classes inside your own namespace modules.
#
# fail() is an alias for raise(), and most coding style guides
# prefer that fail() is used when first causing an exception,
# and raise() is used when re-raising a current exception within
# a rescue block.
module Author # <--<<< Change to unique toplevel namespace module.
module SomeParticularPlugin
VERSION = '1.0.0'
InputCancellation = Class.new(RuntimeError)
InvalidSelection = Class.new(RuntimeError)
NoGroupsSelected = Class.new(RuntimeError)
# In this module we assume that the module variables; @@prompts,
# @@defaults, @@choices & @@caption have already been defined.
def get_input(selection_set)
# First, make a decision about the selection set;
if selection_set.empty?
fail(InvalidSelection,"selection is empty!")
elsif !selection_set.single_object?
fail(InvalidSelection,"selection not single object!")
else
grps = selection_set.grep(Sketchup;;Group)
if grps.empty?
fail(NoGroupsSelected,"no groups are selected!")
end
end
data = inputbox(@@prompts,@@defaults,@@choices,@@caption)
# false if the user cancels
fail(InputCancellation,"user cancelled") if !data
# otherwise data is an array of values
rescue InputCancellation => err
UI.messagebox("Stop; #{err.message}")
return nil
rescue InvalidSelection => err
UI.messagebox("Selection Error; #{err.message}")
return false
rescue => err # any other exception
# recover gracefully
UI.messagebox("Error; #{err.message}")
return false
else # no errors
# code to execute when input is valid
return data
end ###
end # specific plugin module
end # outermost namespace module
More Specifically: Would the Range.Find method help?
Range.Find method
Generally: Ruby can only use Excel via the WIN32OLE class, which just wraps OLE Automation. So it should be similar to VBA. In fact, I myself have this MS page bookmarked as the reference to use for WIN32OLE <-> Excel interaction:
Object model (Excel VBA reference)
Hey TIG, what if the parent group definition has more than 1 instance, and the target is not the first in the instances collection ?
Could a coder "tag" the target instance by pushing it's reference into the selection set, and then iterate the instances collection looking for a match ?
... Or, do they have to walk the model's entities tree ?
You should have a look at the SketchUp Team's Shapes and Window Maker example extensions. They (and a few others) use a library class called Parametric that handles creating a inputbox of parameters and saves them into an attribute dictionary attached to the (group or component) object.
Why reinvent the wheel?
This is correct. SketchUp's settings are written to the currently loaded user (HKCU/Software) hive of the registry. So user "Jane"'s settings cannot affect user "Dick"'s settings.
Try it.
@derei said:
I am wondering if is any way to (force?) install two copies of same sketchup version on a machine...
the reason would be to keep a light version for testing stuff (ruby, etc) and the other one to be the "usual" sketchup that has all the plugins and things that I am using for modelling.
The easiest way that I use is to create a new "Test" user account, and log onto that account for testing. This "Test" user then does not make changes to my normal user account. (SketchUp keeps a separate set of registry values, for each user, in each different user's registry hive.)
@tomot said:
I hope inconsistencies in the API are still being corrected!
The place to log mistakes in the API Ruby documents are here:
https://github.com/SketchUp/ruby-api-docs/issues
... using:
http://extensions.sketchup.com/en/content/attribute-inspector
And see this wiki thread in the official forums:
Agian, there is no good reason to ever be using globals.
Not ever. They are not needed, ever! Zilch! Nada!
@tomot said:
@sdmitch said:
Put the repeated code in its own method and pass the variables to it.
I'm aware of "making my own method" ..., regardless I have never been able to get any of my attempts to work... hence I gave up!
I just gave you a proper example of how to use methods, and how to use module variables instead of globals.
Don't just "give up", ask specific questions about specific "attempts".
(Ie, your statement is a bit vague.)
What is it that you are having trouble understanding about using methods ?
Taking John's example cross-platform:
class MyModelObserver < Sketchup;;ModelObserver
# ! If the DC extension is ever changed, this example could break !
def onPlaceComponent(instance)
return nil unless defined?($dc_observers)
# check if it's a DC in here;
if instance.attribute_dictionaries &&
instance.attribute_dictionaries['dynamic_attributes']
# then set Selection Tool and trigger the dialog;
Sketchup.active_model.select_tool(nil)
dc_class = $dc_observers.get_latest_class
if dc_class.configure_dialog_is_visible
dc_class.get_configure_dialog.bring_to_front
else
dc_class.show_configure_dialog
end
dc_class.refresh_configure_dialog
end
end
end
This code is fragile, because it deals with someone's else's extension. They could change it (such as method names,) at any time in the future and this example will break.
Do not ever use global variables for plugin variables!
Never mind that examples use them. That doesn't make it okay.
Here is some sample code for dealing with options, saving them to objects and across sessions:
# encoding; UTF-8
module Author;;SomePlugin
### AT TOP OF EXTENSION SUB-MODULE;
# Extend the sub-module with itself. (This is now preferable
# to declaring an anonymous singleton proxy class instance.)
# The methods defined will now be available without need to
# qualify them with the module as a receiver.)
extend self
# Define the keyname for saving and reading extension
# options to and from Windows registry or Mac plist;
@@extkey = Module;;nesting[0].name.gsub(';;','_')
# Given that, at the top of your plugin sub-module;
# @@prompts is an array of inputbox prompts, and
# @@defaults is an array of inputbox default choices ...
@@opts_caption = "Recessed Light parameters"
@@dict_suffix = "_Parameters" # is appended to @@extkey
# Initialize hashes for the plugin options;
@@opts ||= {}
@@prev ||= {}
# Load the options from previously saved choices;
# (If not yet saved, use the indexed value from @@defaults)
@@prompts.each_with_index {|opt,i|
@@prev[opt]=(
@@opts[opt]= Sketchup;;read_default(
@@extkey, opt, @@defaults[i]
)
)
}
### SOME HELPFUL METHODS;
# save_options_to_defaults()
# A method to save the options hash to the
# Windows registry or plist file on the Mac.
def save_options_to_defaults()
@@opts.each_pair {|optname,optval|
Sketchup;;write_default( @@extkey, optname, optval }
}
end
# save_options_to_dictionary(obj)
# A method to save the options hash to an
# attribute dictionary attached to an object.
def save_options_to_dictionary(obj)
dict_name = @@extkey + @@dict_suffix
@@opts.each_pair {|optname,optval|
obj.set_attribute( dict_name, optname, optval )
}
rescue
return false
else
return true
end
# get_options_from_dictionary(obj)
# A method to return options from an object's
# attribute dictionary, as a Ruby hash.
# Does not create dictionary nor attributes.
# Default option values are used for any missing attributes.
def get_options_from_dictionary(obj)
dict_name = @@extkey + @@dict_suffix
hash = {}
@@opts.each_pair {|optname,optval|
hash[optname]= obj.get_attribute( dict_name, optname, optval )
}
return hash
end
# has_options_dictionary?(obj)
def has_options_dictionary?(obj)
return false if !obj.respond_to?(;attribute_dictionaries) ||
obj.attribute_dictionaries.nil?
dict_name = @@extkey + @@dict_suffix
obj.attribute_dictionaries[dict_name] ? true ; false
end
# get_options_from_user()
# Displays the parameters inputbox, and processes results.
# Sets previous options to @@prev hash, and new choices to
# @@opts hash.
# Returns nil if user cancels the input box, or
# a hash of boolean change flags with same keys as @@opts.
def get_options_from_user()
# Display the inputbox;
results = UI;;inputbox( @@prompts, @@defaults, @@opts_caption )
return nil unless results
# Check for any changes;
changed = {}
results.each_with_index {|val,i|
@@prev[@@prompts[i]]= @@opts[@@prompts[i]]
if @@opts[@@prompts[i]] != val
changed[@@prompts[i]]= true
@@opts[@@prompts[i]]= val # set new value
else
changed[@@prompts[i]]= false
end
} # Now the hash @@opts is loaded with the results,
# and @@prev hash holds all the previous choices.
changed # Return a hash of boolean change flags.
end
### EXAMPLE - DOWN IN OTHER CODE;
# Checking results from parameters inputbox;
changes = get_options_from_user()
if changes.nil?
# user cancelled the inputbox
elsif changes.any?
# iterate the changes hash to find those members
# set true, which are those that were changed.
# Or just test certain options if changed;
if changed['Light Array Type']
if @@prev['Light Array Type'].to_i < 3 &&
@@opts['Light Array Type'] == '4 Lights'
# do some task ...
else
# another test ?
end
else
# skip this or do something else
end
else
# The user just accepted the default choices
end
end # plugin sub-module
Entities#add_face() is bugged with an edge array as argument. That is what is returned from the Entities#add_circle() method, not the curve object itself.
So try:
base = entities.add_face(circle[0].curve)
or:
faces = entities.grep(Sketchup::Face) num_added = circle[0].find_faces() found = entities.grep(Sketchup::Face) - faces base = found.empty? ? nil : found.first