 Find the orientation of a component relative to the model

Hello,

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)
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°.

Angle.skp (85.5 KB)

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

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

Perhaps like this ?:

1 Like

P.S. - Please use correct coding language prefix so code is rendered correctly color lexed for Ruby.

It doesn’t seem to work with components.
I tried to readjust the formula for a selected component and the result is always -90 °.

mod = Sketchup.active_model
sel = mod.selection
sel.grep(Sketchup::ComponentInstance).each do |s|
tr = s.transformation
xaxis = tr.xaxis
yaxis = tr.yaxis
normal = Z_AXIS
angle = Math.atan2((yaxis * xaxis) % normal, xaxis % yaxis)
end

I am not sure which are vector1 and vector2 in the example eneroth3?

Is the code formatted correctly now?
While editing I am unable to format my code in ruby format.
Yet I use: The Discourse Forum lexer does not know what “rubis” is.

Use ```ruby

Julia’s example has a method description:

Counter-clockwise angle from vector2 to vector1, 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.

2 Likes

But if the instance is at the top model entities context, then you can use the world X_AXIS as a reference.

1 Like

Amazing it works!

def angle_in_plane(vector1, vector2, normal = Z_AXIS)
Math.atan2((vector2 * vector1) % normal, vector1 % vector2)
end

mod = Sketchup.active_model
sel = mod.selection

This proves once again that I still have a lot to learn.

I can also write it like this:

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

Or like this:

Sketchup.active_model.selection.grep(Sketchup::ComponentInstance).each{|s| p Math.atan2((X_AXIS * s.transformation.xaxis) % Z_AXIS, s.transformation.xaxis % X_AXIS).radians.round }

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.

Why would you want to?

It is more self-documenting to use the angle_in_plane method within your extension submodule or class.

You do not really need to use Array#each.

You are right Dan!

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 scene Page can 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
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.

Before we start Happy New Year everyone!

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)
face_axes = []
face_axes(sel,face_axes)
axes1 = face_axes.flatten!
page.axes.set(ORIGIN, axes1,axes1,axes1)
mod.pages.selected_page = mod.pages #Force the update of the page scene object
sel.grep(Sketchup::Face).each do |f|
inst = grp.to_component
defname = inst.definition.name = "Component#1"
end
mod.pages.selected_page = mod.pages
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.