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

    Issues with a simple material copy raycast script

    Scheduled Pinned Locked Moved Developers' Forum
    25 Posts 2 Posters 7.0k Views 2 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.
    • TIGT Offline
      TIG Moderator
      last edited by

      Read up on this:
      http://ruby.sketchup.com/Sketchup/UVHelper.html
      and this:
      http://ruby.sketchup.com/Sketchup/Face.html#position_material-instance_method

      PS: Please format your code examples using

      [code]...[/code]
      

      rather that

      [ruby]...[/ruby]
      

      It's much easier to read...

      TIG

      1 Reply Last reply Reply Quote 0
      • C Offline
        chanz
        last edited by

        @tig said:

        Read up on this:
        http://ruby.sketchup.com/Sketchup/UVHelper.html
        and this:
        http://ruby.sketchup.com/Sketchup/Face.html#position_material-instance_method

        PS: Please format your code examples using

        [code]...[/code]
        

        rather that

        [ruby]...[/ruby]
        

        It's much easier to read...

        Always wondered why my posts looked so strange right after being posted but then fixed themselves.

        Thanks for the links.

        So what I understand from this block:

        		samples = []
        		samples << face_two.vertices[0].position			   ### 0,0 | Origin
        		samples << samples[0].offset(face_two.normal.axes.x) ### 1,0 | Offset Origin in X
        		samples << samples[0].offset(face_two.normal.axes.y) ### 0,1 | Offset Origin in Y
        		samples << samples[1].offset(face_two.normal.axes.y) ### 1,1 | Offset X in Y
        		xyz = [];uv = []### Arrays containing 3D and UV points.
        		uvh = face_two.get_UVHelper(true, true, texture_writer)
        		samples.each { |position|
        		xyz << position ### XYZ 3D coordinates
        		
        		# I switched this to front_UVQ
        		uvq = uvh.get_front_UVQ(position) ### UV 2D coordinates
        		uv << self.flattenUVQ(uvq)
        		}
        	  
        		pts = [] ### Position texture.
        		(0..3).each { |i|
        		pts << xyz[i]
        		pts << uv[i]
        		}
        	  
        		# set the position and material of face_one
        		mat = face_two.material
        		face_one.position_material(mat, pts, true)
        
        

        From this function that I have adapted from your script, it seems like it should be correct.

        1. Gets the position of the four points from the face which we want to sample (0, 0), (1, 0), (0, 1), and (1, 1)
        2. Put the vertex position and UV coordinates (flattened - not sure why although not flattening had no effect) into two arrays (UVs returned in a point form, but only the x and y are used as u and v).
        3. Interlace these arrays as the position_material function calls for.
        4. Call position_material. Supply the correct material (which I do), the points (which I obviously don't) and true as it is a front face.

        I understand what's going on here. I am a bit confused about why we are flattening the UVs but the math behind the function to do so makes sense.

        Still stumped on what exactly I am getting wrong about the point array...

        Edit: Interestingly, if I just leave the model flat and raycast down, it gets the texture and orientation correct. Now I am wondering if it's the raycast, but I really doubt it...

        Also, it didn't seem to matter if I included just three points (0,0), (0,1) and (1,1) for both the points and uvs.

        Edit: I have attached what I have so far (code wise)


        The latest episode in my endless struggle for simplification πŸ™‚

        1 Reply Last reply Reply Quote 0
        • C Offline
          chanz
          last edited by

          Played around with it a bit more last night. Couldn't find anything that jumped out as incorrect. Does anyone have any more suggestions? Happy to try anything πŸ˜„

          I appreciate it!

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

            Try this example.

            ### CopyBelowMaterials.rb
            ### by Chanz
            require 'sketchup.rb'
            ###
            module DAN
              ###
              def self.copymaterials()
                model=Sketchup.active_model
                ents=model.active_entities
                #ss=model.selection ### use selected upper faces to speed it up
                faces = ents.grep(Sketchup;;Face)
                #faces = ss.grep(Sketchup;;Face) ### use selected upper faces to speed it up
                ###
                return nil unless faces[0]
                ###
                begin
                  model.start_operation("DAN.copymaterials", true)
                rescue
                  model.start_operation("DAN.copymaterials")
                end
                ###
                faces.each {|face| 
                  rayt = model.raytest(self.find_centroid(face), Z_AXIS.reverse)
                  if rayt
                    rayt[1].each{|f|
                      if f.is_a?(Sketchup;;Face)
                        self.process_face(face, f)
                      end
                    }
                  end
                }
                ###
                model.commit_operation
                ###
              end
            
              # Finds an adjusted point, so we don't raycast down to an edge ;)
              ### OK if triangulated... 
              ### BUT IF the face is L shaped the centroid might not fall on it !!!
              def self.find_centroid(face)
                vertices=face.vertices
                vertices[vertices.length]=vertices[0]
                centroid=Geom;;Point3d.new()
                a_sum=0.0
                y_sum=0.0
                x_sum=0.0
                for i in (0...vertices.length-1)
                  temp=(vertices[i].position.x*vertices[i+1].position.y)-(vertices[i+1].position.x*vertices[i].position.y)
                  a_sum+=temp
                  x_sum+=(vertices[i+1].position.x+vertices[i].position.x)*temp
                  y_sum+=(vertices[i+1].position.y+vertices[i].position.y)*temp
                end###for
                centroid.x=x_sum/(3*a_sum)
                centroid.y=y_sum/(3*a_sum)
                centroid.z=face.bounds.min.z
                return centroid
              end
              
              # NOTE; face_upper is in the model.ents||selection, face_lower is the face the raytest hit
              def self.process_face(face_upper, face_lower)
                ###
                texture_writer=Sketchup.create_texture_writer
                ###
                if face_upper.material && ! face_upper.material.texture
                  ### paint the plain material if it exists but it has no texture
                  face_lower.material=face_upper.material
                elsif face_upper.material ### A material and it has a texture...
                  # Sample the UVs from UPPER face
                  samples = []
                  samples << face_upper.vertices[0].position             ### 0,0 | Origin
                  samples << samples[0].offset(face_upper.normal.axes.x) ### 1,0 | Offset Origin in X
                  samples << samples[0].offset(face_upper.normal.axes.y) ### 0,1 | Offset Origin in Y
                  samples << samples[1].offset(face_upper.normal.axes.y) ### 1,1 | Offset X in Y
                  xyz = [];uv = [] ### Arrays containing 3D and UV points.
                  uvh = face_upper.get_UVHelper(true, true, texture_writer)
                  samples.each { |position|
                    xyz << position ### XYZ 3D coordinates
                    uvq = uvh.get_front_UVQ(position) ### UV 2D coordinates
                    uv << self.flattenUVQ(uvq)
                  }
                  pts = [] ### Position texture.
                  (0..3).each { |i|
                    pts << xyz[i]
                    pts << uv[i]
                  }
                  # Set the position for textured material from face_upper onto face_lower front
                  face_lower.position_material(face_upper.material, pts, true)
                end
                ###
              end
              
              ### Get UV coordinates from UVQ matrix.
              def self.flattenUVQ(uvq)
                return Geom;;Point3d.new(uvq.x / uvq.z, uvq.y / uvq.z, 1.0)
              end
              
              ###
              ### menu
              unless file_loaded?(__FILE__) 
                menu=UI.menu("Plugins").add_submenu("Copy Materials...")
                menu.add_item("To Nearest Below"){self.copymaterials()}
              end
              file_loaded(__FILE__)
            
            end
            

            You had muddled face1 and face2 - I renamed them face_upper and face_lower to avoid confusion.
            Also note the following...
            ss=model.selection... allows you to select just the upper mesh faces, halving the processing, since the lower will be tested and ignorred anyway !
            return nil unless faces[0] is added to skip out if no faces
            model.start_operation... is moved into the right place in the code.
            self.find_centroid(face) could produce unexpected results in a concave L shaped faces, but will work OK if it's triangulated...
            I also let it copy un-textured material from upper to lower too...
            I've also made a few more minor tweaks elsewhere...

            TIG

            1 Reply Last reply Reply Quote 0
            • C Offline
              chanz
              last edited by

              Hey TIG! Thanks a bunch for the reply πŸ˜„

              I think there was maybe some confusion about what I was trying to accomplish here and perhaps the function name is misleading.

              I have textured terrain from an old game with way too many subdivisions and I want to simplify it. So I make a terrain with the correct size, and then use Drop Vertices to make the terrains the same height. I then move the new terrain up directly over the old terrain and then run my script which would do the raycast and copies from the model below. I think you assumed I would do it the other way around and my function name "Copy Materials -> To Nearest Below" was totally misleading. My apologies.

              I switched the the face arguments in the process_face function and it works again.

              When I run the script, I still get some strange looking results. In the image below, the left is the original and the right is the one that the script made.

              https://i.imgur.com/efmfMP1.png

              Notice that the UV mapping looks quite off, still. If I run the script on a flat sandbox mesh above the original mesh, it copies the UVs fine but I need it to be fine when the mesh is not flat. Or find a way to drop the vertices without messing up the UVs.

              I have reduced the size of the model I am using to test so hopefully you're able to try it with this test case.

              Again, your time is greatly appreciated. Thank you!


              A test scene for this plugin. Select the faces in the top (untextured) model and go to Extensions->Copy Materials->From Nearest Below


              The script with TIG's revisions and the argument order in the process_faces reversed

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

                If you want to recast [all but flip ?] it, so the face_below.material BELOW gets 'projected' onto the face_above... then you firstly need to swap my naming conventions etc, AND also change the Z_AXIS code to project your raytest upward, rather than down...

                I'm sure that if we can agree a common 'framework' this can be resolved...

                TIG

                1 Reply Last reply Reply Quote 0
                • C Offline
                  chanz
                  last edited by

                  @tig said:

                  If you want to recast [all but flip ?] it, so the face_below.material BELOW gets 'projected' onto the face_above... then you firstly need to swap my naming conventions etc, AND also change the Z_AXIS code to project your raytest upward, rather than down...

                  I'm sure that if we can agree a common 'framework' this can be resolved...

                  Isn't face_above raytesting down to face_below and then sampling the UVs the same as face_below raytesting up to face_above and setting the UVs?

                  The original script I had written did the raytest downwards from the face_above and got the material and UV from face_below.

                  Here is a terrible illustration to show what I mean:

                  https://i.imgur.com/COl2dsP.png

                  Either way, it doesn't bother me which way is preferred. All I want is for the Texture mapping uvs to look on the above mesh, just like the below mesh.

                  The easiest way to see what I am talking about would be to download the sample project and script I attached. Select the faces in the model that's on top and then run the script.

                  Thanks TIG πŸ˜„

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

                    In your image face_below is the 'source' face [the inverse of my assumptions].
                    And therefore face_above is the 'target', so your code needs to swap them to suit.
                    AND the ' raytest' then needs to follow Z_AXIS.reverse, so it look up NOT down...

                    BOTH will work, BUT just be consistent...

                    TIG

                    1 Reply Last reply Reply Quote 0
                    • C Offline
                      chanz
                      last edited by

                      @tig said:

                      In your image face_below is the 'source' face [the inverse of my assumptions].
                      And therefore face_above is the 'target', so your code needs to swap them to suit.
                      AND the ' raytest' then needs to follow Z_AXIS.reverse, so it look up NOT down...

                      BOTH will work, BUT just be consistent...

                      Exactly. So we are raytesting from face_above downwards to face_below (the source) to get the materials and UV. Apologies for the confusion.

                      This still has no effect on the UVs though, which are still not working. I have attached the script with the above agreed upon naming convention and additional clarifying comments. Try it in the test scene if you get a chance. Thanks TIG. πŸ˜„


                      Fixed naming conventions

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

                        So your convention is upper surface has 'no-material', lower surface has 'textures'.
                        Unless you known the two meshes have identical faces you can't safely tale a point on an upper facet and project down and be sure of hitting a lower facet...
                        But for now let's assume all goes well and we have 'hits', how are you going to get the lower facet's texture ?
                        Isn't it better to process each lower textured facet, and project upwards to get a matching upper facet.
                        You then need to get the UV mapping for the lower facet, and apply it to the lower facet's material which is applied onto the equivalent upper facet.

                        Just think about which is which...

                        TIG

                        1 Reply Last reply Reply Quote 0
                        • C Offline
                          chanz
                          last edited by

                          @tig said:

                          So your convention is upper surface has 'no-material', lower surface has 'textures'.
                          Unless you known the two meshes have identical faces you can't safely tale a point on an upper facet and project down and be sure of hitting a lower facet...
                          But for now let's assume all goes well and we have 'hits', how are you going to get the lower facet's texture ?
                          Isn't it better to process each lower textured facet, and project upwards to get a matching upper facet.
                          You then need to get the UV mapping for the lower facet, and apply it to the lower facet's material which is applied onto the equivalent upper facet.

                          Just think about which is which...

                          It really doesn't matter to me which direction is taken. I'm absolutely fine using the method you suggest where we raycast up applying the material and UVs from the raycast source face to the hit face. Works for me.

                          I think your edits to my script were doing just that. But I got the same results as mine where the UVs were messed up

                          Thinking about it, because I'm using drop vertices and the meshes are nearly identical but not perfectly the same, could that be causing issues with the UVs? Perhaps that's the issue where it copies it just fine but because the faces differ, we get issues.

                          So if that's the case, the next questions is how difficult it would be to modify drop vertices to preserve the UVs when it drops. Vertex Tools has a similar feature where you can lock the UVs.

                          Would that be an easier approach?

                          Thanks as always, TIG.

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

                            In my earlier version, if I make the to textured and project it onto a lower surface the top one's UV mapping is repeated on the lower surface.
                            So I know it works.
                            You just need to swap top/bottom properly to apply to you own chosen set up...

                            If you use a blank face's UV mapping you won't get what's on the UV-mapped face...

                            Try it with just a few facets first...

                            TIG

                            1 Reply Last reply Reply Quote 0
                            • C Offline
                              chanz
                              last edited by

                              @tig said:

                              In my earlier version, if I make the to textured and project it onto a lower surface the top one's UV mapping is repeated on the lower surface.
                              So I know it works.
                              You just need to swap top/bottom properly to apply to you own chosen set up...

                              If you use a blank face's UV mapping you won't get what's on the UV-mapped face...

                              Try it with just a few facets first...

                              Maybe I'm having trouble understanding. I'll use the script you posted and the test model I posted. What was your exact process to get the UVs on the top model looking like the original on the bottom? Maybe our process is just different. I assumed you used the test model I uploaded?

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

                                I can see some issues, BUT you need to clarify what face's texture you are copying with UVs and onto which face you apply them...

                                TIG

                                1 Reply Last reply Reply Quote 0
                                • C Offline
                                  chanz
                                  last edited by

                                  @tig said:

                                  I can see some issues, BUT you need to clarify what face's texture you are copying with UVs and onto which face you apply them...

                                  Maybe I'm just explaining it wrong, but I don't know how to be more clear about what I'm after. I have uploaded a few pictures about what I'm after as well as a model which allows me to test.

                                  In the model I have the original on the bottom and the recreated mesh on the top. I want the textures and UVs of the faces on the bottom to be copied to the untextured model above.

                                  Sorry. Maybe I'm just misunderstanding what you're asking.

                                  Thanks for being patient.

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

                                    I think this code does what you want.

                                    I have used your original conventions...
                                    The upper faces are the un-textured ones and the lower faces are textured.
                                    Each of the upper faces centroids now casts a ray downwards, and if it hits a lower face then the material from the lower face is applied onto the upper face, using the lower face's UV mapping.
                                    We can't be 100% sure that the ray will always hit a face below, because the upper and lower faces are different shapes etc, so if the ray hits a lower edge rather than a lower face, then there's no face to get a material from - so in that case a lower face belonging to that lower edge is found and used in lieu - that way we never have a 'blank' face in the upper set.
                                    Because the faces differ in some places the UV mapping can look a little odd on occasion...

                                    ### CopyBelowMaterials.rb
                                    ### by Chanz
                                    require 'sketchup.rb'
                                    ###
                                    module DAN
                                      ###
                                      def self.copymaterials()
                                        model=Sketchup.active_model
                                        ents=model.active_entities
                                        #ss=model.selection ### use selected UNtextured [upper] faces to speed it up
                                        faces = ents.grep(Sketchup;;Face)
                                        #faces = ss.grep(Sketchup;;Face) ### use selected UNtextured faces to speed it up
                                        ###
                                        return nil unless faces[0]
                                        ###
                                        begin
                                          model.start_operation("DAN.copymaterials", true)
                                        rescue
                                          model.start_operation("DAN.copymaterials")
                                        end
                                        ###
                                        faces.each {|face|
                                          rayt = model.raytest(c=self.find_centroid(face), Z_AXIS.reverse)
                                    			### UPPER >>> LOWER
                                          if rayt
                                            f = rayt[1].grep(Sketchup;;Face)[0]
                                    				unless f
                                    				  f = nil
                                    				  e = rayt[1].grep(Sketchup;;Edge)
                                    					f = e[0].faces[0] if e
                                    				end
                                    				self.process_face(face, f) if f ### UPPER >>> LOWER
                                          end
                                        }
                                        ###
                                        model.commit_operation
                                        ###
                                      end
                                    
                                      # Finds an adjusted point, so we won't raycast down to an edge ;) HOPEFULLY
                                      ### OK if triangulated...
                                      ### BUT IF the face is L shaped the centroid might not fall on it !!!
                                      def self.find_centroid(face)
                                        vertices=face.vertices
                                        vertices[vertices.length]=vertices[0]
                                        centroid=Geom;;Point3d.new()
                                        a_sum=0.0
                                        y_sum=0.0
                                        x_sum=0.0
                                        for i in (0...vertices.length-1)
                                          temp=(vertices[i].position.x*vertices[i+1].position.y)-(vertices[i+1].position.x*vertices[i].position.y)
                                          a_sum+=temp
                                          x_sum+=(vertices[i+1].position.x+vertices[i].position.x)*temp
                                          y_sum+=(vertices[i+1].position.y+vertices[i].position.y)*temp
                                        end###for
                                        centroid.x=x_sum/(3*a_sum)
                                        centroid.y=y_sum/(3*a_sum)
                                        centroid.z=face.bounds.min.z
                                        return centroid
                                      end
                                     
                                      # NOTE; face_upper is in the model.ents||selection, face_lower is the TEXTURED face the raytest hit
                                      def self.process_face(face_upper, face_lower)
                                        ###
                                        texture_writer=Sketchup.create_texture_writer
                                        ###
                                        if face_lower.material && ! face_lower.material.texture
                                          ### paint the plain material if it exists but it has no texture
                                          face_upper.material=face_lower.material
                                        elsif face_lower.material ### A material and it has a texture...
                                          # Sample the UVs from LOWER face
                                          samples = []
                                          samples << face_lower.vertices[0].position             ### 0,0 | Origin
                                          samples << samples[0].offset(face_lower.normal.axes.x) ### 1,0 | Offset Origin in X
                                          samples << samples[0].offset(face_lower.normal.axes.y) ### 0,1 | Offset Origin in Y
                                          samples << samples[1].offset(face_lower.normal.axes.y) ### 1,1 | Offset X in Y
                                          xyz = [];uv = [] ### Arrays containing 3D and UV points.
                                          uvh = face_lower.get_UVHelper(true, true, texture_writer)
                                          samples.each { |position|
                                            xyz << position ### XYZ 3D coordinates
                                            uvq = uvh.get_front_UVQ(position) ### UV 2D coordinates
                                            uv << self.flattenUVQ(uvq)
                                          }
                                          pts = [] ### Position texture.
                                          (0..3).each { |i|
                                            pts << xyz[i]
                                            pts << uv[i]
                                          }
                                          # Set the position for textured material from face_upper onto face_lower front
                                          face_upper.position_material(face_lower.material, pts, true)
                                        end
                                        ###
                                      end
                                     
                                      ### Get UV coordinates from UVQ matrix.
                                      def self.flattenUVQ(uvq)
                                        return Geom;;Point3d.new(uvq.x / uvq.z, uvq.y / uvq.z, 1.0)
                                      end
                                     
                                      ###
                                      ### menu
                                      unless file_loaded?(__FILE__)
                                        menu=UI.menu("Plugins").add_submenu("Copy Materials...")
                                        menu.add_item("To Nearest Below"){self.copymaterials()}
                                      end
                                      file_loaded(__FILE__)
                                    	
                                    end
                                    

                                    TIG

                                    1 Reply Last reply Reply Quote 0
                                    • C Offline
                                      chanz
                                      last edited by

                                      Thanks again for the reply TIG. Unfortunately, I see the same issues. To verify it wasn't the small differences in faces from DropVertices, I made a copy of the bottom textured model and moved it directly above. I removed the top materials and then ran the script. UV issue is still there. Have you actually downloaded the file and run this script with the top mesh faces selected? Have you seen the UV issues or did it look okay for you? Honestly, I guess I will just give up at this point.

                                      I have figured out another sort of workaround where I make a sandbox mesh above the original, run this script we wrote (which does perfectly with the texture and UVs when the raycast is from a flat surface), then use UVToolkit to save the UVs, then drop the vertices and then restore the UVs. It works. A few extra steps but this script has been enough of a headache.

                                      Regardless, thanks for your help.

                                      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