With the method below, I am able to find the tilt angle in X of a component relative to the SketchUp scene.

mod = Sketchup.active_model
sel = mod.selection
sel.grep(Sketchup::ComponentInstance).each do |s|
xaxis = s.transformation.xaxis
angle_between = xaxis.angle_between(X_AXIS)
angle_radians = angle_between.radians
p angle_radians
end

The problem is that if I tilt my component at 45 ° positive or negative on Y, the feedback will always be positive at 45°.

Is it possible to find the component orientation to display -45 ° and 45 °?

Thank you in advance for your help.

Ps: I have already gone through the Sketchup :: Axes, Geom :: Transformation, Geom :: BoundingBox, Geom :: Point3d and Geom :: Vector3d class without finding a solution.

Counter-clockwise angle fromvector2tovector1, as seen from normal.

So therefore, vector1 is the vector that is being tested, and vector2 is the reference vector.

Of course a local X_AXIS vector will always be 90 degrees from it’s complementary Y_AXIS with an orthogonal axes.

You need to use the parent entities context’s reference vector which may not be equal to the world (model) X_AXIS if the parent instance has been rotated.

mod = Sketchup.active_model
sel = mod.selection
sel.grep(Sketchup::ComponentInstance).each do |s|
xaxis = s.transformation.xaxis
angle = Math.atan2((X_AXIS * xaxis) % Z_AXIS, xaxis % X_AXIS).radians.round
p angle
end

I still have little experience with ruby and trigonometry is a distant memory from school.
But I am going to study this wonderful method which is full of teaching.

I am doing this only to better understand what is going on.
In reality if the code is executed in a single block, it is easier for newbies in ruby like me to understand what is going on.

This is how I dissect a method to better understand it:

# Calculate the angle of rotation X of a selected component (Explanation)
mod = Sketchup.active_model
sel = mod.selection
sel.grep(Sketchup::ComponentInstance).each do |s|
p xaxis = s.transformation.xaxis #X axis of the selected component => Vector3d (-0.707107, 0.707107, 0)
p X_AXIS #X axis of SketchUp Scene # => Vector3d (1, 0, 0)
p ( X_AXIS * xaxis ) # Method * is used to calculate the cross product between two vectors => Vector3d (0, 0, 0.707107)
p Z_AXIS #Z axis of the SketchUp Scene => Vector3d (0, 0, 1)
p ( X_AXIS * xaxis ) % Z_AXIS #The % method is used to calculate the dot product between two vectors => 0.7071067811865475
p ( xaxis % X_AXIS ) #Calculate the dot product between the X axis of the component and the X axis of the scene => 0.7071067811865476
p Math #Module for trigonometric functions
p Math.atan2((X_AXIS * xaxis) % Z_AXIS, xaxis % X_AXIS) #Compute the given arc tangent y and x => 0.7853981633974483
p Math.atan2((X_AXIS * xaxis) % Z_AXIS, xaxis % X_AXIS).radians #The radians method is used to convert radians to degrees => 45.0°
end

It might not make sense to you, but it helps me learn.

Please note that your comment is not exactly true in all cases.

The API constants ORIGIN, X_AXIS, Y_AXIS, and Z_AXIS are the preset world (model) axes of the IDENTITY transformation. These should never change (ie, they are constants.)

Each scenePagecan have it’s own axes settings that can differ from the unchangeable world (model) axes.

Note that the API code example does not work correctly and omits updating the scene page object.

This is a working example to set a special axes for a scene page:

def new_scene_with_special_axes
model = Sketchup.active_model
page = model.pages.add("Example Page")
xaxis = Geom::Vector3d.new(3, 5, 0)
yaxis = xaxis * Z_AXIS
model.axes.set([10,0,0], xaxis, yaxis, Z_AXIS)
page.update(PAGE_USE_ALL)
page.axes
end
a = new_scene_with_special_axes()
a.origin

Notice how it borrows the Z world axis, but sets it’s own x axis and calculates it’s y axis using the dot product of the x and Z axis vectors.

Your example Dan opened unexpected doors for me to solve a problem that seems difficult to solve otherwise.

Explanation:
By default, if we create a component with an inclined face, the bounding box will be based on the constants of the API ORIGIN, X_AXIS, Y_AXIS, and Z_AXIS:

So to get around the problem in order to limit the engobing box to the dimensions of the faces, we had to find a way to change the axes of the page according to the normals of the face.

def face_axes(sel,face_axes)
sel.grep(Sketchup::Face).each do |f|
face_axes << f.normal.axes
end
end
mod = Sketchup.active_model
sel = mod.selection
ents = mod.active_entities
mod.start_operation('Convert faces to components', true)
mod_page = mod.pages.add("Constant axess")
page = mod.pages.add("New page axes")
face_axes = []
face_axes(sel,face_axes)
axes1 = face_axes.flatten!
page.axes.set(ORIGIN, axes1[0],axes1[2],axes1[1])
mod.pages.selected_page = mod.pages[1] #Force the update of the page scene object
sel.grep(Sketchup::Face).each do |f|
grp = ents.add_group(f)
inst = grp.to_component
defname = inst.definition.name = "Component#1"
sel.add(inst)
end
mod.pages.selected_page = mod.pages[0]
mod.pages.erase(page)
mod.pages.erase(mod_page)
mod.commit_operation

We now have a bounding box which follows the normals of the face:

As you said the sample API code is not working properly and omits updating the scene page object.
In my case even “page.update (PAGE_USE_ALL)” does not allow the update.

So I worked around the problem with the “selected_page” method which forces the update of the scene page object before creating the component.

I’ve been studying vectors, axes, and transformations for a few days now.
It seems like a big learning curve because geometry in 3D space is not the easiest to assimilate.