• Login
sketchucation logo sketchucation
  • Login
ℹ️ GoFundMe | Our friend Gus Robatto needs some help in a challenging time Learn More

Ruby Sketchup Programmatic Union of Multiple items

Scheduled Pinned Locked Moved Developers' Forum
10 Posts 4 Posters 2.2k Views
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.
  • H Offline
    hank
    last edited by 16 Apr 2017, 16:23

    Hello. If you want to union multiple solids programmatically, how would you do it? With the union tool (Tools > Solid Tools > Union [Pro Only]) you pick one solid, then another but can continue to pick solids, adding to the Union Result. Using #union(group) however, I am having trouble keeping a handle on the union result in order to continue adding to it.

    If I do

    
    union_result = group_a.union(group_b)
    
    

    I can't seem to refer to union_result when I try to run union again for subsequent objects.

    Here is the total picture that may provided more insight...

    Disclaimer: this seems like a dumb way to do this but, because when I loop through all my target groups, I need something to union the first one with, I am creating a small dummy group like so...

    
    mod = Sketchup.active_model
    ent = mod.active_entities
    
    # create a dummy object for initiating union
    dummy_face = ent.add_face([0,0,0],[1,0,0],[1,1,0],)
    dummy_group = ent.add_group(dummy_face)
    dummy_group.name = 'DUMMY GROUP'
    dummy_face.pushpull(1,true)
    
    

    Then here is the actual iterating (WARNING - BUG SPLATS!):

    
    ent.grep(Sketchup;;Group).each_with_index do |item, i|
    	next if item.name!='MY_TARGET_GROUPS'
    	if i <= 1 # first run so union with dummy
    		union_temp = item.union(dummy_group)
    		union_temp.name = 'TEMP_RESULT'
    	else # subsequent runs can union with prior union result
    		union_final = item.union(union_temp)
    		union_final.name = 'UNION_RESULT'
    	end
    end
    
    

    I also tried using entities[-1] for the last created entity and that ALSO BUG SPLATS:

    union_final = item.union(ent[-1])
    

    Perhaps there is something I am misunderstanding about iterating that "clears" the variables created during each loop? Do I need a global or something using the @ designation?

    Also, instead of using a dummy object, perhaps I should just skip the first group - but then I would need a handle to be able to refer to it during the next iteration?
    Your thoughts would be appreciated.

    1 Reply Last reply Reply Quote 0
    • D Offline
      Dan Rathbun
      last edited by 17 Apr 2017, 01:34

      Yes other coders use dummy groups, but usually just push a cpoint into it, which can be easily removed later.

      Iterating collections. You cannot modify the same collection that you are iterating, ie changing the number of members. The iteration loses it's way, and members are either skipped, or referenced after they've been deleted. (We've said this so many time here, you'll likely get search hits on "iterate" etc.)
      So we usually make a copy of the collection, and iterate the copy. Also check each iterative reference for validity and loop next if it's no longer valid. Ie:
      next unless item.valid?

      I'm not here much anymore.

      1 Reply Last reply Reply Quote 0
      • H Offline
        hank
        last edited by 17 Apr 2017, 02:38

        Perfect Dan - Thank you. That was a two for: next unless item.valid? will be so helpful too.

        BTW, I tried @variables and skipping the first element as shown below and it worked!

        
        ent.grep(Sketchup;;Group).each_with_index do |item, i|
            next if item.name!='MY_TARGET_GROUPS'
        	if i < 1
        		puts "skipping first iteration for item; #{item} | #{i}"
        		@first_one = item
        	elsif i == 1
        		@first_union = @first_one.union(item)
        		@subsequent_handle = @first_union
        	else
        		@result_union = @subsequent_handle.union(item)
        	end
        end
        
        

        So the @variable was "remembered" as TIG explained to me here which seemed to be what I needed.

        Now I just have to streamline the iteration BEFORE the loop because this method uses an index that can't see the next if item.name!='MY_TARGET_GROUPS' check. If I have 10 groups and only 3 of them qualify, the index will be off and I might save the wrong @first_one. If I could get grep to do a Group check AND a name check, I could iterate with an accurate index. Can grep be passed multiple patterns in one call?

        1 Reply Last reply Reply Quote 0
        • H Offline
          hank
          last edited by 22 Apr 2017, 13:20

          Dan, that is genius. Thank you.

          I think this

          g.name = 'MY_TARGET_GROUPS'
          

          needs to be changed to

          g.name == 'MY_TARGET_GROUPS'
          

          Right? It changed set all group names rather than testing for equivalence.

          Anyway, grep with a more complex block seems like such a more efficient way of getting objects than getting an array then looping it to test for certain properties, then looping THAT array to test for others, and so on... which is what I felt like I was doing before. This will be really helpful generally.

          And got it about the 2 spaces... Just didn't paste in from Dreamweaver cleanly but will do!

          1 Reply Last reply Reply Quote 0
          • D Offline
            Dan Rathbun
            last edited by Dan Rathbun 25 Apr 2017, 14:32

            @hank said:

            If I could get grep to do a Group check AND a name check, I could iterate with an accurate index. Can grep be passed multiple patterns in one call?

            grep() can be passed a block.

            OR, you can call another of the iterative filter methods from Enumerable.

            And read the Ruby core docs when you wish to know what a method can and cannot do.
            http://ruby-doc.org/core-2.2.4/Enumerable.html
            Enumerable is a mixin library module that is mixed into most all collection classes.

            Would something like this also work ?

            
            grps = ent.grep(Sketchup::Group).find_all {|g| g.name == 'MY_TARGET_GROUPS' }
            # check here to be sure grps is not empty
            @union = grps.shift # slice the first member from grps array
            grps.each do |item|
              next unless item.valid?
              @union = @union.union(item)
            end
            # after the loop, refs in "grps" are all likely invalid;
            grps.clear
            
            

            P.S. - Ruby uses 2 space indents. There is NO coding language in the world that uses odd number of spaces in indents. (Really annoying.)

            I'm not here much anymore.

            S 1 Reply Last reply 26 Apr 2024, 07:58 Reply Quote 0
            • D Offline
              Dan Rathbun
              last edited by 25 Apr 2017, 14:59

              @hank said:

              I think this = needs to be changed to == ...

              Right? It changed set all group names rather than testing for equivalence.

              Correct. My bad. I've fixed it in the above example.

              @hank said:

              Anyway, grep with a more complex block seems like such a more efficient way of getting objects than getting an array ...

              grep always first creates an array result from the pattern argument.

              If you read the description of the method in the docs, it is quite clear there will be a two stage operation if a block is used with grep. The drawback is that the block will process ALL the members returned by the pattern, and push the result of the block into the output array. This will not be a problem if you are not going to use the output array any further. (Ie, the output array might be a series of valid group objects [that passed the name property check,] and false references for any group that did not.)

              The most important part of grep'ing with a class identifier, is that it is extremely fast at filtering out any other item of other classes (that are not subclasses.)
              More specifically, text comparison in Ruby is slow, whilst class identity comparison is fast.

              @hank said:

              ... then looping it to test for certain properties, then looping THAT array to test for others, and so on... which is what I felt like I was doing before.

              It is not a big deal to do that once as I did in the above example, using grep to get all groups, then filtering out only those with a certain name.

              But, yes you can do compound conditional tests within iterator blocks:

              
              name_prefix = "MY_TARGET_GROUPS"
              starts_with = /\A#{name_prefix}/
              grps.each {|g|
                if g.name =~ starts_with && g.material.name = "Green"
                  # Do something with matching group instance
                else
                  # Do something else
                end
              }
              
              

              I'm not here much anymore.

              1 Reply Last reply Reply Quote 0
              • H Offline
                hank
                last edited by 28 Apr 2017, 14:00

                Thanks Dan. Feelin' the power of grep!

                1 Reply Last reply Reply Quote 0
                • S Offline
                  Sklik Rab @Dan Rathbun
                  last edited by 26 Apr 2024, 07:58

                  @Dan-Rathbun
                  How to check if @union become nil?

                  TIGT D 2 Replies Last reply 26 Apr 2024, 16:27 Reply Quote 0
                  • TIGT Offline
                    TIG Moderator @Sklik Rab
                    last edited by 26 Apr 2024, 16:27

                    @Sklik-Rab
                    If

                    @union
                    

                    is an array use

                    if ! @union[0]...
                    

                    To see if it has any elements left ?

                    TIG

                    1 Reply Last reply Reply Quote 0
                    • D Offline
                      Dan Rathbun @Sklik Rab
                      last edited by 27 Apr 2024, 18:48

                      @Sklik-Rab said in Ruby Sketchup Programmatic Union of Multiple items:

                      @Dan-Rathbun
                      How to check if @union become nil?

                      To check if the reference points at nil, you can use:
                      @union.nil? which is inherited by Array from Object#nil?

                      To check if the array is empty, you can use:
                      @union.empty?

                      I'm not here much anymore.

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

                      Advertisement