Layers vs Pages visibility study -->> mistakes in documentation

-we know: page<>scene & layer<>tag :stuck_out_tongue_winking_eye:.-

Based on @DanRathbun code and sWilliams idea I have made a dirty plugin for changing several properties of layers at once.

Some more background info

When I’m doing the opacity changes of the layer, in reality, I’m adding new layer with required opacity and “moving” all entities to that new layer and deleting the original layer, then renaming back to original name…Since I want to “inherit” the original visibility of the layers per page I have to store and restore these properties…

Now I’m facing some problems with the documented and real situation.
So, I made some study and concluded that the Ruby API documentation is not correct:
Specifically, this: Page#layers instance method

However, if you read another part of the documentation like:
Layer#page_behavior instance method
You may start to suspect what the situation is…

At first: If a user switched off to save layers visibility aka if page#use_hidden_layers? is false
then page#layers method returns nil. NOT documented!

At secondly: Otherwise page#layers method returns non-visible layers /array with zero or more layers objects/ but, wrongly documented! (You can use this ‘term’ only if page_behavior has not been ‘touched’.)
As my study is colluded: actually, it will return array containing these layers if layer page_behavior ‘flag query’ / LAYER_HIDDEN_BY_DEFAULT / different than default page visibility state (behavior) for that page. I hope this is the right wording and situation. (Note: My mother never talked to me in English.)

As an excuse the SU Ruby API documentation of layer#page_behavior saying: “A page keeps a list of layers that do not have their default behavior.”

Here is the snippet what I have been used and improved during the study:

# -----------
# test in SU2019.3 (2020.02.22) @Dezmo
# I have 3 scenes and Layer0 Layer1 Layer2 in a model
# Layer2 for reference: it is hidden on all scenes and no any changes on it during test
# -----------
#
# This part is my "concluded" method to get if layer hidden or not on the page
def hidden_on_page?(layer, page)
  # If a user switched off to save layers visibility aka
  # if page#use_hidden_layers? is false then page#layers method returns nil (NOT documented!)
  # else #layers method returns non-visible layers /array with zero or more layers objects/
  #      but (WRONGLY documented!) you can use this 'term' only if page_behavior has not been 'touched'.
  #    So: actually it will return array containing this layer if layer page_behavior 'flag query'
  #       / LAYER_HIDDEN_BY_DEFAULT / different than default page visibility state (behavior) for that page.
  #              Easy, isn't it? (Note: My mother never talked to me in English. ;-) )
  # As an excuse the SU Ruby API documentation of layer#page_behavior saying:
  # "A page keeps a list of layers that do not have their default behavior."
  if page.use_hidden_layers?
    return page.layers.include?(layer) == !hidden_by_default?(layer)
  else
    #this is the only visibility I can query if not stored in scene
    return !layer.visible?
  end
end
#check if somebody changed the default page_behavior
def hidden_by_default?(layer)
  ( layer.page_behavior & LAYER_HIDDEN_BY_DEFAULT ) == LAYER_HIDDEN_BY_DEFAULT
end

#Testing Get/set a layer's visibility / page behavior
#parameters:
#   layer_name_or_index: the layer name or index what you want to test/query (default1) or nothing
#   (I'm lazy) set: new page_behavior if you want to set ( 0, 1, 16, 17, 32, 33 ) or nothing
#prints out to console:
#   the detailed page_behavior  (five flags + integer)
#   the result array or nil of page#layers method (for scene 0,1,2)
#   my "concluded" (aka real) hidden state of layer on the page (for scene 0,1,2)
#usage e.g.: vis_behav  or  vis_behav 1  or  vis_behav "Layer1", 1 ...etc.
def vis_behav(layer_name_or_index=1, set=false)
  flags = [ LAYER_VISIBLE_BY_DEFAULT,
            LAYER_HIDDEN_BY_DEFAULT,
            LAYER_USES_DEFAULT_VISIBILITY_ON_NEW_PAGES,
            LAYER_IS_VISIBLE_ON_NEW_PAGES,
            LAYER_IS_HIDDEN_ON_NEW_PAGES
          ]
  layers = Sketchup.active_model.layers
  layer = layers[layer_name_or_index]
  pages = Sketchup.active_model.pages
  
  # during testing layer visibility changed manually on one scene
  # this is just update that current page saving some clicks...
  pages.selected_page.update if !set
  
  # conditionally set page_behavior
  layer.page_behavior = set if set
  # update all pages to get effect of page_behavior
  pages.each(&:update) if set

  # if #use_hidden_layers? is false then #layers method                     returns nil !!
  layers_hidden_onpage0 = (pages[0].layers)? pages[0].layers.map(&:name) : pages[0].layers
  layers_hidden_onpage1 = (pages[1].layers)? pages[1].layers.map(&:name) : pages[1].layers
  layers_hidden_onpage2 = (pages[2].layers)? pages[2].layers.map(&:name) : pages[2].layers
  
  #query page_behavior
  behavs = []
  flags.each_with_index { |flag, i|
    behavs[i] = (layer.page_behavior & flag ) == flag
  }
  #Just to see the integer 
  behavs<<layer.page_behavior
  
  # printouts
  puts "#{layer.name} page_behavior: #{behavs}"
  puts
  puts "layers_hidden_on page0 #{layers_hidden_onpage0}"
  puts "#{layer.name} hidden_on page0 #{hidden_on_page?(layer, pages[0])}"
  puts
  puts "layers_hidden_on page1 #{layers_hidden_onpage1}"
  puts "#{layer.name} hidden_on page1 #{hidden_on_page?(layer, pages[1])}"
  puts
  puts "layers_hidden_on page2 #{layers_hidden_onpage2}"
  puts "#{layer.name} hidden_on page2 #{hidden_on_page?(layer, pages[2])}"
  puts "-------------------------------------"
