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.
    • 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
                          • TIGT Offline
                            TIG Moderator
                            last edited by

                            To check if a defn with instances actually has one or more if these inserted in the model, or also it is inside a 'container' that is itself inserted in the model, OR inside something else that is inserted in the model... etc...
                            Start count=0.
                            Look at each instance in turn and get its parent.
                            If its parent is the model then it is inserted (count+=1).
                            Elsif its parent is another component-defn you'll need to check if that definition has instances and iterate those and if their parent is the model (count+=1 and break) but if its parent is another component-defn repeat the nested testing of that definition having instances etc...

                            With an instance of Component#1 inside Component#2, and two instances of both in model.

                            ` instance_counter()

                            Component Name: Component#1
                            All Instances: 4
                            Inserted Instances: 2
                            Nested Instances: 2

                            Component Name: Component#2
                            All Instances: 1
                            Inserted Instances: 1
                            Nested Instances: 0

                            true`

                            With only 2 instances of Component#1

                            ` instance_counter()

                            Component Name: Component#1
                            All Instances: 2
                            Inserted Instances: 0
                            Nested Instances: 2

                            Component Name: Component#2
                            All Instances: 1
                            Inserted Instances: 1
                            Nested Instances: 0

                            true`

                            This is only to demonstrate the principal - clearly you'd want to hae a proper model/method that returned the counts for you...

                            require('sketchup.rb')
                            def instance_counter()
                            	def instances?(d)
                            		model=Sketchup.active_model
                            		cont=0
                            		d.instances.each{|i|
                            			parent=i.parent
                            			if parent==model
                            				cont+=1
                            			else
                            				cont+=instances?(parent)
                            			end
                            		}
                            		return cont
                            	end
                            	model=Sketchup.active_model
                            	puts
                            	model.definitions.each{|d|
                            		next if d.image? || d.group?
                            		puts "Component Name; \t#{d.name}"
                            		puts "All Instances;  \t#{d.instances.length}"
                            		count=0
                            		ncount=0
                            		d.instances.each{|i|
                            			parent=i.parent
                            			if parent==model
                            				count+=1
                            			else
                            				ncount+=instances?(parent)
                            			end
                            		}
                            		puts "Inserted Instances; \t#{count}"
                            		puts "Nested Instances;   \t#{ncount}"
                            		puts
                            	}
                            	return true
                            end
                            

                            Perhaps a method that takes the defn as its argument and returns the instances count in three arrayed integers [all, model, nested] ... [4, 2, 2] - like this:

                            def instance_count(d=nil) #d=defintion
                            	return nil if !d || !d.is_a?(Sketchup;;ComponentDefinition)
                            	return false if d.image? || d.group?
                            	def instances?(d)
                            		model=Sketchup.active_model
                            		cont=0
                            		d.instances.each{|i|
                            			parent=i.parent
                            			if parent==model
                            				cont+=1
                            			else
                            				cont+=instances?(parent)
                            			end
                            		}
                            		return cont
                            	end
                            	model=Sketchup.active_model
                            	count_array=[d.instances.length]
                            	count=0
                            	ncount=0
                            	d.instances.each{|i|
                            		parent=i.parent
                            		if parent==model
                            			count+=1
                            		else
                            			ncount+=instances?(parent)
                            		end
                            	}
                            	count_array << count
                            	count_array << ncount
                            	return count_array
                            end
                            

                            TIG

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

                              yes, generally this tends to do the trick, but we found that it was a tad faster to have your own lookup table that keeps track of definition relevance. The only downside, is that you are at the mercy of the observer system in that case, but the observers these days seem fairly stable, so that isn't currently an issue.

                              Devin Kendig
                              Developer

                              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