sketchucation logo sketchucation
    • Login
    ⚠️ Attention | Having issues with Sketchucation Tools 5? Report Here

    PolygonMesh triangulation... ?

    Scheduled Pinned Locked Moved Developers' Forum
    23 Posts 6 Posters 2.4k Views 6 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • A Offline
      Anton_S
      last edited by

      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)

      1 Reply Last reply Reply Quote 0
      • A Offline
        Anton_S
        last edited by

        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
        }
        
        1 Reply Last reply Reply Quote 0
        • Dan RathbunD Offline
          Dan Rathbun
          last edited by

          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.

          I'm not here much anymore.

          1 Reply Last reply Reply Quote 0
          • A Offline
            Anton_S
            last edited by

            Nope, it wont. I'm using 3 dots (...), not 2 (..). 3 dots mean n minus 1.

            1 Reply Last reply Reply Quote 0
            • Dan RathbunD Offline
              Dan Rathbun
              last edited by

              @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'

              I'm not here much anymore.

              1 Reply Last reply Reply Quote 0
              • O Offline
                oajfh
                last edited by

                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.

                1 Reply Last reply Reply Quote 0
                • A Offline
                  Anton_S
                  last edited by

                  @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.

                  1 Reply Last reply Reply Quote 0
                  • fredo6F Offline
                    fredo6
                    last edited by

                    @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

                    1 Reply Last reply Reply Quote 0
                    • Dan RathbunD Offline
                      Dan Rathbun
                      last edited by

                      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, the for 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.

                      I'm not here much anymore.

                      1 Reply Last reply Reply Quote 0
                      • Dan RathbunD Offline
                        Dan Rathbun
                        last edited by

                        @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 and each, but not for.
                        So now we have a quandary if we publish for older SketchUps, with older Ruby.

                        I'm not here much anymore.

                        1 Reply Last reply Reply Quote 0
                        • thomthomT Offline
                          thomthom
                          last edited by

                          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.

                          Thomas Thomassen — SketchUp Monkey & Coding addict
                          List of my plugins and link to the CookieWare fund

                          1 Reply Last reply Reply Quote 0
                          • 1
                          • 2
                          • 2 / 2
                          • First post
                            Last post
                          Buy SketchPlus
                          Buy SUbD
                          Buy WrapR
                          Buy eBook
                          Buy Modelur
                          Buy Vertex Tools
                          Buy SketchCuisine
                          Buy FormFonts

                          Advertisement