sketchucation logo sketchucation
    • Login
    ℹ️ Licensed Extensions | FredoBatch, ElevationProfile, FredoSketch, LayOps, MatSim and Pic2Shape will require license from Sept 1st More Info

    Best way to iterate all nested entities

    Scheduled Pinned Locked Moved Developers' Forum
    29 Posts 9 Posters 5.9k Views 9 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.
    • liquid98L Offline
      liquid98
      last edited by

      Gentlemen,

      Thanks! I wrote my code indeed along the lines TIG wrote..
      edit:
      Thomthom ok I'll do that!, thnx

      Things that flourish fall into decay. This is not-Tao, And what is not-Tao soon ends ~ Lao tse

      1 Reply Last reply Reply Quote 0
      • TIGT Offline
        TIG Moderator
        last edited by

        TT makes a good point because Images do not have a '.entities' method - unless you create one...
        so at the start of the code dealing with definitions use

        ...
        Sketchup.active_model.definitions.each{|defn|
          next if defn.image?
          defn.entities.each{|face|.....
        

        to skip all Images.
        Similarly defn.group? spots a ' group', while anything else in the definitions is a ' component'...

        TIG

        1 Reply Last reply Reply Quote 0
        • D Offline
          david.
          last edited by

          This method is fine if you are iterating over the entire model. I have situations when I only want to iterate over a selection. There is no definitions method in that case. I've been using recursion to iterate over a list of components/groups in the selection.

          1 Reply Last reply Reply Quote 0
          • TIGT Offline
            TIG Moderator
            last edited by

            matching_faces=[]
            Sketchup.active_model.selection.each{|e|
                if e.is_a?(Sketchup;;Face)
                  ### check for compliance with some 'property' and then
                  matching_faces << e if match
                elsif e.is_a?(Sketchup;;Group)
                  e.entities.parent.entities.each{|face|
                    next unless face.is_a?(Sketchup;;Face)
                    ### check for compliance with some 'property' and then
                    matching_faces << face if match
                  }
                elsif e.is_a?(Sketchup;;ComponentInstance)
                  e.parent.entities.each{|face|
                    next unless face.is_a?(Sketchup;;Face)
                    ### check for compliance with some 'property' and then
                    matching_faces << face if match
                  }       
                end
            }
            

            etc...
            You can of course use the matching to do any number of things... that's up to you to decide... πŸ˜•

            TIG

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

              @david. said:

              I've been using recursion ...

              The bad news is that recursion is bad in Ruby, and worse in embedded Ruby.

              It can cause a stack overflow, which crashes Ruby, and in the case of SketchUp embedded Ruby, will then crash SketchUp.

              The good news is that you can write a method to collect the definitions whose instances are present in the selection.

              I'm not here much anymore.

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

                @dan rathbun said:

                @david. said:

                I've been using recursion ...

                The bad news is that recursion is bad in Ruby, and worse in embedded Ruby.

                It can cause a stack overflow, which crashes Ruby, and in the case of SketchUp embedded Ruby, will then crash SketchUp.

                The good news is that you can write a method to collect the definitions whose instances are present in the selection.

                I posted examples in a separate Code Snippet topic:
                [ Code ] (Iterating) Finding Definitions

                πŸ’­

                I'm not here much anymore.

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

                  In reality,... instances (Group or Component,) do NOT really have entities collections.

                  It is their definition that has the entities collection. (The group.entities() method is a shortcut that often deceives novices into thinking that the group instance has entities. It actually is a wrapper method for group.entities.parent.entities )

                  Also a the Group class is a special kind of ComponentInstance class.

                  Both have a parent ComponentDefinition

                  So it is much more efficient to iterate the model's DefinitionList collection, and access the instances through a definitions instances collection.


                  EDIT: Fixed second paragraph, group.entities.parent.entities was group.parent.entities in error.

                  I'm not here much anymore.

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

                    @dan rathbun said:

                    @david. said:

                    I've been using recursion ...

                    The bad news is that recursion is bad in Ruby, and worse in embedded Ruby.

                    It can cause a stack overflow, which crashes Ruby, and in the case of SketchUp embedded Ruby, will then crash SketchUp.

                    The good news is that you can write a method to collect the definitions whose instances are present in the selection.

                    I use recursion for digging into nested components levels - because the recursion level is not going to max out the call stack unless you have a model that was designed to crash it. Nobody nests geometry that deep.

                    Recursing when traversing over connected geometry on the other hand will quickly hit the limit of the call stack.

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

                    1 Reply Last reply Reply Quote 0
                    • S Offline
                      shannonnovus
                      last edited by

                      @tig said:

                      
                      >     elsif e.is_a?(Sketchup;;ComponentInstance)
                      >       e.parent.entities.each{|face|
                      >         next unless face.is_a?(Sketchup;;Face)
                      >         ### check for compliance with some 'property' and then
                      >         matching_faces << face if match
                      >       }       
                      >     end
                      > }
                      

                      One comment: This section of the code for components didn't work for me. It wasn't giving any error messages, it just wasn't making any changed to the components in my model. I changed the second line to e.definition.entities.each .... and that worked.

                      One question: I'm not processing faces like this example, but edges. I'm modifying another script from TIG to delete all vertical edges in my model. I need it to also recurse into groups and components, so I've combined it with the script here.

                      So ... the first time I run the script, it deletes all vertical lines that are not within groups/components. From groups/components, however, it leaves one vertical line in each. (This is run on a simple test file that has three cubes, one is ungrouped, one is a group, the last in a component.) If I run the script a second time, it deletes the remaining vertical lines from the groups/components. I'd appreciate if anyone could explain what's going on here, and if there is a way to write the code such that each vertical edge is dealt with in the first go.

                      Many thanks,
                      Shannon

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

                        The API's Entities collection(s), is/are actually thinly-exposed (to Ruby,) C++ collection(s).

                        You CANNOT safely both iterate such a set AND delete items from the set AT THE SAME TIME.

                        Doing so creates a "fence post error" in which the iteration reference skips one (or more) items in the set.

                        This has been covered so many times before. (Here and in other forums.)

                        Seems every newbie must fall for this boo boo.

                        Use the standard Ruby to_a() or grep() method to take a "snapshot" Ruby array copy of API collections.
                        Then iterate THAT Ruby copy, viz:
                        entities.grep(Sketchup::Edge).each {|e| e.erase! if is_vertical?(e) }

                        is_vertical?() is a hypothetical utility query method within your plugin's class or module.

                        Would be something like:

                        def is_vertical?(edge)
                          vec = edge.start.position.vector_to(edge.end.position).normalize
                          vec == [0,0,1] || vec == [0,0,-1]
                        end
                        

                        Note that some of those old examples were written before we knew how very fast the grep() method was.
                        USE it to filter collections when you want only one class of object. It is FAST!
                        (This method comes from the mixin module Enumerable, which is mixed into many collection classes.)

                        I'm not here much anymore.

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

                          When "drilling down" into the entities of component or groups, it is so very much faster to go through the model's definitions collection, checking each definition if it's instances collection has size > 0, (and possibly if it is image? == false,) and if so...

                          ... delete from the definitions entities collection, and all instances are changed.

                          I'm not here much anymore.

                          1 Reply Last reply Reply Quote 0
                          • tt_suT Offline
                            tt_su
                            last edited by

                            .grep is an iterator itself - so no need for .each:

                            entities.grep(Sketchup::Edge) {|edge| edge.erase! if is_vertical?(edge) }

                            1 Reply Last reply Reply Quote 0
                            • tt_suT Offline
                              tt_su
                              last edited by

                              Saying that - bulk methods is much faster than individual actions. Use entities.erase_entities when you erase multiple entities - it also avoids the pitfall of erasing the collection you are erasing from.

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

                                Yea so you can also do this:

                                verts = entities.grep(Sketchup::Edge).find_all {|edge| is_vertical?(edge) } entities.erase_entities(verts) unless verts.empty?

                                πŸ’­

                                P.S. @TT Yea I knew grep() is an iterator, but I usually avoid using a block with it, as it returns an array of block results which is a bit weird. (Especially when you expect a smaller subset than the whole.)
                                I tend to just use it as a filter, and then call another method on the filtered results. IMHO the code is more readable. (.. and I don't confuse myself as much.)

                                I'm not here much anymore.

                                1 Reply Last reply Reply Quote 0
                                • S Offline
                                  shannonnovus
                                  last edited by

                                  @dan rathbun said:

                                  Use the standard Ruby to_a() or grep() method to take a "snapshot" Ruby array copy of API collections.
                                  Then iterate THAT Ruby copy, viz:
                                  entities.grep(Sketchup::Edge).each {|e| e.erase! if is_vertical?(e) }

                                  Thanks Dan! The grep method worked great!

                                  @dan rathbun said:

                                  def is_vertical?(edge)
                                  >   vec = edge.start.position.vector_to(edge.end.position).normalize
                                  >   vec == [0,0,1] || vec == [0,0,-1]
                                  > end
                                  

                                  Your example for how to test for a vertical edge did not work for me, which could be entirely my fault. TIG's code from the other forum I mentioned did.

                                  edge.line[1].z.abs==1
                                  
                                  1 Reply Last reply Reply Quote 0
                                  • tt_suT Offline
                                    tt_su
                                    last edited by

                                    To use the same tolerance as SketchUp does, use the methods built into the Ruby API:

                                    vector = edge.line[1] vector.samedirection?(Z_AXIS)

                                    http://www.sketchup.com/intl/en/developer/docs/ourdoc/vector3d.php#samedirection?

                                    The components of a vector are floating point values so they should never be compared without a tolerance. For more information about floating point precision: http://floating-point-gui.de/

                                    1 Reply Last reply Reply Quote 0
                                    • S Offline
                                      slbaumgartner
                                      last edited by

                                      @tt_su said:

                                      To use the same tolerance as SketchUp does, use the methods built into the Ruby API:

                                      vector = edge.line[1] vector.samedirection?(Z_AXIS)

                                      http://www.sketchup.com/intl/en/developer/docs/ourdoc/vector3d.php#samedirection?

                                      The components of a vector are floating point values so they should never be compared without a tolerance. For more information about floating point precision: http://floating-point-gui.de/

                                      Excellent advice! Do you know whether Point3d#on_line? and #on_plane? also include the tolerance? The API docs don't say.

                                      1 Reply Last reply Reply Quote 0
                                      • tt_suT Offline
                                        tt_su
                                        last edited by

                                        Yes it does. Do does Geom::Point3d, Geom::Vector3d and Length - which is why it's recommended you use those types when doing calculations, instead of using arrays and floats.

                                        Also note that Length + Length == Float (annoyingly). So you need to ensure you have a Length before outputting that to a string.

                                        1 Reply Last reply Reply Quote 0
                                        • dkendigD Offline
                                          dkendig
                                          last edited by

                                          definition.instances.empty? won't give you an accurate indicator of the usefulness of a definition unfortunately. It can cut out some obvious ones to skip, but not all of the useless ones. If an instance is used in a definition, and that parent definition isn't instanced anywhere, the first definition will still say it has one instance. It's not wrong... but it's not helpful either. It would be nice to know if a definition is actually used in your model somewhere. Of course if you purge unused first, you should be fine. This can be especially frustrating when working with a model in which you are not allowed to purge unused definitions.

                                          Example:

                                          1. Make a cube
                                          2. Make the cube a component
                                          3. Make a copy of the component instance
                                          4. Make a new component out of the two instances
                                          5. Delete the resulting component instance
                                          6. Type this in the ruby console: Sketchup.active_model.definitions.each{|df| puts "definition #{df.name} instance count: #{df.instances.size}"};nil

                                          Devin Kendig
                                          Developer

                                          1 Reply Last reply Reply Quote 0
                                          • tt_suT Offline
                                            tt_su
                                            last edited by

                                            Ah yes - very good point.

                                            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