Get_attributes has stopped working

attribute
attributedictionary
attributes

#12

do normal DC’s work…

does ‘Component Attributes’ work…

have you updated it since the release of SU v17 [even if you didn’t upgrade]…

there have been other reports of this happening…

the DC rotation methods are often hijacked as well…

john


#13

@eneroth3, it is in the API under Entity…

#get_attribute(dict_name, key, default_value = nil) ⇒ Object

Returns value - the retrieved value

Parameters:
dict_name — The name of an attribute dictionary.
key — An attribute key.
default_value (optional) (defaults to: nil) — A default value to return if no attribute is found.
Returns:
 value - the retrieved value

but I’m sure DC’s mess with it…

john


#14

that is get_attribute which I know about. get_attributes worked like this
entity.get_attributes(“dict_name”)
and returned a hash of keys and values, the keys being the attribute key names and the values being the attribute values

I think I’ll try to write my own version of it although obviously it would be GREAT!! if the API just had. How about it you SketchUp people?


#15

Nope, that’s another method. get_attribute returns the value of a single attribute. get_attributes apperantly reurns the whole dictionary as a hash.

DC does something similar to this.

class Sketchup::Entity
    method get_attributes(dict_name)
        attribute_dictionaries[dict_name].to_h
    end
end

Note that this code isn’t identical. to_h only exists in newer Ruby versions.


#16

yes, my mistake, **attributes ** with the S is the DC version…

I forgot how much I try to avoid running DC because of all the errors it generates, so I

just looked for an example from one of mine and I seem to have just used long hand…

title = model.title
      model.set_attribute('JcB_SS', 'title', title)
      model.set_attribute('JcB_SS', 'face_count', @faces)
      bg = model.rendering_options['BackgroundColor']
      model.set_attribute('JcB_SS', 'bg', bg)
      hl = model.rendering_options['HighlightColor']
      model.set_attribute('JcB_SS', 'hl', hl)
      fc = model.rendering_options['ForegroundColor']
      model.set_attribute('JcB_SS', 'fc', fc)
      ed = model.rendering_options['EdgeDisplayMode']
      model.set_attribute('JcB_SS', 'ed', ed)

      sh = model.shadow_info['DisplayShadows']
      model.set_attribute('JcB_SS', 'sh', sh)
      fv = cam.fov
      model.set_attribute('JcB_SS', 'fv', fv)
      fl = cam.focal_length
      model.set_attribute('JcB_SS', 'fl', fl)
      pr = cam.perspective?
      model.set_attribute('JcB_SS', 'pr', pr)

#and later when the script finishes...
      hl = model.get_attribute('JcB_SS', 'hl')
      sh = model.get_attribute('JcB_SS', 'sh')
      fv = model.get_attribute('JcB_SS', 'fv')
      fl = model.get_attribute('JcB_SS', 'fl')
      pr = model.get_attribute('JcB_SS', 'pr')
      model.rendering_options['HighlightColor'] = hl
      model.rendering_options['ModelTransparency'] = false
      model.rendering_options['DisplaySectionPlanes'] = false
      model.rendering_options['DisplaySectionCuts'] = false
      model.shadow_info['DisplayShadows'] = sh

I guess I was to lazy to write a method at the time…

john


#17

Thank you for all the information. Lucky I haven’t used DC rotate or whatever.
Happy programming!


#18

(Moved to correct category: Developers > Ruby API )

Here is a method that creates a hash internally, then converts it to JSON.
It can be modified and renamed, omit the the call to load the json library, remove the last statement that creates the JSON String object, and replace it with a “return h” statement:

Basically this:

def self.get_attrs( obj, dict_name )
  d = obj.attribute_dictionary(dict_name,false)
  return false if d.nil?
  h = {} # create a hash
  d.each_pair {|k,v| h[k]=v }
  return h
end

#19

You can simulate the scenario of the objects getting such a method,
by defining singleton methods on specific objects:

# In your module or class, use this to add a 
#   hash getter method to specific objects:
def allow_attrs_as_hash( obj )
  if !obj.respond_to?(:get_attributes)
    obj.define_singleton_method(:get_attributes) do |dict_name|
      d = obj.attribute_dictionary(dict_name,false)
      h = {} # create a hash
      d.each_pair {|k,v| h[k]=v } if !d.nil?
      return h
    end
  end
end

Then elsewhere in your code, make your objects respond to a get_attributes call on a case by case basis:

allow_attrs_as_hash( face )
attr_hash = face.get_attributes("My_Dictionary")

Of, course I’d use shorter method names in actual use.

I might instead add a singleton .to_hash method to the specific dictionary objects I need to access in this way.


Example html dialog (suite)
#20

