PolygonMesh triangulation... ?
-
Here is an example that might give some idea(s):
obj
can be the model, a group or component definition. (Anything that has anentities
method and returns a reference to it's collection of entities.)<span class="syntaxdefault"> obj </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">active_model<br /> eset </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> obj</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">entities<br /><br /> </span><span class="syntaxcomment"># Use hash for speed and<br /></span><span class="syntaxdefault"> </span><span class="syntaxcomment"># because keys are unique.<br /></span><span class="syntaxdefault"> pset </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">{}<br /></span><span class="syntaxdefault"> <br /> </span><span class="syntaxcomment"># Use for...in because it does not create a<br /></span><span class="syntaxdefault"> </span><span class="syntaxcomment"># new scope with each iteration, (ie, speed!)<br /></span><span class="syntaxdefault"> for f in eset</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">grep</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Face</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault"> for v in f</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">vertices<br /> pset</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">v</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">position</span><span class="syntaxkeyword">]=</span><span class="syntaxdefault"> v </span><span class="syntaxcomment"># use pts as hash keys<br /></span><span class="syntaxdefault"> end<br /> end<br /> <br /> pts </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> pset</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">keys </span><span class="syntaxcomment"># array of unique pts<br /></span><span class="syntaxdefault"> <br /> mesh </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">PolygonMesh</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault"> pts</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">size </span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault"> <br /> for pt in pts<br /> idx </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">add_point</span><span class="syntaxkeyword">(</span><span class="syntaxdefault"> pt </span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault"> </span><span class="syntaxcomment"># Use idx for subsequent calls<br /></span><span class="syntaxdefault"> </span><span class="syntaxcomment"># to set_uv() and add_polygon(). <br /></span><span class="syntaxdefault"> end<br /></span>
But
eset
could also be the selection set, ie:
Sketchup::active_model.selection
-
Thanks for the tips (I didn't know about grep, seems like it'll save having to recurse through ComponentInstances and Groups).
I think I'll try and keep the normals, so I might have to proceed a bit differently, but this should set me on the right track.
The PolygonMesh doc is confusing though. I was interpreting it differently than what its philosophy suggests, and I'm probably not the first person to do so.
-
@oajfh said:
(I didn't know about grep, seems like it'll save having to ...).
OH,
grep()
comes from the API collection classes mixing in, viainclude()
, the library module Enumerable. There are quite a number of nifty filter, query and iteration methods that get mixed in.Also, remember that a class inherits all it's ancestors methods and constants all the way back to
BasicObject
, (orObject
for the 1.8 branch of Ruby.)You can see if the class [or module] has mixin modules included, and what it's ancestor classes are [if it's a class,] via:
Identifier.ancestors
(Note that the method returns an array of object references. They are not strings or symbols, but you can useto_s
orinspect
to get a string representation of the array, or thename()
method for stringnames of individual ancestor members. -
@oajfh said:
(..., seems like it'll save having to recurse through
ComponentInstance
s andGroup
s).The fastest and easiest way to "walk" group and component instances, is via the model's singleton DefinitionList collection.
Group's are a special kind of Component, that also has a definition in the collection. You check each definition for a boolean result to
group?
orimage?
. Bothfalse
and it's a component definition. If of coursegroup?
istrue
than it is a definition for a group. Ignore any that are for images.This is where those nifty Enumerable methods come in handy.
Getting the group definitions only:
dlist = Sketchup::active_model.definitions glist = dlist.find_all {|d| d.group? }
Getting the image definitions only:
ilist = dlist.find_all {|d| d.image? }
- NOTE: Modifying images via Ruby has caused SketchUp to crash, or become unstable. Team members recommend
explode
of an instance be the only manipulation via Ruby.
Getting the component definitions only:
dlist = Sketchup::active_model.definitions clist = dlist.find_all {|d| !d.group? && !d.image? }
The results are Array instances. The Array class also has Enumerable mixed in.
Let's say you are not interested in processing definitions that do not have any instances:
clist.reject! {|d| d.count_instances == 0 }
The count_instances method is nice (for speed,) because you need not get a reference to the definition's instances collection in order to callsize
.For those that do have instances in the model, you can process all of them at once, by iterating it's instances collection. (To get the transformation of each one, for example.)
Since all instances share their definition's entities collection, ... you might as well process it once, as you go thru the DefinitionList.
- NOTE: Modifying images via Ruby has caused SketchUp to crash, or become unstable. Team members recommend
-
I also have a method that returns PolygonMesh object of a group/component:
# Get group/component entities. # @param [Sketchup;;Group, Sketchup;;ComponentInstance] entity # @return [Sketchup;;Entities] def get_entities(entity) entity.is_a?(Sketchup;;ComponentInstance) ? entity.definition.entities ; entity.entities end # Get triangular mesh of the group. # @param [Sketchup;;Group, Sketchup;;ComponentInstance] entity # @param [Boolean] recursive Whether to include all the child groups and # components. # @param [Boolean] transform Whether to give points in global coordinates. # @yield A procedure to determine whether particular child group/component # should be considered a part of the collection. # @yieldparam [Sketchup;;Group, Sketchup;;ComponentInstance] entity # @yieldreturn [Boolean] Pass true to consider an entity as part of the # collection. Pass false to not consider an entity as part of the # collection. # @return [Geom;;PolygonMesh] def get_triangular_mesh(entity, recursive = true, transform = false, &entity_validation) mesh = Geom;;PolygonMesh.new get_entities(entity).each { |e| if e.is_a?(;;Sketchup;;Face) e.mesh.polygons.each_index{ |i| pts = e.mesh.polygon_points_at(i+1) mesh.add_polygon(pts) } elsif recursive && (e.is_a?(;;Sketchup;;Group) || e.is_a?(;;Sketchup;;ComponentInstance)) && entity_validation.call(e) mesh2 = get_triangular_mesh(e, true, true, &entity_validation) mesh2.polygons.each_index { |i| mesh.add_polygon(mesh2.polygon_points_at(i+1)) } end } mesh.transform!(entity.transformation) if transform mesh end
Same usage as using get_polygons_from_faces method, but instead of returning triplets it returns a PolygonMesh object containing information about all faces of group geometry. One can simply recreate a group through mesh:
group = Sketchup.active_model.entities[0] mesh = get_triangular_mesh(group, true, true){ |e| true } pt = Geom::Point3d.new(100,0,0) mesh.transform!(pt) # Move mesh 100 units on xaxis. Sketchup.active_model.entities.add_faces_from_mesh(mesh, 0)
-
It is easy to obtain face triangulation using PolygonMesh. Triangular data is how SketchyPhysics and MSPhysics gather their information about group geometry. Here is a way to gather triangular data from one face:
triplets = [] face.mesh.polygons.each_index{ |i| triplets << e.mesh.polygon_points_at(i+1) }
In this little snippet a
triplets
array contains triangles of the face. Each triangle represents an array of three Point3d objects.Now, this snippet was to get triangular data from single face. But, what if you want to get triangular data from the whole group and what about its sub-groups? Well, it's not easy to write such method, but it's easy to paste it when you have it:
# Get group/component entities. # @param [Sketchup;;Group, Sketchup;;ComponentInstance] entity # @return [Sketchup;;Entities] def get_entities(entity) entity.is_a?(Sketchup;;ComponentInstance) ? entity.definition.entities ; entity.entities end # Get triplets from all faces of a group/component. # @param [Sketchup;;Group, Sketchup;;ComponentInstance] entity # @param [Boolean] recursive Whether to include all the child groups and # components. # @param [Boolean] transform Whether to give points in global coordinates. # @yield A procedure to determine whether particular child group/component # should be considered a part of the collection. # @yieldparam [Sketchup;;Group, Sketchup;;ComponentInstance] entity # @yieldreturn [Boolean] Pass true to consider an entity as part of the # collection. Pass false to not consider an entity as part of the # collection. # @return [Array<Array<Geom;;Point3d>>] An array of polygons. Each polygon # represents an array of three points - a triplex. def get_polygons_from_faces(entity, recursive = true, transform = false, &entity_validation) triplets = [] get_entities(entity).each { |e| if e.is_a?(Sketchup;;Face) e.mesh.polygons.each_index{ |i| triplets << e.mesh.polygon_points_at(i+1) } elsif recursive && (e.is_a?(Sketchup;;Group) || e.is_a?(Sketchup;;ComponentInstance)) && entity_validation.call(e) triplets.concat get_polygons_from_faces(e, true, true, &entity_validation) end } if transform tra = entity.transformation for i in 0...triplets.size triplets[i].each { |pt| pt.transform!(tra) } end end triplets end
Read the Yardoc generation comments above the method for parameters info. Here is an example:
group = Sketchup.active_model.entities[0] recursive = true # Have geometry gathered from sub group too. transform = false # We want our coordinates to be relative to group transformation. triplets = get_polygons_from_faces(group, recursive, transform) { |e| true # Have every sub-group/sub-component be considered }
-
This:
for i in 0...triplets.size
will cause a "fence post error" (attempt to read past the last member.)Use instead:
for trip in triplets trip.each { |pt| pt.transform!(tra) } end
There is no sense in using a index when you want the members of the enumerable collection.
-
Nope, it wont. I'm using 3 dots (...), not 2 (..). 3 dots mean n minus 1.
-
@anton_s said:
Nope, it wont. I'm using 3 dots (...), not 2 (..). 3 dots mean n minus 1.
OK, but it's still a bit obscure styling. .. and each iteration you're calling the
[]
method when you don't need to. Just sayin' -
Thanks again for the replies !
Anton, your piece of code that constructs a mesh is exactly what I was looking for. I'd tried a couple of things that weren't too different in their approach, but your code handles Transformations really cleanly.
On a side note, you do need to add an extra check at the beginning of your get_triangular_mesh, otherwise it will fail as Edge objects do not have an 'entities' method.
On another side note, in another approach I'd had, I used Sketchup::Set instances to store points and make sure I wasn't getting multiple objects which actually referred to the same point. Strangely enough, the Sketchup::Set does manage to get unique points, but the Object::Set does not - I guess the Sketchup::Set used something other than hashes and eql? to compare points. Yet the Sketchup API directs users to the Object::Set class since Sketchup::Set is now deprecated. Aren't there going to be compatibility issues if users create Sets of points ?
Anyway, thanks again for the answers. Dan, the tips you gave me will also definitely come in handy when I start optimizing my bad Ruby code.
-
@dan rathbun said:
OK, but it's still a bit obscure styling. .. and each iteration you're calling the
[]
method when you don't need to. Just sayin'I think that each loop is slower than for loop. That's why I used for loop.
@oajfh said:
On a side note, you do need to add an extra check at the beginning of your get_triangular_mesh, otherwise it will fail as Edge objects do not have an 'entities' method.
I had these in checkers. I just removed them because these checkers were from other functions, which I did not wan't to paste because they would have probably made the code a bit confusing:
# Validate object type. # @param [Object] object # @param [Object, Array<Object>] types An object or an array of objects to # check against. # @return [void] # @raise [TypeError] if object class doesn't match with any of the specified # types. def validate_type(object, *types) types = types.flatten return if types.empty? types.each { |type| return if object.is_a?(type) } string = case types.size when 1 types[0] when 2 "#{types[0]} or #{types[1]}" else "#{types[0...-1].join(', ')}, or #{types[-1]}" end raise TypeError, "Expected #{string}, but got #{object.class}.", caller end def get_triangular_mesh(entity, recursive = true, transform = false, &entity_validation) validate_type(entity, ;;Sketchup;;Group, ;;Sketchup;;ComponentInstance) mesh = Geom;;PolygonMesh.new get_entities(entity).each { |e| if e.is_a?(;;Sketchup;;Face) e.mesh.polygons.each_index{ |i| pts = e.mesh.polygon_points_at(i+1) mesh.add_polygon(pts) } elsif recursive && (e.is_a?(;;Sketchup;;Group) || e.is_a?(;;Sketchup;;ComponentInstance)) && entity_validation.call(e) mesh2 = get_triangular_mesh(e, true, true, &entity_validation) mesh2.polygons.each_index { |i| mesh.add_polygon(mesh2.polygon_points_at(i+1)) } end } mesh.transform!(entity.transformation) if transform mesh end
On another side, I know nothing about Sketchup::Set or Object::Set, although I do recall reading a thread mentioning them. I'm pretty sure other developers have good knowledge of them.
-
@anton_s said:
I think that each loop is slower than for loop. That's why I used for loop.
Not so sure in Ruby 2.x. See this interesting benchmark.
It is also said that the fastest is the
while
loop, when this is appropriate.Fredo
-
BUT.. that is not the point I was making. You do not need to mess with an index, in order to iterate the members of an Enumerable collection.
Restated and simplified, as this:
for t in triplets t.each { |pt| pt.transform!(tra) } end
is faster and more readable (better practice) than:
for i in 0...triplets.size triplets[i].each { |pt| pt.transform!(tra) } end
Why faster?
- No calling the
size
method at the beginning. - No instantiation of a
Range
object via the...
operator - No need to call the
[]
method during each iteration.
There is no sense in using a index when you want the members of the enumerable collection.
Because, thefor
loop can directly serve up each enumerable member in turn, without the need to maintain an index variable on the Ruby side.(Of course the C-side implementation using for over there does use an iterator, but the C-side does not need to expose the iterator to the Ruby side.)
The
for
construct is not a method it's built into Ruby. And it does not create a new scope during each iteration. I image that other built-in looping constructs (while
,until
) can also be fast. - No calling the
-
@fredo6 said:
@anton_s said:
I think that each loop is slower than for loop. That's why I used for loop.
Not so sure in Ruby 2.x. See this interesting benchmark.
It is also said that the fastest is the
while
loop, when this is appropriate.Well that is interesting. Seems they optimized
while
andeach
, but notfor
.
So now we have a quandary if we publish for older SketchUps, with older Ruby. -
IN Ruby 1.8 for loops used to be faster - at least in the tests I performed on my extensions. But Ruby 2.0 swapped that around.
In any case - you don't really know until you profile your specific case.
Advertisement