In my usual study, the question arose as to how the order of the layers and folders in the user interface could be replicated? ([Github] )
Since the version of 2018(?), the layer names are sorted naturally on the UI.
(The layer names on SU2017 ordered as e.g.: 1, 11, 22, 3 on later versions shown as 1, 3, 11, 22)
In Ruby the “normal” sort
method will give us the former. I was curious how can we got the natural?
But actually sorting is a bit more complicated when you want to consider the parent-child folder-layer structure.
So I wrote a code snippet that is suitable for giving a kind of answer to the above questions.
The code will create a an array of hashes (with a nested array of hashes, if relevant) to represent the structure, then print it to the console and open a HtmlDialog with a simple table.
While folders don’t have much sense in older versions, the code is still compatible with the 2017 version.
Notes:
-
I’m using natcmp.rb natural order comparison of two strings, please see the copyright notes inside the code.
-
I’m also using Tabulator to display the result. I love it!
(Tabulator directly loaded from the UNPKG CDN servers, so first run could be slow. I usually give it packaged with the extension.)
-
I’m not sure what will influenced (with the sorting) if the the layer folder names are same…but please be aware of notes from documentation of sort method ( The result is not guaranteed to be stable. When the comparison of two elements returns
0
, the order of the elements is unpredictable.) -
The provided code just quickly tested on SU2017 and SU2021.1 on Windows, and does not have a special purpose, just for fun and for learning. Use your own risk!
-
Other sources: Sketchup::Layer, Sketchup::LayerFolder, Sketchup::Layers
Sorry for the crazy names of layers…
Code
Dezmo_test_sort_layers.rb (6.8 KB)
# encoding: UTF-8
# <!-- SortLayers Copyright 2021, Dezmo -->
require 'pp.rb'
require 'json.rb'
module Dezmo
module TestSortLayers
extend self
SUVER200 ||= Sketchup.version.to_f >= 20.0
SUVER210 ||= Sketchup.version.to_f >= 21.0
LAYER0 ||= SUVER200 ? "Untagged" : "Layer0"
def build_layers_hierarchy(layers)
if !SUVER210 || layers.count_folders == 0
layers_root = []
layers.each{|layer|
layers_root<<layer_hash(layer)
}
return sorter(layers_root)
else
folders_hierarchy = []
layers.each_folder{|folder|
folders_hierarchy<<folder_hash(folder)
}
layers_root = []
layers.each_layer{|layer|
layers_root<<layer_hash(layer)
}
end
sorter(folders_hierarchy) + sorter(layers_root)
end
def layer_hash(layer)
layer_hash = {}
layer_hash[:name] = SUVER200 ? layer.display_name : layer.name
layer_hash[:object] = layer
layer_hash
end
def folder_hash(folder)
folder_hash = {}
folder_hash[:name] = folder.name
folder_hash[:object] = folder
if folder.folders[0] || folder.layers[0]
folder_hash[:_children] = []
folder.each_folder{|c_folder|
folder_hash[:_children]<<folder_hash(c_folder)
}
layers_children = []
folder.each_layer{|layer|
layers_children<<layer_hash(layer)
}
folder_hash[:_children] = sorter(folder_hash[:_children]) + sorter(layers_children)
end
folder_hash
end
def sort_layers
model = Sketchup.active_model
layers = model.layers
layers_h_natsort = build_layers_hierarchy(layers)
layers_h_UIsort = layers_h_natsort.unshift(
layers_h_natsort.delete_at(layers_h_natsort.index{ |h|
h[:name] == LAYER0
})
)
puts
puts "Layers hierarchy:"
pp layers_h_UIsort
display_result_tatulator(layers_h_UIsort)
nil
end
def sorter(array)
array.sort{ |a,b| natcmp(a[:name], b[:name], true) }
# On SU 2017 to match to UI:
# array.sort{ |a,b| a[:name]<=> b[:name] }
end
def display_result_tatulator(array)
json = array.to_json
html = %{
<!DOCTYPE html>
<html>
<head>
<link href="https://unpkg.com/tabulator-tables@4.9.3/dist/css/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="https://unpkg.com/tabulator-tables@4.9.3/dist/js/tabulator.min.js"></script>
<style type='text/css'>
body{
font:100% sans-serif, Helvetica, Arial, Segoe UI;
}
</style>
</head>
<body>
<div id="example-table"></div>
<script>
var tabledata = JSON.parse('#{json}');
var table = new Tabulator("#example-table", {
data:tabledata,
autoColumns:true,
headerSort:false,
dataTree:true,
dataTreeStartExpanded:true,
});
</script>
</body>
</html>
}
@dialog = UI::HtmlDialog.new(
:dialog_title => "Layers hierarchy test © Dezmo 2021",
:preferences_key => "Dezmo_example_Layers_hierarchy",
:width => 600,
:height => 600,
:left => 100,
:top => 100,
:style => UI::HtmlDialog::STYLE_DIALOG
)
@dialog.set_html(html)
@dialog.show
end
#The method below is not mine, but I made a little changes to adapt
# see: "...by dezmo" comments
# downloaded from below mentioned link
# ############################################################### #
# natcmp.rb
#
# Natural order comparison of two strings
# e.g. "my_prog_v1.1.0" < "my_prog_v1.2.0" < "my_prog_v1.10.0"
# which does not follow alphabetically
#
# Based on Martin Pool's "Natural Order String Comparison" originally written in C
# http://sourcefrog.net/projects/natsort/
#
# This implementation is Copyright (C) 2003 by Alan Davies
# <cs96and_AT_yahoo_DOT_co_DOT_uk>
#
# This software is provided 'as-is', without any express or implied
# warranty. In no event will the authors be held liable for any damages
# arising from the use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not
# claim that you wrote the original software. If you use this software
# in a product, an acknowledgment in the product documentation would be
# appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.
## #class String # commented out by dezmo
# 'Natural order' comparison of two strings
# def String.natcmp(str1, str2, caseInsensitive=false) # commented out by dezmo
def natcmp(str1, str2, caseInsensitive=false) # new by dezmo
str1, str2 = str1.dup, str2.dup
compareExpression = /^(\D*)(\d*)(.*)$/
if caseInsensitive
str1.downcase!
str2.downcase!
end
# Remove all whitespace
str1.gsub!(/\s*/, '')
str2.gsub!(/\s*/, '')
while (str1.length > 0) or (str2.length > 0) do
# Extract non-digits, digits and rest of string
str1 =~ compareExpression
chars1, num1, str1 = $1.dup, $2.dup, $3.dup
str2 =~ compareExpression
chars2, num2, str2 = $1.dup, $2.dup, $3.dup
# Compare the non-digits
case (chars1 <=> chars2)
when 0 # Non-digits are the same, compare the digits...
# If either number begins with a zero, then compare alphabetically,
# otherwise compare numerically
if (num1[0] != 48) and (num2[0] != 48)
num1, num2 = num1.to_i, num2.to_i
end
case (num1 <=> num2)
when -1 then return -1
when 1 then return 1
end
when -1 then return -1
when 1 then return 1
end # case
end # while
# Strings are naturally equal
return 0
end # natcmp
# end # class String # commented out by dezmo
end# module TestSortLayers
end# module Dezmo
Dezmo::TestSortLayers.sort_layers
Ps: If someone didn’t know: The API retains the use of “Layer” for compatibility and is synonymous with “Tag”.