First of all get_attributes returns empty hash, not nil, when the dictionary is missing (yes, it’s a very odd return value but it’s what it does).

Secondly and more importantly I would highly discourage this kind of monkey patching. I don’t even know if it would pass the EW review. If Camlaman were to define this method for the Entity class he is no better than the people who developed DC. Other developers might find the method, thinking it’s a part of the API (which is logical when it’s defined to the API’s namespace) and rely on it only to encounter errors that are hard to detect the day Camlaman’s plugin isn’t installed.

You don’t touch the API classes unless you have a really good reason for it, e.g. Aerilus LaunchUp plugin that hijacks Menu#add_item to know the names and Ruby calls for all menu items added by plugins. If you can manage without such a hack, don’t use such a hack.

EDIT: Sorry, misread the comment. Adding methods directly to the object is much better than monkey patching the class.


#21

I think I’ll just write a method completely internal to my module and replace the ent.get_attributes(“dict_name”) calls with my get_attributes(ent, dict_name). Seems the most straightforward thing to do. I know it means going through my code with a fine tooth comb to replace all the original calls but notepad++ search will find them.

Here is the code
`require 'sketchup’
def get_attributes(entity, attribute_dictionary_name)
return nil unless entity.is_a? Sketchup::Entity
return nil unless attribute_dictionary_name.is_a? String
dictionaries = entity.attribute_dictionaries
return nil unless dictionaries
dictionary = dictionaries[attribute_dictionary_name]
return nil unless dictionary
attribute_hash = {}
dictionary.each{|key, value|
attribute_hash[key] = value
}
return attribute_hash
end #of get_attributes

This tests it:-
model=Sketchup.active_model
definitionList = model.definitions
component_definition = definitionList.add “fms_tempy"
component_definition.set_attribute(“fms_Temp”, “fms_Temp_detail_level”, 13)
component_definition.set_attribute(“fms_Temp”, “fms_Temp_width”, 5.4)
attribs_hash = get_attributes(component_definition, “fms_Temp”)# return the attributes as a hash
puts"attribs_hash is #{attribs_hash}”`

Thank you again everyone for all the super helpful comments!


#22

(1) You need to delimit multiline code in the forum with triple backtick lines, so indents are not stripped (and a coding language name on the first delimiter line to color lex the code.)

The single backtick delimiters can only be used within a single line of forum text.

IE:

```ruby

code here

```

So it should lex like so in the forum:

def get_attributes(entity, attribute_dictionary_name)
  return nil unless entity.is_a? Sketchup::Entity
  return nil unless attribute_dictionary_name.is_a? String
  dictionaries = entity.attribute_dictionaries
  return nil unless dictionaries 
  dictionary = dictionaries[attribute_dictionary_name]
  return nil unless dictionary
  attribute_hash = {}
  dictionary.each{|key, value|
    attribute_hash[key] = value
  }
  return attribute_hash	
end #of get_attributes

(2) You never need any external script / ruby file / library, etc. unless there is a dependency for the functionality of the file.

So the

require 'sketchup'

call is frivolous (but not harmful.)

But if at the top of every single file, (for every extension,) it cumulatively can slow SketchUp’s extension loading as the $LOADED_FEATURES array needs to be checked for each call, and comparing strings in Ruby is slow.

So, if you must, I’d suggest putting it only at the top of the extension registrar script (the one in the “Plugins” folder that registers you extension object. This is the file that will cause all the rest of your extension’s file to be loaded, so there is no more need to subsequently call for the requirement of the “sketchup.rb” file.)


#23

Julia, I’m not sure if you are commenting upon my example, or the original that @Camlaman was referring to, which BTW, I searched for but could not find. (I seem to remember this old method and previous discussions about it. But without seeing it, I cannot say what it returns upon type mismatch or failure.)

With regard to my philosophy, I follow Ruby convention, not incorrect SketchUp API conventions !


My actual implementation, would raise TypeError exceptions instead of how Camlaman’s just returns nil (in his first 2 method lines.)


raise(TypeError,"Sketchup::Entity expected for argument(1)") unless entity.is_a?(Sketchup::Entity)
raise(TypeError,"String expected for argument(2)") attribute_dictionary_name.is_a?(String)

This helps when debugging or some strange situation occurs and the values passed to the method are unexpected types. The exceptions and their messages tell you immediately that the problem is happening before the method call, and not internal to the method itself.

Returning nil in these situations just makes your job as a programmer testing and maintaining the code harder.


Secondly, my personal preference, is to mimic the core Ruby .to_hash and .to_a methods, which are expected to return a collection object (Hash or Array respectively.) If the operation does not produce valid members for the collection, then the resultant returned collection is expected to be and should be empty.
Ie, it should work like Enumerable.find_all().