end
vis_behav

I leave the suggestion on how to change the documentation to those who are better in English and in Ruby. :blush:
Sure, there can be mistakes and possible improvements…
Of course, any comments or corrections are welcome. :raising_hand_man:t4:

We also discussed this situation in the API Issue Tracker …


Julia is two months ahead of you …

And I logged this …

1 Like

Oops. I did not follow github :blush: (bookmarked now…)

I’m glad to see if Julia (@eneroth3) thinks similar than me.
However she’s method will fail if page#use_hidden_layers? is false

I experimented further …
Specially I find Layer#page_behaviora little over-complicated…and confused.

First of all the questions:

  1. Does the user have any opportunity (ability) to influence or change Layer#page_behavior at all? I mean in the SU user interface. (I guess not.)
  2. The doc say: “A page keeps a list of layers that do not have their default behavior.
    If a layer does not have his default behavior, than what else he have? How can I determine it? I can query by the 2nd flag (LAYER_HIDDEN_BY_DEFAULT) and if it is true, then that layer will not have his default behavior?? Or what else “does not have his default behavior” is means?
  3. The doc say: The #page_behavior= method is used to control the layer’s visibility behavior on existing and new pages.
    When you Update a page (as opposed to creating a new page) the current visibility of the layer is used.
    So, then what does page_behavior have to do with visibility?
    I concluded in my study that: nothing really directly, it is only to influence the list of layers stored in the page#layers method. Am I right?
    ( …* as opposed to creating a new page* has not been studied yet… but would be nice to get an explanation too …)

According to my current thoughts I have been tuned my method further. Despite the fact that I did not understand everything, It seems it is doing what it’s have to do…

#This part is my "concluded" method to get if layer hidden or not on the page
# @param layer (Sketchup::Layer)
# @param page (Sketchup::Page)
# @return true or false (Boolean)
def hidden_on_page?(layer, page)
  # If a user switched off to save layers visibility aka
  # if page#use_hidden_layers? is false then page#layers method returns nil (NOT documented!)
  # else #layers method returns non-visible layers /array with zero or more layers objects/
  #      but (WRONGLY documented!) you can use this 'term' only if page_behavior has not been 'touched'.
  #    So: actually it will return array containing this layer if layer page_behavior 'flag query'
  #       / LAYER_HIDDEN_BY_DEFAULT / different than default page visibility state (behavior) for that page.
  #              Easy, isn't it? (Note: My mother never talked to me in English. ;-) )
  # As an excuse the SU Ruby API documentation of layer#page_behavior saying:
  # "A page keeps a list of layers that do not have their default behavior."
  if page.use_hidden_layers?
    return page.layers.include?(layer) == !hidden_by_default?(layer)
  else
    #'snapshot' if the page is the current...
    return !layer.visible? if page.parent.selected_page == page
    #...otherwise determined by selected_page (current page)
    return hidden_on_page?(layer, page.parent.selected_page)
  end
end
#check layer#page_behavior by 2nd flag
# @param layer (Sketchup::Layer)
# @return true or false (Boolean)
def hidden_by_default?(layer)
  ( layer.page_behavior & LAYER_HIDDEN_BY_DEFAULT ) == LAYER_HIDDEN_BY_DEFAULT
end

Can anyone give an advice, guidance, comment?

This isn’t necessarily a bad thing. I’d rather have it raise an exception when making such a call than returning an arbitrary value for what is undetermined. If you check if a layer is hidden or not in a scene that doesn’t save layer visibility, there is probably a to call to use_hidden_layers? missing previously in your code.

1 Like

Sure not. :slight_smile:
The bad thing is: page#layers method returns nil is NOT documented!
image
So, something like this

The layers method retrieves array containing these layers if layer page_behavior ‘flag query’ / LAYER_HIDDEN_BY_DEFAULT / different than default page visibility state (behavior) for that page
Returns:

  • layers - an array with zero or more Layers objects.
  • or nil if page#use_hidden_layers? is false

__

First I have to determine if a layer is hidden or not : this is my goal with the study.
Save it and restore will be more easy afterwards, i guess… :slight_smile:

BTW use_hidden_layers? has been “hidden” there:

# if #use_hidden_layers? is false then #layers method                     returns nil !!
  layers_hidden_onpage0 = (pages[0].layers)? pages[0].layers.map(&:name) : pages[0].layers
  layers_hidden_onpage1 = (pages[1].layers)? pages[1].layers.map(&:name) : pages[1].layers
  layers_hidden_onpage2 = (pages[2].layers)? pages[2].layers.map(&:name) : pages[2].layers

However, There a lots of things are missing in my code above…It is just a kind “of study brute-force” method to check how SU is react …
As you see my post above yours, I have plenty of other confusions… :blush: Any advice?