I am working in a plugin code to export a sketchup file containing a room to other applications. That requires the creation of a text file with a numerical description of the surfaces contained in the room identified by the position of the vertices (or maybe the edges) defining each of the surfaces. I can find all the information I need in the “entities” (i.e. Sketchup.active_model.entities).
My problem is that in order to access these “entities” I need beforehand to explode all Groups and Components of the room, which I am observing is terribly slow for rooms containing a high number of Groups/Components. I am using this code for that:
defs = Sketchup.active_model.definitions
while therearegroupsleft
therearegroupsleft = false
defs.each do |d|
d.instances.each do |e|
if (e.layer.visible?) # NOTE( In this way I only explode groups and Components belonging to visible layers)
if (e.is_a? Sketchup::Group)
e.explode
therearegroupsleft = true
elsif (e.is_a? Sketchup::ComponentInstance)
e.explode
therearegroupsleft = true
end
end
end
end
end
I have the following questions:
1.- Do we need to explode to access the required information? Can I extract somehow from definitions or instances the info I need?
2.- Is this procedure for exploding optimal?, there is a way to use all the computational power of my PC? (maybe parallelize the process or do it in a different way that increases the performance)
Thank you very much for your help, please let me know if you need further information to give me an answer.
Exploding a whole model is not ideal, and for reading information it’s certainly should not be needed. But in order to provide information to what to do instead we need some more information to what the task here is.
I’m going to make a guess to what is going on, are you using model.entities to read the entities of the model? If so, then note that you only get the root level entities via that. In order to traverse the whole model you need to look for groups and components and recursively dig into them.
Something like this:
module Example
def self.do_something
model = Sketchup.active_model
self.read_entities(model.entities)
end
def self.read_entities(entities, transformation = IDENTITY)
entities.each { |entity|
case entity
when Sketchup::Group, Sketchup::ComponentInstance
# Note: Older versions of the SketchUp API did not have .definition for
# groups.
definition = entity.definition
# When you read the positions of vertices inside component definitions
# they are local to the definition. In order to get the global
# coordinates for each vertex in each instance you need to combine
# the transformation to the instance path.
tr = entity.transformation * transformation
# Recurse into child-instances.
self.read_entities(definition.entities, tr)
else
# All other entities...
end
}
end
end
The Ruby API is single process only. You can only call it from the main thread. Explode is in itself very slow. That along with it being a destructive operation makes it something you want to avoid when you really just need to read data from the model. Traversing the model without modifying it will be the fastest you get.
And assume you have a method named export_point() that takes an array of [x,y,z] numerics …
the “all other entities” else clause might look similar to:
else
# All other entities...
case entity
when Sketchup::Edge
export_point(entity.start.position.transform(tr).to_a)
export_point(entity.end.position.transform(tr).to_a)
when Sketchup::Face
entity.loops.each {|loop|
if loop == entity.outer_loop
# this is the outer bounds of the face
else
# this is an inner hole of the face
end
loop.vertices.each {|vertex|
export_point(vertex.position.transform(tr).to_a)
}
}
when Sketchup::Curve, Sketchup::ArcCurve
if entity.is_polygon? # made with PolygonTool
if entity.first_edge.start == entity.last_edge.end
# it is a closed Polygon
end
end
entity.vertices.each {|vertex|
export_point(vertex.position.transform(tr).to_a)
}
else
# There are some other entities that may appear
# such as SectionPlane, Dimension, Image, Text,
# ConstructionLine, ConstructionPoint, etc.
end
end
And I would use extend self inside the module (as one of the first few lines,)
so you do not need to qualify each method call with self.method_name, etc.
After exploding all rooms and Components with the code above, I simply look at entities that are Sketchup::Face.
If the face has a single Loop then I write to a file the vertices, If there are several loops then I write to
the file the edges for all loops contained in the face. This is all I need to do.
The problem is that to create the entities in a model containing Groups/Components then I need to explode, is that correct?
In the code you are showing you are reading model.entities, Do I need to explode before reading the entities as you propose in case the room contains Groups/Components?.
Using ThomThom’s recursive ‘read_entities’, you don’t have to explode anything. Applying the transformation to the vertex.position will give you the ‘real world’ value for that point.
Thank you for being so clear!, I manage to do what I need using read_entities and without exploiding
Actually, I had some problems with the transformation thing, sometimes I observed that some of the groups
where not exported to the right place. I tried Thomthom’s self.read_entities using
And things looks nice now (transformations are not always commutative).
Other thing suggested by Dan is the use of the method .to_a when exporting the points. I observed that the resulting
output do not show the unities symbol after each component of the point (which is very convenient when writting all the points
of a room in a text file). How can I know the length unities of exported points? can I choose the unities to be meters?
Sketchup stores all data as decimal inches. However you can convert your data to current units by using a_var.to_l where a_var is your variable. You can then set units and turn on or off units from within model info \ units.
Whoops! Thanks for posting back the correction to that. Got my wires crossed when I typed up that example.
To add to that, the SketchUp API will return a Length type for most things representing units. When you do Length#to_s (explicitly or implicitly) it’ll be formatted according to the model units. To serialize coordinates you want to extract the underlying raw value (Length#to_f) which is always represented in inches.