The coder would expect to call result.empty? upon the return reference.

Yes, I know there are some API calls that return nil in situations when they should have returned an empty collection, and one of the API team members agreed it was unconventional, but also too late to change it now. (It’d break code “in the wild”.)

Example from the API docs:

Entity.attributedictionaries()

Returns:
attributedictionaries - the AttributeDictionaries object associated with the entity, or nil if there are no attribute_dictionary objects associated with the model. Care must be taken if nil is returned, for example: invoking attribute_dictionaries.length will throw a NoMethodError exception, not return 0.

This is what I mean about returning nil unconventionally. It sets up coders for failure in successive statements.

An empty Ruby collection could have been returned from this method instead, so that subsequent getter methods for specific member dictionaries could return nil or methods like length could return 0. (Even if under the hood, no empty collection object were added to the model database. Ie, just like empty groups being cleaned up when the reference goes out of scope.)

There could have been .any_dictionaries?() and .has_dictionary?() boolean query methods to make entities more Rubyish. (I may have even requested such … but I forget all the requests I’ve made over the years. It is normal to have boolean query methods that do not actually make Ruby create object references.)

One of the most common “gotchas” that occurs when too many scenarios return nil on failure, is the ol’ exception:

Error: #<NoMethodError: undefined method `[]' for nil:NilClass>

… or calling some other Hash method upon the result.

When the result is an empty hash, the [] method call returns nil by default when the key used is not in the hash.
I’d rather test for nil at that time against individual member accesses.

But to each his own.

I know Julia, you are going to say, “But it is not a .to_hash() method, it’s a .get_attributes() method and should mimic the APIishness.”

Perhaps, but I would not actually use the name “get_attributes()”, I’d use something more like “get_hash()”.


#24

It is not coming from Dynamic Components:

ent = Sketchup.active_model.selection[0]
meth = ent.method(:get_attributes)
meth.source_location

returns:

".../sketchup/plugins/su_advancedcameratools/advancedcameratools_main.rbs",
line 145

And I see that:

ent.get_attributes("UnusedDictionaryName") 
# returns {}

#25

Thank you for this advice. I added the call to require sketchup.rb because I was just thinking in case the method was made a module method and in a file on it’s own or with other module methods and this file was the first called.

I also agree it would be nicer for my get_attributes method to return something more useful than nil and to be called get_attributes_as_hash so that it was called what it did.

By the way, how do I get ‘triple backtick’ format to happen?


#26

that explains why I get so many random ‘no method errors’ when DC calls ‘get_attributes’…

ACT is almost always turned off…

it must happen on Make as well, unless they needlessly load ACT for the one method…

john


#27

I guess this is how multi line code should be formatted. Hope it comes out right the other end.

I have renamed the method and added your suggested error messages, plus a bit.

Method code

	def get_attributes_as_hash(entity, attribute_dictionary_name)
		raise(TypeError,"Sketchup::Entity expected for argument(1)") unless entity.is_a?(Sketchup::Entity)
		raise(TypeError,"String expected for argument(2)") unless attribute_dictionary_name.is_a?(String)
		dictionaries = entity.attribute_dictionaries
		raise(TypeError,"Sketchup entity failed to return Sketchup dictionaries.") unless dictionaries
		dictionary = dictionaries[attribute_dictionary_name]
		raise(TypeError,"Sketchup dictionaries failed to return a Sketchup dictionary.")  unless dictionary
		attribute_hash = {}
		dictionary.each{|key, value|
				attribute_hash[key] = value
		}
		return attribute_hash	
	end #of get_attributes_as_hash

Test code starts here.

	model=Sketchup.active_model
	definitionList = model.definitions
	component_definition = definitionList.add "fms_tempy"
	component_definition.set_attribute("fms_Temp", "fms_Temp_detail_level", 13)
	component_definition.set_attribute("fms_Temp", "fms_Temp_width", 5.4)
	attribs_hash = get_attributes_as_hash(component_definition, "fms_Temp")# return the attributes as a hash
	puts"attribs_hash is #{attribs_hash}"
	attribs_hash = get_attributes_as_hash("Jim", 13)# raises Error!

Thank you for the help.


#28

On a separate line before your code:
Three backticks then the word ruby (no spaces anywhere). I’m not sure if ruby is case sensitive, but I know that lower case works.

Then your code

Then on a new line, after the code, again with no white space, three backticks.

Again starting on a new line, the next part of your post.


#29

It is not really needed to put the name of the method into the error message,
as Ruby generates a “in #{method_name}:#{line_number}” as the first line of the error backtrace.


#30

Of course you are right. I’ll take the name out. I’m so used to using the puts method for debugging where the name stands out if it is in debug string.


closed #31

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