What tells you that there is something wrong? At the moment this code has little to do with serialization but rather with tree traversal.
- Do you have a specific question about traversing the SketchUp scene graph?
- What is your use case, do you want to export all types of entities or just specific ones (here you only consider groups)?
So far I can only give some remarks:
-
Put triple backticks ``` on the empty lines before and after your code in this forum to make the forum format it properly.
-
Use
putsinstead ofUI.messageboxto avoid pain in the fingers and mouse abrasion. -
You do not need to construct an array with
Array.new(), just write[]. -
Semicolons are not wrong, but the prefered style is not to use them.
-
The name says nothing about whether an entity is a leaf. The term “leaf” is actually means a node that has no children, so for example a (non-empty) group would never be a leaf.
-
You can iterate
entities.each{…}without first checking how many elements it has. If it is empty, the iteration will finish immediately. Also preferlengthorsizein Ruby, becausecountis often slower (linear complexity). -
You can
require "json"from the standard library provided by SketchUp and then serialize Ruby Hash and Array objects to JSON. -
Conceptionally, tree traversal works like this (here recursive depth-search, pre-order):
def traverse(node)
puts "Visiting node #{node}"
node.children.each{ |child|
traverse(child)
}
end
def main
# Start the traversal.
traverse(root)
end
Transfering this to the SketchUp API, a node is either the model (root) or a group or a component instance. We use a Sketchup::Entities collection to access children.
- A special complexity is that a component definition can have many instances, that means several nodes in the tree are based on the same definition (so this primitive approach would visit the same definition multiple times). Alternatively, one can traverse the definitions list.
- Another specialty is that coordinates refer to local coordinate systems (defined by a Geom::Transformation of the parent group or component instance).
def traverse(entity, transformation = IDENTITY)
# Here, do something with this entity. Check its type when calling methods that not all entities have.
# To convert points to global coordinates, transform them with `point.transform(transformation)`
puts("Visiting node #{entity.inspect}")
# If this node can have children, traverse its children.
if entity.is_a?(Sketchup::Model)
entity.entities.each{ |child_entity|
traverse(child_entity, transformation)
}
elsif entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
# Multiply the outer coordinate system with the group's/component's local coordinate system.
transformation *= entity.transformation
entity.definition.entities.each{ |child_entity|
traverse(child_entity, transformation.clone)
}
end
end
def main
# Start the traversal.
traverse(Sketchup.active_model)
end
As an exercise, you can implement a nicer, more reusable variation of the above example, where traverse takes a block &visitor as last argument and calls it in place of puts("Visiting node..."). Another argument passed to the block can be the entity’s instance path.
traverse(Sketchup.active_model){ |entity, transformation, instance_path|
puts("Visiting node #{entity.inspect} at #{instance_path}")
}