# Uv\_tile\_at question

**URL:** <https://forums.sketchup.com/t/uv-tile-at-question/262519>\
**Category:** Ruby API\
**Tags:** sketchup, advice\
**Created:** [March 5, 2024, 6:49pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519 "2024-03-05T18:49:38Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![joe2](https://avatars.discourse-cdn.com/v4/letter/j/3e96dc/32.png) [@joe2](https://forums.sketchup.com/u/joe2)\
**Post date:** [March 5, 2024, 6:49pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/1 "2024-03-05T18:49:38Z")

</div>

Hello!  
I’m making a simple script that lets me create a keyboard short that finds all the faces in my selection and rotates their UV texture coordinates 45 degrees. This saves me a lot of time.

I have it working great for square textures, but with anything rectangular it skews the image. I don’t think it’s the math that is incorrect, as I did some trouble shooting and used the same math to rotate a geometric face/rectangle as intended.

I’m wondering if their something in the way the UV coordinates are handled that I am missing?The pseudo script is simpilifed as: I use the uv\_tile\_at method to get the 8 points, modify the 4 UV points (by 45 degrees) and then reapply the material position. Happy to share my script if that’s allowed.

Thanks so much

---

<div class="post-metadata">

**Author:** ![dezmo](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/dezmo/32/51058_2.png) [@dezmo](https://forums.sketchup.com/u/dezmo)\
**Post date:** [March 5, 2024, 7:20pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/2 "2024-03-05T19:20:36Z")

</div>

> [@joe2](#):
>
> share my script if that’s allowed.

In fact, it’s a “must” if you want meaningful help… 😉  
(I moved you topic into the right category, where you can also see how many millions of script has been shared… you may even find an answer with a properly initiated search. Personally I do not have experience with UVs)

Would be also nice if you can fill in your forum profile, than we can see which version of SketchUp you are using.

---

<div class="post-metadata">

**Author:** ![joe2](https://avatars.discourse-cdn.com/v4/letter/j/3e96dc/32.png) [@joe2](https://forums.sketchup.com/u/joe2)\
**Post date:** [March 5, 2024, 8:14pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/4 "2024-03-05T20:14:02Z")

</div>

Thanks! Appreciate the advice (I updated my profile, but I am using 2023).  
I copied my code below. Any criticism/tips are very welcome.

```ruby
   ANGLE = Math::PI / 4

   def self.turnTexture
        MODEL.start_operation("Rotate_Textures", true)
        # creates an array with only faces that have texture coordinates
        faces = MODEL.selection.select { |entity| entity.typename == 'Face' && entity.material&.texture != nil }
        faces.to_a unless faces.length < 0
        # iterates through each face to
        # 1 pull UV coordinates
        # 2 change those coordinates by 45 degrees with Math
        # 3 import those new coords into the face for material modification
        faces.each do |face|
          material = face.material
          reference = face.vertices.first.position
          current_mapping = face.uv_tile_at(reference, true)
          on_front = true
          new_uv_mapping = rotate_UV_Coordinates_By_Degrees(current_mapping)
          face.position_material(material, new_uv_mapping, on_front)
        end
        MODEL.commit_operation
      end

      private

      def self.rotate_UV_Coordinates_By_Degrees(positions)
        # Extract Coordinates for each point
        old_points = positions.select.with_index { |_, index| index.odd? }.map(&:to_a)
        x1, y1 = old_points[0]
        x4, y4 = old_points[3]
        # calculate midpoint coordinates
        mid_x = (x1 + x4) / 2.0
        mid_y = (y1 + y4) / 2.0
        new_uv_points = []
        # perform rotation for each point and reassign them ad 3dPoints
        new_uv_points = old_points.map { |x, y|
          translated_x = x - mid_x
          translated_y = y - mid_y
          rotated_x = (translated_x * Math.cos(ANGLE)) - (translated_y * Math.sin(ANGLE)) + mid_x
          rotated_y = (translated_x * Math.sin(ANGLE)) + (translated_y * Math.cos(ANGLE)) + mid_y
          Geom::Point3d.new(rotated_x, rotated_y, 0).tap { |point| new_uv_points << point }
          [rotated_x, rotated_y]
        }
        # Convert new points to 8 Pair UV Coordinates
        positions.each_with_index { |point, index| positions[index] = new_uv_points[index / 2] unless index.even? }
        positions
      end

```

does that make sense?

---

<div class="post-metadata">

**Author:** ![DanRathbun](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/danrathbun/32/500_2.png) [@DanRathbun](https://forums.sketchup.com/u/DanRathbun)\
**Post date:** [March 6, 2024, 12:05am UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/5 "2024-03-06T00:05:25Z")

</div>

MODEL and ANGLE should not be constants.

---

<div class="post-metadata">

**Author:** ![joe2](https://avatars.discourse-cdn.com/v4/letter/j/3e96dc/32.png) [@joe2](https://forums.sketchup.com/u/joe2)\
**Post date:** [March 6, 2024, 1:13am UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/6 "2024-03-06T01:13:34Z")

</div>

Thanks for responding Dan!  
Would a class variable be more appropriate? ie @@angle

---

<div class="post-metadata">

**Author:** ![sWilliams](https://avatars.discourse-cdn.com/v4/letter/s/838e76/32.png) [@sWilliams](https://forums.sketchup.com/u/sWilliams)\
**Post date:** [March 6, 2024, 4:42am UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/7 "2024-03-06T04:42:10Z")

</div>

Yes, if you’re transforming UVQ positions then you have to somehow deal with the texture.image\_height and texture.image\_width when they are not equal.

Here’s some example code where we rotate the the vertex positions instead of the UVQs and then calculate new UVQ values from the previous mapping.

And a file to apply the code to.  
[rotate texture.skp](https://forums.sketchup.com/uploads/short-url/2SvkjClMb3gczEryDo2PF2C0Zqe.skp) (123.3 KB)

```ruby
# grab the first face
p face = Sketchup.active_model.active_entities.grep(Sketchup::Face).first

# 45 degree rotate 
vector = Geom::Vector3d.new(0, 0, 1)
angle = 45.degrees # Return 45 degrees in radians.
tr = Geom::Transformation.rotation(ORIGIN, vector, angle) # * tr
  
# Get the UVHelper
tw = Sketchup.create_texture_writer
uvHelp = face.get_UVHelper(true, true, tw)

# For each Vertex.position
# - rotate the Vertex by 45 degress around the ORIGIN
# - collect the original vertex position and the new UVQ
mapping = []
mapping = face.vertices.collect { | v |
  pos = v.position
  pos_new = pos.transform(tr)
  uvq = uvHelp.get_front_UVQ(pos_new)
  [pos, uvq]
}
 
mapping.flatten!
face.position_material(face.material, mapping, true)

nil

```

---

<div class="post-metadata">

**Author:** ![DanRathbun](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/danrathbun/32/500_2.png) [@DanRathbun](https://forums.sketchup.com/u/DanRathbun)\
**Post date:** [March 6, 2024, 3:37pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/8 "2024-03-06T15:37:56Z")

</div>

> [@joe2](#):
>
> Would a class variable be more appropriate? ie @@angle

First of all we hope that you are wrapping your code in a unique top-level namespace module and your extension in a submodule of this namespace module.

There are reasons to use a `@@var` and reasons to use a `@var`. The main reason to use a variable is that the value **will change** and it will be used so many places that passing it around from method to method would make the code more difficult to maintain.

A local constant should only be used if the value will **not change**. This will most often not be true for a **model** reference, especially on the Mac where multiple models can be open at the same time.

Lastly a local reference would be preferred if the reference is only to be used within a single method. When the method ends and returns all local references go “out of scope” and are marked for garbage collection.

* * *

Okay, so let us look at your use of these references.

`MODEL` is only used within the `turnTexture` method, and **not within a loop** , so really it should be a local reference and the first line :

```ruby
model = SketchUp.active_model

```

`ANGLE` is also only used within the second method, but within loop nested in another loop. I think again a local reference defined in the first method **before** the outer loop, and then passed into the second method as a parameter. Ex:

```ruby
   def self.turnTexture
        angle = Math::PI / 4

```

… and within the loop:

```ruby
          new_uv_mapping = rotate_UV_Coordinates_By_Degrees(current_mapping, angle)

```

And then add the `angle` parameter to the second method definition:

```ruby
     def self.rotate_UV_Coordinates_By_Degrees(positions, angle)

```

… and change all `ANGLE` to use the `angle` reference.

You might even go further and create reusable references **outside** the `old_points` mapping loop …

```ruby
        cos = Math.cos(ANGLE)
        sin = Math.sin(ANGLE)
        new_uv_points = []

```

… so you are not repeatedly calling those methods **inside** the loop.

* * *

### Other tidbits

In Ruby the convention is to use all lower case letters for method names and separate words with an underscore character. Ie: `turn_texture()` rather than `turnTexture()`. The latter looks like VB or JavaScript. But it’s not enforced rigidly by the interpreter.

#### In the first method:

```ruby
        faces = MODEL.selection.select { |entity| entity.typename == 'Face' && entity.material&.texture != nil }

```

.. can also be:

```ruby
        faces = model.selection.grep(Sketchup::Face).select { |face| face.material&.texture }

```

Basically **avoid** using [`Entity#typename`](https://ruby.sketchup.com/Sketchup/Entity.html#typename-instance_method) comparisons as they are slow. Grepping by class identifier is **fast**.  
Even using `entity.is_a?(Sketchup::Face)` within a block is faster than comparing a typename string.

Also, you need not call [`BasicObject#!=()`](https://docs.ruby-lang.org/en/3.2/BasicObject.html#method-i-21-3D) to compare against **`nil`** as in Ruby only **`nil`** and **`false`** evaluate **Falsely**. Everything else including `0`, empty strings and empty arrays, etc. evaluate **Truthy**. Therefore, in a Boolean expression, any object reference to a `Sketchup::Material` or a `Sketchup::Texture` will eval as **`true`**.

---

<div class="post-metadata">

**Author:** ![joe2](https://avatars.discourse-cdn.com/v4/letter/j/3e96dc/32.png) [@joe2](https://forums.sketchup.com/u/joe2)\
**Post date:** [March 6, 2024, 4:48pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/9 "2024-03-06T16:48:12Z")

</div>

Thank you both for the quick and detailed response!  
@dezmo I had looked at the get\_UVHelper and wondered if that was the right direction, I’ll play around with this!  
@DanRathbun Thanks for spending the time writing that critique, incredibly helpful, I really appreciate it!

---

<div class="post-metadata">

**Author:** ![joe2](https://avatars.discourse-cdn.com/v4/letter/j/3e96dc/32.png) [@joe2](https://forums.sketchup.com/u/joe2)\
**Post date:** [March 6, 2024, 9:51pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/10 "2024-03-06T21:51:08Z")

</div>

Works! Thanks @dezmo and @DanRathbun  
Incredibly helpful advice

\*\*Almost Working Still have to work on it

---

<div class="post-metadata">

**Author:** ![sWilliams](https://avatars.discourse-cdn.com/v4/letter/s/838e76/32.png) [@sWilliams](https://forums.sketchup.com/u/sWilliams)\
**Post date:** [March 8, 2024, 3:46am UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/11 "2024-03-08T03:46:53Z")

</div>

Hey Joe,  
I’m not sure if you’d like hints, you seem to be more than capable of sorting this out on your own.

But for the benefit of the general public I threw together a diagram of the what the example code is doing each time it is invoked.

 ![1105x619](https://global.discourse-cdn.com/sketchup/original/3X/1/b/1bae9e2a3fc41ccabcf4f33e2a29a05762ada71c.jpeg)

---

<div class="post-metadata">

**Author:** ![joe2](https://avatars.discourse-cdn.com/v4/letter/j/3e96dc/32.png) [@joe2](https://forums.sketchup.com/u/joe2)\
**Post date:** [March 8, 2024, 6:47pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/12 "2024-03-08T18:47:45Z")

</div>

Hints are great! thank you,  
For face (ie a circle) with more than 4 points, I had to slice the mapping array to 8 values for it to work properly.  
The only issue I’m working out now is when I attempt to rotate a texture on a vertical face (y,z) it scales the texture. I think I need to get the correct values for the Transformation class per face in this bit of code

vector = Geom::Vector3d.new(0,0,1)  
tr = Geom::Transformation.rotation(ORIGIN, vector, angle)

---

<div class="post-metadata">

**Author:** ![sWilliams](https://avatars.discourse-cdn.com/v4/letter/s/838e76/32.png) [@sWilliams](https://forums.sketchup.com/u/sWilliams)\
**Post date:** [March 9, 2024, 1:29pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/13 "2024-03-09T13:29:47Z")

</div>

> [@joe2](#):
>
> I think I need to get the correct values for the Transformation class per face in this bit of code

Score!

You want to rotate around a vector that is perpendicular to the face. This is called the Normal to the face and is part of the Ruby API [Face.normal](https://ruby.sketchup.com/Sketchup/Face.html#normal-instance_method)

Also, you don’t need the textureWriter and Back Face in the [uvHelper](https://ruby.sketchup.com/Sketchup/Face.html#get_UVHelper-instance_method)

```ruby
# Get the UVHelper
uvHelp = face.get_UVHelper(true, false)

```

---

<div class="post-metadata">

**Author:** ![joe2](https://avatars.discourse-cdn.com/v4/letter/j/3e96dc/32.png) [@joe2](https://forums.sketchup.com/u/joe2)\
**Post date:** [March 14, 2024, 4:32pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/14 "2024-03-14T16:32:15Z")

</div>

[JW-RotateTextures.rbz](https://forums.sketchup.com/uploads/short-url/g2kBbL15HctUTOGY76bgtTfxClz.rbz) (777.0 KB)

Works as intended! Thanks so much @sWilliams, appreciate the time

---

<div class="post-metadata">

**Author:** ![medeek](https://sea2.discourse-cdn.com/sketchup/user_avatar/forums.sketchup.com/medeek/32/10675_2.png) [@medeek](https://forums.sketchup.com/u/medeek)\
**Post date:** [November 9, 2024, 10:05pm UTC](https://forums.sketchup.com/t/uv-tile-at-question/262519/15 "2024-11-09T22:05:06Z")

</div>

This was helpful, thank-you for sharing lots of useful information and coding recommendations.
