# Copyright 2014-2021, Trimble Inc.
#
# License: The MIT License (MIT)
require "sketchup.rb"
require "stringio"
module Trimble
module AttributeHelper
PLUGIN = self
class << self
attr_reader :app_observer
attr_reader :model_observer
attr_reader :selection_observer
end
def self.visualize_selected
content = self.traverse_selected
html = self.wrap_content(content)
options = {
:dialog_title => "Attribute Visualizer",
:preferences_key => 'AttributeVisualizer',
:scrollable => true,
:resizable => true,
:height => 300,
:width => 400,
:left => 200,
:top => 200
}
@window ||= UI::WebDialog.new(options)
@window.set_html(html)
@window.set_on_close {
@window = nil
self.detach_observers
}
unless @window.visible?
@window.show
self.attach_observers
end
end
def self.attach_observers
@app_observer ||= AppObserver.new
@model_observer ||= ModelObserver.new
@selection_observer ||= SelectionObserver.new
model = Sketchup.active_model
Sketchup.remove_observer(@app_observer)
model.remove_observer(@model_observer)
model.selection.remove_observer(@selection_observer)
Sketchup.add_observer(@app_observer)
model.add_observer(@model_observer)
model.selection.add_observer(@selection_observer)
end
def self.detach_observers
Sketchup.remove_observer(@app_observer)
Sketchup.active_model.remove_observer(@model_observer)
Sketchup.active_model.selection.remove_observer(@selection_observer)
end
def self.traverse_selected
html = StringIO.new
model = Sketchup.active_model
selection = model.selection
if selection.empty?
if model.active_path.nil?
entity = model
else
entity = model.active_path.last
end
else
return "Invalid selection size" unless selection.size == 1
entity = selection[0]
end
html.puts "
#{self.escape_html(entity)}
"
if entity.respond_to?(:name)
html.puts "#{self.escape_html(entity.name)}
"
end
if entity.attribute_dictionaries
entity.attribute_dictionaries.each { |dictionary|
html.puts self.format_dictionary(dictionary)
}
else
html.puts "No dictionaries"
end
if entity.is_a?(Sketchup::Group)
definition = entity.entities.parent
elsif entity.is_a?(Sketchup::ComponentInstance)
definition = entity.definition
else
definition = nil
end
if definition && definition.attribute_dictionaries
html.puts "#{self.escape_html(definition)}
"
html.puts "#{self.escape_html(definition.name)}
"
definition.attribute_dictionaries.each { |dictionary|
html.puts self.format_dictionary(dictionary)
}
end
html.string
end
def self.format_dictionary(dictionary, path = "")
html_name = self.escape_html(dictionary.name)
path = "#{path}:#{html_name}"
html = StringIO.new
html.puts ""
html.puts "#{html_name}"
html.puts ""
dictionary.each { |key, value|
html_key = self.escape_html(key)
html_value = self.escape_html(value)
node_path = "#{path}:#{html_key}"
html.puts "| #{html_key} | #{html_value} | #{value.class} |
"
}
if dictionary.attribute_dictionaries
dictionary.attribute_dictionaries.each { |sub_dic|
html.puts "| "
html.puts self.format_dictionary(sub_dic, path)
html.puts " |
"
}
end
html.puts ""
html.puts "
"
html.string
end
def self.escape_html(data)
data.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">")
end
def self.wrap_content(content)
html = <<-EOT
#{content}
EOT
end
class SelectionObserver < Sketchup::SelectionObserver
def onSelectionAdded(selection, element)
selection_changed()
end
def onSelectionBulkChange(selection)
selection_changed()
end
def onSelectionCleared(selection)
selection_changed()
end
def onSelectionRemoved(selection, element)
selection_changed()
end
private
def selection_changed
PLUGIN.visualize_selected
end
end # class SelectionObserver
class ModelObserver < Sketchup::ModelObserver
def onActivePathChanged(model)
PLUGIN.visualize_selected
end
def onTransactionCommit(model)
model_changed(model)
end
def onTransactionEmpty(model)
model_changed(model)
end
def onTransactionRedo(model)
model_changed(model)
end
def onTransactionUndo(model)
model_changed(model)
end
private
def model_changed(model)
if @timer.nil?
@timer = UI.start_timer(0.0, false) {
@timer = nil
PLUGIN.visualize_selected
}
end
end
end # class ModelObserver
class AppObserver < Sketchup::AppObserver
def onNewModel(model)
observe_model(model)
end
def onOpenModel(model)
observe_model(model)
end
private
def observe_model(model)
model.add_observer(PLUGIN.model_observer)
model.selection.add_observer(PLUGIN.selection_observer)
PLUGIN.visualize_selected
end
end # class AppObserver
unless file_loaded?(__FILE__)
command = UI::Command.new("Attribute Helper") { self.visualize_selected }
command.status_bar_text = "Inspect and edit the attributes of a selection."
menu_name = Sketchup.version.to_f < 21.1 ? 'Plugins' : 'Developer'
menu = UI.menu(menu_name)
menu.add_item(command)
file_loaded(__FILE__)
end
end # module AttributeHelper
end # module Sketchup